C++进阶:多态(四)

355 阅读2分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第11天,点击查看活动详情

💦 虚函数表

✔ 测试用例一:

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
private:
	int _b = 1;
	char _ch = 'a';
};

int main()
{
	cout << sizeof(Base) << endl;
	Base b;

	return 0;
}
  • 这是常考一道笔试题,遇到这种题时打死也不可能是 8,因为是 8 的话那就是考查结构体的内存对齐,为啥还要搞个类呢。

  • 当一个类有虚函数后,这个类会增加 4 个字节在前面,这 4 个字节是一个指针,这个指针叫做虚函数表指针,简称虚表指针 __vfptr (v 是 virtual、f 是 function、ptr 是指针,但是 __vftptr 更准确,就是说这个指针不是指向虚函数,而是指向虚函数表,表里才是虚函数),__vfptr 指向的表是虚函数表,简称虚表,这个表你可以认为它是函数指针数组,表里存储的是虚函数的地址 (注意虚函数存储于虚表中这种说法不完全对,因为虚函数被编译成指令后,跟普通函数一样存储在代码段,只是它的地址放到了虚表中)。注意区分继承中谈的虚基表指针,它所指向的表所存储的是偏移量,用于查找基类。

    在这里插入图片描述

✔ 测试用例二:

#include<iostream>
using namespace std;

class Base
{
public:
	virtual void Func1()
	{
		cout << "Base::Func1()" << endl;
	}
	virtual void Func2()
	{
		cout << "Base::Func2()" << endl;
	}
	void Func3()//非虚函数
	{
		cout << "Base::Func3()" << endl;	
	}
private:
	int _b = 1;
	char _ch = 'a';
};
class Drive : public Base
{
public:
	virtual void Func1()//重写Func1
	{
		cout << "Drive::Func1()" << endl;	
	}
private:
	int _d = 2;
};

int main()
{
	Base b1;
	Base b2;
	Base b3;

	Drive d1;
	Drive d2;

	return 0;
}
  • 可以看到普通函数并不会放到虚函数表中。

  • 可以看到 Base 和 Drive 所创建的对象的虚表指针不同。

    如果虚函数 Func2 没有被重写,子类中的虚表中放的依旧是 Func2 的虚函数的地址;如果虚函数 Func1 重写,我们说重写也叫做覆盖,你可以理解为子类的虚表是把父类的虚表拷贝过来 (当然这里没必要做写时拷贝),谁完成了重写,就把重写的位置覆盖成重写的虚函数,所以你可以认为重写是语法层的概念,覆盖是原理层的概念;如果都不完成重写,虽然父子类中虚表的内容是一样的,但是并不代表着它们要共用一张虚表,也没必要,因为空间用的不多。

    所以一个类的所有对象共享一张虚表;父子类无论是否完成虚函数重写,都有各自独立的虚表;

    在这里插入图片描述

💦 动态绑定与静态绑定

  • 有些地方也会称普通调用是静态绑定,多态调用称后期绑定等,这可能是由于翻译等其它原因,导致了有不同的术语。
  • 静态绑定又称为前期绑定 (早绑定),在程序编译链接期间确定了程序的行为,也称为静态多态,比如函数重载。
  • 动态绑定又称为后期绑定 (晚绑定),在程序运行期间,根据具体拿到的类型确定程序具体的行为,调用具体的函数,也称为动态多态,比如多态。