C++ 多态及虚函数
多态
什么是多态?通俗来说就是相同对象收到不同消息或不同对象收到相同消息时产生不同的动作。
静态多态
静态多态也叫做早绑定,如下所示代码,程序在编译阶段根据参数个数确定调用哪个函数:
class Rect { // 矩形类
public:
// 函数名相同, 参数个数不同, 是互为重载的两个函数
int calcArea(int width);
int calcArea(int width, int height);
};
int main() {
Rect.rect;
rect.calcArea(10);
rect.calcArea(10,20);
return 0;
}
动态多态
动态多态也叫做晚绑定,是以封装和继承为基础的。若我们想实现 Circle 类和 Rect 类派生自 Shape 类,Circle 类和 Rect 类各有自己的计算面积的方法。先写程序如下:
class Shape { // 形状类
public:
double calcArea() {
cout<<"calcArea"<<endl;
return 0;
}
};
class Circle: public Shape { // 公有继承自形状类的圆形类
public:
Circle(double r);
double calcArea();
private:
double m_dR;
};
double Circle::calcArea() {
return 3.14 * m_dR * m_dR;
}
class Rect: public Shape { //公有继承自形状类的矩形类
public:
Rect(double width, double height);
double calArea();
private:
double m_dWidth;
double m_dHeight;
};
double Rect::calcArea() {
return m_dWidth * m_dHeight;
}
int main() {
Shape *shape1 = new Circle(4.0);
Shape *shape2 = new Rect(3.0, 5.0);
shape1->calcArea();
shape2->calcArea();
return 0;
}
但以上程序结果只会打印两行 "calcArea",而不是相应的面积,因为调用到的都是父类的 calcArea 函数,并不是我们想要的那样去分别调用各自的计算面积的函数。如果要想实现动态多态则必须使用虚函数。
关键字 virtual → 虚函数
虚函数主要就是为了实现多态而存在的。用 virtual 去修饰成员函数使其成为虚函数。所以以上的函数修改部分如下:
class Shape {
public:
virtual double calcArea() { // 虚函数
...
}
...
};
...
class Circle: public Shape {
public:
Circle(double r);
virtual double calcArea(); // 此处的 virtual 不是必须的, 系统会自动加上
...
};
...
class Rect: public Shape {
public:
Rect(double width,double height);
virtual double calcArea();
...
};
这样就可以达到预期的结果了。
纯虚函数
对于一些类,我们有时不希望它被实例化,因为可能实例化之后也没什么用。这时可以将类内的函数声明为纯虚函数,含有纯虚函数的类被称为抽象类。抽象类的子类只有把抽象类当中的所有的纯虚函数都做了实现才可以实例化对象。
虚函数表及内存布局
同一个类的不同实例共用同一份虚函数表,它们都通过一个虚函数表指针 __vfptr 指向该虚函数表。
1. 本身不存在虚函数的单继承类
class Base1 {
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1: public Base1 {
public:
int derive1_1;
int derive1_2;
};
Derive1 的内存布局:基类在上、继承类的成员在下依次定义:
2. 除通过继承而来的基类虚函数外, 自身没有其它虚函数的单继承类
class Base1 {
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1: public Base1 {
public:
int derive1_1;
int derive1_2;
virtual void base1_fun1() {} // 覆盖基类虚函数
};
Derive1 类重写了 Base1 类的 base1_fun1() 函数,也就是常说的虚函数覆盖,现在 Derive1 的内存布局:
无论是通过 Derive1 的指针还是 Base1 的指针来调用此方法,调用的都将是被继承类重写后的那个方法(多态发生了)。
3. 定义了基类没有的虚函数的单继承类
class Base1 {
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Derive1 : public Base1 {
public:
int derive1_1;
int derive1_2;
virtual void derive1_fun1() {}
};
结果不难猜,依然是虚函数表最前、接着是基类成员和子类成员,虚函数表内依次是继承来的和自己定义的:
4. 多继承且同时存在虚函数覆盖和自身定义的虚函数的类
class Base1 {
public:
int base1_1;
int base1_2;
virtual void base1_fun1() {}
virtual void base1_fun2() {}
};
class Base2 {
public:
int base2_1;
int base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
class Derive1: public Base1, public Base2 { // 多继承
public:
int derive1_1;
int derive1_2;
virtual void base1_fun1() {} // 基类虚函数覆盖
virtual void base2_fun2() {} // 基类虚函数覆盖
virtual void derive1_fun1() {} // 自身定义的虚函数
virtual void derive1_fun2() {} // 自身定义的虚函数
};
我们知道构造函数的执行顺序取决于定义派生类时所指定的各基类顺序,内存布局也是这样,只不过 Derive1 自己定义的虚函数会放在第一个具有虚函数的基类的虚函数表内:
5. 如果第一个直接基类没有虚函数
class Base1 {
public:
int base1_1;
int base1_2;
};
class Base2 {
public:
int base2_1;
int base2_2;
virtual void base2_fun1() {}
virtual void base2_fun2() {}
};
class Derive1: public Base1, public Base2 {
public:
int derive1_1;
int derive1_2;
virtual void derive1_fun1() {} // 自身定义的虚函数
virtual void derive1_fun2() {} // 自身定义的虚函数
};
那么谁有虚函数表,谁就放在前面:
如果两个基类都没有虚函数表,子类的虚函数表也始终在最前面: