MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

C#基础语法与数据类型详解

2022-11-074.2k 阅读

C# 基础语法

一、命名规则

  1. 标识符命名 在 C# 中,标识符是用来给变量、函数、类、命名空间等元素命名的。其命名规则如下:

    • 必须以字母、下划线(_)或 @ 开头。例如:myVariable_privateVar@temp 都是合法的标识符开头。不过,以 @ 开头的标识符主要用于与 C# 关键字冲突的情况,实际编程中较少使用。
    • 后续字符可以是字母、数字、下划线。例如:myVar123 是合法的,而 my - var 是不合法的,因为其中包含了连字符。
    • 不能与 C# 关键字同名,除非使用 @ 前缀。例如,不能直接命名变量为 class,但可以命名为 @class。不过,为了代码的可读性,尽量避免使用这种方式。
  2. 命名规范 虽然命名规则定义了哪些标识符是合法的,但命名规范则有助于提高代码的可读性和可维护性。常见的命名规范有:

    • Pascal 命名法:每个单词的首字母大写,用于类、方法、属性等命名。例如:MyClassCalculateTotalUserFullName
    • Camel 命名法:第一个单词的首字母小写,其余单词首字母大写,常用于变量命名。例如:myVariableuserAge
    • 常量命名:通常使用全大写字母,单词间用下划线分隔。例如:MAX_VALUEPI

二、语句和块

  1. 语句 C# 中的语句是执行程序操作的基本单位。例如,声明变量并赋值的语句:
int number = 10;

这条语句声明了一个整型变量 number 并将其初始化为 10。语句以分号(;)结束,分号表示语句的终结。

  1. 块是由一对花括号({})括起来的零条或多条语句。例如:
if (number > 5)
{
    Console.WriteLine("The number is greater than 5.");
    int newNumber = number * 2;
    Console.WriteLine("The new number is: " + newNumber);
}

在这个例子中,if 条件满足时执行的代码部分就是一个块。块可以包含多条语句,并且可以嵌套在其他块中,比如在 ifwhilefor 等控制结构中。

三、注释

