C++ 虚函数剖析

虚函数之虚表

带有虚函数的类的对象模型剖析

(编译环境Vs2013)
看到这个题目,很多的人可能很想知道什么是虚表????为什么要说这个东西呢???这个虚表有什么作用呢????
当然,先容我买个关子,后面再说,,,,现在我们先来看看带有虚函数类的结构剖析:::
大家来看看一段代码:
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

class B
{
public:
	B(int b  = 0 )
		:_b(b)
	{
		cout << "B::B(int b  = 0 )" << endl;
	}
	void Test1()
	{
		cout << "void B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "void B::Test2()" << endl;
	}
	virtual void Test3()
	{
		cout << "void B::Test3()" << endl;
	}
	virtual void Test4()
	{
		cout << "void B::Test4()" << endl;
	}

private:
	int _b;
};

int main()
{
	B b(1);
	int a = sizeof(b) ;
	cout << a << endl;
	return 0;
}
这段代码中定义了一个B类类中    包含 成员变量_b ,1个成员函数Test1 ,3个虚函数Test2、Test3、Test4
代码输出后结果得到:
  
可以看到得到的B类对象的大小为   8,按照之前的类的大小计算,只应包含一个成员变量的大小为4,为什么还多出了4字节呢????
那就然我们来看看编译器在内存中的存储吧!!!!!!!!!!!!

从编译器的内存调试窗口中 ,我们可以明显看出   B类对象b包含两部分:
一部分呢是   一串数字(猜测可能是存了个地址)
另一部分呢  就是B类对象的成员变量  在内存保存的是给的1     ;;;;
第一部分看上去是个指针 ,那我们就当他们是指针 ,里面保存的是地址 ,,,
如果保存的是地址,那么这个 地址里保存的是什么呢?????
查看这段地址后 ,得到了这幅图;;;;;
在这段地址里,保存了有四部分,前三部分像是地址 ,第四部分是 0 ;
正好在这个类中有三个虚函数,我们这三个地址保存的是三个虚函数的地址,0表示结束。。。
既然假设了之后 ,,,,我们就 得验证一下,但要怎么验证呢?????
其实我们可通过反证法来试试,,,,我们就当它们保存的是虚函数,那么我们只需定义一个函数指针
用这三个地址来调用,看看结果,,,就知道是不是代表的是虚函数的地址》》》》》》
代码如下:
typedef void(*ptest)();//定义个函数指针ptest 

int main()
{
	B b(1);
	
	ptest *p = (ptest*)(*((int*)(&b)));//p表示的是指向函数指针的指针;
	while (*p)//当遇到我们在内存中看到的数字0
	{
		(*p)();
		p++;
	}
	return 0;
}
最后输出的结果输出为

通过输出的结构我们可以得出结论:这些地址表示的就是虚函数的地址。。。。。
在此得出结论后 ,我们基本了解到
带有虚函数的类在构造函数时,,,会先为对象空间中首位放上一个指针, 内部存储的是    一个表格
这个表格   内部存储的是类中虚函数的地址 ,并且在结束时,保存一个0 ;;;;
我们把这个表叫做是           虚表
那么开头遇到的问题是不是就解决了吐舌头吐舌头

带有虚函数的继承的对象模型剖析

上面我们基本了解到了什么是虚表,虚表要怎么存储????
但是上面的知识基础知识 ,,,,,
我们都知道,虚函数是用来实现多态的,多态肯定要会包含到继承,,,,
那么,接下来我们就来看看,在继承中中的虚表存储

单继承虚表

我们先从简单的单继承来说起!!!!!!基类对象的结构剖析,我们之前也了解到了,那么我们现在主要说说派生类中的虚表。
先看一段代码:
class B
{
public:
	B(int b = 0)
		:_b(b)
	{}
	void Test1()
	{
		cout << "void B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "void B::Test2()" << endl;
	}
	virtual void Test3()
	{
		cout << "void B::Test3()" << endl;
	}
	virtual void Test4()
	{
		cout << "void B::Test4()" << endl;
	}

private:
	int _b;
};

