C++ 底层分析 5.动态绑定、多态

本文探讨了C++中的多继承和虚函数表的实现细节,包括无函数覆盖和有函数覆盖的情况。在多继承中,每个基类都有自己的虚函数表,子类的虚函数表会包含所有基类的虚函数。当有函数覆盖时,子类的重写函数会替换基类的相应函数。此外,还解释了前期绑定与动态绑定的概念,指出只有virtual函数才具备动态绑定特性,实现多态。最后,提出了析构函数为何需要声明为virtual的原因,以确保正确调用析构路径。
摘要由CSDN通过智能技术生成

1.多继承无函数覆盖

struct Base1						
{		
	int a;
	int b;				
public:	
	Base1()
	{
		a = 1;
		b = 2;
	}
    virtual void Fn_1()						
    {						
        printf("Base1:Fn_1...\n");						
    }						
    virtual void Fn_2()						
    {						
        printf("Base1:Fn_2...\n");						
    }						
};						
struct Base2						
{			
	int a;
	int b;			
public:		
	Base2()
	{
		a = 3;
		b = 4;
	}			
    virtual void Fn_3()						
    {						
        printf("Base2:Fn_3...\n");						
    }						
    virtual void Fn_4()						
    {						
        printf("Base2:Fn_4...\n");						
    }						
};						
struct Sub:Base1,Base2						
{
	int a;
	int b;						
public:	
	Sub()
	{
		a = 5;
		b = 6;
	}
    virtual void Fn_5()						
    {						
        printf("Sub:Fn_5...\n");						
    }						
    virtual void Fn_6()						
    {						
        printf("Sub:Fn_6...\n");						
    }						
};						
int main(int argc, char* argv[])						
{						
	//查看 Sub 的虚函数表					
    Sub sub;						
						
	//通过函数指针调用函数,验证正确性					
    typedef void(*pFunction)(void);						
										
	//对象的前四个字节是第一个Base1的虚表					
	printf("Sub 的虚函数表地址为:%x\n",*(int*)&sub);					
						
	pFunction pFn;					
						
	for(int i=0;i<6;i++)					
	{					
		int temp = *((int*)(*(int*)&sub)+i);				
		if(temp == 0)				
		{				
			break;			
		}				
		pFn = (pFunction)temp;				
		pFn();				
	}					
						
	//对象的第4个四字节是Base2的虚表					
	printf("Sub 的虚函数表地址为:%x\n",*(int*)((int)&sub+0xC));					
	
	pFunction pFn1;					
	
	for(int k=0;k<2;k++)					
	{					
		int temp = *((int*)(*(int*)((int)&sub+0xC))+k);				
		pFn1 = (pFunction)temp;				
		pFn1();				
	}					
	
	return 0;					
}							

总结:
在这里插入图片描述
在这里插入图片描述
有几个父类就有几个表
子类的虚函数放在第一个表

2.多继承有函数覆盖

struct Base1					
{					
public:					
    virtual void Fn_1()					
    {					
        printf("Base1:Fn_1...\n");					
    }					
    virtual void Fn_2()					
    {					
        printf("Base1:Fn_2...\n");					
    }					
};					
struct Base2					
{					
public:					
    virtual void Fn_3()					
    {					
        printf("Base2:Fn_3...\n");					
    }					
    virtual void Fn_4()					
    {					
        printf("Base2:Fn_4...\n");					
    }					
};					
struct Sub:Base1,Base2					
{					
public:					
    virtual void Fn_1()					
    {					
        printf("Sub:Fn_1...\n");					
    }					
    virtual void Fn_3()					
    {					
        printf("Sub:Fn_3...\n");					
    }					
	virtual void Fn_5()				
    {					
        printf("Sub:Fn_5...\n");					
    }					
};					
int main(int argc, char* argv[])					
{					
	//查看 Sub 的虚函数表				
    Sub sub;					
					
	//通过函数指针调用函数,验证正确性				
    typedef void(*pFunction)(void);						
	//对象的前四个字节是第一个Base1的虚表				
	printf("Sub 的虚函数表地址为:%x\n",*(int*)&sub);				
				
	pFunction pFn;				
					
	for(int i=0;i<6;i++)				
	{				
		int temp = *((int*)(*(int*)&sub)+i);			
		if(temp == 0)			
		{			
			break;		
		}			
		pFn = (pFunction)temp;			
		pFn();			
	}				
					
	//对象的第二个四字节是Base2的虚表				
	printf("Sub 的虚函数表地址为:%x\n",*(int*)((int)&sub+4));				
					
	pFunction pFn1;				
					
	for(int k=0;k<2;k++)				
	{				
		int temp = *((int*)(*(int*)((int)&sub+4))+k);			
		pFn1 = (pFunction)temp;			
		pFn1();			
	}								
	return 0;				
}					

