http://www.cnblogs.com/CaiNiaoZJ/archive/2011/08/11/2134673.html
多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。
C++支持两种多态性:编译时多态性,运行时多态性。
a. 编译时多态性:通过重载函数和运算符重载实现。
b. 运行时多态性:通过虚函数和继承实现。
虚函数
虚函数是在基类中被声明为virtual的函数。为了重载和多态的需要,在基类中是有定义的,即便定义是空,所以子类中可以重写也可以不写基类中的函数。
纯虚函数
是一种特殊的虚函数,在许多情况下,在基类中不能对虚函数给出有意义的实现,而把它声明为纯虚函数,它的实现留给该基类的派生类去实现,很像java中的接口函数。这就是纯虚函数的作用。
引入原因:在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
class <类名>
{
virtual <类型><函数名>(<参数表>)=0;
…
};
抽象类
包含纯虚函数的类叫虚基类,这种基类不能直接生成对象,而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。
由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
class A{
protected:
void foo(); //普通类函数
virtual void foo1(); //虚函数
virtual void foo2() = 0; //纯虚函数
}
虚基类
在派生类继承基类时,加上一个virtual关键词则为虚拟基类继承,如:
(3) 虚基类子对象是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。最远派生类是指在继承结构中建立对象时所指定的类。
(4) 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
class derive:virtual public base {};
虚基类主要解决在多重继承时,基类可能被多次继承,虚基类主要提供一个基类给派生类,如:
class B{};
class D1:public B{};
class D2:public B{};
class C:public D1,public D2{};
这里C在D1,D2上继承,但有两个基类,造成混乱。因而使用虚基类,即:
class B{};
class D1:virtual public B{};
class D2:virtual public B{};
class C:public D1,public D2{};
在使用虚基类时要注意:
(1) 一个类可以在一个类族中既被用作虚基类,也被用作非虚基类。
(2) 在派生类的对象中,同名的虚基类只产生一个虚基类子对象,而某个非虚基类产生各自的子对象。(3) 虚基类子对象是由最远派生类的构造函数通过调用虚基类的构造函数进行初始化的。最远派生类是指在继承结构中建立对象时所指定的类。
(4) 派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用;如果未列出,则表示使用该虚基类的缺省构造函数。
(5) 从虚基类直接或间接派生的派生类中的构造函数的成员初始化列表中都要列出对虚基类构造函数的调用。但仅仅用建立对象的最远派生类的构造函数调用虚基类的构造函数,而该派生类的所有基类中列出的对虚基类的构造函数的调用在执行中被忽略,从而保证对虚基类子对象只初始化一次。
(6)在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。
派生类构造函数的调用次序有三个原则:
(1)虚基类的构造函数在非虚基类之前调用;
(2)若同一层次中包含多个虚基类,这些虚基类的构造函数按它们说明的次序调用;
(3)若虚基类由非虚基类派生而来,则仍先调用基类构造函数,再调用派生类的构造函数。
构造函数和析构函数是否可以为虚函数?
答:
构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。(不建议在构造函数和析构函数里面调用虚函数。)
构造函数不能声明为虚函数的原因是:
1. 构造一个对象的时候,必须知道对象的实际类型,而虚函数行为是在运行期间确定实际类型的。而在构造一个对象时,由于对象还未构造成功。编译器无法知道对象的实际类型,是该类本身,还是该类的一个派生类,或是更深层次的派生类,无法确定。
2. 虚函数的执行依赖于虚函数表。而虚函数表在构造函数中进行初始化工作,即初始化vptr,让他指向正确的虚函数表。而在构造对象期间,虚函数表还没有被初始化,将无法进行。虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。
析构函数设为虚函数的作用:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。 例:
#include "stdafx.h" #include "stdio.h" class A{ public:A(); virtual~A(); }; A::A(){} A::~A(){ printf("Delete class A "); } class B : public A{ public:B(); ~B(); }; B::B(){} B::~B(){ printf("Delete class B "); } int main(int argc, char* argv[]){ A *b=new B; delete b; return 0; }
输出结果为:
Delete class B Delete class A 如果把A的virtual去掉:那就变成了Delete class A也就是说不会删除派生类里的剩余部分内容,也即不调用派生类的虚函数因此在类的继承体系中,基类的析构函数不声明为虚函数容易造成内存泄漏。所以如果你设计一定类可能是基类的话,必须要声明其为虚函数。正如Symbian中的CBase一样。
Note:
1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了。 2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特性来自父类。 3. 有虚函数的类,几乎可以确定要有个虚析构函数。 4. 如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。 5. 析构函数的异常退出会导致析构不完全,从而有内存泄露。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作。 6. 在构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。 7.在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。
8. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分,不能忘记了。