C++修炼之路之多态---多态的原理(虚函数表)

这篇具有很好参考价值的文章主要介绍了C++修炼之路之多态---多态的原理(虚函数表)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

目录

一:多态的原理 

1.虚函数表

 2.原理分析

3.对于虚表存在哪里的探讨

4.对于是不是所有的虚函数都要存进虚函数表的探讨

二:多继承中的虚函数表

三:常见的问答题 

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

接下来的日子会顺顺利利,万事胜意,生活明朗-----------林辞忧 

接上篇的多态的介绍后,接下来介绍多态的原理以及虚函数表的相关知识

一:多态的原理 

1.虚函数表

这里从一道经典笔试题引入

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

对于这道题我们可能想到的是计算类 大小的对齐规则,结果为4,但结果为8,这是因为有虚函数的类要多考虑一指针

在32位系统下是8

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

如果这里再添加几个虚函数呢?

C++修炼之路之多态---多态的原理(虚函数表),c++,c++ 

所以在这里不管类里面有多少个虚函数 ,只要是包含虚函数的类计算大小都要考虑添加一指针,再考虑对齐

但这里的一指针是什么呢?

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

但这里我们就看到在b1中除了_b还存有 一个_vfptr的指针在对象的前面,这个指针就叫做虚函数表指针,其中v代表virtual,f代表funcation

每一个含有虚函数的类都至少有一个虚函数表指针,他的类型为函数指针数组,而虚函数的地址是存放在虚函数表中的,虚函数表也叫虚表

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

 2.原理分析

class Base
{
public:
	virtual void Func1()
    {
		cout << "Func1()" << endl;
    }
	virtual void Func2()
	{
		cout << "Func2()" << endl;
	}
	
private:
	int _b = 1;
};
class Derived : public Base
{
	virtual void Func1()
	{
		cout << "Func()" << endl;
	}
private:
	int _a = 0;
};

int main()
{
	Base b1;
	Derived d1;
	return 0;
}

C++修炼之路之多态---多态的原理(虚函数表),c++,c++ 

解释多态调用的两个条件

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

对于条件一:必须是父类的指针或引用来调用函数

1.父类的指针指向父类对象时,依据虚函数表指针(vfptr),在虚函数表中找到函数的地址,再call这个地址来执行接下来的操作

2.父类的指针指向子类对象时,先完成切片,找到父类的那一部分,依据虚函数表指针(vfptr),在虚函数表中找到函数的地址,再call这个地址来执行接下来的操作

3.由于经过虚函数的重写后,虚函数的地址是不相同的,所以结果是不相同的,这是就形成了多态

对于编译器来说上面的两个调用是执行的同样的操作,都只是取对象的头四个字节,就是虚函数表指针,然后去虚表中找到对应调用函数的地址,然后执行接下来的操作

4.如果是父类的对象调用函数的话这时就要分析可能会总成的结果

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

这时尤其是这样的场景,Person* ptr=new Person,Student s;   *ptr=s ,这样如果支持能拷贝虚函数表指针的话,这时delete  ptr,就调用的是 Student类的析构函数,导致直接错误的

5.对于多态调用是在运行时,去虚表里面找到函数指针,确定函数指针后,调用函数;

对于普通调用是在编译链接时,确定函数地址

6.派生类中只有一个虚表指针(菱形继承除外),同一个类的对象共用一张虚表

7.虚函数也是也是和成员函数一样存在代码段的,不同的是虚函数会将自己的地址存在虚表中

对于条件二:虚函数的重写

从上面就可以看出虚函数的重写也叫覆盖,覆盖了原先虚函数的地址,重写是语法层的叫法,而覆盖是原理层的叫法

三:派生类的虚表生成

1.先将基类中的虚表内容拷贝一份到派生类的虚表中

2.如果派生类重写了基类中的某个虚函数,用派生类自己的虚函数的地址来覆盖虚表中基类的虚函数地址

3.派生类自己新增的虚函数按其在派生类中的声明顺序增加到派生类虚表的最后

3.对于虚表存在哪里的探讨

对于栈和堆是不可能的,只有代码段或者静态区,但我们可以自己验证是存在哪里的

验证代码

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};
void func()
{
	cout << "void func()" << endl;
}
int main()
{
	Base b1;
	Base b2;

	static int a = 0;
	int b = 0;
	int* p1 = new int;
	const char* p2 = "hello world";
	printf("静态区:%p\n", &a);
	printf("栈:%p\n", &b);
	printf("堆:%p\n", p1);
	printf("代码段:%p\n", p2);
	printf("虚表:%p\n", *((int*)&b1));
	printf("虚函数地址:%p\n", &Base::func1);
	printf("普通函数地址:%p\n", func);

	return 0;
}

