
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关键字时,无论是虚函数,还是虚继承,编译器都会生成一个表,由于虚表里可能是属性或者函数,统一称为虚表
- 当我们使用虚继承的时候,会产生一个虚表和一个虚基类、和一个虚指针,由于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进制显示)
- 当我们使用虚函数的时候也会出现虚表如果父类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函数的形参就会根据子类的不同调用不同子类的虚函数了,这就是动态多态的实现。
问题来了,如果用值传递的方式来进行自下而上的转换呢,显然,这时候不会发生多态,因为直接把子类对象赋值给父类对象的时候,不会发生类型的转换,只有指针,即地址形式可以发生转换,而且即使把子类赋值给父类对象,赋值后的父类对象的属性仍然是自己原有的属性,函数也是自己原有的函数,这和正常的赋值不同。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)