class D : public B
{
public:
	D(int b = 10, int d = 20)
	:B(b)
	, _d(d)
	{}
	virtual void Test2()
	{
		cout << "void D::Test2()" << endl;
	}
	virtual void Test3()
	{
		cout << "void D::Test3()" << endl;
	}
	virtual void Test5()
	{
		cout << "void D::Test5()" << endl;

	}
private:
	int _d ;
};
int main()
{
	B b(1);
	D d;
	cout << sizeof(d) << endl;
	return 0;
}
这段代码中,对于类D继承B类 ,,,并且对于基类中 的虚函数Test2 ,Test3进行了重写。,
又加了一个虚函数Test5
最后得到的类D的对象的大小为12 ;; ; ;
D类对象在内存中的存储为:
   
 从图中可以看出   对象中包含了虚表的地址0x0113dd6c   还有 基类 ,派生类自己的成员变量
接下来我们看看这个虚表和我们在基类中看到的有什么不同!!!!!!
虚表中明显多了一个地址, 那么多的这个地址就只会是Test5的地址了》》
通过函数指针来,调用这些函数看看》》》》
typedef void(*ptest)();//定义个函数指针ptest 
int main()
{
	B b(1);
	D d;
	ptest *p = (ptest*)(*((int*)(&d)));//p表示的是指向函数指针的指针;
	while (*p)//当遇到我们在内存中看到的数字0
	{
		(*p)();
		p++;
	}
	return 0;
生成的结果为::::

从图上我们可以看出,
Test2   ,Test3 调用的是派生类自己的,  Test4调用的任然是基类的, Test5只能调用派生类的。。。。。
从中我们可以知道,派生类的在构造对象时,先调用基类的构造函数,
然后对于虚表进行了一定程度上的修改。。。

多继承虚表

上面我们初步了解到了在继承中派生类对象虚表的模型 , ,,  ,下面我们来看看多继承的虚表的结构。。。
下面的代码就是说多继承的虚表
class B1
{
public:
	B1(int b = 0)
		:_b1(b)
	{}
	virtual void Test2()
	{
		cout << "void B1::Test2()" << endl;
	}
	virtual void Test4()
	{
		cout << "void B1::Test4()" << endl;
	}

private:
	int _b1;
};
class B2
{
public:
	B2(int b2 = 2)
		:_b2(b2)
	{}
	virtual void Test3()
	{
		cout << "void B2::Test3()" << endl;
	}
	virtual void Test5()
	{
		cout << "void B2::Test5()" << endl;
	}

private:
	int _b2;
};

class D : public B1,public B2
{
public:
	D(int b1 = 10,int b2 = 20, int d = 30)
	:B1(b1)
	,B2(b2)
	, _d(d)
	{}
	virtual void Test2()
	{
		cout << "void D::Test2()" << endl;
	}
	virtual void Test5()
	{
		cout << "void D::Test5()" << endl;

	}
	virtual void Test6()
	{
		cout << "void D::Test6()" << endl;

	}
private:
	int _d ;
};
int main()
{
	D d;
	return 0;
}
简单介绍基类  B1, B2,类  派生类D
B1  ::  成员变量_b1; 虚函数Test2,Test3
B2  ::  成员变量_b2; 虚函数Test4,Test5
D:::  成员变量 _d;; 对基类B1的Test2  ,基类B2的Test5 进行重写  ;;;添加派生类自己的虚函数Test6
最后生成的D类对象   ——d的内存为

在图中我们可以大致将它分为三部分:::::
1、  B1类对象  (B1的虚表, B1的成员)
2、  B2类对象  (B2的虚表, B2的成员)
3、  D  类对象 
我们可以调用这两个虚表来看看内部保存的虚函数   

调用这些函数地址:
typedef void(*ptest)();//定义个函数指针ptest 
int main()
{
	D d;
	ptest *p1 = (ptest*)(*((int*)(&d)));//p表示的是指向函数指针的指针;
	while (*p1)//当遇到我们在内存中看到的数字0
	{
		(*p1)();
		p1++;
	}
	cout << endl;
	ptest *p2 = (ptest*)*(((int*)(&d)) + 2);//p表示的是指向函数指针的指针;
	while (*p2)//当遇到我们在内存中看到的数字0
	{
		(*p2)();
		p2++;
	}
	return 0;
}
生成的结果:


通过这个结果我们可以得出结论:
派生类对象在构造时》》》》》》  ,先构造 两个基类 ,然后依次对每个基类的虚表进行改写 , ,, , ,


关于派生类自己的虚函数地址,,,,,一般放在继承的基类的虚表中。 。 。。 。

菱形继承对象模型剖析

 说到菱形继承 , , 一般都离不开虚拟继承的。。。。。
如果要是不知什么是虚继承的话 , , ,可以看这篇博客C++之虚拟继承
为什么要单独说这个 菱形继承呢 ???
主要是因为,在虚拟继承时生成的类会在类对象之前也加上一个指针。。。。内部存的是
1、对象首位置到指针的偏移。
2、基类对象到指针的偏移。。。。
下面是一段带有虚函数的菱形继承(虚继承)的代码;;;;
#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;


class B
{
public:
	B(int b = 1)
		:_b(b)
	{}
	virtual void Test1()
	{
		cout << "void B::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "void B::Test2()" << endl;
	}
private:
	int _b;
};

class C1 :virtual public B
{
public:
	C1(int b = 1,int c1= 2)
		:B(b)
		, _c1(c1)
	{}
	virtual void Test2()
	{
		cout << "void C1::Test2()" << endl;
	}

private:
	int _c1;
};

class C2 :virtual public B
{
public:
	C2(int b = 1, int c2 = 3)
		:B(b)
		, _c2(c2)
	{}
	virtual void Test1()
	{
		cout << "void C2::Test1()" << endl;
	}

private:
	int _c2;
};

class D :public C1, public C2
{
public:
	D(int d = 4)
		:C1()
		,C2()
		, _d(d)
	{}
	virtual void Test1()
	{
		cout << "void D::Test1()" << endl;
	}
	virtual void Test2()
	{
		cout << "void D::Test2()" << endl;
	}
	
private:
	int _d;
};

typedef void (*ptest)();
int main()
{
	C1 c1;
	C2 c2;
	D d;
	ptest *p1 = (ptest *)*((int *)(&c1)+3);
	while (*p1)
	{
		(*p1)();
		p1++;
	}
	ptest *p2 = (ptest *)*((int *)(&c2) + 3);
	while (*p2)
	{
		(*p2)();
		p2++;
	}
	ptest *p3 = (ptest *)*((int *)(&d) + 6);
	while (*p3)
	{
		(*p3)();
		p3++;
	}
	cout << sizeof(c1) << endl;
	cout << sizeof(c2) << endl;
	cout << sizeof(d)  << endl;
	return 0;
}
代码简要说明:
B类 :成员变量  _b,,,带有虚函数Test1,Test2;
C1类(继承B类):成员变量_c1,对Test2,进行重写;;;
C2类(继承B类):成员变量_c2,对Test1,进行重写;;i,;
D类(多继承C1,C2类):成员变量_d,对Test1,Test2进行重写;;;;
根据代码我们生成的类的大小分别为 
C1   20;
C2   20;
D     32;
在内存中对象的存储分别为:
C1   与C2 对象

从图中我们可以看出两个对象可以分为5个部分:
1、一个指针;;
2、C1 、C2类自己的成员;
3、一个0;
4、一个地址;
5、基类的成员;;;;
我们以C1对象为例,来看看上面的两个地址表示的到底是什么????
第一个地址:

第二个地址:

从两幅图中我们可以得出结论:
第一个地址表示的是虚拟继承得到的地址;
第二个地址表示的是虚函数的虚表的地址。
因此我们 可以大致猜测得到一个结论::::
(带有虚函数的)菱形继承的C1,与 C类的结构模型大致为:::::

那么再来看看D类对象的成员分布:
D类是一个继承两个子类,,,,
内存中的分布为
             
从图中类可以分为六部分:
1、C1的成员;
2、C2的成员;
3、D类自己的成员;
4、00 00 00 00 
5、指针;
6、B类的成员。。。。。
我们从中可以得知的是上面的指针表示的是虚表的地址
因此我们可以的得到,菱形继承生成的D类的模型结构为:::::
    这个图是相对于这段代码来写的;;;;;;
关于在内存中保存的     00  00  00  00 到底表示的是什么意思????
就留给大家下面来探讨吧!!!!!!!!
代码最后生成的结果::::



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值