总结:
在这里插入图片描述

3.多层继承有函数覆盖

struct Base1			
{			
public:			
    virtual void Fn_1()			
    {			
        printf("Base1:Fn_1...\n");			
    }			
    virtual void Fn_2()			
    {			
        printf("Base1:Fn_2...\n");			
    }			
};			
struct Base2:Base1			
{			
public:			
    virtual void Fn_3()			
    {			
        printf("Base2:Fn_3...\n");			
    }			
};			
struct Sub:Base2			
{			
public:			
	virtual void Fn_1()		
    {			
        printf("Sub:Fn_1...\n");			
    }			
	virtual void Fn_3()		
    {			
        printf("Sub:Fn_3...\n");			
    }			
};			
int main(int argc, char* argv[])			
{			
	//查看 Sub 的虚函数表		
    Sub sub;			
			
	//观察大小:虚函数表只有一个		
	printf("%x\n",sizeof(sub));		
			
	//通过函数指针调用函数,验证正确性		
    typedef void(*pFunction)(void);			
			
			
	//对象的前四个字节是就是虚函数表		
	printf("Sub 的虚函数表地址为:%x\n",*(int*)&sub);		
			
	pFunction pFn;		
			
	for(int i=0;i<6;i++)		
	{		
		int temp = *((int*)(*(int*)&sub)+i);	
		if(temp == 0)	
		{	
			break;
		}	
		pFn = (pFunction)temp;	
		pFn();	
	}		
			
	return 0;		
}			

总结:从上往下最后一个重写函数才会写入虚函数表
在这里插入图片描述

4.前期绑定与动态绑定

绑定:就是将函数调用与地址关联起来。
前期绑定:又称编译期绑定,即编译时已将函数地址写入,E8直接call
后期绑定:又称动态绑定、运行期绑定,即编译时没有直接写入地址,而是FF间接call
多态:相同的调用产生不同的行为

class Base				
{				
public:				
	int x;			
public:				
	Base()			
	{			
		x = 100;		
	}			
    void Function_1()				
    {				
        printf("Base:Function_1...\n");				
    }				
    virtual void Function_2()				
    {				
        printf("Base:Function_2...virtual\n");				
    }				
};				
class Sub:public Base				
{				
public:				
	int x;			
public:				
	Sub()			
	{			
		x = 200;		
	}			
    void Function_1()				
    {				
        printf("Sub:Function_1...\n");				
    }				
    virtual void Function_2()				
    {				
        printf("Sub:Function_2...virtual\n");				
    }				
};				
void TestBound(Base* pb)			
{			
	int n = pb->x;		
	printf("%x\n",n);		
			
	pb->Function_1();		//函数调用
			
	pb->Function_2();		
}			
int main(int argc, char* argv[])			
{			
    Base base;//Sub base;
    TestBound(&base);
	return 0;		
}			

当传入对象为Sub指针
在这里插入图片描述
当传入对象为Base指针
在这里插入图片描述
运行结果:
Base base;
在这里插入图片描述
Sub base;
在这里插入图片描述
总结:

  1. 只有virtual的函数是动态绑定.
  2. 动态绑定还有一个名字:多态

5.作业

在这里插入图片描述

#include<string>
#include <iostream>

using namespace std;
class Base		
{		
public:		
	int x;	
	int y;
public:		
	Base()	
	{	
		x = 1;
		y = 2;
	}	
    void Print()		
    {		
        printf("Base:%x %x\n",x,y);		
    }			
};		
class Sub1:public Base		
{			
public:	
	int A;

	Sub1()	
	{	
		 x = 4;
		 y = 5;
		 A = 6;
	}	
    void Print()		
    {		
        printf("Sub1:%x %x %x\n",x,y,A);		
    }	
};		


class Sub2:public Base		
{			
public:	
	int B;
	Sub2()	
	{	
		x = 7;
		y = 8;
		B = 9;
	}	
    void Print()		
    {		
        printf("Sub2:%x %x %x\n",x,y,B);		
    }	
};		
class Sub3:public Base		
{			
public:	
	int C;
	Sub3()	
	{	
		x = 10;
		y = 11;
		C = 12;
	}	
    void Print()		
    {		
        printf("Sub3:%x %x %x\n",x,y,C);		
    }	
};	
void TestBound()		
{		
	Base b;
	Sub1 s1;
	Sub2 s2;
	Sub3 s3;
	
	Base* arr[] = {&b,&s1,&s2,&s3};
	for(int i=0;i<4;i++)
	{
		arr[i]->Print();
	}
}

不写成virtual
在这里插入图片描述
写成virtual
在这里插入图片描述
思考题:为什么析构函数写成virtual?
定义成虚函数才能准确退出,不然类用完不知道调用的哪个析构函数

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值