定义
如果没有使用关键字 virtual ,程序将根据引用类型或指针类型选择方法;如果使用了 virtual 关键字,程序将根据引用或指针指向的对象的类型来选择方法(C++ Primer Plus)
例如 不使用关键字 virtual
1 | class Father |
使用关键字 virtual
1 | class Father |
所以如果要在派生类中重新定义基类的方法,通常应将基类方法声明为虚的
这样,程序将根据对象类型而不是引用或指针的类型来选择方法版本
虚函数的原理
虚函数表
只有在拥有关键字 virtual 修饰的方法才会产生虚函数表
- 虚函数表的地址存放在对象的首地址中
- 存放虚函数地址的数组称之为虚表,对象中指向虚表的地址称之为虚表指针
- 一般虚表放在全局数据区
- 含有虚表指针的类比没有虚表指针的类大4个字节
- 虚函数调用,首先拿到对象的首地址,从对象的首地址取出虚表指针,然后从虚表取出对应的虚函数的地址,调用虚函数,这种调用方式称之为虚调用(间接调用)。
- 每个类都有自己的虚表,每个类都在自己的构造函数中填入自己的虚表指针,这个实际构造函数执行时间早
1 | class Father |
创建 Son
实例,打开内存窗口查看 son 内存
1 | 0x0019FF0C 6c 68 41 00 lhA. |
首地址为虚表指针并指向 0x0041686c
打开 0x0041686c
以下为虚函数表
1 | 0x0041686C 2d 10 41 00 -.A. |
最后的 0x00000000
为虚函数表终止符
打开反汇编窗口输入地址,分别找到它们对应基类和子类的成员函数
0x0041102d | 0x00411177 | 0x004110cd | 0x0041106e |
---|---|---|---|
Father:func1() | Father:func2() | Son::func3() | Son::func4() |
如果将类 Son
中的成员函数 func3()
改为 func1()
1 | class Father |
此时虚函数表的内容为
1 | 0x0041686C 17 12 41 00 ..A. |
对应的方法为
0x00411217 | 0x00411177 | 0x0041106e |
---|---|---|
Son::func1() | Father:func2() | Son::func3() |
可以看到 Son
的虚函数表中,此时的 fun1()
不再是 Father:func1()
而是 Son::func1()
对其进行了覆盖
为何需要设置虚析构函数
在使用delete释放由new分配的对象代码时,如果析构函数不是虚的,则将只调用对应于指针类型的析构函数
例如
1 | class Father |
此时仅调用了父类 Father
中的析构函数
如果在析构函数中添加了 virtual 关键字,则析构将正常进行,顺序为先子类后父类
语法细节
在子类的一般成员函数中调用虚函数,也存在多态效果
在父类的一般成员函数中调用虚函数,存在多态效果
在构造成员函数中调用虚函数,没有多态效果
在析构成员函数中调用虚函数,没有多态效果
构造成员函数不能设置为虚函数
析构成员函数必须是虚函数