【C++ 学习 ⑱】- 多态(上)

目录

一、多态的概念和虚函数

1.1 - 用基类指针指向派生类对象

1.2 - 虚函数和虚函数的重写

1.3 - 多态构成的条件

1.4 - 多态的应用场景

二、协变和如何析构派生类对象

2.1 - 协变

2.2 - 如何析构派生类对象

三、C++11 的 override 和 final 关键字


 


一、多态的概念和虚函数

1.1 - 用基类指针指向派生类对象

基类指针可以指向派生类对象,但是通过基类指针只能使用基类的成员(包括成员变量和成员函数),不能使用派生类的成员

#include <iostream>
using namespace std;
​
class Person
{
public:Person(const char* name = "张三", int age = 18): _name(name), _age(age){ }
​void Print() const{cout << _name << "今年" << _age << "岁了。" << endl;}
protected:string _name;int _age;
};
​
class Student : public Person
{
public:Student(const char* name = "张三", int age = 18, int id = 0): Person(name, age), _id(id){ }
​void Print() const{cout << _name << "今年" << _age << "岁了,学号是" << _id << "。" << endl;}
protected:int _id; 
};
​
int main()
{Person p("李四", 19);Person* pp = &p;pp->Print();  // 李四今年19岁了。Student s("王五", 20, 2);pp = &s;pp->Print();  // 王五今年20岁了。return 0;
}

1.2 - 虚函数和虚函数的重写

如果在基类的成员函数前面加上 virtual 关键字,把它声明为虚函数,并且在派生类中对基类的虚函数进行重写(覆盖),那么当基类指针指向派生类对象时,就可以调用派生类中同名的成员函数,通过派生类中同名的成员函数,也就可以访问派生类对象的成员变量

当派生类中有一个跟基类完全相同的虚函数时,即派生类虚函数与基类虚函数的返回值类型、函数名、参数列表完全相同,就称派生类重写(覆盖)了基类的虚函数

#include <iostream>
using namespace std;
​
class Person
{
public:Person(const char* name = "张三", int age = 18): _name(name), _age(age){ }// 声明为虚函数virtual void Print() const{cout << _name << "今年" << _age << "岁了。" << endl;}
protected:string _name;int _age;
};
​
class Student : public Person
{
public:Student(const char* name = "张三", int age = 18, int id = 0): Person(name, age), _id(id){ }// 注意:在派生类中重写基类的虚函数时,派生类的虚函数可以不加 virtual 关键字,// 但是这种写法不是很规范,不建议这样操作。virtual void Print() const{cout << _name << "今年" << _age << "岁了,学号是" << _id << "。" << endl;}
protected:int _id;
};
​
int main()
{Person p("李四", 19);Person* pp = &p;pp->Print();  // 李四今年19岁了。Student s("王五", 20, 2);pp = &s;pp>->Print();  // 王五今年20岁了,学号是2。return 0;
}

有了虚函数,基类指针指向基类对象时就使用基类的成员函数,指向派生类对象时就使用派生类的成员函数,基类指针表现出了多种形态,这种现象我们称之为多态(Polymorphism)

1.3 - 多态构成的条件

通过以上的内容,可以总结出构成多态的条件

  1. 必须存在继承关系;

  2. 继承关系中派生类必须对基类的虚函数进行重写。

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

因为引用在本质上是通过指针的方式实现的,所以,既然借助基类指针可以实现多态,那么借助基类引用也可以实现多态

Person& rp = p;
p.Print();  // 李四今年19岁了。
Person& rs = s;
rs.Print();  // 王五今年20岁了,学号是2。

1.4 - 多态的应用场景

在应用开发中,我们可以在基类的成员函数中实现基本的功能,并且把基类的成员函数设置为虚函数,留给派生类去扩展和优化、实现个性化的功能

当然,要达到以上的目的,不一定要使用虚函数和多态,例如

#include <iostream>
using namespace std;
​
class Hero
{
public:void skillQ() { cout << "英雄释放了 Q 技能" << endl; }void skillW() { cout << "英雄释放了 E 技能" << endl; }void skillE() { cout << "英雄释放了 W 技能" << endl; }void skillR() { cout << "英雄释放了 R 技能" << endl; }
protected:int HP;  // 体力值、血量int MP;  // 魔法值int AD;  // 物理伤害int AP;  // 法术伤害
};
​
class A : public Hero
{
public:void skillQ() { cout << "英雄 A 释放了 Q 技能" << endl; }void skillW() { cout << "英雄 A 释放了 E 技能" << endl; }void skillE() { cout << "英雄 A 释放了 W 技能" << endl; }void skillR() { cout << "英雄 A 释放了 R 技能" << endl; }
};
​
class B : public Hero
{
public:void skillQ() { cout << "英雄 B 释放了 Q 技能" << endl; }void skillW() { cout << "英雄 B 释放了 E 技能" << endl; }void skillE() { cout << "英雄 B 释放了 W 技能" << endl; }void skillR() { cout << "英雄 B 释放了 R 技能" << endl; }
};
​
class C : public Hero
{
public:void skillQ() { cout << "英雄 C 释放了 Q 技能" << endl; }void skillW() { cout << "英雄 C 释放了 E 技能" << endl; }void skillE() { cout << "英雄 C 释放了 W 技能" << endl; }void skillR() { cout << "英雄 C 释放了 R 技能" << endl; }
};
​
int main()
{int option = 0;cout << "请选择英雄(1-A;2-B;3-C):";cin >> option;
​if (option == 1){A a;a.skillQ();a.skillW();a.skillE();a.skillR();}else if (option == 2){B b;b.skillQ();b.skillW();b.skillE();b.skillR();}else if (option == 3){C c;c.skillQ();c.skillW();c.skillE();c.skillR();}return 0;
}

但是使用多态可以让编程更方便,代码更精简

#include <iostream>
using namespace std;
​
class Hero
{
public:virtual void skillQ() { cout << "英雄释放了 Q 技能" << endl; }virtual void skillW() { cout << "英雄释放了 E 技能" << endl; }virtual void skillE() { cout << "英雄释放了 W 技能" << endl; }virtual void skillR() { cout << "英雄释放了 R 技能" << endl; }
protected:int HP;  // 体力值、血量int MP;  // 魔法值int AD;  // 物理伤害int AP;  // 法术伤害
};
​
class A : public Hero
{
public:virtual void skillQ() { cout << "英雄 A 释放了 Q 技能" << endl; }virtual void skillW() { cout << "英雄 A 释放了 E 技能" << endl; }virtual void skillE() { cout << "英雄 A 释放了 W 技能" << endl; }virtual void skillR() { cout << "英雄 A 释放了 R 技能" << endl; }
};
​
class B : public Hero
{
public:virtual void skillQ() { cout << "英雄 B 释放了 Q 技能" << endl; }virtual void skillW() { cout << "英雄 B 释放了 E 技能" << endl; }virtual void skillE() { cout << "英雄 B 释放了 W 技能" << endl; }virtual void skillR() { cout << "英雄 B 释放了 R 技能" << endl; }
};
​
class C : public Hero
{
public:virtual void skillQ() { cout << "英雄 C 释放了 Q 技能" << endl; }virtual void skillW() { cout << "英雄 C 释放了 E 技能" << endl; }virtual void skillE() { cout << "英雄 C 释放了 W 技能" << endl; }virtual void skillR() { cout << "英雄 C 释放了 R 技能" << endl; }
};
​
int main()
{int option = 0;cout << "请选择英雄(1-A;2-B;3-C):";cin >> option;
​Hero* p = nullptr;if (option == 1)p = new A;else if (option == 2)p = new B;else if (option == 3)p = new C;
​if (p){p->skillQ();p->skillW();p->skillE();p->skillR();delete p;}return 0;
}


二、协变和如何析构派生类对象

2.1 - 协变

协变就是在派生类中重写基类虚函数时,基类虚函数的返回值类型为基类对象的指针或引用,派生类虚函数的返回值类型为派生类对象的指针或引用

协变是虚函数重写的一种例外

class A {};
​
class B : public A {};
​
class Person
{
public:virtual A* func() { return new A; }
};
​
class Student : public Person
{
public:virtual B* func() { return new B; }
};

2.2 - 如何析构派生类对象

用基类指针指向派生类对象是多态的精髓,但是如果用基类指针销毁派生类对象的时候,不能调用派生类的析构函数,就可能会造成内存泄漏,因为在应用开发中,我们一般会把释放资源的代码写在析构函数中,例如释放堆区申请的内存空间

#include <iostream>
using namespace std;
​
class Person
{
public:~Person() { cout << "~Person()" << endl; }
};
​
class Student : public Person
{
public:~Student() { cout << "~Student()" << endl; }
};
​
int main()
{Person* pp = new Person;delete pp;  // ~Person()
​pp = new Student;delete pp;// ~Person() --> 说明没有调用派生类的析构函数return 0;
}

不过解决方法很简单,只要把基类的析构函数设置为虚函数,然后在派生类中重写基类的虚函数即可

但问题是基类和派生类的析构函数的函数名是不可能相同的,违背了虚函数重写的规则,答案则是 C++ 编译器对它们的名称做了特殊的处理,编译后统一处理成 destructor

#include <iostream>
using namespace std;
​
class Person
{
public:virtual ~Person() { cout << "~Person()" << endl; }
};
​
class Student : public Person
{
public:virtual ~Student() { cout << "~Student()" << endl; }
};
​
int main()
{Person* pp = new Person;delete pp;  // ~Person()
​pp = new Student;delete pp;// ~Student()// ~Person()return 0;
}


三、C++11 的 override 和 final 关键字

C++11 的 override 和 final 关键字能让我们的程序在继承类和重写虚函数时更安全,以及更清晰。

  1. override 关键字可以让编译器检查我们在派生类中重写的基类的虚函数是否正确

    class Person
    {
    public:virtual void func() const { cout << "hello world~" << endl; };
    };
    ​
    class Student : public Person
    {
    public:virtual void func() override { cout << "你好,世界~" << endl; };
    };

    这是因为我们在派生类中重写基类的虚函数时,忘记加 const

  2. final 关键字则可以防止派生类重写基类的虚函数

    class Person
    {
    public:virtual void func() const final { cout << "hello world~" << endl; }
    };
    ​
    class Student : public Person
    {
    public:virtual void func() const { cout << "你好,世界~" << endl; }
    };

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

webrtc的Sdp中的Plan-b和UnifiedPlan

在一些类似于视频会议场景下&#xff0c;媒体会话参与者需要接收或者发送多个流&#xff0c;例如一个源端&#xff0c;同时发送多个左右音轨的音频&#xff0c;或者多个摄像头的视频流&#xff1b;在2013年&#xff0c;提出了2个不同的SDP IETF草案Plan B和Unified Plan&#x…

android framework之Applicataion启动流程分析

Application启动流程分析 启动方式一&#xff1a;通过Launcher启动app 启动方式二&#xff1a;在某一个app里启动第二个app的Activity. 以上两种方式均可触发app进程的启动。但无论哪种方式&#xff0c;最终通过通过调用AMS的startActivity()来启动application的。 根据上图…

ABeam×Startup | 德硕管理咨询(深圳)创新研究团队拜访微漾创客空间

近日&#xff0c;德硕管理咨询&#xff08;深圳&#xff09;&#xff08;以下简称&#xff1a;“ABeam-SZ”&#xff09;创新研究团队前往微漾创客空间&#xff08;以下简称&#xff1a;微漾&#xff09;拜访参观&#xff0c;并展开合作交流。会议上&#xff0c;双方相互介绍了…

每日一题 57. 插入区间

读研了&#xff0c;开始用python刷题 今天的题目是力扣 每日一题 57. 插入区间 难度&#xff1a;中等 思路&#xff1a; 处理新区间起点&#xff0c;要么在两个老区间之间&#xff0c;要么被一个老区间包含处理新区间中点&#xff0c;同起点一样 我的代码如下 class Solut…

解锁市场进入成功:GTM 策略和即用型示例

在最初的几年里&#xff0c;创办一家初创公司可能会充满挑战。根据美国小企业管理局的数据&#xff0c;大约三分之二的新成立企业存活了两年&#xff0c;几乎一半的企业存活了五年以上。导致创业失败的因素有市场需求缺失、资金短缺、团队不合适、成本问题等。由此&#xff0c;…

合宙Air724UG LuatOS-Air LVGL API控件--按钮 (Button)

按钮 (Button) 按钮控件&#xff0c;这个就不用多说了&#xff0c;界面的基础控件之一。 示例代码 – 按键回调函数 event_handler function(obj, event) if event lvgl.EVENT_CLICKED then print(“Clicked\n”) elseif event lvgl.EVENT_VALUE_CHANGED then print(“To…

服务器数据库中了locked勒索病毒怎么办,locked勒索病毒恢复工具

最近一段时间网络上的locked勒索病毒非常嚣张&#xff0c;自从6月份以来&#xff0c;很多企业的计算机服务器数据库遭到了locked勒索病毒的攻击&#xff0c;起初locked勒索病毒攻击用友畅捷通T用户&#xff0c;后来七月份开始攻击金蝶云星空客户&#xff0c;导致企业的财务系统…

揭秘视频号创收计划:松松一个月赚1300+

我是卢松松&#xff0c;点点上面的头像&#xff0c;欢迎关注我哦&#xff01; 这是卢松松一个月视频号的收益&#xff0c;1300元。自从视频号在五月份推出创作者分成计划以来&#xff0c;许许多多的视频号创作者开始获得了一些收益&#xff0c;这绝对是一项挺不错的进展。 目前…

R-Meta分析核心技术教程

详情点击链接&#xff1a;全流程R-Meta分析核心技术教程 一&#xff0c;Meta分析的选题与检索 1、Meta分析的选题与文献检索 1)什么是Meta分析 2)Meta分析的选题策略 3)精确检索策略&#xff0c;如何检索全、检索准 4)文献的管理与清洗&#xff0c;如何制定文献纳入排除标准 …

回归预测 | MATLAB实现TSO-ELM金枪鱼群优化算法优化极限学习机多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现TSO-ELM金枪鱼群优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现TSO-ELM金枪鱼群优化算法优化极限学习机多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;效…

【大数据】Linkis:打通上层应用与底层计算引擎的数据中间件

Linkis&#xff1a;打通上层应用与底层计算引擎的数据中间件 1.引言2.背景3.设计初衷4.技术架构5.业务架构6.处理流程7.如何支撑高并发8.用户级隔离度和调度时效性9.总结 Linkis 是微众银行开源的一款 数据中间件&#xff0c;用于解决前台各种工具、应用&#xff0c;和后台各种…

无锁并发:探秘CAS机制的魔力

&#x1f60a; 作者&#xff1a; 一恍过去 &#x1f496; 主页&#xff1a; https://blog.csdn.net/zhuocailing3390 &#x1f38a; 社区&#xff1a; Java技术栈交流 &#x1f389; 主题&#xff1a; 无锁并发&#xff1a;探秘CAS机制的魔力 ⏱️ 创作时间&#xff1a; 2…

4.6 TCP面向字节流

TCP 是面向字节流的协议&#xff0c;UDP 是面向报文的协议 操作系统对 TCP 和 UDP 协议的发送方的机制不同&#xff0c;也就是问题原因在发送方。 UDP面向报文协议&#xff1a; 操作系统不会对UDP协议传输的消息进行拆分&#xff0c;在组装好UDP头部后就交给网络层处理&…

Flask狼书笔记 | 03_模板

文章目录 3 模板3.1 模板基本使用3.2 模板结构组织3.3 模板进阶 3 模板 模板&#xff08;template&#xff09;&#xff1a;包含固定内容和动态部分的可重用文件。Jinja2模板引擎可用于任何纯文本文件。 3.1 模板基本使用 HTML实体&#xff1a;https://dev.w3.org/html5/htm…

kafka学习笔记

1、kafka是什么&#xff1f; kafka是一个高吞吐&#xff0c;分布式&#xff0c;基于发布/订阅的消息系统&#xff0c;最大的特性就是可以实时的处理大量的数据以满足各种需求场景&#xff1a;日志收集&#xff0c;离线和在线的消息消费&#xff0c;等等 2、kakfa的基础架构&am…

算法竞赛入门【码蹄集新手村600题】(MT1220-1240)C语言

算法竞赛入门【码蹄集新手村600题】(MT1220-1240&#xff09;C语言 目录MT1221 分数的总和MT1222 等差数列MT1223 N是什么MT1224 棋盘MT1225 复杂分数MT1226 解不等式MT1227 宝宝爬楼梯MT1228 宝宝抢糖果MT1229 搬家公司MT1230 圆周率MT1231圆周率IIMT1232 数字和MT1233 数字之…

c++的分文件编写

前言 在C中&#xff0c;你可以将代码分割成多个文件来提高可维护性和组织性。分割文件有助于将代码模块化&#xff0c;使大型项目更易于管理。以下是C中关于分文件的一些规则和概念&#xff1a; 理论知识 头文件&#xff08;Header Files&#xff09;&#xff1a; 头文件通常…

聚类分析 | MATLAB实现基于DBSCAD密度聚类算法可视化

聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化 目录 聚类分析 | MATLAB实现基于LP拉普拉斯映射的聚类可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 基于DBSCAD密度聚类算法可视化&#xff0c;MATLAB程序。 使用带有KD树加速的dbscan_with_kdtree函数进行…

Gateway简述

前言 ​ 在微服务架构中&#xff0c;一个系统会被拆分为很多个微服务。那么作为客户端调用多个微服务接口的地址。另外微服务架构的请求中&#xff0c;90%的都携带认证信息/用户登录信息&#xff0c;都需要做相关的限制管理&#xff0c;API网关由此应允而生。 这样的架构会存…

DevOps系列文章之 Python基础

列表 Python中的列表类似于C语言中的数组的概念&#xff0c;列表由内部的元素组成&#xff0c;元素可以是任何对象 Python中的列表是可变的 简单的理解就是&#xff1a;被初始化的列表&#xff0c;可以通过列表的API接口对列表的元素进行增删改查 1、定义列表 1.可以将列表当成…