C++多态实现原理

本文深入探讨了C++中的多态性,包括静态多态和动态多态的实现方式,特别是动态多态依赖于虚函数。详细阐述了虚函数表的概念,分析了虚函数表的构成,包括不同继承情况下的虚表变化,以及虚继承如何解决二义性问题。此外,还总结了多态的实现原理,强调了虚指针和虚函数表在实现多态性中的关键作用。

目录

C++多态实现原理

1 多态的介绍

1.1例:

1.2 静态多态实现

1.3 动态多态的实现

2 虚函数表

2.1 虚表指针初始化问题

2.2 虚函数表的构成

  2.2.1 虚函数的虚表剖析

    2.2.2没有有覆盖公有继承 派生类的虚表

    2.2.3有覆盖的公有继承派生类的虚表

  2.2.2多继承

  2.2.3 菱形继承

2.3 虚继承

  2.3.1 单继承

  2.3.2 多继承

​编辑   2.3.3 菱形虚拟继承,解决了二义性

   2.4 总结

3 多态的实现原理

4 结尾


C++多态实现原理

1 多态的介绍

多态含义为一个事物有多种形态。在C ++程序设计中,多态性是指具有不同功能的函数可以用同一个函数名,这样就可以用一个函数名调用不同内容的函数。一般来说多态分为两种:

  • 静态多态:也称为编译时多态,主要包括参数多态,过载多态和强制多态。参数多态:采用参数化模板,通过给出不同的类型参数,使得一个结构有多种类型。如 C++语言中的函数模板和类模板属于参数多态。参数多态又叫静态多态,它的执行速度快,异常少,调用在编译时已经确定。过载多态:同一个名字在不同的上下文中所代表的含义不同。典型的例子是运算符重载和函数重载。强制多态:编译程序通过语义操作,把操作对象的类型强行加以变换,以符合函数或操作符的要求。程序设计语言中基本类型的大多数操作符,在发生不同类型的数据进行混合运算时,编译程序一般都会进行强制多态。程序员也可以显示地进行强制多态的操作。如 int+double,编译系统一般会把 int 转换为 double,然后执行 double+double 运算,这个int->double 的转换,就实现了强制多态,即可是隐式的,也可显式转换。强制多态属于静态多态。
  • 动态多态:也称运行时多态,主要包括:包含多态。包含多态的基础是虚函数。主要是通过类的继承和虚函数来实现,当基类和子类拥有同名同参同返回的方法,且该方法声明为虚方法,当基类对象,指针,引用指向的是派生类的对象的时候,基类对象,指针,引用在调用基类的方法,实际上调用的是派生类方法。

重载多态和强制多态是指特定多态, 重载多态和强制多态称为特殊多态性,用来刻画语义上无关联的类型间的关系;参数多态和包含多态是指通用多态,类型参数化多态和包含多态称为一般多态性,用来系统地刻画语义上相关的一组类型。

在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数。

如果对象类型是子类,就调用子类的函数;如果对象类型是父类,就调用父类的函数,(即指向父类调父类,指向子类调子类)此为多态的表现。


1.1例:

class Person   
{   
public :       
    virtual void BuyTickets()      
    {           
    cout<<" 买票"<< endl;     
    }  
protected :      
    string _name ;   // 姓名   
};  
  
class Student : public Person   
{   
public :  
     virtual void BuyTickets()  
     {           
     cout<<" 买票-半价 "<<endl ;  
     }  
protected :  
     int _num ;   //学号  
};  
  
//void Fun(Person* p)   
void Fun (Person& p)  
{  
     p.BuyTickets ();  
}  
  
void Test ()  
 {  
     Person p ;  
     Student s ;  
     Fun(p );  
     Fun(s );  
}  

1.2 静态多态实现

静态多态靠编译器来实现,简单来说就是编译器对原来的函数名进行修饰。可以根据函数参数的类型,个数,以及修饰函数const,这就使得函数可以重载。同理,模板也是可以实现的,针对不同类型的实参来产生对应的特化的函数,通过增加修饰,使得不同的类型参数的函数得以区分。


1.3 动态多态的实现

动态多态靠运行时的类型检查,从而来进行函数的绑定。声明一个类时,如果类中有虚方法,则自动在类中增加一个虚函数指针,该指针指向的是一个虚函数表,虚函数表中存着每个虚函数真正对应的函数地址。动态多态采用一种延迟绑定技术,普通的函数调用,在编译期间就已经确定了调用的函数的地址,所以无论怎样调用,总是那个函数,但是拥有虚函数的类,在调用虚函数时,首先去查虚函数表,然后在确定调用的是哪一个函数,所以,调用的函数是在运行时才会确定的。


2 虚函数表

2.1 虚表指针初始化问题

当创建子类对象时,编译器的执行顺序其实是这样的:

  1. 对象在创建时,由编译器对 vptr 进行初始化
  2. 子类的构造会先调用父类的构造函数,这个时候 vptr 会先指向父类的虚函数表
  3. 子类构造的时候,vptr 会再指向子类的虚函数表
  4. 对象的创建完成后,vptr 最终的指向才确定

2.2 虚函数表的构成

  2.2.1 虚函数的虚表剖析

    2.2.2没有有覆盖公有继承 派生类的虚表

