1、什么是虚函数,虚函数的工作方式
当基类希望派生类定义适合自己的版本,就将这些函数声明为虚函数(virtual)。类中有虚函数,就会创建一个虚函数表。编译器处理虚函数的方法是:为每个类对象添加一个隐藏成员,隐藏成员中保存了一个指向函数地址数组的指针,称为虚表指针(vptr),数组为虚函数表。
虚函数依赖虚函数表工作的,表来保存虚函数地址,当我们用基类指针指向派生类时,虚表指针vptr指向派生类的虚函数表。 这个机制可以保证派生类中的虚函数被调用到。
2、虚函数表的存放内容
2.1 单继承:
虚函数表的每一个表项保存了指向该类的所有虚函数的函数指针。
- 当派生类重写了基类的虚函数,此时派生类的虚函数表将保存重写的虚函数地址。
- 如果基类中的虚函数没有在派生类中重写,那么派生类将继承基类中的虚函数,而且派生类中虚函数表将保存基类中未被重写的虚函数的地址。
- 如果派生类中定义了新的虚方法,则该虚函数的地址也将被添加到派生类虚函数表中。
2.2 多继承:
- 多重继承会有多个虚函数表,几重继承,就会有几个虚函数表。
- 这些表按照派生的顺序依次排列,如果子类改写了父类的虚函数,那么就会用子类自己的虚函数覆盖虚函数表的相应的位置,如果子类有新的虚函数,那么就添加到第一个虚函数表的末尾。
3、析构函数和构造函数要设为虚函数吗
4、内联函数、静态成员函数和友元函数可以是虚函数吗?
inline,friend,static三种函数都不能带有virtual关键字。
- inline是在编译时展开,必须要有实体。内联函数是指在编译期间用被调用函数体本身来代替函数的调用指令,但虚函数的多态特性需要在运行时根据对象类型才知道调用哪个虚函数,所以没法在编译时进行内联函数展开。
- static属于class自己的,类相关,必须有实体;static成员没有this指针。virtual函数一定要通过对象来调用,有隐藏的this指针,实例相关。
- 友元函数不能为虚函数,因为友元函数不是类成员,只有类成员才能是虚函数。
5、什么是纯虚函数
- 纯虚函数只有定义,没有实现;纯虚函数只是一个接口,是个函数的声明而已,它希望子类去实现自己的版本。
- 含有纯虚拟函数的类称为抽象类,它不能生成对象。抽象类是不完整的,它只能用作基类。
- 建立公共接口目的是为了将子类公共的操作抽象出来,可以通过一个公共接口来操纵一组类,且这个公共接口不需要事先完全实现。
6、 什么是多态,动态绑定
不同继承关系的类对象,调用同一函数产生不同行为。继承中构成多态还有两个条件:①调用函数的对象必须是指针或者引用②被调用的函数必须是虚函数(virtual)。
动态绑定:绑定的是动态类型,所对应的函数或属性依赖于对象的动态类型,发生在运行期
7、静态多态和动态多态
- 静态多态就是在系统编译期间就可以确定程序执行到这里将要执行哪个函数。静态多态通过重载、模板来实现。
- 动态多态则是利用虚函数实现了运行时的多态,也就是说在系统编译的时候并不知道程序将要调用哪一个函数,只有在运行到这里的时候才能确定接下来会跳转到哪一个函数。
8、 为什么需要虚继承?虚继承实现原理解析
8.1、虚继承的目的:
虚继承是为了解决多继承的命名冲突和冗余数据问题,C++提供虚继承,使得派生类只保留一份间接基类成员,多继承是指从多个直接基类中产生派生类的能力,派生类继承了所有父类的成员。
它的目的是让某个类做出声明,愿意共享它的基类,其中,这个被共享的基类就称为虚基类(Virtual Base Class),其中A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
8.2、成员的可见性:
因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
以图中的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
- 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。
- 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
- 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。
|