注释是在代码中添加的解释性文本,编译器会忽略注释内容。C# 支持三种类型的注释:

  1. 单行注释:以双斜杠(//)开头,到本行末尾结束。例如:
int age = 25; // 这是一个表示年龄的变量
  1. 多行注释:以 /* 开始,以 */ 结束,可以跨越多行。例如:
/*
这是一段多行注释,
可以用于对复杂代码块进行详细说明。
这里的代码是计算两个数的和。
*/
int sum = num1 + num2;
  1. 文档注释:以 /// 开头,主要用于为代码生成 XML 文档。例如:
/// <summary>
/// 计算两个整数的和
/// </summary>
/// <param name="a">第一个整数</param>
/// <param name="b">第二个整数</param>
/// <returns>两个整数的和</returns>
public static int Add(int a, int b)
{
    return a + b;
}

文档注释中的标签(如 <summary><param><returns>)可以被工具解析,生成详细的代码文档,方便其他开发人员理解代码的功能和使用方法。

四、控制结构

  1. if - else 语句 if - else 语句用于根据条件执行不同的代码块。基本语法如下:
if (condition)
{
    // 条件为真时执行的代码
}
else
{
    // 条件为假时执行的代码
}

例如:

int score = 85;
if (score >= 60)
{
    Console.WriteLine("Passed");
}
else
{
    Console.WriteLine("Failed");
}

可以有多个 else if 子句来处理多个条件:

if (score >= 90)
{
    Console.WriteLine("A");
}
else if (score >= 80)
{
    Console.WriteLine("B");
}
else if (score >= 70)
{
    Console.WriteLine("C");
}
else if (score >= 60)
{
    Console.WriteLine("D");
}
else
{
    Console.WriteLine("F");
}
  1. switch - case 语句 switch - case 语句用于基于一个表达式的值来执行不同的代码块。语法如下:
switch (expression)
{
    case value1:
        // 当 expression 等于 value1 时执行的代码
        break;
    case value2:
        // 当 expression 等于 value2 时执行的代码
        break;
    default:
        // 当 expression 不等于任何 case 值时执行的代码
        break;
}

例如,根据一周中的某一天输出对应的信息:

int day = 3;
switch (day)
{
    case 1:
        Console.WriteLine("Monday");
        break;
    case 2:
        Console.WriteLine("Tuesday");
        break;
    case 3:
        Console.WriteLine("Wednesday");
        break;
    case 4:
        Console.WriteLine("Thursday");
        break;
    case 5:
        Console.WriteLine("Friday");
        break;
    case 6:
        Console.WriteLine("Saturday");
        break;
    case 7:
        Console.WriteLine("Sunday");
        break;
    default:
        Console.WriteLine("Invalid day");
        break;
}

在每个 case 块中,break 语句用于跳出 switch 语句。如果没有 break,代码会继续执行下一个 case 块,直到遇到 breakswitch 结束。

  1. 循环结构
    • for 循环:用于执行已知次数的循环。语法为:
for (initialization; condition; iteration)
{
    // 循环体
}

例如,打印 1 到 10 的数字:

for (int i = 1; i <= 10; i++)
{
    Console.WriteLine(i);
}

在这个例子中,int i = 1 是初始化部分,i <= 10 是条件部分,i++ 是迭代部分。每次循环开始时,先检查条件是否满足,满足则执行循环体,然后执行迭代部分,再重新检查条件。 - while 循环:用于在条件为真时重复执行代码块。语法为:

while (condition)
{
    // 循环体
}

例如,计算 1 到 100 的累加和:

int sum = 0;
int num = 1;
while (num <= 100)
{
    sum += num;
    num++;
}
Console.WriteLine("Sum: " + sum);

这里先检查 num <= 100 的条件,满足则执行循环体,在循环体中更新 num 的值,然后再次检查条件。 - do - while 循环:与 while 循环类似,但它先执行一次循环体,然后再检查条件。语法为:

do
{
    // 循环体
} while (condition);

例如:

int input;
do
{
    Console.WriteLine("Enter a number between 1 and 10 (0 to quit): ");
    input = int.Parse(Console.ReadLine());
    if (input >= 1 && input <= 10)
    {
        Console.WriteLine("You entered: " + input);
    }
} while (input!= 0);

在这个例子中,无论条件是否满足,都会先执行一次循环体,然后根据输入的值决定是否继续循环。

C# 数据类型

一、值类型

  1. 整数类型 C# 提供了多种整数类型,用于表示不同范围的整数值。
    • sbyte:8 位有符号整数,范围是 -128 到 127。例如:
sbyte smallNumber = -100;
- **byte**:8 位无符号整数,范围是 0 到 255。例如:
byte positiveNumber = 200;
- **short**:16 位有符号整数,范围是 -32,768 到 32,767。例如:
short mediumNumber = -10000;
- **ushort**:16 位无符号整数,范围是 0 到 65,535。例如:
ushort unsignedMediumNumber = 30000;
- **int**:32 位有符号整数,范围是 -2,147,483,648 到 2,147,483,647。这是最常用的整数类型,例如:
int largeNumber = 1000000;
- **uint**:32 位无符号整数,范围是 0 到 4,294,967,295。例如:
uint unsignedLargeNumber = 2000000000;
- **long**:64 位有符号整数,范围是 -9,223,372,036,854,775,808 到 9,223,372,036,854,775,807。例如:
long veryLargeNumber = 1000000000000;
- **ulong**:64 位无符号整数,范围是 0 到 18,446,744,073,709,551,615。例如:
ulong unsignedVeryLargeNumber = 1000000000000000000;
  1. 浮点类型
    • float:32 位单精度浮点数,用于表示小数。它的精度大约是 7 位有效数字。例如:
float piApprox = 3.14159f;

注意,在赋值时需要在数字后面加上 fF,以表明这是一个 float 类型。 - double:64 位双精度浮点数,精度大约是 15 - 17 位有效数字。这是更常用的表示小数的类型,例如:

double eApprox = 2.71828182845904523536;
  1. decimal 类型 decimal 类型用于表示高精度的小数,适合用于财务和货币计算。它具有 28 - 29 位有效数字。例如:
decimal price = 19.99m;

同样,在赋值时需要在数字后面加上 mM,以表明这是一个 decimal 类型。与 floatdouble 相比,decimal 类型在表示精确小数方面更有优势,因为 floatdouble 在某些小数的表示上可能存在精度损失。例如,计算 0.1 + 0.2 时,floatdouble 可能无法得到精确的 0.3,而 decimal 可以:

float floatSum = 0.1f + 0.2f;
double doubleSum = 0.1 + 0.2;
decimal decimalSum = 0.1m + 0.2m;
Console.WriteLine("Float sum: " + floatSum);
Console.WriteLine("Double sum: " + doubleSum);
Console.WriteLine("Decimal sum: " + decimalSum);
  1. 字符类型 char 类型用于表示单个字符,它是 16 位无符号整数,对应于 Unicode 字符集。例如:
char letter = 'A';

可以使用转义序列来表示特殊字符,例如 \n 表示换行,\t 表示制表符,\\ 表示反斜杠等。例如:

Console.WriteLine("This is a line\nThis is a new line");
  1. 布尔类型 bool 类型只有两个值:truefalse,用于表示逻辑值。例如:
bool isDone = true;
if (isDone)
{
    Console.WriteLine("The task is completed.");
}

二、引用类型

  1. 字符串类型 string 类型用于表示字符串,即一系列字符。字符串是不可变的,这意味着一旦创建了一个字符串对象,就不能修改其内容。例如:
string greeting = "Hello, World!";

可以使用 + 运算符来连接字符串:

string part1 = "Hello";
string part2 = " World";
string fullGreeting = part1 + part2;

也可以使用 string.Format 方法来格式化字符串:

int age = 30;
string message = string.Format("The person is {0} years old.", age);

在 C# 6.0 及更高版本中,还可以使用字符串插值:

string interpolatedMessage = $"The person is {age} years old.";
  1. 类类型 类是 C# 中最重要的引用类型之一,它封装了数据和行为。定义一个类的基本语法如下:
public class Person
{
    // 字段
    private string name;
    private int age;

    // 属性
    public string Name
    {
        get { return name; }
        set { name = value; }
    }

    public int Age
    {
        get { return age; }
        set { age = value; }
    }

    // 构造函数
    public Person(string initialName, int initialAge)
    {
        name = initialName;
        age = initialAge;
    }

    // 方法
    public void Introduce()
    {
        Console.WriteLine($"Hello, I'm {name} and I'm {age} years old.");
    }
}

可以通过以下方式使用这个类:

Person person1 = new Person("Alice", 25);
person1.Introduce();
  1. 数组类型 数组是一种用于存储多个相同类型元素的数据结构。声明和初始化数组的方式有多种。例如,声明一个整型数组并初始化:
int[] numbers = new int[5] { 1, 2, 3, 4, 5 };

也可以省略数组大小,直接根据初始化的值来确定:

int[] otherNumbers = { 10, 20, 30 };

可以通过索引访问数组元素,索引从 0 开始。例如:

int firstNumber = numbers[0];

还可以创建多维数组,例如二维数组:

int[,] matrix = new int[2, 3] { { 1, 2, 3 }, { 4, 5, 6 } };
int value = matrix[1, 2]; // 获取第二行第三列的元素
  1. 接口类型 接口定义了一组方法、属性、事件等的签名,但不包含实现。类可以实现接口,以表明它提供了接口定义的功能。定义接口的语法如下:
public interface IPrintable
{
    void Print();
}

然后一个类可以实现这个接口:

public class Document : IPrintable
{
    public void Print()
    {
        Console.WriteLine("Printing document...");
    }
}

这样,任何 Document 对象都可以被当作 IPrintable 对象使用,例如:

IPrintable printable = new Document();
printable.Print();

三、类型转换

  1. 隐式类型转换 隐式类型转换是自动进行的,当从一个较小范围的类型转换到一个较大范围的类型时,通常会发生隐式类型转换。例如,从 intlong
int num = 100;
long bigNum = num;

因为 long 可以表示比 int 更大的范围,所以这种转换是安全的,编译器会自动进行。同样,从 intfloatdouble 等也会发生隐式转换:

float floatNum = num;
double doubleNum = num;
  1. 显式类型转换 当从一个较大范围的类型转换到一个较小范围的类型时,或者在不兼容的类型之间转换时,需要进行显式类型转换,也称为强制类型转换。例如,从 doubleint
double doubleValue = 10.5;
int intValue = (int)doubleValue; // 结果为 10,小数部分被截断

对于不兼容的类型,如 stringint,需要使用特定的转换方法。例如:

string numberString = "123";
int parsedNumber = int.Parse(numberString);

但如果字符串内容无法正确解析为 int,会抛出异常。为了避免异常,可以使用 int.TryParse 方法:

string badString = "abc";
int result;
bool success = int.TryParse(badString, out result);
if (success)
{
    Console.WriteLine("Parsed successfully: " + result);
}
else
{
    Console.WriteLine("Failed to parse.");
}
  1. 装箱和拆箱 装箱是将值类型转换为引用类型,拆箱则是将引用类型转换回值类型。例如,将 int 装箱为 object
int value = 42;
object boxedValue = value; // 装箱

然后可以将其拆箱回 int

int unboxedValue = (int)boxedValue; // 拆箱

需要注意的是,在拆箱时,如果引用对象的实际类型与要拆箱的类型不匹配,会抛出 InvalidCastException 异常。装箱和拆箱操作会带来一定的性能开销,因为涉及到堆内存的分配和类型检查,所以在性能敏感的代码中应尽量避免不必要的装箱和拆箱。

四、自定义数据类型

  1. 结构体 结构体是一种值类型,它可以包含不同类型的成员。定义结构体的语法与类类似,但结构体是值类型,而类是引用类型。例如:
public struct Point
{
    public int X;
    public int Y;

    public Point(int x, int y)
    {
        X = x;
        Y = y;
    }

    public void Move(int dx, int dy)
    {
        X += dx;
        Y += dy;
    }
}

可以这样使用结构体:

Point point1 = new Point(10, 20);
point1.Move(5, 5);
Console.WriteLine($"X: {point1.X}, Y: {point1.Y}");

结构体在创建时直接在栈上分配内存,而类对象在堆上分配内存。结构体适合用于表示轻量级的数据结构,如几何点、尺寸等。 2. 枚举 枚举是一种特殊的值类型,用于定义一组命名的常量。例如,定义一个表示一周中各天的枚举:

public enum DayOfWeek
{
    Monday,
    Tuesday,
    Wednesday,
    Thursday,
    Friday,
    Saturday,
    Sunday
}

可以使用枚举类型的变量:

DayOfWeek today = DayOfWeek.Wednesday;
if (today == DayOfWeek.Saturday || today == DayOfWeek.Sunday)
{
    Console.WriteLine("It's the weekend!");
}
else
{
    Console.WriteLine("It's a weekday.");
}

枚举常量默认从 0 开始赋值,可以显式指定值。例如:

public enum StatusCode
{
    Success = 200,
    NotFound = 404,
    ServerError = 500
}

这样,StatusCode.Success 的值就是 200,StatusCode.NotFound 的值就是 404 等。枚举在代码中用于提高可读性和可维护性,避免使用魔法数字。

  1. 委托 委托是一种引用类型,它可以存储对方法的引用。委托允许将方法作为参数传递给其他方法,实现回调机制。定义委托的语法如下:
public delegate void MyDelegate();

然后可以定义一个方法与该委托匹配:

public void MyMethod()
{
    Console.WriteLine("This is my method.");
}

可以创建委托实例并调用它:

MyDelegate myDelegate = new MyDelegate(MyMethod);
myDelegate();

也可以使用匿名方法或 lambda 表达式来初始化委托:

MyDelegate anonymousDelegate = delegate () { Console.WriteLine("This is an anonymous method."); };
anonymousDelegate();

MyDelegate lambdaDelegate = () => Console.WriteLine("This is a lambda expression.");
lambdaDelegate();

委托在事件处理、异步编程等方面有广泛应用。例如,在图形用户界面编程中,按钮的点击事件可以通过委托来处理。

  1. 事件 事件是基于委托的一种机制,用于在对象状态改变或特定操作发生时通知其他对象。定义事件通常分为以下几步:
    • 定义委托类型:
public delegate void MyEventHandler(object sender, EventArgs e);

这里,object sender 表示引发事件的对象,EventArgs e 包含事件相关的数据。EventArgs 是一个基类,可根据需要创建自定义的事件参数类继承自它。 - 在类中定义事件:

public class MyClass
{
    public event MyEventHandler MyEvent;

    public void DoSomething()
    {
        // 执行一些操作
        if (MyEvent!= null)
        {
            MyEvent(this, EventArgs.Empty);
        }
    }
}
- 订阅和处理事件:
public class Program
{
    public static void Main()
    {
        MyClass myObject = new MyClass();
        myObject.MyEvent += MyEventHandlerMethod;
        myObject.DoSomething();
    }

    public static void MyEventHandlerMethod(object sender, EventArgs e)
    {
        Console.WriteLine("The event was raised.");
    }
}

在这个例子中,当 myObject.DoSomething() 方法被调用时,如果有对象订阅了 MyEvent 事件,就会执行订阅的事件处理方法 MyEventHandlerMethod。事件是实现观察者模式的重要手段,它使得对象之间的交互更加灵活和可维护。

通过深入了解 C# 的基础语法和数据类型,开发人员可以编写出高效、可读且易于维护的代码,为构建各种类型的应用程序奠定坚实的基础。无论是桌面应用、Web 应用还是移动应用,对这些基础知识的掌握都是至关重要的。