目录
多态,即同一事物的多种形态。不同的对象做同一件事会有不一样的结果。
举个例子,同样都是买票这件事,成人买票就是成人票,儿童就是儿童票,这就是一种多态。
在C++继承体系中体现的多态:儿童类继承了成人类。成人对象在售票系统买票,产生结果是成人票;儿童对象在售票系统买票,产生结果是儿童票。当然这里的程序需要进行一定的处理才能分辨究竟是该产生成人票还是儿童票,如何处理呢?使用下文中的多态。
这里先给出售票的多态代码来观察一下多态,将需要使用多态的函数用virtual修饰,使用基类的指针或引用调用基类和子类的多态函数。注意:要使用多态的函数必须要在基类中加上virtual,子类中可以不加。
代码一:
- //代码一
- #include "iostream"
- using namespace std;
-
- class Person {
- public:
- virtual void Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
- };
-
- class Child : public Person {
- public:
- virtual void Buy_tickets() {
- cout << "身份:儿童" << endl;
- cout << "出售儿童票" << endl;
- }
- };
-
- //售票系统
- void Ticketing_system(Person& per) {
- per.Buy_tickets();
- }
-
- int main() {
- Child chi;
- Ticketing_system(chi);
- cout << "==========" << endl;
- Person per;
- Ticketing_system(per);
- }
这是代码一的打印结果:可以发现,售票系统只有一个,却实现了不同的功能。这就是多态。
2.多态定义与实现
(1)多态的构成条件
1.多态是在继承体系中,基类函数中需要加virtual,子类中可以不加,但最好加上。这样的函数被称为虚函数。
2.通过基类的指针或引用调用虚函数,因为在程序运行时会通过不同的引用或指针调用不同的虚函数。
3.被调用的必须是虚函数,且派生类必须对虚函数进行重写。
对于重写,后文会介绍,这里只需要知道定义:派生类中有一个跟基类完全相同的虚函数(完全相同指的是返回值,函数名,参数列表,但还有两个例外,后面仔细讲一下)
下面来验证三条规则。
[1]规则1
去掉继承关系后,儿童对象调用售票系统会直接报错,因为没有办法把Child类型转为Person类型。由此证明多态必须在继承体系中。
代码二:
- //代码二
- class Person {
- public:
- virtual void Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
- };
-
- class Child{
- public:
- virtual void Buy_tickets() {
- cout << "身份:儿童" << endl;
- cout << "出售儿童票" << endl;
- }
- };
[2]规则2
将售票系统的参数改成Person per,进行值传递。这样也会报错,而代码一中使用指针和引用则不会报错。
代码三:
- //代码三
- //售票系统
- void Ticketing_system(Person per) {
- per.Buy_tickets();
- }
[3]规则3
将儿童类的函数返回值修改后,直接报错。证明一般情况下子类中的虚函数必须和基类的虚函数相同。(返回值,函数名,参数列表)
代码四:
- //代码四
-
- class Person {
- public:
- virtual void Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
- };
-
- class Child : public Person{
- public:
- //此函数报错
- virtual int Buy_tickets() {
- cout << "身份:儿童" << endl;
- cout << "出售儿童票" << endl;
- return 0;
- }
- };
(2)虚函数
虚函数形式很简单,被virtual修饰的成员函数就是虚函数。
代码五:
- //代码五
- virtual void Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
(3)虚函数的重写(超重要)
[1]概念
虚函数的重写(覆盖):派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。
上面这条已经在多态的构成条件中验证了,接下来看看虚函数重写的两个例外。
[2]虚函数的两个例外
1. 协变(基类与派生类虚函数返回值类型不同)
派生类重写基类虚函数时,与基类虚函数返回值类型可以不同,但需要满足以下条件。
基类虚函数返回基类对象的指针或者引用,
子类虚函数返回子类对象的指针或者引用。
这里同色的必须是同一个继承体系,异色的可以是也可以不是。如:红色的基类和子类必须在同一个继承体系,但是红色和黄色的可以在也可以不在。
举例:下面A,B是同一个继承体系,Person,Child是同一个继承体系,但他们是属于两个不同的继承体系。Person基类中就返回了A基类的对象的引用,Child子类中就返回了B子类的对象的引用。
代码六:
- //代码六
- #include "iostream"
- using namespace std;
-
- class A {};
- class B :public A {};
-
- class Person {
- public:
- virtual A* Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- return new A;
- }
- };
-
- class Child : public Person{
- public:
- //此函数报错
- virtual B* Buy_tickets() {
- cout << "身份:儿童" << endl;
- cout << "出售儿童票" << endl;
- return new B;
- }
- };
-
- //售票系统
- void Ticketing_system(Person& per) {
- per.Buy_tickets();
- }
-
- int main() {
- Child chi;
- Ticketing_system(chi);
- cout << "==========" << endl;
- Person per;
- Ticketing_system(per);
- }
2. 析构函数的重写(基类与派生类析构函数的名字不同)
如果基类的析构函数为虚函数,此时派生类析构函数只要定义,无论是否加virtual关键字,都与基类的析构函数构成重写,虽然基类与派生类析构函数名字不同。
只有析构函数才可以这样!!!
(4)C++11的override和final
[1]override
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
这是为了防止用户把函数名字打错给出的关键字,加上这个关键字后,一旦没有重写某个虚函数,就会报错。(只能用在子类中)
用法:返回值 子类虚函数名 (参数列表) override {}
代码七:
- //代码七
- class Person {
- public:
- virtual void Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
- };
-
- class Child : public Person{
- public:
- //此函数报错
- virtual void Buy_tickets111() override{
- cout << "身份:儿童" << endl;
- cout << "出售儿童票" << endl;
- }
- };
[2]final
1.修饰虚函数,表示该虚函数不能再被重写。
用法:virtual 返回值 函数名 () final{}
代码八:
- //代码八
- class Person {
- public:
- virtual void Buy_tickets() final {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
- };
-
- class Child : public Person{
- public:
- //此函数报错
- void Buy_tickets() {
- cout << "身份:儿童" << endl;
- cout << "出售儿童票" << endl;
- }
- };
2.修饰类,表示这个类不可以被继承。
使用方法:class 类名 final {}
代码九:
- //代码九
- class Person final{
- public:
- void Buy_tickets() {
- cout << "身份:成人"<
- cout << "出售成人票" << endl;
- }
- };
(5)重载,重写(覆盖),重定义(隐藏)的对比
[1]重载
重载条件:只有函数可以重载;要重载的函数在同一作用域;函数名相同;参数个数,参数类型,参数顺序需要不同(满足一个就可以)。
[2]重写(覆盖)
重写条件:只有成员函数可以重写;继承体系中;要重写的函数形式必须完全相同,除了两个例外;重写时基类必须加上virtual,子类加不加都可以;子类与基类中的虚函数访问权限可以不同。
重写的例外:协变;析构函数。
[3]重定义(同名隐藏)
重定义条件:成员函数和成员变量都可以重写;继承体系中;子类与基类的函数名或变量名相同即可形成重定义。
3.抽象类
(1)纯虚函数
纯虚函数是为了告诉编译器这个类中的虚函数不在这个类中实现,让它的子类去实现。
语法格式:virtual 返回值 函数名(参数列表) = 0;
代码十:
- //代码十
- virtual void Buy_tickets() = 0;
(2)抽象类概念
抽象类:包含纯虚函数的类叫做抽象类(或者接口类)。抽象类不可实例化对象,派生类继承抽象类后也不可以实例化,除非将抽象类中的纯虚函数全部实现。
那么为什么会出现抽象类?举个简单的例子,现在让你写一个图形类,里面要写出计算面积的函数。可是并没有说是什么图形,面积函数怎么写?完全写不了,那就不写了,这就出现了纯虚函数,出现了抽象类。里面的函数让子类具体某个图形使用的时候自己去实现。
这里为了节省篇幅并没有写构造函数。
代码十一:
- //代码十一
- #include "iostream"
- using namespace std;
-
- //图形类
- class graphical {
- public:
- virtual void Area() = 0;
- };
- //正方形类
- class square : public graphical {
- public:
- int len;//边长
- public:
- void Area() {
- cout << len * len << endl;
- }
- };
- int main() {
- square s;
- s.len = 3;
- s.Area();
- }
(3)接口继承和实现继承
[1]接口继承
接口是什么?通俗的说,接口其实是一种行为规范,一种规则。抽象类的继承就是接口继承,抽象类中我们并不实现函数,因为并不具体,例如上文的图形类。我们在抽象类中只是规定了子类需要实现的函数。接口继承就像是一种规则,它要求子类根据它的规则去实现。
[2]实现继承
普通继承就是一种实现继承,里面切切实实的实现了函数,把这个函数直接给了子类去使用,没有对子类的行为作出限制。
-
相关阅读:
MySQL进阶教程汇总
软件测试基础理论知识—用例篇
【算法】哈希学习笔记
【图像分割】基于matlab直方图的自适应阈值方法分割前景与背景【含Matlab源码 2144期】
沁恒CH32V003(二): Ubuntu20.04 MRS和Makefile开发环境配置
全面解读视频生成模型Sora
docker 安装本地starrocks测试环境
Linux基础教程:10、进程通讯(管道通讯)
3D包容盒子
【Verilog 教程】6.2Verilog任务
-
原文地址:https://blog.csdn.net/weixin_57761086/article/details/126657881