1. 多态的目的
我们不希望把对象看作是某一个特殊类型的成员,而是希望把它看作一个基本类型的成员。这样就允许我们编写不依赖于特殊类型的代码。
这样就可以不必修改一般的处理函数,而只需要通过派生新的类型来达到程序拓展的目的。
要达到这个目的,就需要根据实际的对象类型来判断重写函数的调用。
a. 如果父类指针指向的是父类对象则调用父类中定义的函数。
b. 如果父类指针指向的是子类对象则调用子类中定义的重写函数。
2. 如何产生多态?
使用virtual声明的函数被重写后即可展现多态特性。多态使用的3个条件:
a. 有继承
b. 有virtual重写
c. 有父类指针(引用)指向子类对象。
3. C++编译器如何实现多态
理论知识如下:
a. 当类中声明虚函数时,编译器会在类中生成一个虚函数表vtable。
b. 虚函数表是一个存储类成员函数指针的数据结构。
c. 虚函数表是由编译器自动生成与维护的。
d. virtual成员函数会被编译器放入虚函数表中。
e. 当存在虚函数时,每个对象中都有一个指向虚函数表的指针,即vptr指针。C++编译器给父类对象、子类对象提前布局vptr指针。
发生调用时,存在两种情况:
1)func不是虚函数:编译器可直接确定被调用的成员函数
2)func是虚函数:编译器根据对象的vptr指针,所指的虚函数表中查找func函数并调用,这里的查找和调用都在运行时完成(动态绑定)。
4. 动态绑定过程
当编译器发现类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中增加一个指针:vptr,
这个指针是指向对象的虚函数表。在多态调用的时候,根据vptr指针,找到虚函数表来实现动态绑定。
注:在对象构建的时候,也就是在对象初始化调用构造函数的时候,编译器默认会在编写的每一个构造函数中,增加一些vptr初始化的代码。
如果没有提供构造函数,编译器会提供默认的构造函数,会在默认构造函数中做这个工作,初始化vptr指针,使它指向本对象的虚函数表。
子类继承基类的vptr指针,这个指针指向基类虚函数表,当子类调用构造函数时,子类的vpointer指针便会指向子类的虚函数表。
通过虚函数表指针Vptr调用重写函数是在程序运行时进行的,编译器在编译的时候需要额外插入许多查找,判断的代码,运行时执行这些代码
才能确定真正应该调用的函数。而普通成员函数是在编译时就确定了调用的函数。在效率上,虚函数的效率要低很多。
出于效率考虑,没有必要将所有成员函数都声明为虚函数。
5. 构造函数和析构函数能不能是virtual的?
构造函数不能用virtual修饰:我们知道多态是发生在对象已经建立的情况下,而构造函数调用时对象还没有建立,也就不存在多态。
析构函数可以用virtual修饰:此时对象已经建立,我们希望在delete p(p为指向子类对象的基类指针)的时候能够调用子类自己的析构函数来释放资源。