想要学好面向对象编程,除了掌握语法结构外最重要的就是熟悉面向对象三大特性,这三大特性不是相互独立的而是相互关联、你中有我我中有你的关系,想要真正了解三大特性必须把这三部分当做一个整体来考虑。
封装
封装就是通过给类及类中的成员变量、属性和方法设置不同的访问修饰符(public、protected、internal、protected internal、private)来控制它们的作用范围,以达到封装的目的。
各访问修饰符作用范围如下:
访问修饰符 |
说明 |
public |
公有访问。不受任何限制。 |
protected |
保护访问。只限于本类内部和子类内部可访问,实例不能访问。 |
internal |
内部访问。只限于本项目内访问,其他不能访问。(相当于项目内的public) |
protected internal |
内部保护访问。只限于本项目和子类访问,其他不能访问。(protected和internal的权限之和) |
private |
私有访问。只限于本类成员内访问,子类、实例都不能访问。 |
protected类型解释:
若被引用的程序集中有子类A:B,那A中public类型的属性、方法和B中public类型的属性、方法都既可以通过A的实例或类名来调用,又可以在A内部直接使用,但A、B中protected类型的属性、方法只能在A内部直接使用,不能通过A的实例或类名来调用;
若引用程序集的一方C继承了A,那A、B、C中public类型的属性、方法都既可以通过C的实例或类名来调用,又可以在C内部直接使用,但A、B、C中protected类型的属性和方法只能在C内部直接使用,不能通过的实例或类名来调用。
默认访问修饰符
在命名空间内部的所有类型(class、struct、abstract class、interface、delegate、enum)的访问修饰符默认是internal,可以人为改成public类型,不能改成protected、protected internal、private类型。
1、命名空间内部各类型使用默认访问修饰符
ConsoleTest项目引用Encapsulation.dll后

2、命名空间内部各类型使用public访问修饰符
ConsoleTest项目引用Encapsulation.dll后
3、类内部的所有成员,默认均为private类型。
4、抽象类的所有成员,默认均为private类型,但抽象方法不能用private修饰。
5、接口的所有成员,默认均为public类型,而且不能手动添加访问修饰符。
6、结构的所有成员,默认均为private类型,而且只能是public、internal、private这三种类型。
继承
1、被继承的类成为父类、基类、超类,而主动继承的类成为子类或派生类。子类继承父类的状态和行为,同时也有自己的特性。
2、System.Object是所有类型的基类
3、C#中继承的写法,class A:B{ }
4、继承具有传递性,由A:B B:C=>A:C
5、构造方法不能继承
6、C#只允许单继承,一个类只能继承于一个父类
7、密封类不能被继承,例如:sealed class Animal{ }
8、子类不仅继承了父类的公有成员,同时继承了父类的私有成员,只是父类的私有成员在子类中不可被访问。
9、class SubClass:MainClass{ }
SubClass subClass = new SubClass();
subClass.MethodTwo();
subClass.MethodThree();
代码解释:
创建subClass对象时会先执行父类的构造方法,再执行子类的构造方法;
创建subClass对象时默认调用父类的无参构造方法,我们可以显示调用父类的含参构造方法;
subClass.MethodTwo();会直接执行MethodTwo()方法;
subClass.MethodThree();会直接执行MethodThree()方法;
如果MainClass还有父类C,那创建subClass对象时就要首先调用C的构造方法,其次调用MainClass的构造方法,最后调用SubClass的构造方法。
10、当父类和子类中有名称相同的方法时,父类中的方法会被隐藏。
假设父类和子类的重名方法为SayHi,发生隐藏时子类中的写法为public new void SayHi(){ },其实子类的方法不加new也可以,因为发生隐藏时系统默认会自动加上。
11、A:B,A和B都有方法SayHi(),当B中的SayHi()是一个虚方法(virtual)时,才能在子类A中被重写/覆写,通过override关键字重写。
重写是对继承自父类的SayHi()方法进行修改,隐藏与重写的本质是完全不同的。
包含关键字virtual、abstract、override的方法才能被重写,密封方法(sealed)不能被重写。
12、里氏替换原则:子类对象可以赋值给父类变量,反之不成立。
A:B,A和B中都有方法SayHi(),当B b = new A();,b.SayHi();默认情况下(SayHi()没有在A中被重写)根据就近原则调用父类B中的SayHi()方法。如果方法SayHi()在子类中被重写(override),那b.SayHi()调用的就是子类A中的SayHi()方法。
实例解释:
Person p = new Chinese();
p.SayHi();
子类Chinese没有重写SayHi()时,p.SayHi()调用的是父类中的SayHi()方法;
子类Chinese重写了SayHi()时,pSayHi()调用的是子类中的SayHi()方法。
Person p = new Person();
p.SayHi();
无论子类Chinese是否重写了SayHi()方法,p.SayHi()调用的都是Person类中的SayHi()方法。
多态
多态有两种形式:重载、重写/覆写。
1、重载
同一个类中有多个方法的名称相同、参数个数不同或类型不同,则属于重载。
同一个类中有多个方法名称相同、参数个数相同、参数类型相同、返回值类型不同,这样不属于重载,编译时会报错。
2、重写/覆写
实现重写有三种情况:
普通类中的virtual方法可在子类中被重写;
抽象类中的abstract方法和abstract属性必须直接或间接在子类中被重写;
接口中的所有成员都必须被子类实现。
第一种情况:
(1)当类为普通类时,只有父类的虚方法才能被子类重写,子类实现重写要用关键字override,如果不加override就会隐藏父类的方法,隐藏需要加上关键字new,例如:public new static void a(){}或public static new void a(){}。
(2)静态类中只能有静态成员,非静态类中可以有静态成员和非静态成员。
(3)静态方法不能被重写,但可以被隐藏。
(4)静态成员不能带override、abstract、virtual。
第二种情况:
(1)抽象类中可以有抽象属性、抽象方法和成员实例,继承抽象类的子类必须直接或间接重写所有的抽象方法,而且参数和返回值类型必须完全一致,成员实例可以不被重写。
(2)非抽象类中只能有成员实例。
(3)抽象类不能直接实例化,但是有构造方法,可以利用里氏替换原则把子类对象赋值给抽象的父类变量。
AbsSubClass absSubClass = new AbsSubClass();
AbsClass absClass = new AbsSubClass();
创建absSubClass对象或absClass对象时,仍然是先执行父类的构造方法,再执行子类的构造方法。
(4)抽象方法必须用abstract修饰,而且不能有方法体。
(5)抽象方法不能用private修饰。
第三种情况:
(1)C#中类是单继承,接口是多继承,如果同时继承类和接口的话,接口必须在基类的后面,否则编译会出错。例如:class SubClass:MainClass,ISay,IEat{ }
(2)接口不能直接实例化,没有构造方法,可以利用里氏替换原则把子类对象赋值给接口类型的父类变量。