class CBase //没有覆盖
{
public:
	virtual void FunTest0(){ cout << "CBase::FunTest0()"; }
	virtual void FunTest1(){ cout << "CBase::FunTest1()"; }
	virtual void FunTest2(){ cout << "CBase::FunTest2()"; }
public:
	int data1;
};
class CDerived :public CBase
{
public:
	virtual void FunTest4(){ cout << "CDerived::FunTest4()"; }
	virtual void FunTest5(){ cout << "CDerived::FunTest5()"; }
	virtual void FunTest6(){ cout << "CDerived::FunTest6()"; }
	int data2;
};
 
typedef void(*FUN_TEST)(); //定义一个函数指针
void PrintVF()
{
	// 1种方法
	//for (int idx = 0; idx < 3;idx++)
	//{
	//	FUN_TEST funTest = (FUN_TEST)(*((int*)*(int *)&b + idx));
	//	// (int *)&b将b的地址值强转换成int*指针
	//	//*(int *)&b 将b的地址值取出来 0021fe04
	//	//(int*)*(int *)&b 将0021fe04 强转换成一个指针   +idx 指针里面的东西偏移idx单元
	//	//*((int*)*(int *)&b 将0021fe04 地址里面的值取出来
	//	funTest();
	//	cout << ": " << (int *)funTest << endl;
	//}
	CDerived d;
	d.data2 = 2;
	d.data1 = 1;
	cout << "CDerived的大小:" << sizeof(CDerived) << endl;
	cout << "CDerived vfptr:" << endl;
	FUN_TEST *funTest2 = (FUN_TEST*)(*(int*)&d);
	while (*funTest2)
	{
		(*funTest2)();
		cout << ": " << *funTest2 &
概要:   DevCon 实用工具是一种命令行实用工具,可以替代设备管理器。使用 DevCon,您可以启用禁用、重新启动、更新、删除查询单个设备或一组设备。DevCon 提供与开发人员有关但无法在设备管理器中看到的信息。   您可以将 DevCon 用于 Windows 2000 、Windows XPWindows vista。不能将 Devcon 用于 Microsoft Windows 95、Windows 98、或 Windows Millennium Edition。   下载:http://download.microsoft.com/download/1/1/f/11f7dd10-272d-4cd2-896f-9ce67f3e0240/devcon.exe 用法及参数说明:   devcon.exe [-r] [-m:\\] [...]   -r 如果指定它,在命令完成后若需要则重新启动计算机。    是目标计算机的名称。    是将要执行的命令(如下所示)。   ... 是命令需要的一个或多个参数。   要获取关于某一特定命令的帮助,请键入:devcon.exe help   classfilter 允许修改类别筛选程序。   classes 列出所有设备安装类别。   disable 禁用与指定的硬件或实例 ID 匹配的设备。   driverfiles 列出针对设备安装的驱动程序文件。   drivernodes 列出设备的所有驱动程序节点。   enable 启用与指定的硬件或 实例 ID 匹配的设备。   find 查找与指定的硬件或 实例 ID 匹配的设备。   findall 查找设备,包括那些未显示的设备。   help 显示此信息。   hwids 列出设备的硬件 ID。   install 手动安装设备。   listclass 列出某一安装类别的所有设备。   reboot 重新启动本地计算机。   remove 删除与特定的硬件或 实例 ID 匹配的设备。   rescan 扫描以发现新的硬件。   resources 列出设备的硬件资源。   restart 重新启动与特定的硬件或 实例 ID 匹配的设备。   stack 列出预期的设备驱动程序堆栈。   status 列出设备的运行状态。   update 手动更新设备。   UpdateNI 手动更新设备,无用户提示   SetHwID 添加、删除更改根枚举设备的硬件 ID 的顺序。 示例:   devcon -m:\\test find pci\* 列出计算机 test 上的所有已知 PCI 设备。(通过使用 -m,您可以指定一个目标计算机。您必须使用“进程间通信”(IPC) 访问此计算机。)   devcon -r install Windows directory\Inf\Netloop.inf *MSLOOP 安装一个新的 Microsoft 环回适配器实例。这将创建一个新的根枚举设备节点,使用此节点您可以安装“虚拟设备”,如环回适配器。如果需要重新启动计算机,此命令还将以安静模式重启计算机。   devcon classes 列出所有已知的安装类别。输出结果包含短的未本地化的名称(例如,“USB”)描述性名称(例如,“通用串行总线控制器”)。 禁用启用网卡的步骤:   1.用devcon hwids PCI*命令得到所有以PCI开头的设备。这时会列出很多设备,那么哪个才是网卡对应的呢?   2.打开设备管理器,展开网络适配器,找到网卡的名称,然后记住到刚才得到的列表中找对应的Name,然后你会在下面看到好几个ID,随便挑一个就行   3.用devcon disable "PCI\VEN_11AB&DEV_4380&SUBSYS_301B17AA&REV_10"禁用网卡(启用的话讲disable换成enable就行了)   4.其实用PCI开头得到的几组设备中一般第一个就是网卡设备 sysdzw 16:01 2010-11-16
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

龙星尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值