类
类
面向对象
- 面向对象的三个基本特征是:封装、继承、多态
- 封装:面向对象的特征之一,是对象和类概念的主要特性。
- 继承:面向对象编程 (OOP) 语言的一个主要功能。
- 继承概念的实现方式有三类
- 实现继承:使用基类的属性和方法而无需额外编码的能力
- 接口继承:仅使用属性和方法的名称、但是子类必须提供实现的能力
- 可视继承:子窗体(类)使用基窗体(类)的外观和实现代码的能力
- 继承概念的实现方式有三类
- 多态:允许将子类类型的指针赋值给父类类型的指针
- 实现多态的三种手段
- 虚方法
- 抽象类
- 接口
- 实现多态的三种手段
类的成员
- 数据成员:字段、常量
- 函数成员:
- 方法、属性、构造函数、析构函数
- 运算符、索引器
- 事件
- 类成员修饰符顺序: [ 特征 ] [ 修饰符 ] 核心声明
访问修饰符
Access Modifiers <br />数据修饰符 | Inside Assembly <br />程序集内 | Inside Assembly <br />程序集内 | Outside Assembly <br />程序集外 | Outside Assembly <br />程序集外 |
---|---|---|---|---|
With Inheritance 继承类 | With Type 类实例 | With Inheritance 继承类 | With Type 类实例 | |
Public | ✓ | ✓ | ✓ | ✓ |
Private | ✘ | ✘ | ✘ | ✘ |
Protected | ✓ | ✘ | ✓ | ✘ |
Internal | ✓ | ✓ | ✘ | ✘ |
Protected Internal | ✓ | ✓ | ✓ | ✘ |
在同程序集下,protected internal 与 internale 相同,体现的是 internal 的性质,即在派生类类内、类外均可访问继承的基类使用 protected internal 修饰的成员变量。
在非同程序集下,protected internal 与 protected 相同,体现的是 protected 的性质,即在只有在派生类的类内通过派生类实例的成员变量才能访问继承的基类使用 protected 修饰的成员变量。
嵌套类
- 嵌套类是声明在类型内部的类;
- 嵌套类成员对封闭类型的成员总是有完全访问权限;
- 封闭类型的成员总是能访问嵌套类型本身,但是只能访问声明了有访问权限的嵌套类的成员;
- 嵌套类的this引用指向嵌套类型的对象,而不是封闭类型的对象,嵌套类要想访问封闭类的对象需要将封闭对象的引用传递给嵌套类的构造函数;
构造函数、初始化器
// 构造函数
public RectangleBeam(Curve c, double h, double w)
{
...
}
// 用另一个构造函数初始化
public RectangleBeam(Curve c, double h, double w, Align ali) : this(c, h, w)
{
...
}
// 调用基类构造函数
public RectangleBeam(Curve c, double h, double w):base( c , h )
{
...
}
// 对象初始化器
Person person = new Person { Name = "Slark", Age = 100, Address = "Xi'an" }; // 初始化器{...}后;不能省略
析构函数
- 析构函数是用于释放非托管资源的,因此,对于只使用.NET类的程序无需编写析构函数;
- 每个类只能有一个析构函数,析构函数不能有参数,不能有访问修饰符;
- 声明:~ClassName {
…
} - 不能再代码中显示调用析构函数,垃圾回收器会自动调用析构函数;
- 析构函数应该只释放外部资源,不要在不需要是实现析构函数,这回严重影响性能;
- 析构函数不应该访问其他对象;
标准Dispose模式
析构函数无法及时被系统调用,需快速释放非托管资源时可以使用标准Dispose模式;
包含非托管资源的类应该实现IDisposable接口,其中包含单一方法Dispose,Dispose中包含用于释放资源的清除代码;
需要在代码中显示调用Dispose方法对资源进行释放,可以同时声明一个析构函数调用Dispose方法防止程序中没有调用Dispose;
同时应该在Dispose方法中调用GC.SuppressFinalize方法,通知CLR不要调用对象的析构函数,因为该对象已经被清除;
class MyClass:IDisposable { bool disposed = false;//释放状态 public void Dispose() { Dispose(true); //程序中可显示调用清理托管资源和非托管资源 GC.SuppressFinalize(this);//通知垃圾回收器全部资源已回收 } ~MyClass() { Dispose(false); } //垃圾回收器调用析构函数清理非托管资源 protected virtual void Dispose(bool disposing) { if(disposed==false) { if (disposing == true) { } //释放托管资源 //...释放非托管资源代码// disposed = true; } } }
Main方法
// 程序启动时不需要参数,程序结束时不返回值
static void Main () { … }
// 允许在程序启动时从命令行传入实参
// 可以有0个或多个命令行参数,0个时args也不为null
// 参数由空格或制表符隔开,每一个参数都被程序解释为字符串,无需使用引号
// Ctrl+R -> cmd 进入命令行环境并定位到程序文件夹输入:程序名 参数1 参数2 …
static void Main ( string [] args ) { … }
// 程序退出是返回给环境一个参数
// 一般用于报告程序是否成功,0通常表示成功
static int main (){ … }
static int main ( string [] args ){ … }
成员方法
- 形参表中参数顺序:必填参数、可选参数、params参数;
值类型参数和引用类型参数
引用参数使用 ref 修饰符
在方法结束之后,引用类型参数对应的对象被改变,值类型参数没有改变
值类型参数
方法的形参复制实参属于浅复制,方法结束后复制的实参也相应的被销毁
只有引用类型参数在堆中的引用会留下方法运行的影响
值类型参数为引用类型时实际上复制了地址
class MyClass { public int Val; } void Fun( MyClass f1 , int f2 ) { f1.Val+=5; f2+=5; } Static void Main ( ) { MyClass a1 = new MyClass ( ) ; int a2 = 10 ; Fun( a1, a2) ; }
引用类型参数
不会在栈上分配内存,而是复制引用指向相同的内存地址;
实参只能接受变量
引用参数没有复制实参,而是将形参设置为实参的别名,使用栈中同一个内存
class MyClass { public int Val; } void Fun( ref MyClass f1 , ref int f2 ) { fi.Val+=5; f2+=5; } Static void Main ( ) { MyClass a1 = new MyClass ( ) ; int a2 = 10 ; Fun( a1, a2) ; }
引用类型作为值参数和引用参数的区别
- 引用类型作为值参数传递:如果在方法内创建一个新对象并赋值给形参,将切断形参和实参之间的关联,方法调用结束后,新对象也被销毁
- 引用类型作为引用参数传递:如果在方法内创建一个新对象并赋值给形参,方法调用结束后,该对象依然存在,并且是实参所引用的值
输出参数 out 修饰符
int a; Fun(out a) { ... }
类似于ref引用参数,形参作为一个别名
参数数组 params 修饰符
// 声明 void ListInts ( params int[ ] val ) { … } // 调用 ListInts ( 10, 11, 12); int[ ] arr = {1, 2 ,3}; ListInts ( arr )
在一个形参列表中只能有一个参数数组且必须作为列表最后一个形参;
参数数组表示的所有参数必须具有相同的类型;
参数数组属于引用类型,它的所有数据都保存在堆中。
如果数组元素类型是值类型,那么方法调用时会将值复制到堆中,实参将不会受到方法的影响。
如果数组元素类型是引用类型,那么方法调用时会将引用复制到堆中,引用的位置的内容将会受到方法的影响。
如果使用数组作为实参,编译器将直接使用数组,而不会重新创建。
命名参数
- 格式:Fun ( name1: p1, name2: p2,
…
) - 位置参数和命名参数只是调用时采用不同的形式,与方法的声明无关;
- 位置参数:调用时严格按照方法声明时形参列表的顺序进行调用;
- 命名参数:调用时实参按照任意顺序,但是每个实参前面必须指明对应的形参名。
- 同时使用位置参数和命名参数时必须先列出位置参数。
可选参数
- 在方法声明时给参数赋初始值,该参数则为可选参数;
- 可选参数只能是值类型,引用类型只允许默认值为空;
- 形参表中参数顺序:必填参数、可选参数、params参数;
- 调用时不能任意省略可选参数,只能省略最后几个连续可选参数,否则会导致使用哪些可选参数不明确;
- 调用时如果不是从最后开始省略可选参数,则需要使用命名参数来明确所使用的参数。
栈帧和递归调用
栈帧是存储方法相关联的数据的内存,方法结束时整个栈帧都会从栈上弹出;
递归调用指方法调用自身;
随着递归越来越深,栈帧也越来越多,栈也越来越大。
索引器
索引器为类的多个数据成员提供 get 和 set 属性,通过索引器可以在多个可能的数据成员中进行选择。
ReturnType this [Type p1,...] { set { ... } get { ... } }
索引器和属性的区别
- 索引器和属性都有 set 和 get 访问器
- 索引器有一个参数列表
- 索引器使用 this 引用,而不是使用名称
部分类
- 将类的声明分割成几个部分进行声明。
- 分部类必须同时编译。
部分方法
分部方法分为定义声明和实现声明,定义包含签名和返回类型,实现包含签名、返回类型和语句块;
返回类型必须是void;
签名不能包含访问修饰符,分部方法是隐式私有;
分部方法不能有Virtual和Extern方法
分部方法可以有ref参数,不能有out参数
定义和实现必须使用关键字partial;
可以只有定义而没有实现;
public partial class People { public string Name { get; set; } public int Age { get; set; } partial void SetDefaultValue(); // 方法声明 } public partial class People { // 实现方法 partial void SetDefaultValue() { Name = "Zhangsan"; Age = 20; } }
类继承
屏蔽基类成员
- 使用 new 修饰符、具有相同的签名;
- 基类的访问使用
base.FunName( … );
基类的引用
派生类的引用指向整个类对象,包括基类部分;
基类对象的引用无法访问派生类中基类以外的部分。
示例:MyDerivedClass 继承自 MyBaseClass,MyDerivedClass 对象 derived,MyBaseClass 对象 mybc 可见性如图
虚方法和覆写方法
基类中使用 virtual 关键字声明虚方法;
派生类中使用 override 关键字重写基类中的虚方法;
虚方法和覆写方法签名、返回类型、可访问性必须相同;
虚方法和覆写方法不能用 static;
方法、属性、索引器、事件都可以被声明为 virtual 和 override;
基类对象通过虚方法将访问到派生类的覆写方法;
对于多层派生类的多层次的 override,使用基类对象访问虚函数时将直接定位的 override 方法的最高派生类中的覆写方法。
抽象类 abstract
- 声明:
abstract class MyClass { … }
- 不能创建抽象类的实例;
- 抽象类可以包含抽象成员和非抽象成员;
- 抽象成员在派生类中必须被实现,除非派生类也是抽象类。
抽象成员
- 必须是一个函数成员(方法、属性、事件、索引),必须用 abstract 修饰;
- 不能有实现代码块;
- 抽象成员只能在抽象类中声明;
- 抽象函数必须在派生类中实现,需要使用 override 修饰符;
密封类 sealed
- 声明:
sealed class MyClass { … }
- 密封类不能被继承
静态类 static
- 声明:
static public class MyClass { … }
- 静态类所有成员必须为静态成员;
- 静态类不能被继承,是隐式密封的;
- 用于存放不受实例数据影响的数据和函数。
扩展方法
允许扩展方法与另一个类进行关联,即将该方法作为另一个类的方法;
声明扩展方法的类必须是静态类,扩展方法必须是静态方法;
扩展方法必须包含关键字 this 并跟它所扩展的类的名称作为它的第一个参数类型;
class MyData { ... } // 在 MyData 类中增加一个 MyFun 的方法 // 直接使用 MyData 实例对象调用 MyFun static class ExtendMyData { public static void MyFun(this MyData md) { ... } }