C++进阶 多态讲解
创始人
2024-04-28 13:19:09
0

作者:@小萌新
专栏:@C++进阶
作者简介:大二学生 希望能和大家一起进步!
本篇博客简介:简单介绍C++中多态的概念

多态

  • 多态的概念
  • 多态的定义及实现
    • 多态的构成条件
    • 虚函数
    • 虚函数的重写
    • 虚函数重写的两个例外
      • 协变
      • 析构函数的重写
    • C++11 override和final
      • final
      • override
    • 重载、覆盖(重写)、隐藏(重定义)的对比
  • 抽象类
    • 抽象类的概念
    • 接口继承和实现继承
  • 总结

多态的概念

多态就是函数调用的多种形态,使用多态能够使得不同的对象去完成同一件事时,产生不同的动作和结果

例如 我们去吃海底捞的时候 普通人去就是原价 学生去就会有学生优惠 这就叫做多态

多态的定义及实现

多态的构成条件

多态是指不同继承关系的类对象,去调用同一函数,产生了不同的行为。语法上 我们这里要满足两个条件

  1. 必须通过基类的指针或者引用调用虚函数。

我们会在文章的后面解释 为什么只能用指针或者是引用 不能使用对象

  1. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写。

还是一样 我们下面会解释 为什么是虚函数 为什么必须要重写

虚函数

被virtual修饰的类成员函数被称为虚函数。

例如下面的这段代码

class Person
{// 虚函数virtual void Print();
};int main()
{return 0;
}

我们的Print就是虚函数

这里有两点需要注意的:

  1. 只有类的非静态成员函数前可以加virtual,普通函数前不能加virtual。

关于这个问题 因为静态成员和普通成员函数是没有this指针的

  1. 虚函数这里的virtual和虚继承中的virtual是同一个关键字,但是它们之间没有任何关系。虚函数这里的virtual是为了实现多态,而虚继承的virtual是为了解决菱形继承的数据冗余和二义性。

这个是关于virtual的用法 就不用过多解释了

虚函数的重写

虚函数的重写在语法层面上叫做重写

在原理层面上叫做覆盖 后面的例子会让大家明白这一点

它有两个必要条件

  1. 必须是虚函数

  2. 三同 即 函数名相同 参数相同 返回值相同

还是一样 我们来看代码

class Person
{
public:virtual void buy_ticket(){cout << "买票 - 原价" << endl;}
private:};class child : public Person
{
public:// 这里的virtual也可以不写 因为语法规定 只要三同 实际上这里的函数就继承了父类的虚函数属性// 但是不管我们平时敲代码 或者写项目的时候都要加上去 保证代码的可读性virtual void buy_ticket(){cout << "买票 - 半价" << endl;}
private:
};class soldier : public Person
{
public:// 为了证明上面说可以省略 virtual 的正确性 这里省略之 void buy_ticket(){cout << "买票 - 优先" << endl;}
private:};

现在我们通过父类的对象指针还有引用调用看看能不能完成多态

void func1(Person& p)
{p.buy_ticket(); 
}void func2(Person* p)
{p->buy_ticket();
}void test_vritual()
{Person p;child c;soldier s;func1(p);func1(c);func1(s);cout << "test ------ ptr" << endl;func2(&p);func2(&c);func2(&s);
}int main()
{test_vritual();return 0;
}

显示效果如下

在这里插入图片描述

虚函数重写的两个例外

协变

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

比如说我们改写下之前写的代码

在这里插入图片描述
我们可以看到这里它们的返回值并不相同 但是依然满足多态 可以运行 这就是协变机制

要记住的一点是 协变的返回值必须是基类或者派生类的指针或引用 不然会报错 类似这样

在这里插入图片描述

析构函数的重写

如果父类的析构函数为虚函数 那么只要子类的析构函数定义了 那么它就与父类中的析构函数构成重写

比如说我们看下面的代码

class a
{
public:virtual ~a(){cout << "~a" << endl;}
};class b : public a
{
public:virtual ~b(){cout << "~b" << endl;}
};

其中 a和b的析构函数就构成多态

怎么证明呢? 我们再来看下面的一段代码

void func(a& p)
{p.~a();
}int main()
{a a1;b b1;cout << "start test" << endl;func(a1);func(b1);cout << "test end" << endl;return 0;
}

运行结果如下

在这里插入图片描述
我们可以发现 我们输入不同的对象引用确实触发了不同的析构函数

至于为什么出现了三次析构函数 可以参考下我上一篇继承的博客

派生类对象在析构时,会先调用派生类的析构函数再调用基类的析构函数。

