C++多态

函数名前加virtual是虚函数

 子类和父类的虚函数如果函数名,参数和返回值都相同,那么子类的虚函数就和父类的虚函数构成重写(也叫覆盖)的关系。

而如果把virtual都去掉,那么他俩就是隐藏关系。

多态条件:

1.虚函数重写是多态条件之一

2.父类的指针或引用调用虚函数(注意父类对象不行)

两个条件都需要满足

红线部分都可以调用。子类调用相当于是父类切片,但也是子类对象做这个事情。

 切片是让父类的指针或引用指向子类,但是它看到的是子类中父类的部分。

此时如果进行如下修改,那么结果如下图所示。

 

 如果再加上这个

 那么上图的结果是

 如果不是引用去调用虚函数

 那么军人和学生调用就相当于切片,就相当于普通调用。

如果用指针调用,也符合多态。

 
 子类虚函数可以不加virtual

 它是把父类的虚函数继承下来,但重写了虚函数的实现。

 协变:派生类重写基类虚函数时,与基类虚函数返回值类型不同。即基类虚函数返回基类对象的指 针或者引用,派生类虚函数返回派生类对象的指针或者引用时,称为协变。

 这样也可以


因为s是后创建的,所以s先析构,此时s调用了一次自己的析构,又调用了继承下来的Person中的析构,然后P是先创建的,第二次析构是P自己调用的自己的析构

 

 如果把~person变成虚函数

上面的调用结果如下,不受影响。 

 

  • 那么析构函数是否建议定义为虚函数呢? 

现在把virtual去掉,下面这个代码没有调用出Student的析构函数。会发生内存泄漏。

因为它是普通调用,ptr2是Person类型的,所以会调用Person的析构。

 那如果想要多态调用呢?

因为子类析构函数和父类析构函数构成隐藏关系(由于多态关系需求,所有析构函数都会特殊处理成destructor函数名),底层会对析构函数名进行处理,子类和父类的析构函数在底层其实是同名的,在不同的环境下 构成隐藏,重写等关系。

必须让它强行相同,才能符合多态条件。

 父类和子类析构函数因为被修改了,它俩这样写在执行过程中名字被视为相同,构成三同。

实现父类时,可以无脑地给析构函数加virtual。


  • 如何实现一个不能被继承的类
  1. 父类构造函数设为私有。继承下来的成员必须调用父类构造函数初始化,但私有后父类构造函数子类不可见,就不能成功继承。

 

 而如果把析构函数私有 

这样不能成功继承。

 但这样就能成功继承了。

 2.类定义时加final。 

 final修饰虚函数表示虚函数不能被重写。

 override放在子类检查(编译时检查)是否完成重写。

 把划线部分原先的virtual去掉,就会报错

 参数不同也会报错。


  • 抽象类 

包含纯虚函数的类叫抽象类。

抽象类不能实例化出对象。

 BMW不能实例化出对象,纯虚函数被继承下来了,BMW也是抽象类。

 而如果子类重写的话,BMW就能成功实例化出对象。因为重写后BMW中的就不是纯虚函数了。

虚函数的重写是接口继承,普通函数继承是实现继承。

 子类也可以不加virtual。

 抽象类一定程度上起到了让子类虚函数重写的作用。

不需要类的对象,可以用抽象类。

 func构成虚函数重写,P调用test函数,实际是A调用的,test函数中的func由this指针调用,这个this指针是A*,所以这个func属于多态调用,(test被继承下来了,但是this指针仍然是A*)由于p是B*,所以它调用的是B中的func,但是重写时,子类接口用的父类,不加virtual也是虚函数,所以它缺省值用的父类,所以打印的是1。

而如果下面这样写。

 结果如下

 因为调用func的this指针是B*,不满足父类指针或引用调用虚函数的条件,所以是普通调用,func就不会虚函数重写,(多态调用用的是虚函数的重写),调用的是B中func本身。

选C, 切片时指针会偏移 ,p1和p3相等,但p1看的是切片Base1部分,p3看的是整体。


这样是4个字节

 这个大小是12个字节,里面有个指向虚函数表的指针(vftptr)在32位系统下占4个字节。

虚函数表本质是函数指针数组。里面存的是虚函数的指针。

 完成重写的虚函数,虚函数表的对应位置覆盖成重写的虚函数。

父类和子类虚函数表不同。


  • 多态的原理 

调用时,虚函数指针指向父类虚函数表,那么就调用父类的虚函数,指向子类的虚函数表,你们就调用子类的虚函数。

静态指的是编译时。比如函数重载。


  • 虚函数表存放在哪个位置? 

 be对象指针十分接近常量区,虚函数表就是存放在常量区的。

底下这两个是一回事。

 同一个类型共享一个虚表,be,b1和b2虚表是同一个。


我们发现子类续表中没有fun3,实际是监视窗口进行了优化。

 在内存监视窗口其实是有的

 函数指针typedef定义的变量位置必须放中间。

函数指针加括号就能调用函数了

不是重写的虚函数放在了虚表的后面。 

 

 这样也可以

  •  多继承的虚函数

现在只打印了一张虚表。 

这样可以打印第二个虚表

多继承中,自己的虚函数(func3)放到第一个继承的类中的虚函数表中。

这种方式也能打印第二个虚表。

 多继承不一定有多个虚表。如果有一个类中没有虚函数,你们就会少一个虚表。

初始化列表初始化的顺序和写的顺序没关系,是按声明顺序初始化的,在继承里面就是学先继承,谁先被初始化。

调换顺序结果也是一样的。

 初始化列表是要对父类进行初始化,如果B和C是直接继承,把virtual去掉,那么D是菱形继承,不是菱形虚拟继承,此时D中只有B和C,初始化列表就没有A了。


下图结果p1>p2=p3

  •  内联函数能是虚函数吗?

多态调用时编译能通过,但编译器会忽略内联属性,它要运行后到虚函数表中去找。

多态调用忽略内联属性,普通调用继续保持内联属性。inline是建议,但是编译器不同意这个建议。

父类的对象为什么不能调用多态。

如果父类对象被子类切片赋值过,那么父类的虚表就是子类的虚表,会产生矛盾。


  • 静态成员函数不能是虚函数。

静态成员函数没有this指针,变成虚函数没有意义。   

  • 构造函数能不能是虚函数

不能,虚表指针是在初始化列表初始化的,而且没有意义。

  • 析构函数最好设为虚函数。
  • 访问普通函数快还是虚函数快?

如果是普通调用,那就是一样快,如果多态调用,那么是普通函数快。

  • 虚表是编译时生成。

构造函数初始化列表中初始化虚表指针。

虚函数表存的是虚函数的地址,为了实现多态,虚基表存的是偏移量,为了找到虚基类。

抽象类强制重写虚函数,抽象类体现了接口继承关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

南种北李

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

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

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

打赏作者

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

抵扣说明:

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

余额充值