抽象类 & 接口
一、抽象类:
抽象类是特殊的类,只是不能被实例化;除此以外,具有类的其他特性;重要的是抽象类可以包括抽象方法,这是普通类所不能的。抽象方法只能声明于抽象类中,且不包含任何实现,派生类必须覆盖它们。另外,抽象类可以派生自一个抽象类,可以覆盖基类的抽象方法也可以不覆盖,如果不覆盖,则其派生类必须覆盖它们。
二、接口:
接口是引用类型的,类似于类,和抽象类的相似之处有三点:
1、不能实例化;
2、包含未实现的方法声明;
3、派生类必须实现未实现的方法,抽象类是抽象方法,接口则是所有成员(不仅是方法包括其他成员);
另外,接口有如下特性:
接口除了可以包含方法之外,还可以包含属性、索引器、事件,而且这些成员都被定义为公有的。除此之外,不能包含任何其他的成员,例如:常量、域、构造函数、析构函数、静态成员。一个类可以直接继承多个接口,但只能直接继承一个类(包括抽象类)。
三、抽象类和接口的区别:
1.类是对对象的抽象,可以把抽象类理解为把类当作对象,抽象成的类叫做抽象类.而接口只是一个行为的规范或规定,微软的自定义接口总是后带able字段,证明其是表述一类类“我能做。。。”.抽象类更多的是定义在一系列紧密相关的类间,而接口大多数是关系疏松但都实现某一功能的类中.
2.接口基本上不具备继承的任何具体特点,它仅仅承诺了能够调用的方法;
3.一个类一次可以实现若干个接口,但是只能扩展一个父类
4.接口可以用于支持回调,而继承并不具备这个特点.
5.抽象类不能被密封。
6.抽象类实现的具体方法默认为虚的,但实现接口的类中的接口方法却默认为非虚的,当然您也可以声明为虚的.
7.(接口)与非抽象类类似,抽象类也必须为在该类的基类列表中列出的接口的所有成员提供它自己的实现。但是,允许抽象类将接口方法映射到抽象方法上。
8.抽象类实现了oop中的一个原则,把可变的与不可变的分离。抽象类和接口就是定义为不可变的,而把可变的座位子类去实现。
9.好的接口定义应该是具有专一功能性的,而不是多功能的,否则造成接口污染。如果一个类只是实现了这个接口的中一个功能,而不得不去实现接口中的其他方法,就叫接口污染。
10.尽量避免使用继承来实现组建功能,而是使用黑箱复用,即对象组合。因为继承的层次增多,造成最直接的后果就是当你调用这个类群中某一类,就必须把他们全部加载到栈中!后果可想而知.(结合堆栈原理理解)。同时,有心的朋友可以留意到微软在构建一个类时,很多时候用到了对象组合的方法。比如asp.net中,Page类,有Server Request等属性,但其实他们都是某个类的对象。使用Page类的这个对象来调用另外的类的方法和属性,这个是非常基本的一个设计原则。
11.如果抽象类实现接口,则可以把接口中方法映射到抽象类中作为抽象方法而不必实现,而在抽象类的子类中实现接口中方法.
虚函数、纯虚函数、非虚函数
虚函数和纯虚函数有以下所示方面的区别。
(1)类里如果声明了虚函数,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被覆盖,这样的话,这样编译器就可以使用后期绑定来达到多态了。纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。
(2)虚函数在子类里面也可以不重载的;但纯虚函数必须在子类去实现,这就像Java的接口一样。通常把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为很难预料到父类里面的这个函数不在子类里面不去修改它的实现。
(3)虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然大家也可以完成自己的实现。纯虚函数关注的是接口的统一性,实现由子类完成。
(4)带纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。抽象类和大家口头常说的虚基类还是有区别的,在C#中用abstract定义抽象类,而在C++中有抽象类的概念,但是没有这个关键字。抽象类被继承后,子类可以继续是抽象类,也可以是普通类,而虚基类,是含有纯虚函数的类,它如果被继承,那么子类就必须实现虚基类里面的所有纯虚函数,其子类不能是抽象类。
纯虚函数
声明了纯虚函数的类是一个抽象类。所以,用户不能创建类的实例,只能创建它的派生类的实例。
纯虚函数最显著的特征是:它们必须在继承类中重新声明函数(不要后面的=0,否则该派生类也不能实例化),而且它们在抽象类中往往没有定义。
定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。
纯虚函数的意义,让所有的类对象(主要是派生类对象)都可以执行纯虚函数的动作,但类无法为纯虚函数提供一个合理的缺省实现。所以类纯虚函数的声明就是在告诉子类的设计者,“你必须提供一个纯虚函数的实现,但我不知道你会怎样实现它”。
顺便说一句,为一个纯虚函数提供定义也是可能的。也就是说,你可以为纯虚函数提供实现,C++编译器也不会阻拦(DEV_CPP中G++(gcc 3.4.2)编译器并不支持为纯虚函数定义缺省行为;在VC6.0支持为纯虚函数定义缺省的实现,派生类的虚函数override基类的纯虚函数),但调用它的唯一方式是通过类名完整地指明是哪个调用(如:pb->Base:: pureVirtual())。
有时,声明一个除纯虚函数外什么也不包含的类很有用。这样的类叫协议类(Protocol class),它为派生类仅提供函数接口,完全没有实现。
虚函数(在此指的是非纯虚函数)
虚函数的情况和纯虚函数有点不一样。照例,派生类继承了函数的接口,但简单虚函数一般还提供了实现,派生类可以选择改写(override)它们或不改写它们。
声明虚函数的目的在于,使派生类继承函数的接口和缺省实现。
虚函数的意义,每个类必须提供一个可以被调用的虚函数,但每个类可以按它们认为合适的任何方式处理。如果某个类不想做什么特别的事,可以借助于基类中提供的缺省处理函数。也就是说,虚函数的声明是在告诉子类的设计者,"你必须支持虚函数,但如果你不想写自己的版本,可以借助基类中的缺省版本。"
实际上,为虚函数同时提供函数声明和缺省实现是很危险的。(当你增加一个派生类继承基类时,必须小心使用虚函数,满足派生类特有的需求,否则就是调用基类的虚函数,可能引起错误)
非虚函数
最后,来谈谈类的非虚函数,当一个成员函数为非虚函数时,它在派生类中的行为就不应该不同。实际上,非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为――不管一个派生类有多特殊。
声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。(所有的派生类都应该完成的使用该函数完成某一个功能)
建议
结合前面的学过的,再次强调一下,如果你没有为类设计虚函数(纯虚函数),该类一般来说应该不具有继承特性(除非确实的存在IS-A关系,即便存在,派生类也没有了特殊性,这种情况一般是设计中抽象的不合理)。当然除了Protocol class也不应该把类的成员函数全部设计成虚函数(纯虚函数),这也说明了类设计的不合理(不能正确的抽象出基类、派生类之间不变的部分)。