至于后面的三次析构则是 a1 和 b1的生命周期结束了 自动调用的

那么这里的问题就来了

父类和子类的析构函数构成重写的意义何在呢?

我们试想下面的场景

我们创建一个父类对象和一个子类对象 并且使用父类的指针指向它们

然后全部delete掉

a* a1 = new a;
a* b1 = new b;delete a1;
delete b1;

此时如果没有重写析构函数的话 两次析构其实都是析构的父类的

这样子就会造成一个内存泄漏的情况

而我们期望的是 delete a1 就是析构父类

delete b1 就是析构父类加子类

本质上是一种多态 所以我们要重写

记不记得我们上面继承提过一个知识点

析构函数的名字会被统一处理成destructor();

现在应该能充分理解为什么这么做的原因了吧 为了多态开路

C++11 override和final

我们从上面的博文中就可以看出 C++对于函数重写比较严格 但是我们有可能由于自身的疏忽 导致字符写反 或者返回值写错等原因无法构成重写

而这种错误要在程序运行之后才能被编译器发现 我们觉得有点太慢了

为了解决这个问题 C++中给出了两个关键字 这里我们来一个个学习下它们

final

final:修饰虚函数,表示该虚函数不能再被重写。

我们来看下面的代码

class Person
{
public:virtual void print() final;
private:};class child : public Person
{
public:void print(){;}
private:};

运行下我们可以发现

在这里插入图片描述

编译的时候会报错 不能够重写

override

override:检查派生类虚函数是否重写了基类的某个虚函数,如果没有重写则编译报错。

我们来看下面的两组对比

在这里插入图片描述
在这里插入图片描述

重载、覆盖(重写)、隐藏(重定义)的对比

在这里插入图片描述
具体的内容看上面这张图就好

抽象类

抽象类的概念

在虚函数的后面写上=0,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类)

抽象类不能实例化出对象

为了证明这个概念 我们写出下面的代码

class person
{
public:virtual void print() = 0;
private:
};int main()
{person p;return 0;
}

在这里插入图片描述
我们可以发现 符合我们上面的结论

派生类继承抽象类后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象

比如说这样子

class child : public person
{
public:
private:
};

在这里插入图片描述

接着我们重写下虚函数试试

class child : public person
{
public:virtual void print() {cout << "child" << endl;}
private:
};

在这里插入图片描述
我们发现 这样子就可以运行了

抽象类既然不能实例化出对象,那抽象类存在的意义是什么?

我们说 意义有二

  1. 抽象类可以更好的去表示现实世界中,没有实例对象对应的抽象类型,比如:植物、人、动物等。

  2. 抽象类很好的体现了虚函数的继承是一种接口继承,强制子类去重写纯虚函数,因为子类若是不重写从父类继承下来的纯虚函数,那么子类也是抽象类也不能实例化出对象。

接口继承和实现继承

实现继承: 普通函数的继承是一种实现继承,派生类继承了基类函数的实现,可以使用该函数。

接口继承: 虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态

总结

在这里插入图片描述
本文主要讲解了C++中多态的一些使用

相关内容

热门资讯

【NI Multisim 14...   目录 序言 一、工具栏 🍊1.“标准”工具栏 🍊 2.视图工具...
银河麒麟V10SP1高级服务器... 银河麒麟高级服务器操作系统简介: 银河麒麟高级服务器操作系统V10是针对企业级关键业务...
不能访问光猫的的管理页面 光猫是现代家庭宽带网络的重要组成部分,它可以提供高速稳定的网络连接。但是,有时候我们会遇到不能访问光...
AWSECS:访问外部网络时出... 如果您在AWS ECS中部署了应用程序,并且该应用程序需要访问外部网络,但是无法正常访问,可能是因为...
Android|无法访问或保存... 这个问题可能是由于权限设置不正确导致的。您需要在应用程序清单文件中添加以下代码来请求适当的权限:此外...
AWSElasticBeans... 在Dockerfile中手动配置nginx反向代理。例如,在Dockerfile中添加以下代码:FR...
北信源内网安全管理卸载 北信源内网安全管理是一款网络安全管理软件,主要用于保护内网安全。在日常使用过程中,卸载该软件是一种常...
AsusVivobook无法开... 首先,我们可以尝试重置BIOS(Basic Input/Output System)来解决这个问题。...
月入8000+的steam搬砖... 大家好,我是阿阳 今天要给大家介绍的是 steam 游戏搬砖项目,目前...
​ToDesk 远程工具安装及... 目录 前言 ToDesk 优势 ToDesk 下载安装 ToDesk 功能展示 文件传输 设备链接 ...