(3)接口中的成员没有访问修饰符,默认是public类型的,而且不能手动添加任何访问修饰符。
(4)实现接口时不需要override,实现接口的类中的方法的参数和返回值类型必须与接口中定义的类型完全一致。
(5)接口命名一般以I开头,表示某种功能,实现某一接口表示具备了某一功能。
三大特性之间的关系
1、继承时父类中的内容需要封装,例如:父类中的某些内容不希望被子类使用,那就需要使用private访问修饰符。
2、重写形式的多态必须依靠继承,不继承是无法实现重写的,这无须多说。
3、继承具有封装功能,继承分普通类、抽象类、接口三种情况,下面举例说明。
普通类继承封装功能实例:
(1)父类截图
(2)子类截图
(3)测试父类变量可调用的方法
由于MainClass中public类型的方法只有A和B,因此通过MainClass的实例对象只能调用A和B。
(4)测试被子类对象赋值的父类变量可调用的方法
虽然SubClass中public类型的方法有A、B、MethodTwo、MethodThree,但是被子类对象赋值的mainClass,只能调用A和B,相当于封装了子类SubClass。
(5)测试子类变量可调用的方法
如你所料SubClass类及其父类MainClass中所有的public方法都可以通过SubClass对象调用。

抽象类继承封装功能实例:
(1)父类截图
(2)子类截图
(3)测试被子类对象赋值的抽象的父类变量可调用的方法
虽然AbsSubClass中public类型的方法有A、B、MethodTwo,但是被子类对象赋值的absSubClass,只能调用A和B,相当于封装了子类AbsSubClass。
(4)测试子类变量可调用的方法
如你所料AbsSubClass类及其父类AbsClass中所有的public方法都可以通过AbsSubClass对象调用。

接口继承封装功能实例:
(1)父类截图
(2)子类截图
(3)测试被子类对象赋值的接口类型的父类变量可调用的方法
虽然Person中public类型的方法有SayChinese、SayChinese、MethodOne,但是被子类对象赋值的person,只能调用SayChinese和SayChinese,相当于封装了子类Person。
(4)测试子类变量可调用的方法
如你所料Person类及其父类ISay中所有的public方法都可以通过Person对象调用。
