C++

C++,第1张

子类转换为父类
class Car
{
	public:
 	int a;
	virtual void show(){
		cout<<"this is virtual Car"<<endl;
	}

  void show2(){
    cout<<"this is Car"<<endl;
  }
};

class miniCar: public Car
{
	public:
  int a=20;
		void show()
		{
			cout<<"this is miniCar"<<endl;
		}
};

void doShow(Car &c)
{
  c.show();
  c.show2();
  cout<<c.a;
}

void doShow2(Car *c)
{
  c->show();
  c->show2();
  cout<<c->a;
}
int main() {
   miniCar mc1;
   doShow(mc1);
   doShow2(mc1);
  //在doShow函数里没有使用引用来进行子类到父类的转换,输出结果为this is virtual Car,this is Car,0
  //如果是void doShow(Car &c)这种写法,输出结果为this  is miniCar,this is Car,0
  //当使用引用进行自下而上的转换,转换后成员属性应和父类的保持一致,但是对于重名的方法,且父类用的是虚函数,子类转换为父类后保留的还是子类的重名函数,
  //虚函数和引用转换同时发生时,属性是父类的,函数是子类的
  //只有一个条件发生时,转换成父类时,所有成员属性还是函数都是父类的
  //所以如果要使用多态,就必须在父类使用虚函数
  //除了引用构成自下而上的转换联合虚函数可以有这种多态效果之外,指针转换也可以有同样的效果
  //doShow2()函数输出也是this  is miniCar,this is Car,0
}
多态实现的本质之虚表 虚表怎么出现的

当我们使用virtual关键字时,无论是虚函数,还是虚继承,编译器都会生成一个表,由于虚表里可能是属性或者函数,统一称为虚表

  1. 当我们使用虚继承的时候,会产生一个虚表和一个虚基类、和一个虚指针,由于virtual是在基类前起到了修饰作用,所以产生的这个虚表里面包含的东西都是虚基类的属性,(函数本身存储的时候并不是属于基类的,所以此时的表里面不会包含基类的函数
    注意:每个经过虚继承的相同的子类创建的对象所包含的虚表是共享的,不同子类会有不同的虚表,不过指向的虚基类是一样的
class miniCar2: virtual public Car{};
class miniCar3: virtual public Car{};
class mininiCar: public miniCar2,public miniCar3{};

 miniCar2 mc2;
  p=(int**)&mc2;
  cout<<"子类 mc2 的虚表地址:"<<p[0]<<endl;
	cout<<"子类 mc2 的虚表中虚属性的地址:0x"<<hex<<p[0][0]<<endl;
  
  miniCar2 mc3;
  p=(int**)&mc3;
  cout<<"子类 mc3 的虚表地址:"<<p[0]<<endl;
	cout<<"子类 mc3 的虚表中虚属性的地址:0x"<<hex<<p[0][0]<<endl;

  miniCar3 mc4;
  p=(int**)&mc4;
  cout<<"子类 mc4 的虚表地址:"<<p[0]<<endl;
	cout<<"子类 mc4 的虚表中虚属性的地址:0x"<<hex<<p[0][0]<<endl;

//以上代码输出结果如下


//相同子类对象虚表是共享的,不同子类对象虚表不共享,但是如果虚表里的变量重复,会进行覆盖
//
(数值是16进制显示)

  1. 当我们使用虚函数的时候也会出现虚表如果父类Car中有虚函数,则编译器为其自动在类的开始位置加上一个虚指针,子类miniCar创建的对象也会有一个虚函数表,继承父类的虚指针,子类的新表会有新的地址,但是虚函数表里面的虚函数地址不会改变(子类对象调用函数会调用父类的函数),但是如果子类中包含了和父类的虚函数同名的函数,则会在子类的虚函数表中覆盖掉原先的虚函数,并赋予该虚函数新的地址。
    注意:虚函数表对于同个子类的所有对象也是共享的
  Car c1;
  p=(int**)&c1;
  cout<<"基类 c1 的虚函数表地址:"<<p[0]<<endl;
  cout<<"基类 c1 的虚函数表中虚函数的地址:0x"<<hex<<p[0][0]<<endl;

  miniCar mc5;
  p=(int**)&mc5;
  cout<<"子类 mc5 的虚表地址:"<<p[0]<<endl;
  cout<<"子类 mc5 的虚表中虚属性的地址:0x"<<hex<<p[0][0]<<endl;
  doShow(mc5);


可以看到,mc5虚函数地址和c1不同,这是因为在继承时重写了函数,所以虚函数地址改变,进行覆盖了,如果miniCar里面没有重名函数,则虚函数地址应该相同。当以引用的方式来进行自下而上的类转换时,属性全部转换为父类的属性,但是引用本质是指针常量,即有一个指针指向被引用的地方,所以用指针的方式和用引用的方式来进行其实是一样的,都是传地址,既然是传地址,那么它就会把整个类,包括虚表的地址都传过去,转换过后的父类的指针在调用虚函数时,就会通过虚指针调用由子类重写好并传过来的虚函数了。这样不同子类传递时都会传不同的虚函数过去,那么doShow函数的形参就会根据子类的不同调用不同子类的虚函数了,这就是动态多态的实现。

问题来了,如果用值传递的方式来进行自下而上的转换呢,显然,这时候不会发生多态,因为直接把子类对象赋值给父类对象的时候,不会发生类型的转换,只有指针,即地址形式可以发生转换,而且即使把子类赋值给父类对象,赋值后的父类对象的属性仍然是自己原有的属性,函数也是自己原有的函数,这和正常的赋值不同。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/789794.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-05
下一篇2022-05-05

发表评论

登录后才能评论

评论列表(0条)

    保存