C++|C++ 虚函数及虚函数表详解

多态”的关键在于通过基类指针或引用调用一个虚函数时,编译时不确定到底调用的是基类还是派生类的函数,运行时才确定。

#include using namespace std; class A{public:int i; virtual void func() {}virtual void func2() {}}; class B : public A{int j; void func() {}}; int main(){cout << sizeof(A) << ", " << sizeof(B); //输出 8,12return 0; }

在 32 位编译模式下,程序的运行结果是:

8, 12

如果将程序中的 virtual 关键字去掉,输出结果变为:

4, 8
A * p = new B() 实现多态
对比发现,有了虚函数以后,对象所占用的存储空间比没有虚函数时多了 4 个字节。实际上,任何有虚函数的类及其派生类的对象都包含这多出来的 4 个字节,这 4 个字节就是实现多态的关键——它位于对象存储空间的最前端,其中存放的是虚函数表的地址。

每一个有虚函数的类(或有虚函数的类的派生类)都有一个虚函数表,该类的任何对象中都放着该虚函数表的指针(可以认为这是由编译器自动添加到构造函数中的指令完成的)。
没有覆盖时的子类,可以看到子类的虚函数表的前面是基类离得虚函数
C++|C++ 虚函数及虚函数表详解
文章图片

有覆盖就是
C++|C++ 虚函数及虚函数表详解
文章图片

子类对象地址为什么能赋值给父类对象指针?
【C++|C++ 虚函数及虚函数表详解】因为,子类对象地址赋值给父类对象指针,父类对象指针就指向了子类的对象空间,父类操作子类的范围是有限制的,只能操作到子类中父类的范围。
基类和子类各有自己的虚函数表vtbl;不管是基类还是子类实例都会在其内存的开头自动创对象即虚函数表指针vptr, 用来访问所在类的虚函数表
想要实现多态,需要动态绑定,需要父类的指针或父类的引用
父类方法为虚方法,子类覆盖父类的虚方法,才能达到多态
子类中父类没有的方法,父类指针也无法访问到,父类指针只能访问到父类自己有的范围
子类要覆盖父类的方法,就是要函数名参数都必须一样才叫覆盖
C++|C++ 虚函数及虚函数表详解
文章图片

C++|C++ 虚函数及虚函数表详解
文章图片

再看一个例子
class A {public:virtual void vfunc1(); virtual void vfunc2(); void func1(); void func2(); private:int m_data1, m_data2; }; class B : public A {public:virtual void vfunc1(); void func1(); private:int m_data3; }; class C: public B {public:virtual void vfunc2(); void func2(); private:int m_data1, m_data4; };

C++|C++ 虚函数及虚函数表详解
文章图片

子类继承父类,子类中有父类的同名方法,访问的是子类的方法,子类会隐藏父类所有的同名方法,即使父类有一个同名的参数不同的方法也是如此。
多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数。
C++|C++ 虚函数及虚函数表详解
文章图片

对于子类实例中的虚函数表,是下面这个样子:
C++|C++ 虚函数及虚函数表详解
文章图片

我们可以看到:
1)每个父类都有自己的虚表。

2)子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。
多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。

下图中,我们在子类中覆盖了父类的f()函数。
C++|C++ 虚函数及虚函数表详解
文章图片

下面是对于子类实例中的虚函数表的图:
C++|C++ 虚函数及虚函数表详解
文章图片

我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。
任何妄图使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法,
总结 本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注脚本之家的更多内容!

    推荐阅读