对于这里的取虚表地址

C++修炼之路之多态---多态的原理(虚函数表),c++,c++ 

可以这样来理解,&b1是整个类的地址,然后强转为(int*),再解引用取得就是头四个字节,即虚表地址 

C++修炼之路之多态---多态的原理(虚函数表),c++,c++ 

我们发现 和虚表地址最接近的为代码段的地址,所以可以确定虚表是存在代码段的

4.对于是不是所有的虚函数都要存进虚函数表的探讨

首先确定答案 一定都是存在虚函数表的

接下来我们在vs上监视窗口来查看

分析代码

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
	void func5() { cout << "Derive::func5" << endl; }
private:
	int b;
};

class X :public Derive {
public:
	virtual void func3() { cout << "X::func3" << endl; }
};

int main()
{
	Base b;
	Derive d;
	X x;

	Derive* p = &d;
	p->func3();

	p = &x;
	p->func3();

	return 0;
}

C++修炼之路之多态---多态的原理(虚函数表),c++,c++  

对于这里监视窗口的显示,在这里对于b是只有两个虚函数都存进了虚函数表中,但对于d和x都应该是四个虚函数存进虚函数表的,但在这里都只存了两个虚函数,但验证多态调用的话,结果为

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

结果是多态调用, 这时我们就不得不质疑此时监视窗口 的结果了

为了进一步的证明。我们可以调用内存窗口来查看

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

在内存中我们就会发现后两个地址与前两个虚函数的地址很接近,所以我们暂时可以认为虚函数是都存在虚函数表中的,

为了确定结果,我们可以使用打印虚表来验证猜想

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

class Base {
public:
	virtual void func1() { cout << "Base::func1" << endl; }
	virtual void func2() { cout << "Base::func2" << endl; }
private:
	int a;
};

class Derive :public Base {
public:
	virtual void func1() { cout << "Derive::func1" << endl; }
	virtual void func3() { cout << "Derive::func3" << endl; }
	virtual void func4() { cout << "Derive::func4" << endl; }
	void func5() { cout << "Derive::func5" << endl; }
private:
	int b;
};

class X :public Derive {
public:
	virtual void func3() { cout << "X::func3" << endl; }
};

typedef void (*VFUNC)();
//void PrintVFT(VFUNC a[])
void PrintVFT(VFUNC* a)
{
	for (size_t i = 0; a[i] != 0; i++)
	{
		printf("[%d]:%p->", i, a[i]);
		VFUNC f = a[i];
		f();
		//(*f)();
	}
	printf("\n");
}

int main()
{
	Base b;
	PrintVFT((VFUNC*)(*((long long*)&b)));//32位的话,可以采用int

	Derive d;
	X x;

	// PrintVFT((VFUNC*)&d);
	PrintVFT((VFUNC*)(*((long long*)&d)));

	PrintVFT((VFUNC*)(*((long long*)&x)));

	return 0;
}

C++修炼之路之多态---多态的原理(虚函数表),c++,c++ 

这样看,只要是虚函数,都会将地址存到类的虚函数表里面的

 文章来源地址https://www.toymoban.com/news/detail-858471.html

二:多继承中的虚函数表

同样的我们可以采用例子来介绍

class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2 {
public:
	virtual void func1() 
	{ 
		cout << "Derive::func1" << endl;
	}

	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

int main()
{
	Derive d;

	Base1* p1 = &d;
	p1->func1();

	Base2* p2 = &d;
	p2->func1();

	return 0;
}

采用监视窗口的话 

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

就会发现对于基类的两张虚表中都没有存derived类的fun3() ,但我们可以使用多态的调用来验证下

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

所以的话,fun3是一定存在基类的两张 虚表中的其中一个里面,这样采用内存来看

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

所以最好的方式,我们还是来打印两个基类的虚函数表的 

typedef void (*VFUNC)();
//void PrintVFT(VFUNC a[])
void PrintVFT(VFUNC* a)
{
	for (size_t i = 0; a[i] != 0; i++)
	{
		printf("[%d]:%p->", i, a[i]);
		VFUNC f = a[i];
		f();
		//(*f)();
	}
	printf("\n");
}
class Base1 {
public:
	virtual void func1() { cout << "Base1::func1" << endl; }
	virtual void func2() { cout << "Base1::func2" << endl; }
private:
	int b1;
};

class Base2 {
public:
	virtual void func1() { cout << "Base2::func1" << endl; }
	virtual void func2() { cout << "Base2::func2" << endl; }
private:
	int b2;
};

class Derive : public Base1, public Base2 {
public:
	virtual void func1() 
	{ 
		cout << "Derive::func1" << endl;
	}

