[C++] 多态的使用分析: 多态使用相关问题、协变、析构函数的多态、final与override关键词、抽象类分析...
什么是多态?
多态的使用
- 必须是父类的指针 或 父类的引用 来调用 虚函数
- 被调用的函数必须是 虚函数, 并且 此 虚函数 必须被 重写
virtual
, 用于 解决菱形继承的数据冗余和二义性的问题, 将菱形继承改为 菱形虚拟继承
不仅是虚拟继承, 虚函数 也是使用 关键字virtual
定义的buyTicket
virtual
, 函数名前加 virtual
的函数被称为虚函数
父类中的虚函数, 在子类中 如果存在 同名、同返回值类型、同参数的函数, 则 构成函数重写, 而不构成隐藏什么是重写?
C++ 类成员函数中 加
virtual
的函数被称为 虚函数 如果这个类存在子类, 且其子类中 存在与父类中的虚函数 函数名、函数参数、函数返回值类型 都相同 的函数, 则称子类重写了父类的虚函数, 或 父类虚函数被重写即 重写 是父类虚函数与子类函数的关系, 且如果想要构成重写, 这需要满足两个必要条件:
- 父类中的函数必须是虚函数, 即必须有
virtual
- 子类中的函数 与父类中 虚函数的函数名、函数参数、函数返回值类型 都相等
必须同时满足这两个条件, 则称 子类重写了父类的虚函数
子类中的函数 不写
virtual
也同样构成重写, 但是建议写上可读性比较强
Person
类中的成员函数 virtual void buyTicket()
分别被 Student
、Elderly
、Soldier
类中的 virtual void buyTicket()
重写 了BuyTicket(Person& per)
使用 父类引用作为参数, 在函数体内调用成员函数 buyTicket
相关问题
问题1: 如果只是父类对象可不可以多态调用呢?
答: 不可以。
示例:
如果 父子类虚函数构成重写, 但 使用父类对象调用虚函数, 则不构成多态
问题2: 如果虚函数不构成重写 构不构成多态?
答: 不构成。
示例:
如果 父子类函数之间不构成重写, 即使使用 父类指针或父类引用, 也是不构成多态的
问题3: 为什么子类重写父类的虚函数时, 不加 virtual 依旧是虚函数
答: 因为在继承体系中, 子类已经继承了父类虚函数的接口部分(函数名、函数返回值、函数参数列表等, 包括virtual), 所以子类中重写父类的虚函数不加virtual也可以
这也说明了, 子类重写父类的虚函数时, 父类虚函数对于子类来说属于 接口继承
也仅限于重写的时候是接口继承
协变
- 当父类虚函数的返回值类型是父类指针时, 子类虚函数返回值类型可以是子类指针, 同样构成重写
- 当父类虚函数的返回值类型是父类引用时, 子类虚函数返回值类型可以是子类引用, 也构成重写
析构函数的多态
~类名()
, 怎么对析构函数进行重写呢?~destructor
**, 并且 解释说这是为了多态的使用~destructor
为多态做准备C++11: final、override
final
和 override
final
:意为最终, 作用也非常的简单: 添加在 虚函数函数名之后, 可以禁止此虚函数被重写, 即表示 此函数已经是最终的函数不能再改变
override
override
的作用, 则是 用于子类的虚函数 检查此虚函数是否完成了对父类虚函数的重写, 若没有完成重写, 则报错
抽象类
=0
那么这个虚函数就变成了纯虚函数继承了抽象类的类, 也就继承了抽象类的纯虚函数, 所以 抽象类的子类也无法实例化对象
一个纯虚函数的函数内容是无意义的
因为 包含纯虚函数的类 不能实例化对象, 其子类也得重写纯虚函数才能实例化对象
所以 纯虚函数的函数内容是无意义的, 这一点更加说明了 父类虚函数被继承且重写时, 对子类来说是接口继承
接口继承分析
关于接口继承有一题, 可以验证对接口继承的理解:
这段代码的输出结果是什么?
答案是:
B->1
如果没有对类的继承、多态、接口继承没有一个明确清晰的了解, 这一题是不容易分析出来的
分析:
首先, B类 new了一个对象, 并将其指针给了 p(B类指针), 即 p 为指向B类对象的B类指针
所以,
p->test();
调用的是 B类对象的A类部分的成员函数, 且A类中的虚函数已被重写进入
test()
函数时,this
指针是A*
类型 的, 指向的是B类中A类的部分:所以
p->test()
内部调用func(); 其实是 this->func();
而此时
this是A*类型
的, 在 B类对象中 使用 A* 调用构成重写的函数是什么?多态调用所以应该执行的语句是:
cout << "B->" << val << endl;
那么问题又来了, 为什么调用B类重写的的
func
, val 为 1这就是 接口继承 的体现了
多态调用构成重写的函数, 编译器将其认定为 接口继承 即 编译器认定
virtual void func(int val = 1)
被继承到了B类中, 所以此函数构成重写的前提下, 修改此函数的部分内容是无效的, 因为编译器已经认定了接口的组成所以, 在多态调用此函数时, 其实是:
而不是:
C++ 这样设计真的离谱!!!
而 如果是正常的调用 B类的
func()
而不通过多态调用, 又会正常调用函数:是因为正常调用, 编译器会只考虑B类本身的内容, 即使A类虚函数被重写了, 但是没有多态调用 编译器不会将其认定为 接口继承, 而是 认定为 实现继承。
所以就 以
调用
作者: 哈米d1ch 发表日期:2022 年 7 月 26 日