<四>虚函数 静态绑定 动态绑定

<四>虚函数 静态绑定 动态绑定

代码1

class Base
{
public:
	Base(int data=10):ma(data){
	   cout<<"Base()"<<endl;
        }
	void show(){cout<<"Base Show()"<<endl;}
	void show(int){cout<<"Base Show(int)"<<endl;}
	~Base(){cout<<"~Base()"<<endl;}
protected:
	int ma;
};

class Derive : public Base
{
	public:
	 Derive(int data=20):Base(data),mb(data){
	      cout<<"Derive()"<<endl;
	 }
	void show(){cout<<"Derive Show()"<<endl;}
        ~Derive(){cout<<"~Derive()"<<endl;}
	private:
        int mb;
};

int main(){

         Derive d(20);
         Base *pb=&d;
	 pb->show();   //静态绑定(编译期间绑定)
	 pb->show(100) //静态绑定(编译期间绑定)
         cout<<sizeof(Base)<<endl; // 4 字节
	 cout<<sizeof(Derive)<<endl; // 8字节

         cout<<typeid(pb).name()<<endl; // clas Base *
	 cout<<typeid(*pb).name()<<endl; // class Base

	 return 0;

}

代码2

class Base
{
public:
	Base(int data=10):ma(data){
	   cout<<"Base()"<<endl;
        }
	//虚函数
	virtual void show(){cout<<"Base Show()"<<endl;}
	//虚函数
	virtual void show(int){cout<<"Base Show(int)"<<endl;}
	~Base(){cout<<"~Base()"<<endl;}

protected:
	int ma;
};

class Derive : public Base
{
	public:
	Derive(int data=20):Base(data),mb(data){
	   cout<<"Derive()"<<endl;
	}
	void show(){cout<<"Derive Show()"<<endl;}
        ~Derive(){cout<<"~Derive()"<<endl;}
	private:
        int mb;
};

int main(){

     Derive d(20);
     Base *pb=&d;
     pb->show();   //动态绑定(运行期间绑定) 
     pb->show(100) //动态绑定(运行期间绑定) )
     cout<<sizeof(Base)<<endl; // 12 字节
     cout<<sizeof(Derive)<<endl; // 16 字节

     cout<<typeid(pb).name()<<endl; // 
     cout<<typeid(*pb).name()<<endl; // 

     return 0;

}

//在上面的代码中,如果一个类中定义了虚函数,编译器会做什么??
//1:一个类里面定义了虚函数,那么编译阶段,编译器给这个类会产生一个唯一的vftable虚函数表,
//  虚函数表中主要存储的内容就是RTTI和虚函数地址,如下图
//2:当程序运行时,每一张(程序中的可能有很多类都有虚函数,每一个类都会对应一个虚函数表)虚函数表都会加载到内存的.rodata区
//  一个类里面定义了虚函数,那么这个类定义的对象,其运行时,在内存中开始部分,多存储一个vfptr虚函数指针,指向相应
//  类型的虚函数表vftable, 一个类型定义的n个对象,指向同一张表
//3:一个类里面虚函数的个数,不影响对象内存大小(vfptr),影响的是虚函数表的大小

//4:如果派生类中的方法,和基类继承来的某个方法,返回值,函数名,参数列表都相同,而且基类的方法是virtual虚函数
//  那么派生类的这个方法,自动处理成虚函数

在上面代码2中,Base 中void show()和 void show(int) 为虚函数, 在Derive 中有返回值,函数名,参数列表都
相同的 show()方法,这个时候 我叫 重写(或覆盖 即要返回值,函数名,参数列表 都相同),这个Derive中的void show()方法也会处理成虚函数.所以有
下图 Derive中的show()方法地址替换掉继承来的Base的show()函数地址 ,同时我们也可以看到Base中的 void show(int) 并没有被覆盖

覆盖:基类和派生类的方法,返回值,函数名以及参数列表都相同,而且基类的方法是虚函数,那么派生类的方法就自动处理成虚函数,他们之前成为覆盖关系.

所以根据上图,我们说 覆盖 其实是指虚函数表中函数地址的覆盖

  代码2中的这段代码
  Base *pb=&d;
  pb->show();   //动态绑定(运行期间绑定) 
  pb->show(100) //动态绑定(运行期间绑定) )

  pb->show() pb是Base类型,所以编译器会去Base中查看void show()函数情况
  如果是普通函数,那么静态绑定(编译期绑定),但是发现是 void show()函数是个虚函数就进行动态绑定了
  同理 pb->show(100)发现Base中的void show(int )也是个虚函数,所以执行动态绑定(运行期绑定).
  但是 这两个函数在Derive虚函数表中的区别就是,void show()被Derive覆盖掉了,即虚函数表中的地址被覆盖了
  而void show(int) 为被覆盖,所以虚函数表中的 地址仍然是Base的void show(int)的地址


  ----------------------------------------
    
  cout<<sizeof(Base)<<endl;   //  4 +8 =12 字节 Base中又多了一个虚函数表指针
  cout<<sizeof(Derive)<<endl; //  8+8  =16 字节 Derive 多了一个虚函数表指针

  ------------------------------------------------------
  cout<<typeid(pb).name()<<endl; // Base *

  cout<<typeid(*pb).name()<<endl; // 
  关于这句代码typeid(*pb).name(),即打印指针指向对象的类型, 先看pb是Base类型,再看有没有虚函数
  1:如果没有虚函数,那么*pb 识别的是编译时期的类型,即 Base class
  2:如果有虚函数,那么 *pb  识别的是运行时的类型,即RTTI类型,RTTI是存储在虚函数表中,所以要在运行期识别
    即  pb->d(vfptr)->Derive vftable -> Class Derive 


原文地址:https://www.cnblogs.com/erichome/archive/2022/11/27/16929335.html