C#面向对象三大特性
面向对象的三个基本特征是:封装、继承、多态。
封装:
隐藏内部功能的具体实现,只保留和外部交流数据的借口。就好比电视机,用一个外壳把内部零部件及控制电路封装起来,只提供按钮或者遥控器接口供人使用。
封装可以隐藏实现细节,使得代码模块化。
封装成类或结构:
类和结构实际上是创建对象的模板,每个对象都包含数据,并提供了处理和访问数据的方法。
(1)类定义了每个类对象(实例)可以包含什么数据和功能。举例来说,如果一个类表示客户,我们就可以定义字段customerId,name和address,以包含该客户的信息,还可以定义处理存储在这些字段中的数据的功能。接着,如我们常写的那样,new一下实例化对象,以表示某个客户,并为这个实例设置这些字段,使用其功能。
结构在内存中的存储方式(类是存储在堆(heap)上的引用类型,而结构是存储在堆栈(stack)上的值类型)、访问方式和一些特征(如结构不支持继承)与类不同。较小的数据类型使用结构可以提高性能。但在语法上,结构和类非常相似,主要的区别是使用关键字struct代替class来声明结构,在c#中,可以把结构看做是缩小的类。
(2)类成员
类中的数据和函数称为类的成员。类成员可以分为数据成员和函数成员。
数据成员包含了类的数据——字段、常量和事件。
函数成员提供了操作类中数据的某些功能,包括方法,构造函数,属性和终结器(finalizer)、运算符和索引器。
(3)特殊的类
在封装类的时候,我们可能会按照实际的需要构造一些“特殊”的类(比如静态类,抽象类等),下面简要介绍说明一下常见的特殊的类。
<1>、静态类
如果类只包含静态的方法和属性,该类就可以是静态的。静态类在功能上与使用私有静态构造函数创建的类相同。不能创建静态类的事例。使用static关键字,编译器可以检查以后是否给该类添加了实例成员,如果是,就会产生一个编译错误。
<2>、密封类
c#允许把类和方法声明为sealed。对于类来说,这就表示不能继承该类;对于方法来说,这表示不能重写该方法。但是在方法上使用sealed是没有意义的,除非该方法本身是某个基类上另一个方法的重写形式。如果定义一个新方法,但不想让别人重写它,首先就不要把它声明为virtual。但如果要重写某个基类方法,sealed关键字就提供了一种方式,可以确保为方法提供的重写代码是最终的代码,其他人不能再重写它。
<3>、抽象类
c#允许把类和方法声明为abstract,抽象类不能被实例化,而抽象函数没有执行代码,必须在非抽象的派生类中重写。
<4>、部分类
partial关键字允许把类,结构或接口放在多个文件中。一般情况下,一个类存储在单个文件中。但有时,多个开发人员需要访问一个类,或者某种类型的代码生成器生成了一个类的某部分,所以把类放在多个文件中是有益的。
<5>、Object类
众所周知,.net类都派生自System.Object。这个类的方法是所有.net类都实现的方法。
方法 访问修饰符 作用
string ToString() public virtual 返回对象的字符串表示
int GetHashCode() public virtual 在实现字典(散列表)时使用
bool Equals(object obj) public virtual 对对象的实例进行相等比较
bool Equals(object obja,object objb) public static 对对象的实例进行相等比较
bool ReferenceEquals(object obja,object objb) public static 比较两个引用是否指向同一个对象
Type GetType() public 返回对象类型的详细信息
object MemberwiseClone() protected(不能重写) 进行对象的浅表复制
void Finalize() protected virtual 析构函数的.net版本
(2)结构(struct)
在许多方面,可以把c#中的结构看做是缩小的类。它基本上与类相同,但更适合于把一些数据组合起来的场合。下面介绍它与类的区别:
<1>、结构是值类型
结构是值类型,它存储在堆栈中或存储为内联(inline)(如果它是另一个对象的一部分,就会保存在堆中),其生存期的限制与简单的数据类型一样。
注意:因为结构是值类型,所以new运算符和类及其他引用类型的工作方式不同。new一个结构并不分配堆中的内存,而是调用相应的构造函数,根据传递给它的参数,初始化所有的字段。
<2>、结构不能被继承
结构不是为继承设计的。不能从一个结构中继承,唯一的例外是结构派生于类System.Object.因此结构也可以访问System.Object的方法。结构的继承链是这样的:每个结构派生于System.ValueType,System.ValueType派生于System.Object。System.ValueType并没有给System.Object添加任何新成员,但提供了一些更适合结构的执行代码。
注意:结构可以实现接口。也就是说结构并不支持实现继承,但支持接口继承。
a、结构总是派生于System.ValueType,它们还可以派生于任意多个接口。
b、类可以派生于用户选择的另一个类,它们还可以派生于任意多个接口。
<3>、结构的构造函数(结构不能包含显式的无参构造函数)
为结构定义构造函数的方式与为类定义构造函数的方式相同,但不允许定义无参数的构造函数,其原因影藏在.net运行库的执行方式中。即:.net运行库不能调用用户提供的定制无参数构造函数,因此ms禁止在c#中的结构内使用无参数的构造函数。
继承:
继承最大的好处是实现代码的高效重用,也更加形象的描述现实世界中对象的关系。
继承的使用:
程序中使用面向对象的继承特性时,主要分为单继承和多继承两种情况,下面分别进行介绍
1.单继承
单继承一般用于类之间的继承,C#中的类只支持单继承,实现单继承时,使用“子类:基类”格式。
2.多继承
如果要使用多继承,需要使用接口,因为C#中的类只支持单继承,而接口支持多继承,实现多继承时,继承的多个接口中间用逗号(,)隔开。
继承的原则:
1.除了object类,每个类有且只有一个直接基类,如果没有显示指定类的直接基类,那么它的直接基类就隐含的设置为object。object类没有任何直接或间接基类,
它是所以有的终极基类。
2.无论基类成员的可访问性如何,除构造函数和析构函数外,所有其他基类的成员都能被子类继承,然而,有些继承成员在子类中可能是不可访问的,比如,基类的private成员在子类中不可访问,但是,如果将子类的对象作为参数传入基类的方法内,那么在基类的代码内部,就可以通过子类或者子类的对象来访问基类的private成员。
3.子类可以扩展它的直接基类。
4.继承是可以传递的,比如C类从B类继承,而B类从A类继承,那么C类就会既继承B类中的成员,又继承A类中的成员。
5.类不能循环继承,比如A类继承于B类,而B类继承于C类,那么C类就不能再去继承A类,因为它们之间存在了一种循环关系。
6.类的直接基类必须至少与类本身具有同样的可访问性,比如,如果从private类派生一个public类,将会导致编译时错误。
7.在子类中可以声明具有相同名称或签名的新成员来隐藏从基类继承而来的成员,但是,隐藏继承而来的成员时并不移除该成员,而只是使被隐藏的成员在子类中不可以直接访问。
8.类中可以声明虚方法等,而子类可以重写这些虚方法的实现
9.C#中只支持类的单一继承,但是支持接口的多重继承。
10.类的实例包含在该类中及它的所有基类中声明的所有实例字段的集合,并且存在一个从子类到它的任何基类的隐式转换,因此,可以将子类的实例看成是其任何基类的实例的引用。
扩充【接口的实现】:
接口的实现通过类继承来实现,一个类虽然只能继承一个基类,但可以继承任意多个接口。声明实现接口的类时,需要在基类列表中包含类所实现的接口的名称。
C#中实现继承的语法格式如下:
class DerivedClass: BaseClass { }
说明:
继承类时,必须在子类和基类之间用冒号(:),另外,如果继承多个接口,那么在继承的每个接口之间用逗号分割(,)。
扩充【显式接口成员实现】
如果类实现两个接口,并且这两个接口包含具有相同签名的成员,那么在类中实现该成员时,将导致两个接口都使用该成员作为它们的实现,然而,如果两个接口成员实现不同的功能,那么这可能会导致其中一个接口的实现不正确或两个接口的实现都不正确,这时可以显式地实现接口成员,即创建一个仅通过该接口调用并且特定于该接口的类成员。显式接口成员实现是使用接口名称和一个句点命名该类成员来实现的。
扩充【接口特征】
1.接口类似于抽象基类:继承接口的任何非抽象类型都必须实现接口的所有成员;
2.不能直接实例化接口;
3.接口可以包含事件、索引器、方法和属性;
4.接口不包含方法的实现;
5.类和结构可从多个接口继承;
6.接口自身可从多个接口继承。
说明:
接口使得服务的协议与实现相分离,它是组件编程的基础,在组件编程中,接口是组件向外公布其功能的唯一方法。
扩充【声明接口】
C#中使用interface关键字声明接口,其语法格式如下:
修饰符 interface 接口名称 :继承的接口列表
{
接口内容;
}
说明:
(1)声明接口时,通常以大写字母“I”开头;
(2)声明接口时,除interface关键字和接口名称外,其它的都是可选项;
(3)可以使用new 、public、protected、internal和private等修饰符声明接口,但接口成员必须是公共的。
多态:
即同一个动作作用不同的对象产生不同的具体行为。比如,驾驶是一个动作,但是把驾驶作用在汽车和飞机上时,产生了不同的具体的驾驶操作与过程。它的好处是规范和简化接口的设计。比如,你所见到的电器的开关标记符号基本都是一样的,这样可以方便用户识别和理解。简单来说就是(使用基类或接口变量编程)
在多态编程中,基类一般都是抽象类,其中拥有一个或多个抽象方法,各个子类可以根据需要重写这些方法。或者使用接口,每个接口都规定了一个或多个抽象方法,实现接口的类根据需要实现这些方法。
因此,多态的实现分为两大基本类别:继承多态和接口多态。
继承多态
设想动物园饲养员每天需要给他所负责死样的狮子、猴子和鸽子喂食。
首先,建立三个类分别代表三个动物
饲养员用Feeder类表示。由于三种动物吃的动物一样,Feeder类必须拥有三个喂动物的公共方法:
过程如下:
如果领导有又把熊猫交给他管理,这是我们的程序不得不给Feeder类在增加一个方法:FeedPanda();
万一小李后来又不管猴子了,又要从Feeder类中删除FeedPigeon()方法。
所以这种编程方式很明显不合理的。
我们可以应用多态的方法解决。
首先因为它们都是动物,因此,可以建立一个Animal抽象基类。
由于不同的动物吃不同的食物,所以在Animal类中定义一个抽象方法:eat();有子类负责实现。
现在,可以将Feeder类的三个喂养方法合并为一个FeedAnimal:
Feeder类代码:
喂养过程:
我们修改一下Feeder类的定义,增加一个新方法FeedAnimals(),新方法远程的功能是喂养一群动物,接受的是Animal的数组:
过程如下:
代码中数组ans的元素为Animal,因此,可以在其中存入任何一个Animal的子类。具有这种特性的数组成为"多态数组"。
编程中应用多态,可以将其简化为一下两句:
应用继承实现对象的统一管理。
应用接口定义对象的行为特征。
使用多态的好处:
当要修改程序并扩充系统时,需要修改的地方较少,对其他部分代码的影响较小。