【C++】多态(上) 多态 | 虚函数 | 重写 | final、override | 接口继承与实现继承 | 抽象类

一、多态

概念

多态,就是多种状态,即不同的对象去完成同一个行为时会产生出不同的状态。比如:买票时,成人要原价买,学生和老人就可以享受优惠价便宜一点儿。同样是买票这个行为,不同的对象来做就有不同的状态,这就是多态的一种体现。

从代码实现上来说,多态指的是通过一个父类指针 or引用调用一个虚函数时,会根据具体对象的类型来调用该虚函数的不同实现。在多态中,相同的操作可以作用于不同的对象,而具体执行的操作则取决于对不同对象的类型判断。(“看人下菜碟🤪”)

那怎么构成多态呢?有两个条件:

1.子类重写父类的虚函数

2.通过父类的指针或引用去调用虚函数

估计你现在是一头雾水:啥是虚函数?啥是重写?多态到底是怎么用的?

不急,你现在对多态的概念一定还是一片混沌。下面我先讲解“虚函数”、“重写”的概念,然后举出多态的代码实例,你才能体会什么是多态。看到后面,再回过头来看多态的概念,会有更透彻的理解。

虚函数

先来学习一个知识点“虚函数”,注意,和菱形继承那里的 虚继承 是两个完全不同的概念!它俩的关系就是金鱼和自行车之间的关系。

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

虚函数必须是非静态的成员函数,非成员函数和静态函数是无法成为虚函数的。

class Person {
public:
    virtual void BuyTicket() { …… }
};

虚函数的作用是:实现多态机制。

重写(覆盖)

若子类定义了一个和父类的虚函数 一模一样的虚函数,那么称子类中的虚函数重写 / 覆盖了父类的虚函数。(拥有相同的名字、返回值、形参列表)

重写是针对虚函数的概念。普通函数是没有重写的说法的。

例:

class Person 
{
public:
    virtual void BuyTicket() {
        cout << "成人票10r" << endl;
    }
};
class Student:public Person
{
public:
    virtual void BuyTicket() {    //子类中的虚函数重写了父类的虚函数
        cout << "学生票5r" << endl;
    }
};

其实,在重写基类虚函数时,派生类的虚函数即使 不加virtual关键字,也是可以构成重写的

这是因为,继承后基类的虚函数被继承下来了,在派生类依旧保持虚函数属性。

但是该种写法不规范,不建议这样使用。我们还是老老实实加上virtual吧。

如何构成多态

构成多态的两个条件:1.子类重写父类的虚函数 2.通过父类的指针或引用去调用虚函数

我们已经有了“虚函数”和“重写”的知识储备,现在我来写一个多态的例子:

#include<iostream>
using namespace std;
class Person 
{
public:
    virtual void BuyTicket() {
        cout << "成人票10r" << endl;
    }
};
class Student:public Person
{
public:
    virtual void BuyTicket() {    //条件1.子类重写父类的虚函数
        cout << "学生票5r" << endl; 
    }
};
void Pay(Person& p) {   
    p.BuyTicket();    //条件2.通过父类的引用去调用虚函数
}
int main() {
    Person p;
    Student s;
    //用引用调用
    Pay(p);
    Pay(s);
    cout << endl;
    //用指针调用
    Person* p1 = &p, * p2 = &s;   //条件2.通过父类的指针去调用虚函数
    p1->BuyTicket();
    p2->BuyTicket();
    return 0;
}

这,就是多态!

Q:

为什么调虚函数 一定得是父类的指针or引用?子类不行吗?

因为父类不仅能接收父类的值,还能接收子类的值;而子类仅能接收子类的值,不能接收父类的值。也就是说,父类更能包罗。

为什么一定得通过指针or引用调用?

这个问题先搁置一下。等讲到多态的底层原理时再说。

重写的例外:协变

(这个知识点实际上不常用,但是笔试题会考)

协变:是函数重写的一种特殊情况,这种情况下,父类和子类的虚函数的返回值是不同的

父类的虚函数返回父类对象的指针or引用,子类的虚函数返回子类对象的指针or引用。这种情况就称为“协变”。

协变的前提是:协变的两个类型必须是父子关系。

例:

class Person 
{
public:
    virtual Person& BuyTicket() {   //注意返回类型
        Person p;
        cout << "成人票10r" << endl;
        return p;
    }
};
class Student:public Person
{
public:
    virtual Student& BuyTicket() {   //注意返回类型
        Student s;
        cout << "学生票5r" << endl;
        return s;
    }
};
int main() {
    Person p;
    Student s;
    Person* p1 = &p, * p2 = &s;
    p1->BuyTicket();
    p2->BuyTicket();
    return 0;
}

既然存在返回值协变的情况,那说明构成多态的虚函数 的返回类型未必相同。这是常见的考点。

析构函数的重写

析构函数是需要重写的。

➡️为什么呢?来看下面这种特殊的情况:

//当析构函数不是虚函数,也未被重写时
class A
{
public:
    ~A() {   
        cout << "~A()" << endl;
    }
};
class B:public A
{
public:
    ~B() {
        cout << "~B()" << endl;
    }
};
int main() {
    A* pa = new B;  
    delete pa;    //我希望调用的是B的析构函数
    return 0;
}

当我用一个父类的指针去指向子类的对象时,我希望 程序能根据对象的类型去调用对应的析构函数,也就是~B()。

但实际上,这里并没有实现多态,程序会老老实实根据pa的类型去调用~A()。而这样,由于B中部分成员未被释放,如果这部分成员涉及资源管理的话,就会导致内存泄露。

所以,析构函数得实现多态。

(这种特定情况需要记住,面试时经常会问到这个问题)

➡️但问题又来了:实现多态的前提是 子类重写父类的虚函数,而这俩的析构函数名字都不一样,怎么能重写呢?

这就不用我们担心了,编译器会出手。可以理解为,编译器对析构函数的名称做了特殊处理,编译后析构函数的名称统一处理成destructor,我们只要加上virtual就可以。

正确的写法:

class A
{
public:
    virtual ~A() {
        cout << "~A()" << endl;
    }
};
class B:public A
{
public:
    virtual ~B() {
        cout << "~B()" << endl;
    }
};
int main() {
    A* pa = new B;
    delete pa;
    return 0;
}

关键字final(C++11)

final,“最终的”,表示这个就是最终版,后继无人了。

final的两个功能:1. 修饰虚函数,表示该虚函数不能再被重写 2. 修饰类,让此类不能被继承

//修饰虚函数
class A
{
public:
    virtual void print() final {   
        ……
    }
};
class B:public A
{
public:
    ……  //不能继承print函数
};

之前我们说过,要想一个类不能被继承,那就把它的构造函数私有化。实际上,这种方式并不好。因为从继承关系来说,它并没有直接阻止继承这种行为,子类依旧是继承了父类。只是创建创建不了子类对象。这是一种间接的阻止。

C++11引入的final,就可以直接阻止,只要父类加了final,那就是不可被继承的:

class A final   
{
public:
    ……
};
​
class B:public A   //会报错"不能将final类类型作为基类"
{
public:
    ……
};

关键字override(C++11)

override: 检查子类虚函数 是否正确地完成重写,如果没有则编译报错。

有时候我们会不小心把函数名写错,或者父子的参数列表顺序不一致,导致无法构成重写。这种错误编译器往往检查不出来,当运行出错误的结果时,我们去Debug半天,才无语地发现原来bug在这。

而override的出现,就是帮助我们去检查错误,确保重写的正确,免得我们调试找BUG。

class A 
{
public:
    virtual void print() {
        ……
    }
};
class B:public A
{
public:
    void pint() {   //这里把函数名写错了,但编译器没检查出来
        ……
    }
};

当加了override以后:

class B:public A
{
public:
    void pint() override {  
        ……
    }
};

接口继承和实现继承

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

虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口。因为目的是重写,达成多态,所以只需要继承接口,函数体就用不着了。

所以说,如果不实现多态,不要把函数定义成虚函数。

重载、重写、重定义的对比

二、抽象类

概念

在虚函数的后面写上 =0 ,则这个函数为纯虚函数,纯虚函数是不需要实现的。

包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象,只能被继承。

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

class Course    //课程是抽象类
{
public:
    virtual void TeachingMethod() = 0;  //纯虚函数
};
class Math:public Course
{
public:
    virtual void TeachingMethod() {
        cout << "Math" << endl;
    }
};
int main() {
    //Course c;   //报错,抽象类无法实例化出对象
    Math m;      //派生类进行了重写,可以实例化出对象
    return 0;
}

纯虚函数的作用是:强制子类去完成重写。

抽象类用于表示抽象的类型。实际中,许多概念是抽象的,没有对应的实体。比如“植物” “形状”,这是笼统的概念,只有把“植物”具体到“含羞草”,把“形状”具体到“三角形”,才能定义出一个具体的类型。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/193799.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【brpc学习实践九】mbvar及bvar可观测

概念、学习地址 mbvar中有两个类&#xff0c;分别是MVariable和MultiDimension&#xff0c;MVariable是多维度统计的基类&#xff0c;MultiDimension是派生模板类。也是主要用来多多线程计数用的。这里用到再详细去了解即可 https://github.com/luozesong/brpc/blob/master/do…

231127 刷题日报

这周值班。。多少写道题吧&#xff0c;保持每天的手感。老婆给买了lubuladong纸质书&#xff0c;加油卷。 1. 131. 分割回文串 写个这个吧&#xff0c;钉在耻辱柱上的题。 为啥没写出来&#xff1a; 1. 递归树没画对 把树枝只看做是1个字母&#xff0c;而且不清楚树枝和节点…

JAVA:深入探讨String性能优化让你的程序更高效

1、简述 在现代软件开发中&#xff0c;字符串&#xff08;String&#xff09;是一个不可或缺的数据类型&#xff0c;几乎每个应用程序都在某种程度上使用字符串。然而&#xff0c;由于字符串操作的频繁性质&#xff0c;它们可能成为程序性能的瓶颈之一。在本文中&#xff0c;我…

【vue】a-table的斑马纹以及hover样式的修改:

文章目录 一、效果&#xff1a;二、实现&#xff08;以jeecg为例&#xff09;&#xff1a; 一、效果&#xff1a; 二、实现&#xff08;以jeecg为例&#xff09;&#xff1a; // 设置基数行样式 // .ant-table-tbody tr:nth-child(n) { // color: #fff; // }// hover时候每行…

第三节HarmonyOS DevEco Studio了解基本工程目录

一、工程级目录 工程的目录结构如下。 目录详情如下&#xff1a; AppScope&#xff1a;存放应用全局所需要的资源文件。Entry&#xff1a;应用的主模块&#xff0c;存放HarmonyOS应用的代码、资源等。oh_modules&#xff1a;工程的依赖包&#xff0c;存放工程依赖的源文件。b…

【STM32】GPIO输入

1 GPIO输出 1.1 按键简介 按键&#xff1a;常见的输入设备&#xff0c;按下导通&#xff0c;松手断开 按键抖动&#xff1a;由于按键内部使用的是机械式弹簧片来进行通断的&#xff0c;所以在按下和松手的瞬间会伴随有一连串的抖动 1.2 传感器模块简介 传感器模块&#xff…

【图像分割】【深度学习】PFNet官方Pytorch代码-PFNet网络PM定位模块解析

【图像分割】【深度学习】PFNet官方Pytorch代码-PFNet网络PM定位模块解析 文章目录 【图像分割】【深度学习】PFNet官方Pytorch代码-PFNet网络PM定位模块解析前言PFNet网络简述主干网络定位模块 Positioning Module通道注意力模块 Channel Attention空间注意力模块 Spatial Att…

1-1、汇编语言概述

语雀原文链接 文章目录 1、机器语言2、汇编语言&#xff08;Assembly Language&#xff09;汇编语言工作过程汇编语言三类指令 3、学习资料电子PDF课件论坛视频教程 1、机器语言 机器语言是机器指令的集合。机器指令展开来讲就是一台机器可以正确执行的命令。电子计算机的机器…

古埃及金字塔的修建

从理论上说&#xff0c;古埃及人完全有能力设计并建造出充满各种奇妙细节的胡夫金字塔&#xff0c;但后世还是不断涌现出质疑之声&#xff0c;原因倒也简单&#xff0c;那就是胡夫金字塔实在太大了。据推算&#xff0c;整座金字塔使用大约230万块巨石&#xff0c;总质量可达约5…

SpringBoot+Redis编写一个抢红包雨的案例。附源码。

案例演示 SpringBootRedis编写一个抢红包雨的案例。附源码 1、案例分析&#xff0c;整体方案介绍 预备上线一个红包雨活动。这个红包雨的思路是活动开始前25分钟&#xff0c;在后台创建活动。然后前端用户进入&#xff0c;到点后将设置的金额拆分成多个小红包&#xff0c;开启倒…

数据分析实战案例:Python 分析员工为何离职(附完整代码)

大家好&#xff0c;今天给大家介绍一个Python数据分析项目实战&#xff0c;不仅包含代码&#xff0c;还提供分析数据集。 员工流失或是员工离开公司的比率是公司关注的一个重要问题。它不仅会导致宝贵人才的流失&#xff0c;还会产生成本并破坏生产力。了解员工辞职的原因对于…

勒索解密后oracle无法启动故障处理----惜分飞

客户linux平台被勒索病毒加密,其中有oracle数据库.客户联系黑客进行解密【勒索解密oracle失败】,但是数据库无法正常启动,dbv检查数据库文件报错 [oraclehisdb ~]$ dbv filesystem01.dbf DBVERIFY: Release 11.2.0.1.0 - Production on 星期一 11月 27 21:49:17 2023 Copyrig…

01-鸿蒙4.0学习之开发环境搭建 HelloWorld

HarmonyOS开发学习 1.环境配置 1.下载地址 开发工具&#xff1a;DevEco Studio 3.1.1 Release 下载地址 安装选择快捷方式 安装nodejs和Ohpm 安装SDK 选择同意Accept 检测8项目是否安装成功 2.创建项目 —— hello word

echarts实际开发中遇到的问题

当tooltip内容过高时&#xff0c;增加滚动条 enterable:true, extraCssText: height:500px;overflow-y:auto;

【JavaScript】alert的使用方法 | 超详细

alert作用效果 alert&#xff08;&#xff09;方法用于显示带有一条指定消息和一个确认的按钮的警告框。 alert使用方法 方法一&#xff1a;直接写在script标签内 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"&…

【MyBatisPlus】通俗易懂 快速入门 详细教程

目录 学习目标 一、MyBatisPlus简介 1. 入门案例 问题导入 1.1 SpringBoot整合MyBatisPlus入门程序 ①&#xff1a;创建新模块&#xff0c;选择Spring初始化&#xff0c;并配置模块相关基础信息 ②&#xff1a;选择当前模块需要使用的技术集&#xff08;仅保留JDBC&…

SSM实践基地管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 实践基地管理系统是一套完善的信息系统&#xff0c;结合springMVC框架完成本系统&#xff0c;对理解JSP java编程开发语言有帮助系统采用SSM框架&#xff08;MVC模式开发&#xff09;&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/…

小白备战蓝桥杯:Java基础语法

一、注释 IDEA注释快捷键&#xff1a;Ctrl / 单行注释&#xff1a; //注释信息 多行注释&#xff1a; /* 注释信息 */ 二、字面量 常用数据&#xff1a;整数、小数、字符串&#xff08;双引号&#xff09;、字符&#xff08;单引号&#xff09;、布尔值&#xff08;tr…

理解Android无埋点技术

首先什么是无埋点呢&#xff0c;其实所谓无埋点就是开发者无需再对追踪点进行埋码&#xff0c;而是脱离代码&#xff0c;只需面对应用界面圈圈点点即可追加随时生效的事件数据点。 无埋点的好处 其实无埋点并不是完全不用写代码&#xff0c;而是尽可能的少写代码。开发者将SDK集…