# C# 基础语法 ## 数据类型 数据类型主要用于指明变量和常量存储值的类型,C# 语言是一种强类型语言,要求每个变量都必须指定数据类型。 ### 值类型和引用类型 C# 语言的数据类型分为值类型和引用类型。 区别: - 值类型直接存储其值,引用类型存储对值得引用。 - 值类型存储在堆栈(stack)上,引用类型存储在托管堆(managed heap)上。 ```C# 1. 我们把内存分为堆空间和栈空间 堆栈:简称栈 Stack 栈空间比较小,但是读取速度快 托管堆: 简称堆 Heap 堆空间比较大,但是读取速度慢 2. 栈的特征:数据只能从栈的顶端插入和删除。把数据放入栈顶称为入栈(push)从栈顶删除数据称为出栈(pop) 3.堆是一块内存区域,与栈不同,堆里的内存能够以任意顺序存入和移除 4.堆(托管堆)存储引用类型,此堆非彼堆,.NET中的堆由垃圾收集器自动管理。 与堆栈不同,堆是从下往上分配,所以自由的空间都在已用空间的上面 ``` 在 C#中,基本数据类型都是值类型,大多数更复杂得数据类型,包括我们自己声明得类,都是引用类型。 值类型包括整型、浮点型、字符型、布尔型、枚举型等;引用类型包括类、接口、数组、委托、字符串等。 从内存存储空间的角度而言,值类型的值是存放到栈中的,每次存取值都会在该内存中操作;引用类型首先会在栈中创建一个引用变量,然后在堆中创建对象本身,再把这个对象所在内存的首地址赋给引用变量。 ### 整型 所谓整型就是存储整数的类型,按照存储值的范围不同,C# 语言将整型分成了 byte 类型、short 类型、int 类型、long 类型等,并分别定义了有符号数和无符号数。 有符号数可以表示负数,无符号数仅能表示正数。 | 类 型 | 取值范围 | | ------ | ------------------------------------ | | sbyte | 有符号数,占用 1 个字节,-27〜27-1 | | byte | 无符号数,占用 1 个字节,0〜28-1 | | short | 有符号数,占用 2 个字节,-215〜215-1 | | ushort | 无符号数,占用 2 个字节,0〜216-1 | | int | 有符号数,占用 4 个字节,-231〜231-1 | | uint | 无符号数,占用 4 个字节,0〜232-1 | | long | 有符号数,占用 8 个字节,-263〜263-1 | | ulong | 无符号数,占用 8 个字节,0〜264-1 | 从上面的表中可以看出 short、int 和 long 类型所对应的无符号数类型都是在其类型名称前面加上了 u 字符,只有 byte 类型比较特殊,它存储一个无符号数,其对应的有符号 数则是 sbyte。 ### 浮点型 浮点型是指小数类型,浮点型在 C# 语言中共有两种,一种称为单精度浮点型,一种称为双精度浮点型。 | 类 型 | 取值范围 | | ------ | ----------------------------------------------- | | float | 单精度浮点型,占用 4 个字节,最多保留 7 位小数 | | double | 双精度浮点型,占用 8 个字节,最多保留 16 位小数 | ### 字符型和字符串类型 字符型只能存放一个字符,它占用两个字节,能存放一个汉字。 字符型用 char 关键字表示,存放到 char 类型的字符需要使用单引号括起来,例如 'a'、'中' 等。 字符串类型能存放多个字符,它是一个引用类型,在字符串类型中存放的字符数可以认为是没有限制的,因为其使用的内存大小不是固定的而是可变的。 在 C# 语言中还有一些特殊的字符串,代表了不同的特殊作用。由于在声明字符串类型的数据时需要用双引号将其括起来,那么双引号就成了特殊字符,不能直接输出,转义字符的作用就是输出这个有特殊含义的字符。 常用的转义字符 | 转义符 | 字 符 名 | 字符的**Unicode 值** | 转义符 | 字 符 名 | 字符的**Unicode 值** | | ------ | ---------------- | -------------------- | ------ | ---------- | -------------------- | | \\' | 单引号 | 0x0027 | \f | 换页 | 0x000c | | \\" | 双引号 | 0x0022 | \n | 新行 | 0x000A | | \\\ | 反斜杠 | 0x005c | \r | 回车 | 0x000D | | \0 | 空字符 | 0x0000 | \t | 水平制表符 | 0x0009 | | \a | 警告(产生蜂鸣) | 0x0007 | \v | 垂直制表符 | 0x000B | | \b | 退格 | 0x0008 | | | ### 布尔类型 在 C# 语言中,布尔类型使用 bool 来声明,它只有两个值,即 true 和 false。 ### 引用类型 | 名称 | 类型 | 说明 | | ------ | ------------- | -------------------------------- | | object | System.Object | 根类型,其他类型都是从它派生来得 | | string | System.String | Unicode 字符串 | 1. 在 C#中,object 类就是最终得父类型,所有内置类型和用户自定义得类型都是从它派生来的。这样,object 类型就可以用于两个目的 - 可以使用 object 引用来绑定任何特定子类型的对象。例如:把堆栈中的值对象装箱,或者用于反射。 - object 类型实现了许多一般用途的基本方法,包括 Equals(),GetHasCode(),GetType()和 ToString()。 2. string 对象被分配到堆上,而不是栈上。 当把一个字符串变量赋值给另一个字符串时,会得到对内存中同一个字符串的两个引用。但是,string 和引用类型的常见行为还是有一些区别。 例如:字符串是不可以修改的。修改其中一个字符串,就会创建一个全新的 string 对象,而另一个字符串不会发生变化。 ## 运算符 ### 算术运算符 算术运算符是最常用的一类运算符,包括加法、减法、乘法、除法等 | 运算符 | 说 明 | | ------ | ---------------------- | | + | 对两个操作数做加法运算 | | - | 对两个操作数做减法运算 | | \* | 对两个操作数做乘法运算 | | / | 对两个操作数做除法运算 | | % | 对两个操作数做取余运算 | ### 逻辑运算符 逻辑运算符主要包括与、或、非等,它主要用于多个布尔型表达式之间的运算。 | 运算符 | 含义 | 说明 | | ------ | ------ | ----------------------------------------------------------------------------------------------------------------------------- | | && | 逻辑与 | 如果运算符两边都为 True,则整个表达式为 True,否则为 False;如果左边操作数为 False,则不对右边表达式进行计算,相当于“且”的含义 | | \|\| | 逻辑或 | 如果运算符两边有一个或两个为 True,整个表达式为 True,否则为 False;如果左边为 True,则不对右边表达式进行计算,相当于“或”的含义 | | ! | 逻辑非 | 表示和原来的逻辑相反的逻辑 | ### 比较运算符 比较运算符是在条件判断中经常使用的一类运算符,包括大于、小于、不等于、大于等于、小于等于等 | 运算符 | 说 明 | | ------ | -------------------------------------------- | | == | 表示两边表达式运算的结果相等,注意是两个等号 | | != | 表示两边表达式运算的结果不相等 | | > | 表示左边表达式的值大于右边表达式的值 | | < | 表示左边表达式的值小于右边表达式的值 | | >= | 表示左边表达式的值大于等于右边表达式的值 | | <= | 表示左边表达式的值小于等于右边表达式的值 | ### 三元运算符 三元运算符也被称为条件运算符,与后面要学习的 if 条件语句非常类似。 在 C# 语言中三元运算符只有一个,具体的语法形式如下。 ```C# 布尔表达式 ? 表达式 1: 表达式 2 ``` 其中: 布尔表达式:判断条件,它是一个结果为布尔型值的表达式。 表达式 1:如果布尔表达式的值为 True,该三元运算符得到的结果就是表达式 1 的运算结果。 表达式 2:如果布尔表达式的值为 False,该三元运算符得到的结果就是表达式 2 的运算结果。 ### 赋值运算符 赋值运算符中最常见的是等号,除了等号以外还有很多赋值运算符,它们通常都是与其他运算符连用起到简化操作的作用。 | 运算符 | 说 明 | | ------ | ------------------------------------------------------------ | | = | x=y,等号右边的值给等号左边的变量,即把变量 y 的值赋给变量 x | | += | x+=y,等同于 x=x+y | | -= | x-=y,等同于 x=x-y | | \*= | x*=y,等同于 x=x*y | | /= | x/=y,等同于 x=x/y | | %= | x%=y,等同于 x=x%y,表示求 x 除以 y 的余数 | | ++ | x++ 或 ++x,等同于 x=x+1 | | -- | x-- 或 --x,等同于 x=x-1 | ### 运算符的优先级 | 运算符 | 结合性 | | ------------------------------------------------------- | -------- | | .(点)、()(小括号)、[](中括号) | 从左到右 | | + (正)、-(负)、++ (自增)、--(自减)、!(逻辑非) | 从右到左 | | \* (乘)、/ (除)、% (取余) | 从左向右 | | + (加)、-(减) | 从左向右 | | <、<=、>、>= | 从左向右 | | ==、!= | 从左向右 | | && | 从左向右 | | \|\| | 从左向右 | | ?: | 从右到左 | | =、+=、-=、\*=、/=、%=、&=、 = | 从右到左 | ### 变量 变量(Variable)是 C# 编程中不可缺失的内容,使用变量可以更容易地完成程序的编写。 变量可以理解为存放数据的容器,并且在将值存放到变量中时还要为变量指定数据类型。 变量和常量是相对的:变量是指所存放的值是允许改变的,而常量表示存入的值不允许改变。 C#有两个方法可确保变量在使用前进行了初始化。 - 变量是类或者结构中的字段,如果没有显示初始化,则在创建这些变量时,默认值是 0 或者 null。 - 方法的局部变量必须在代码中显示初始化,之后才能在语句中使用他们的值。 ### 类型推断 类型推断使用 var 关键字。 规则: 1. 变量必须初始化。否则,编译器就没有推断变量类型的依据 2. 初始化器不能为空。 3. 初始化器必须放在表达式中。 4. 不能把初始化器设置为一个对象,除非在初始化器中创建了一个新对象。 ### 变量的作用域 变量的作用域是可以访问该变量的代码区域。 1. 只要类在某个区域内,其字段也在该作用域内 2. 局部变量存在于表示声明该变量的块语句或方法结束的右花括号之前的作用域内 3. 在 for、while 或类似语句中声明的局部变量存在于该循环体内 ### C#变量的定义 在定义变量时,首先要确认在变量中存放的值的数据类型,然后再确定变量的内容,最后根据 C# 变量命名规则定义好变量名。 ```C# 数据类型 变量名; ``` ```C# 数据类型 变量名 = 值; ``` ### 常量 与变量不同的是,常量在第一次被赋值后值就不能再改变。定义常量需要使用关键字 const 来完成。 特点: - 常量必须在声明是初始化。指定了其值后,就不能改写了。 - 常量的值必须能在编译时进行计算。因此,不能用从变量中提取的值来初始化常量,如果必须这么做,应使用只读字段。 好处: - 常量是程序变得更容易阅读。 - 常量使程序更容易修改。 - 常量更容易避免程序出现错误。 ```C# const 数据类型 常量名 = 值; ``` ## C# if else 语句 C# if else 语句是最常用的条件语句,并且 if else 语句的形式有多种,包括单一条件的 if 语句、二选一条件的 if else 语句以及多选一条件的 if else if 语句。下面将详细介绍这 3 种形式。 ### **单一条件的 if 语句** 单一条件的 if 语句是最简单的 if 语句,只有满足 if 语句中的条件才能执行相应的语句。 具体的语法形式如下。 ```C# if(布尔表达式) { 语句块; } ``` 这里语句块是指多条语句。当布尔表达式中的值为 True 时执行语句块中的内容,否则不执行。 ## **二选一条件的 if 语句** 二选一条件的 if 语句与前面介绍的三元运算符完成的效果是一样的,只是比三元运算符实现的过程灵活一些。 具体的语法形式如下。 ```C# if(布尔表达式) { 语句块 1; }else{ 语句块 2; } ``` 上面语句的执行过程是当 if 中的布尔表达式的结果为 True 时执行语句块 1,否则执行语句块 2。 ### **多选一条件的 if 语句** 多选一条件是最复杂的 if 语句,但是语法形式并不难。 具体的语法形式如下。 ```C# if(布尔表达式 1) { 语句块 1; }else if(布尔表达式 2){ 语句块 2; } ... else{ 语句块 n; } ``` 上面语句的执行过程是先判断布尔表达式 1 的值是否为 True,如果为 True,执行语句块 1,整个语句结束,否则依次判断每个布尔表达式的值,如果都不为 True,执行 else 语句中的语句块 n。 需要注意的是,在上面的语法中最后一个 else{} 语句是可以省略的。如果省略了 else{} 语句,那么多分支的 if 语句中如果没有布尔表达式的值为 True 的语句,则不会执行任何语句块。 ## C# switch case 语句 C# switch case 语句也是条件语句的一种,但在判断条件的选择上会有一些局限性。 具体的语法形式如下。 ```C# switch(表达式) { case 值 1: 语句块 1; break; case 值 2: 语句块 2; break; ... default: 语句块 n; break; } ``` 在这里,switch 语句中表达式的结果必须是整型、字符串类型、字符型、布尔型等数据类型。 如果 switch 语句中表达式的值与 case 后面的值相同,则执行相应的 case 后面的语句块。 如果所有的 case 语句与 switch 语句表达式的值都不相同,则执行 default 语句后面的值。 default 语句是可以省略的。需要注意的是,case 语句后面的值是不能重复的。 ## C# for 循环 循环语句和条件语句一样都是每个程序中必不可少的,循环语句是用来完成一些重复的工作的,以减少编写代码的工作量。 C# for 循环是最常用的循环语句,语法形式非常简单,多用于固定次数的循环。 具体的语法形式如下。 ```C# for(表达式 1; 表达式 2; 表达式 3) { 表达式 4; } ``` 其中: - 表达式 1:为循环变量赋初值。 - 表达式 2:为循环设置循环条件,通常是布尔表达式。 - 表达式 3:用于改变循环变量的大小。 - 表达式 4:当满足循环条件时执行该表达式 4。 for 循环语句执行的过程是,先执行 for 循环中的表达式 1,然后执行表达式 2,如果表达式 2 的结果为 True,则执行表达式 4,再执行表达式 3 来改变循环变量,接着执行表达式 2 看是否为 True,如果为 True,则执行表达式 4,直到表达式 2 的结果为 False,循环结束。 - 提示:在 for 循环中表达式 1、表达式 2、表达式 3 以及表达式 4 都是可以省略的,但表达式 1、表达式 2、表达式 3 省略时它们之间的分号是不能省略的。 ## C# while 循环 C# while 循环与 for 循环类似,但是 while 循环一般适用于不固定次数的循环。 while 循环的语法形式如下。 ```C# while(布尔表达式) { 语句块; } ``` while 语句执行的过程是,当 while 中布尔表达式的结果为 True 时,执行语句块中的内容,否则不执行。通常使用 for 循环可以操作的语句都可以使用 while 循环完成。 ## C# do while 循环 C# do while 循环可以说是 while 循环的另一个版本,与 while 循环最大的区别是它至少会执行一次。 具体的语法形式如下。 ```C# do { 语句块; }while(布尔表达式); ``` do while 语句执行的过程是,先执行 do{} 中语句块的内容,再判断 while() 中布尔表达式的值是否为 True,如果为 True,则继续执行语句块中的内容,否则不执行,因此 do while 语句中的语句块至少会执行一次。 ## C# goto 语句 C# goto 语句用于直接在一个程序中转到程序中的标签指定的位置,标签实际上由标识符加上冒号构成。 语法形式如下。 ```C# goto Labell; 语句块 1; Labell 语句块 2; ``` 如果要跳转到某一个标签指定的位置,直接使用 goto 加标签名即可。 在上面的语句中使用了 goto 语句后,语句的执行顺序发生了变化,即先执行语句块 2,再执行语句块 1。 此外,需要注意的是 goto 语句不能跳转到循环语句中,也不能跳出类的范围。 由于 goto 语句不便于程序的理解,因此 goto 语句并不常用。 ## break 语句 break 语句可以用于退出 switch,for,foreach,while,do while 循环,该语句会使控制流执行循环后的语句。 ## continue 语句 continue 语句类似于 break 语句,也必须在 for,foreach,while,do while 循环中使用,但它只是退出循环的当前迭代,开始执行循环的下一次迭代,而不是退出循环。 ## return 语句 return 语句用于退出类的方法, 把控制权返回方法的调用者。如果方法有返回类型,return 语句必须返回这个类型的值;如果方法返回 void,应使用没有表达式的 return 语句。 ## 枚举 枚举是用户定义的整数类型。在声明一个枚举时,要指定该枚举的实例可以包含的一组可接受的值。 优点: - 枚举可以使代码更易于维护,有助于确保给变量指定合法的、期望的值。 - 枚举使代码更清晰,允许用描述性的名称表示整数值,而不是用含义模糊、变化多端的数来表示。 - 枚举也使得代码更易于输入。 枚举类型是一种值类型,定义好的值会存放到栈中。 枚举类型在定义时使用 enum 关键字表示,枚举类型的定义与类成员的定义是一样的,或者直接定义在命名空间中。 ```C# 注意不能直接将枚举类型定义到方法中。 ``` 定义枚举类型的变量的语法形式如下。 ```C# 访问修饰符 enum 变量名 : 数据类型 { 值l, 值2, } ``` 其中: 1. 访问修饰符 与类成员的访问修饰符一样,省略访问修饰符也是代表使用 private 修饰符的。 2. 数据类型 指枚举中值的数据类型。只能是整数类型,包括 byte、short、int、long 等。 3. 值 1、值 2、…… 在枚举类型中显示的值。但实际上每个值都被自动赋予了一个整数类型值,并且值是递增加 1 的,默认是从 0 开始的,也就是值 1 的值是 0、值 2 的值是 1。 如果不需要系统自动为枚举值指定值,也可以直接为其赋一个整数值。 每个没有指定值的枚举值,它的初始值都是上一个枚举类型的值加 1。 通常设置的枚举值都是不同的,其整数值也是不同的。 ## 命名空间 命名空间提供了一种组织相关类和其他类型的方式。与文件或组件不同,命名空间是一种逻辑组合,而不是物理组合。 ### using 语句 显然命名空间相当长,输入起来很繁琐,用这种方式指定某个类也不是总时必要的。 因此,要在文件的顶部列出类的命名空间,前面加上 using 关键字,在文件的其他地方,就可以使用其类型名称来引用命名空间中的类型了。 如果 using 语句引用的两个命名空间包含同名称的类型,就必须使用完整的名称,确保编译器知道访问哪个类型。 ### 命名空间的别名 using 关键字的另一个用途就是给类和命名空间指定别名。如果命名空间的名称特别长,又在代码中多次引用,但不希望该命名空间的名称包含在 using 语句中(避免类冲突)。 ## Main()方法 C#程序时从方法 Main()开始执行的。根据执行环境,有不同的要求: - 使用了 static 修饰符 - 在任意类中 - 返回 int 或者 void 类型 ## 注释 注释有助于阅读代码的其他开发人员理解代码。 ### 内部注释 单行注释: // 多行注释: /\* \*/ ## 预处理指令 源代码指定了程序的定义,预处理指令知识编译器如何处理源代码。 ### 基本规则 1. 预处理指令必须和 C#代码在不同行 2. 与 C#语句不用,预处理指令不需要已分号结尾 3. 包含预处理指令的每一行必须以#字符开始 - 在#字符前可以有空格 - 在#字符和指令之间可以用空格 4. 允许行尾注释 5. 在预处理指令所在行不允许分隔符注释 ### #define 和 #undef \#define 指令声明一个编译符号 可以定义符号,但是无法对符号赋值。 \#define 指令必须在使用任何也不是指令的指令之前出现在文件中。#define Debug1 必须写在所有 using 之前 用 #define 创建的符号的范围是在其中定义该符号的文件。 \#undef 指令取消一个编译符号 ### 条件编译 条件编译允许我们根据摸个编译符号是否被定义,标注一段代码被编译或跳过。 \#if,\#else,\#elif,\#endif ### 诊断指令 诊断指令产生用户自定义的编译时警告以及错误信息 \#warning,\#error ### 行号指令 行号指令可以做很多事情。 - 改变由编译器警告和错误消息报告的出现行数; - 改变被编译源文件的文件名; - 对交互式调试器隐藏一些行 ### 区域指令 区域指令允许我们标注和有选择性的命名一段代码 \#region,\#endregion