虚函数&虚继承
之前不了解虚继承,一度以为是和虚函数相关的东西,后来查了一下才知道是完全不相关的特性。特此记录一下。
虚函数
1. 写法
类中的成员函数,定义时在最左侧加上virtual
关键字,就被定义为了虚函数。
2. 意义
实现多态性。用父类指针指向子类对象时可以调用子类中的函数(前提:在父类中被定义为虚函数并在子类中进行了覆盖)。
3. 作用
基类中的虚函数可以在子类中被重新定义。使用基类指针指向子类对象并调用虚函数时,调用到的会是子类中的函数。
子类中重新实现的虚函数,其参数列表需要与基类中的虚函数相同。virtual修饰符加不加都可以。
子类的子类仍然可以重新定义该虚函数。
#include <iostream>
using namespace std;
class A{
public:
virtual void func(){
cout << "I am A." << endl;
}
};
class B: public A{
public:
void func(){
cout << "I am B." << endl;
}
};
class C: public B{
public:
void func(){
cout << "I am C." << endl;
}
};
int main(int argc, const char * argv[]) {
A* pointer = new C();
pointer->func(); // 调用结果为: I am C.
return 0;
}
4. 原理
一旦类中有函数被定义为了虚函数,类就会拥有一个虚函数表。
虚函数表中,保存着一系列函数指针,指向这个类中的虚函数实际的起始地址。
为了能获取到类的虚函数表,类的每个对象中就需要保存一个指针,指向自己的类的虚函数表(vtbl,也称虚表)。这个指针称为虚函数表指针(vptr)。
当使用基类指针指向子类对象并调用虚函数时,程序会通过对象的虚表指针去子类虚表中查找虚函数的实际地址。
也是因为这个原因,定义了虚函数的类的对象中会多一个4/8字节(取决于32位/64位)的指针,存于变量的起始位置。
虚继承
1. 写法
一个类型在继承另一个类时加上virtual修饰符,这个继承就是一个虚继承,其基类称为虚基类。
2. 意义
虚继承是为了解决C++中的多重继承问题而存在的特性。
3. 作用
如果一个子类继承了两个不同的基类,这两个类的继承链中又有相同的基类,那么在子类中,公共基类会被继承两次。
比如菱形继承:D继承了B、C,B和C又都是A的子类。
这样会导致两个后果:
一是浪费空间,公共基类的成员存在两份;
二是公共基类的成员来自两条不同的继承路径,在使用时不能确定使用的是哪一个,因此存在二义性。
虚继承可以解决这一问题。
由虚继承方式被继承的基类在子类的子类中只会存在一份内存结构。
4. 原理
虚继承中,子类并不像普通继承那样拥有一份基类的内存结构,而是加了一个虚基类表指针指向虚基类。
当虚基类的子类被继承时,虚基类表指针也会被继承。
因此,当某个子类通过不同路径继承了同一个虚基类时,该子类的内存中只会存在一份虚基类的拷贝,避免了冲突。