C#学习笔记
前言
参考书籍:《C#图解教程》
一、C#编程概述
一个简单的C#程序
1 |
|
文件名为SimpleProgram.cs
,这点和Java不太一样,类名和文件名可以不一样。
输入和输出字符串
使用函数Write
和WriteLine
,其中格式化方法可以使用类似python的方法。例如:
1 |
|
对数字格式化时可以使用更复杂的表达方式如**{index,alignment:format}**其中index是必选项。
二、类型、储存和变量
栈和堆
以下数据存放在栈中:
- 某些类型变量的值
- 程序当前的执行环境
- 传递给方法的参数
值类型和引用类型
值类型只需要一段单独的内存,用于储存实际的数据,它总是位于堆中。
引用类型需要两段内存,第一段储存实际的数据,它总是位于堆中;第二段是一个引用,指向数据在堆中存放的位置。
三、类
类有两种成员,一种是数据成员,一种是函数成员。
类成员
如果没有初始化,字段的值会被编译器设置为默认值(0和null)。
实例化
实例化一个类的手段和C++类似,都是使用new关键字,同时带上圆括号,需要时可以加上参数。
访问修饰符
- private只在类的内部可访问
- public任何类可以访问
- protected所有继承该类的类可以访问
- internal该程序集内声明的类可以访问
- protected internal所有继承该类或者在该程序集内声明的类可以访问
静态成员
可以从类的内部和外部访问这个静态成员,从外部访问时,可以使用using static
关键字,从而省去前缀。
静态字段的生命期不需要依赖类,而普通字段只有在实例创建后才开始它的生命期。
静态函数成员也类似,但是静态函数成员不能访问实例成员
常量
常量比较特殊,它们表现得像静态值,但是它们没有真正的存储位置,而是在编译时被编译器替换,类似C/C++的define
属性
属性与字段的相同点和不同点如下:
- 相同
- 都是类成员
- 都有类型
- 都可以被赋值和读取
- 不同
- 属性是一个函数成员
- 属性不一定为数据存储分配内存
- 它执行代码
且需要一个set访问器和get访问器
- set访问器总是拥有一个单独的、隐式的值参,名称为value,与属性的类型相同,拥有一个返回类型void
- get访问器没有参数,返回一个与属性类型相同的返回类型
1 |
|
属性访问器尝尝使用字段作为后备储存。
不能显式地调用set和get访问器,只能使用类似赋值语句的方法。
lambda表达式
在C# 7.0以上,引入的lambda表达式,可以用更简洁的语言来表达访问器。
1 |
|
只有get访问器的属性称为只读属性,只有set访问器的属性称为只写属性,一个属性至少要有两种访问器的其一,否则编译器会报错。与字段相比,属性的好处是可以在读取和输出时完成一些处理。
自动属性
如果想要使用自动属性的话,将get和set的方法体省略,如下
1 |
|
静态属性
静态属性有以下特点:
- 不能访问类的实例成员,但能被实例成员访问
- 不管类是否有实例,它都存在
- 在类的内部,可以仅使用名称来引用静态属性
- 在类的外部,可以通过类名或者使用using static结构来引用静态属性
构造函数
如果没有为类声明任何构造函数,则编译器会自动定义一个无参数、无函数体的默认构造函数;而如果你已经声明了任何一个构造函数,则这个默认构造函数将不会存在。
静态构造函数
当静态成员被调用时,静态构造函数会随之被调用,静态构造函数只会被调用一次,用于初始化静态成员变量。
readonly修饰符
与const区别如下:
- const只能在字段的声明语句中初始化,而readonly还可以在类的任何构造函数中使用。
- const的值必须在编译时确定,而readonly可以在运行时确定。
- const是静态的,在内存中没有存储位置,而readonly有。
索引器
索引器是一组get和set访问器,与属性类似,属性通常表示单个数据成员,而索引器表示多个数据成员。可以认为索引器是为类的多个数据成员提供set和get访问的属性。
索引器总是实例成员,因此不能被声明为static
声明索引器
1 |
|
使用this关键字作为名称,参数列表在方括号中,且至少声明一个参数。
索引器可以理解为,用方括号中的参数作为索引,然后在get和set中人为地判断当前索引所指向的成员字段,从而在后续的编程中节省精力。索引器像方法一样,可以被重载,只需要参数类型不同
四、方法
参数
形参:声明在方法的参数列表中
实参:由于初始化形参和表达式或变量
值参数
值类型作为值参数在被传递时,会在栈中开辟一个空间,并将值复制给形参。所以,在方法中无法改变值参数原来的值(除非采用ref关键字),与此相对的是引用类型作为值参数,在方法内修改引用参数的字段时,会相应地修改原实参的值。
引用参数
特点是:
- 不会在栈上为形参分配内存
- 形参的参数名作为实参的别名
使用ref关键字来声明一个引用参数。
引用类型作为值参数传递
此时,若从方法体内部修改这个引用类型变量,会直接切断形参与实参之间的联系,可以把引用类型看成一个指针,可以通过指针修改引用类型变量的字段值,但是若想要修改这个引用变量本身,则不会对实参产生任何效果。
引用类型作为引用参数传递
此时可以把形参当做实参的别名使用。
输出参数
使用out关键字来表达一个输出参数,输出参数有以下要求:
- 在方法内部,给输出参数赋值后才能读取它。
- 在方法内部,代码中的每条路径都必须为所有输出参数赋值。
在C# 7.0中,可以不需要预先声明一个变量来用作out参数。
1MyMethod(out MyClass a1, out int a2);
参数数组
关键字为params。
参数数组的重点有:
- 一个参数列表中只能有一个参数数组
- 如果有,它必须是最后一个
- 参数数组表示的所有参数必须是同一类型
1 |
|
可以传递由逗号分隔的若干个参数,或一个一维数组来作为实参,当数组在堆中被创建时,实参的值被复制到数组中,所以,可以说参数数组是值参数。
ref局部变量和ref返回
1 |
|
创建一个别名,对x和y变量的修改变得同步。
可以将此技巧和ref返回共同使用。
1 |
|
这样,就可以获得类成员的引用(即使是private限制)
这个功能有以下限制:
- ref return表达式不能返回以下内容:
- 空值
- 常量
- 枚举成员
- 类或者结构体的属性
- 指向只读位置的指针
- 只能指向原先就在调用域内的位置或者字段,不能指向方法内的局部变量
- ref局部变量只能被赋值一次
- 即使一个方法被声明为ref返回方法,这个方法返回时没有加ref关键字,则这个方法返回的也只是普通类型
命名参数
类似python,但是使用的是冒号而不是等于号
1 |
|
这样可以不按声明的顺序来调用方法,且代码可读性也得到了提升,但是位置参数必须在命名参数前面列出。
可选参数
类似python中的默认参数,调用时可以不为其赋值,则采用声明时的默认值。
运算符重载
1 |
|
该方法必须是静态的和公共的,且运算符必须是要操作的类或者结构的成员。
需要注意,递增和递减运算符的前置和后置操作是有所区别的。
五、类和继承
屏蔽类的成员
可以使用相同的名称来屏蔽父类的成员,若想屏蔽函数成员,则签名必须相同。要让编译器知道你在故意屏蔽父类的成员,可以使用new修饰符,否则编译器可以会发出警告。
访问类的成员
与其他语言不同,C#使用base关键字来表示父类(或者说是基类)
使用基类的引用
可以用类型转化运算将派生类转化为基类,即使两个引用指向的是同样一个实例,但是派生类类型的引用可以“看到”更多成员,而基类类型的引用只能看到在基类中声明的内容(即使被屏蔽)。
虚方法和覆写方法
想要在基类中使用派生类的方法,可以使用虚方法和覆写方法来实现,这个方法有以下要求:
- 派生类的方法和基类的方法要有相同的签名
- 基类的方法使用virtual标注
- 派生类的方法使用override标注
这样,即使派生类的实例被转化成基类,也可以通过这个基类的引用来访问到派生类中的方法。
可以继续让新的类继承这个派生类,并再次覆写某个方法,这样调用基类时,就会优先调用覆写层数最高的方法。
与屏蔽(使用new修饰符)不同,屏蔽的方法只会向上传递一级。
构造函数的执行
构造函数会先执行基类的构造函数。默认情况下,派生类会调用基类的无参数构造函数,但是当你不希望调用无参数构造函数,或基类压根就没有无参数构造函数时,必须在构造函数初始化语句中指定它。
调用父类的构造函数:
1 |
|
调用自己的构造函数:
1 |
|
这种手段在存在好几种构造方法时特别好用。
抽象类
抽象类不能被实例化,只能被继承。
抽象成员和虚成员类似,但是抽象成员没有实现体,必须在派生类中被实现;且抽象方法只能在抽象类中存在。
密封类
使用sealed关键字来制定一个密封类,密封类无法被继承。
六、语句和运算符
typeof
typeof返回一个System.Type对象,参数是一个类。
1 |
|
nameof
nameof返回一个字符串,这个字符串是一个变量的非限定名称。
using语句
using语句和using指令不同,using语句是用于打开文件的(类似python的with语句),而using指令是对某个包的使用
1 |
|
相当于using语句隐式地使用了try…finally…语句。using语句的括号中可以使用多个资源。
七、枚举
1 |
|
默认情况下,编译器把int类型的0,1,2…赋值给枚举值,也可以人为地为其赋值来设置自己想要的值。枚举变量可以作为右值赋值给其他变量。
1 |
|
上面这个程序段显示了如何显示和隐式地给枚举赋值,值得注意的是,枚举中的值可以重复。
位标志
可以为枚举值赋诸如0x01,0x02,0x04,0x08这样的值来使用位标志。
1 |
|
八、数组
数组是对象,其表达方式和C语言有所不同
1 |
|
交错数组
1 |
|
交错数组的声明方式与矩形数组有所不同,可以把它看成是数组的数组,声明之后在为第一个维度的数组赋值。
交错数组可以和foreach
语句配合使用
1 |
|
数组的成员
成员 | 类型 | 生存期 | 意义 |
---|---|---|---|
Rank | 属性 | 实例 | 数组的维度数 |
Length | 属性 | 实例 | 所有维度的元素总数 |
GetLength | 方法 | 实例 | 返回指定维度的长度 |
Clear | 方法 | 静态 | 将某一范围内的元素设置为0或者null |
Sort | 方法 | 静态 | 对一维数组进行排序 |
BinarySearch | 方法 | 静态 | 二分搜索 |
Clone | 方法 | 实例 | 浅复制 |
IndexOf | 方法 | 静态 | 返回一维数组中遇到的第一个值 |
Reverse | 方法 | 静态 | 反转一维数组中某一范围内的元素 |