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 << 
  • 5
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

龙星尘

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

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

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

打赏作者

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

抵扣说明:

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

余额充值