探索虚函数与多态



测试环境 :win10 64位 vs2013

一些概念

       虚函数:类的成员函数前面加上virtual关键字,则此成员函数即为虚函数。

       重写:在子类定义了一个与父类完全相同的虚函数,则称子类的虚函数重写了父类的虚函数。

       多态:多态就是多种形态,C++的多态分为静态多态和动态多态。静态多态就是重载,因为在编译期间决定调用哪个函数,所以称为静态多态;动态多态是通过继承重写基类的虚函数实现的多态,因为是在运行期间决定调用哪个函数,所以称为动态多态。

1.单继承对象模型

#pragma once

#include <iostream>

using namespace std;

class Base
{
public:
	virtual void f1()
	{
		cout << "Base::f1" << endl;
	}
	virtual void f2()
	{
		cout << "Base::f2" << endl;
	}
private:
	int _a;
};
class Derive :public Base
{
public:
	virtual void f1()
	{
		cout << "Derive::f1" << endl;
	}
	virtual void f3()
	{
		cout << "Derive::f3" << endl;
	}

private:
	int _b;
};
typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
	cout << "虚表地址:" << VTable << endl;
	for (int i = 0; VTable[i] != 0; ++i)
	{
		printf("第%d个虚函数地址 :0%x,->", i, VTable[i]);
		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}
void test1()
{
	Base b1;
	Derive d1;
	int* VTable1 = (int*)(*(int*)&b1);
	int* VTable2 = (int*)(*(int*)&d1);
	PrintVTable(VTable1);
	PrintVTable(VTable2);
}

单继承原理:

       对于b1: 在构造时,为所有虚函数创建一个虚表,虚表首地址存在对象b1的首地址中。

       对于d1: 在构造时,同样要建立一个虚表,由于继承了父类;所以首先构造父类内成员,然后再构造d1内其他成员,在构造父类后,由于存在重写f1。所以将d1中f1覆盖到父类f1位置,以下为测试。

内存分布:

       运行代码发现在监视窗口并没有显示d1完整的虚函数地址,打开内存窗口,输入d1的虚表地址


上述数据基本符合预期,为了更直观,将上述虚函数地址打印,如下:

现在在Base里添加两个Display()函数,一个传参,一个不传,使他们构成重载。

void test2()
{
	Derive d2;
	Base& b2 = d2;
	b2.f1();
	b2.Display();
	b2.Display(1);
}

查看反汇编,明显得出动态多态与静态在寻址时极为不同的方式

b2.f1();                ;动态多态,虚表寻找地址

mov     eax,dword ptr [b2]       ;b2地址给eax

mov     edx,dword ptr [eax]     ;eax指向的内容给edx

mov     esi,esp 

mov     ecx,dword ptr [b2]       ;b2地址给eax

mov     eax,dword ptr [edx]     ;edx内容给eax

call      eax 

cmp     esi,esp 

call     __RTC_CheckEsp (01B1361h) 

b2.Display();       ;静态多态,编译时确定地址

mov     ecx,dword ptr [b2] 

call      Base::Display (01B124Eh) 

b2.Display(1);

push        1 

       b2.Display(1);

mov       ecx,dword ptr [b2] 

call       Base::Display (01B10E1h)

2.多重继承对象模型


#include <iostream>

using namespace std;

class Base1
{
public:
	virtual void f1()
	{
		cout<<"Base1::f1"<<endl;
	}

	virtual void f2()
	{
		cout<<"Base1::f2"<<endl;
	}

private:
	int _b1;
};

class Base2
{
public:
	virtual void f1()
	{
		cout<<"Base2::f1"<<endl;
	}

	virtual void f2()
	{
		cout<<"Base2::f2"<<endl;
	}

private:
	int _b2;
};


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

	virtual void f3()
	{
		cout<<"Derive::f3"<<endl;
	}

private:
	int _d1;
};

typedef void(*FUNC)();
void PrintVTable(int* VTable)
{
	cout << "虚表地址:" << VTable << endl;
	for (int i = 0; VTable[i] != 0; ++i)
	{
		printf("第%d个虚函数地址 :0x%x,->", i, VTable[i]);
		FUNC f = (FUNC)VTable[i];
		f();
	}
	cout << endl;
}
void test1()
{
	Derive d;
	PrintVTable((int*)(*((int*)&d)));
	PrintVTable((int*)(*((int*)((char*)&d + sizeof(Base1)))));
}


多重继承原理:

       在重多继承过程中,子类的成员函数重写(上例f1),如果两个父类同时包含重写的函数,将子类的其他虚函数存在优先继承的虚函数表中(上例先继承Base2)。

内存分布:

打开监视窗口仍然发现不能显示全部虚函数的地址,如下:

然后我们可以打印出虚函数地址,如下:

于是我们得到多重继承模型:

3.菱形虚拟继承

#include <iostream>

using namespace std;

class A
{
public:
	virtual void f1()
	{}

	int _a;
};

class B : virtual public A
{
public:
	virtual void f1()
	{}

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

	int _b;
};
class C : virtual public A
{
public:
	virtual void f1()
	{}

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

	int _c;
};

class D : public B, public C
{
public:
	virtual void f1()
	{
		cout << "D::f1()" << endl;
	}

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

	int _d;
};

void test1()
{
	D d1;
	d1._a = 1;
	d1._b = 2;
	d1._c = 3;
	d1._d = 4;
	PrintVTable((int*)(*(int*)&d1));
	PrintVTable((int*)(*((int*)&d1 + 3)));		//+3是因为用sizeof计算崩溃
	PrintVTable((int*)(*((int*)&d1 + 7)));		//所以对照内存表人工计算
}

通过查看内存分配和打印每个虚表内函数地址,画出如下d1模型图,由于虚继承将A构造的对象,放置在了最下面,然后有一虚基表指向此处,以后可以访问














  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值