	virtual void func3() { cout << "Derive::func3" << endl; }
private:
	int d1;
};

int main()
{
	Derive d;
     PrintVFT((VFUNC*)(*(int*)&d));

     //PrintVFT((VFUNC*)(*(int*)((char*)&d+sizeof(Base1))));
     Base2* ptr = &d;
     PrintVFT((VFUNC*)(*(int*)ptr));

	
	/*Base1* p1 = &d;
	p1->func1();

	Base2* p2 = &d;
	p2->func1();*/

	return 0;
}

C++修炼之路之多态---多态的原理(虚函数表),c++,c++ 

所以此时我们就会知道,派生类的虚函数地址是存在第一个基类的虚函数表里面的 

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

三:常见的问答题 

C++修炼之路之多态---多态的原理(虚函数表),c++,c++

 

到了这里,关于C++修炼之路之多态---多态的原理(虚函数表)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【C++】虚函数表 & 多态的原理 & 动态绑定和静态绑定

    梳理虚函数表、多态原理、动静态绑定的知识 目录 一、虚函数表 二、多态的原理 三、动态绑定和静态绑定 在学习多态原理之前,我们需要了解一下虚函数表的概念  我们先一起来看下下面这段代码 通过测试我们发现b对象是8bytes, 除了_b成员,还多一个__vfptr指针放在对象

    2024年02月03日
    浏览(30)
  • 【C++修炼之路】C++入门(上)

    👑作者主页:@安 度 因 🏠学习社区:安度因的学习社区 📖专栏链接:C++修炼之路

    2024年01月17日
    浏览(31)
  • 【C++修炼之路】内存管理

    👑作者主页:@安 度 因 🏠学习社区:StackFrame 📖专栏链接:C++修炼之路

    2024年02月16日
    浏览(22)
  • C++类和对象-多态->多态的基本语法、多态的原理剖析、纯虚函数和抽象类、虚析构和纯虚析构

    #includeiostream using namespace std; //多态 //动物类 class Animal { public:     //Speak函数就是虚函数     //函数前面加上virtual,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。     virtual void speak()     {         cout \\\"动物在说话\\\" endl;     } }; //猫类 class Cat

    2024年02月20日
    浏览(26)
  • 【C++修炼之路】33.特殊类设计

    每一个不曾起舞的日子都是对生命的辜负 掌握常见特殊类的设计方式 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C++98 将拷贝构造函数与赋值运算符重载只声明不

    2024年02月13日
    浏览(38)
  • 【C++修炼之路】list 模拟实现

    👑作者主页:@安 度 因 🏠学习社区:StackFrame 📖专栏链接:C++修炼之路

    2024年02月16日
    浏览(24)
  • 【C++庖丁解牛】面向对象的三大特性之一多态 | 抽象类 | 多态的原理 | 单继承和多继承关系中的虚函数表

    🍁你好,我是 RO-BERRY 📗 致力于C、C++、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 需要声明的,本节课件中的代码及解释都是在vs2013下的x86程序中,涉及的指针都是4bytes。如果要其他平台

    2024年04月10日
    浏览(40)
  • 【C++进阶之路】多态篇

     多态,顾名思义,就是 一件事物具备不同的形态 ,是继承之后, 面向对象的第三大特性 ,可以这样说: 有了继承才有了类的多态,而类的多态是为了更好的实现继承。  多态的列车即将起航,不知你准备好了吗?   继承与多态相辅相成 。 举个例子:  我们都是人(

    2024年02月15日
    浏览(29)
  • c++的学习之路:22、多态(1)

    本章主要是说一些多态的开头。 目录 摘要 一、多态的概念 二、多态的定义及实现 2.1、多态的构成条件 2.2、虚函数 2.3、虚函数的重写 2.4、C++11 override 和 final  2.5、重载、覆盖(重写)、隐藏(重定义)的对比 三、思维导图 多态的概念:通俗来说,就是多种形态,具体点就是去

    2024年04月14日
    浏览(59)
  • 【C++程序员的自我修炼】拷贝构造函数

    心存希冀 追光而遇目有繁星 沐光而行 目录 拷贝构造函数概念 拷贝构造的特征 无穷递归的解释 浅拷贝 总结:  深拷贝 拷贝构造函数典型调用场景 总结  契子 ✨ 在生活中总有很多琐事,不做不行做了又怕麻烦,有时候想要是有个和自己一模一样的人就好了 可以帮我上早读

    2024年04月14日
    浏览(36)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包