【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 - 多态构成的条件
通过以上的内容,可以总结出构成多态的条件:
-
必须存在继承关系;
-
继承关系中派生类必须对基类的虚函数进行重写。
-
必须通过基类的指针或引用调用虚函数。
因为引用在本质上是通过指针的方式实现的,所以,既然借助基类指针可以实现多态,那么借助基类引用也可以实现多态。
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 关键字能让我们的程序在继承类和重写虚函数时更安全,以及更清晰。
-
override 关键字可以让编译器检查我们在派生类中重写的基类的虚函数是否正确:
class Person { public:virtual void func() const { cout << "hello world~" << endl; }; }; class Student : public Person { public:virtual void func() override { cout << "你好,世界~" << endl; }; };
这是因为我们在派生类中重写基类的虚函数时,忘记加
const了。 -
final 关键字则可以防止派生类重写基类的虚函数:
class Person { public:virtual void func() const final { cout << "hello world~" << endl; } }; class Student : public Person { public:virtual void func() const { cout << "你好,世界~" << endl; } };
相关文章:
【C++ 学习 ⑱】- 多态(上)
目录 一、多态的概念和虚函数 1.1 - 用基类指针指向派生类对象 1.2 - 虚函数和虚函数的重写 1.3 - 多态构成的条件 1.4 - 多态的应用场景 二、协变和如何析构派生类对象 2.1 - 协变 2.2 - 如何析构派生类对象 三、C11 的 override 和 final 关键字 一、多态的概念和虚…...
合宙Air724UG LuatOS-Air LVGL API控件--进度条 (Bar)
进度条 (Bar) Bar 是进度条,可以用来显示数值,加载进度。 示例代码 – 创建进度条 bar lvgl.bar_create(lvgl.scr_act(), nil) – 设置尺寸 lvgl.obj_set_size(bar, 200, 20); – 设置位置居中 lvgl.obj_align(bar, NULL, lvgl.ALIGN_CENTER, 0, 0) …...
图神经网络与分子表征:番外——基组选择
学过高斯软件的人都知道,我们在撰写输入文件 gjf 时需要准备输入【泛函】和【基组】这两个关键词。 【泛函】敲定计算方法,【基组】则类似格点积分中的密度,与计算精度密切相关。 部分研究人员借用高斯中的一系列基组去包装输入几何信息&am…...
rabbitmq笔记-rabbitmq客户端开发使用
连接RabbitMQ 1.创建ConnectionFactory,给定参数ip地址,端口号,用户名和密码等 2.创建ConnectionFactory,使用uri方式实现,创建channel。 注意: Connection可以用来创建多个channel实例,但c…...
13.Oracle中nvl()与nvl2()函数详解
Oracle中nvl()与nvl2()函数详解: 函数nvl(expression1,expression2)根据参数1是否为null返回参数1或参数2的值; 函数nvl2(expression1,expression2,expression3)根据参数1是否为null返回参数2或参数3的值 1.nvl:根据参数1是否为null返回参数…...
设置某行被选中并滚动到改行
<el-table :data"tableDamItem" ref"singleTable" stripe style"width: 100%" height"250" highlight-current-row v-on:row-click"handleTableRow"></el-table>/*** 设置表格行被选中,并滚动到该行* param po…...
React钩子函数之useRef的基本使用
React钩子函数中的useRef是一个非常有用的工具,它可以用来获取DOM元素或者保存一些变量。在这篇文章中,我们将会讨论useRef的基本使用。 首先,我们需要知道useRef是如何工作的。它返回一个可变的ref对象,这个对象可以在组件的整个…...
无风扇迷你电脑信息与购买指南
本文将解释什么是无风扇迷你电脑,以及计算产品组合中你可以购买的一些不同的无风扇迷你电脑的信息指南。 无风扇迷你电脑是一种小型工业计算机,旨在处理复杂的工业工作负载。迷你电脑是通过散热器被动冷却可在各种类型的易失性环境中部署。无风扇微型计…...
比特币是怎么回事?
比特币是怎么回事? 一句话描述就是,初始化几个比特币,申请成为矿工组织,发生交易时抢单记账成功可以比特币奖励,随着比特币数量的增加,奖励越来越少。怎么记账成功呢,通过交易信息幸运数字哈希…...
vue3+ts+uniapp小程序端自定义日期选择器基于内置组件picker-view + 扩展组件 Popup 实现自定义日期选择及其他选择
vue3ts 基于内置组件picker-view 扩展组件 Popup 实现自定义日期选择及其他选择 vue3tsuniapp小程序端自定义日期选择器 1.先上效果图2.代码展示2.1 组件2.2 公共方法处理日期2.3 使用组件 3.注意事项3.1refSelectDialog3.1 backgroundColor"#fff" 圆角问题 自我记…...
Java进阶篇--泛型
前言 Java 泛型(generics)是 JDK 5 中引入的一个新特性, 泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。它允许在定义类、接口和方法时使用类型参数。这种技术使得在编译期间可以使用任何类型,而…...
android framework之Applicataion启动流程分析
Application启动流程分析 启动方式一:通过Launcher启动app 启动方式二:在某一个app里启动第二个app的Activity. 以上两种方式均可触发app进程的启动。但无论哪种方式,最终通过通过调用AMS的startActivity()来启动application的。 根据上图…...
Linux Day10 ---Mybash
目录 一、Mybash介绍 1.1.mybash.c 打印函数 分割函数 命令函数 二、Mybash实现 2.1.打印函数 2.1.1需要使用到的功能函数 1.获取与当前用户关联的UID 2.获取与当前用户的相关信息---一个结构体(passwd) 3.获取主机信息 4.获取当前所处位置 5.给…...
Flask-Sockets和Flask-Login联合实现websocket的登录认证功能
flask_login 提供了一个方便的方式来管理用户会话。当你在 Flask 的 HTTP 视图中使用它时,你可以简单地使用 login_required 装饰器来确保用户已登录。 但是,flask_sockets 并没有直接与 flask_login 集成。如果你想在建立 WebSocket 连接时检查用户是否…...
东盟全面覆盖?长城战略部署核心区域市场,首个百万粉丝国产品牌
根据最新消息,长城汽车在东南亚地区取得了巨大的成功,成功进军了亚洲最大的汽车市场之一-印度尼西亚。这标志着长城汽车已经实现了东盟核心市场的全面覆盖,成为全球布局的重要一步。 在过去的几年里,长城汽车在东盟地区的市场布局…...
基于PHP的电脑商城系统
有需要请加文章底部Q哦 可远程调试 基于PHP的电脑商城系统 一 介绍 此电脑商城系统基于原生PHP开发,数据库mysql,前端bootstrap。用户可注册登录,购物下单,评论等。管理员登录后台对电脑商品,用户,订单&a…...
无客户端网络准入方案,为集成电路企业终端管理开启省事更省心模式
宁盾无客户端网络准入控制方案正在成为先进制造、高科技互联网企业等创新型客户的优选方案。创新型客户以技术密集型、研发人员占比高著称,在进行网络准入建设时,如何平衡好用户体验与顺利达成项目预期之间的矛盾,是创新企业 IT 安全团队格外…...
5G与4G的RRC协议之异同
什么是无线资源控制(RRC)? 我们知道,在移动通信中,无线资源管理是非常重要的一个环节,首先介绍一下什么是无线资源控制(RRC)。 手机和网络通过无线信道相互通信,彼此交…...
横扫“盲区”、“看透”缺陷,维视智造推出短波红外相机
在可见光领域,工业相机的视觉应用已经十分成熟,但在日常的客户咨询中,我们也经常接到一些“超纲需求”——客户想要检测“白底上的白色缺陷”、“不透明包装内的透明物体有无”等,均属于可见光无法实现的检测,而市面上…...
cgo踩坑:交叉编译过程出现的问题could not determine kind of name for C.XXX
尝试了网上的几种解决方法,都不行,现总结起来: 确认 /* #include <stdio.h> */ import "C"不要有空行 确认你引用的头文件存在(stdio.h这种编译器自带的不需要你确认) 如果引用了多个包,…...
第19节 Node.js Express 框架
Express 是一个为Node.js设计的web开发框架,它基于nodejs平台。 Express 简介 Express是一个简洁而灵活的node.js Web应用框架, 提供了一系列强大特性帮助你创建各种Web应用,和丰富的HTTP工具。 使用Express可以快速地搭建一个完整功能的网站。 Expre…...
手游刚开服就被攻击怎么办?如何防御DDoS?
开服初期是手游最脆弱的阶段,极易成为DDoS攻击的目标。一旦遭遇攻击,可能导致服务器瘫痪、玩家流失,甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案,帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...
IT供电系统绝缘监测及故障定位解决方案
随着新能源的快速发展,光伏电站、储能系统及充电设备已广泛应用于现代能源网络。在光伏领域,IT供电系统凭借其持续供电性好、安全性高等优势成为光伏首选,但在长期运行中,例如老化、潮湿、隐裂、机械损伤等问题会影响光伏板绝缘层…...
HashMap中的put方法执行流程(流程图)
1 put操作整体流程 HashMap 的 put 操作是其最核心的功能之一。在 JDK 1.8 及以后版本中,其主要逻辑封装在 putVal 这个内部方法中。整个过程大致如下: 初始判断与哈希计算: 首先,putVal 方法会检查当前的 table(也就…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
DingDing机器人群消息推送
文章目录 1 新建机器人2 API文档说明3 代码编写 1 新建机器人 点击群设置 下滑到群管理的机器人,点击进入 添加机器人 选择自定义Webhook服务 点击添加 设置安全设置,详见说明文档 成功后,记录Webhook 2 API文档说明 点击设置说明 查看自…...
【从零开始学习JVM | 第四篇】类加载器和双亲委派机制(高频面试题)
前言: 双亲委派机制对于面试这块来说非常重要,在实际开发中也是经常遇见需要打破双亲委派的需求,今天我们一起来探索一下什么是双亲委派机制,在此之前我们先介绍一下类的加载器。 目录 编辑 前言: 类加载器 1. …...
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement
Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...
算术操作符与类型转换:从基础到精通
目录 前言:从基础到实践——探索运算符与类型转换的奥秘 算术操作符超级详解 算术操作符:、-、*、/、% 赋值操作符:和复合赋值 单⽬操作符:、--、、- 前言:从基础到实践——探索运算符与类型转换的奥秘 在先前的文…...
