一直对于C++的虚函数的概念比较模糊,今天上网查资料然后把虚拟继承这一块给搞懂了,给大家分享。
继承是C++的一大特性,继承是复用的重要手段,。通过继承一个类,继承是类型(一个类)之间的关系建模,共享父类的一些资源,但是有些数据是共享补了的,每个类都有自己要实现的东西,所以本质是不同的。
在这里我就不再赘述继承的概念的东西,相信懂一点C++的程序员都会使用继承,但是在这里强调一下继承的赋值兼容规则
- 子类对象可以赋值给父类对象(切片:将子类对象中切割出父类大小的一个对象赋值给父类对象(指针))
- 父类对象不可以复制给子类对象。
- 父类的指针/引用可以指向子类对象。
- 子类指针/对象不可以指向父类的对象。(除非强制类型转换,但是这是不好的)。
赋值兼容规则在虚拟继承中会使用到,所以在这里复习一下。
虚拟继承主要指解决菱形继承的二义性问题:
菱形继承:多个子类继承同一个基类,最后又被一个子类继承。
从对象模型我们可以看见Assistat的虚表(vTable),其实虚表有很多叫法:
- VMT
- vftable
- virtual call table
- dispatch table
- vtable
我们一般称为vtable。虚表是C++利用runtime来实现多态的工具,也就是在运行时决定到底选用的是哪一个虚表。所以,我们借助virtual关键字将函数代码地址存入vTable来躲开静态编译期,当编译器看到virtual关键字时,编译器自动跳过。
我们来看一个没有使用虚函数的代码:
#include<iostream>
using namespace std;
class Person
{
<span style="white-space:pre"> </span>public :
<span style="white-space:pre"> </span>void BuyTickets()
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>cout << " 买票" << endl;
<span style="white-space:pre"> </span>}
protected:
<span style="white-space:pre"> </span>string _name; // 姓名
};
class Student : public Person
{
<span style="white-space:pre"> </span>public :
<span style="white-space:pre"> </span>virtual void BuyTickets()
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>cout << " 买票-半价 " << endl;
<span style="white-space:pre"> </span>}
protected:
<span style="white-space:pre"> </span>int _num; //学号
};
//void Fun(Person* p)
void Fun(Person& p)
{
<span style="white-space:pre"> </span>p.BuyTickets();
}
int main()
{
<span style="white-space:pre"> </span>Person p;
<span style="white-space:pre"> </span>Student s;
<span style="white-space:pre"> </span>Fun(p);
<span style="white-space:pre"> </span>Fun(s);
<span style="white-space:pre"> </span>return 0;
}
运行程序,输出的是两个买票,说明父类和子类都是调用的父类的void BuyTickets(),因为没有使用virtual关键字修饰在静态编译期就确定了调用Person的void BuyTickets()。
然后,我们在void BuyTickets()的前面加上vitral会怎样呢:
class Person
{
public :
virtual void BuyTickets()
{
cout << " 买票" << endl;
}
protected:
string _name; // 姓名
};
正如你看到的输出是买票和买票-半价,这就说明多态已经成功的实现了,但是,为什么呢?????
首先,我们得先理解一下几点:
- 函数只要有virtual,我们就需要把它添加键入vtable。
- 每个类都有自己的虚表。
- 虚表的位置一般存放在模块的常量段中,从始至终都只有一份。
- 虚表里有虚函数指针,指向本类中带有virtual的函数。
虚函数指针的大小是4个字节,就本例来说虚表的大小就是4个字节,因为只有一个虚函数:
我们可以看到虽然Student继承了Person但是,Student任然有自己的虚表,并且Student重写了virtual void BuyTickets()。这就是原因。
以下是覆盖的规则: