C++学习笔记:多态

C++学习笔记:多态

  • 什么是多态?
  • 多态的构成条件?
  • C++11中的final和override
  • 抽象类是什么?
  • 什么是虚表?
  • 多继承中的虚表

什么是多态?

多态是在不同继承关系的类对象,去调用同一函数,产生了不同的行为。
例如:学校在餐厅的某个档口为老师们提供了教师优惠,同样的一份食物,如果是学生去吃饭的话需要10元,而老师去吃饭的话仅需要8元,这就是多态的一种体现.

多态的构成条件?

多态构成需要有两个条件:

  1. 必须是虚函数,并且对虚函数的调用必须是指针或者引用
  2. 对所继承的虚函数必须进行重写
    例如:
class Person 
{
public:virtual void BuyTicket() { cout << "买票-全价" << endl; }
};
class Student : public Person 
{
public:virtual void BuyTicket() { cout << "买票-半价" << endl; }void Func(Person& p)
{ p.BuyTicket(); }int main()
{
Person ps;
Student st;
Func(ps);
Func(st);return 0;
}

同样的函数,调用后的结果却不同,这就是多态的使用

C++11中的final和override

因为在继承和多态中对虚函数是否重写有着比较高的要求,为了防止有时候因为疏忽而出现问题,C++11提出了关键字final和override

  1. final —— 在虚函数名后加final可以限制该虚函数不能被重写
class Car
{
public:virtual void Drive() final {}
};
class Benz :public Car
{
public:virtual void Drive() {cout << "Benz-舒适" << endl;}//此处报错
};
  1. override——在虚函数后加上override可以规定该函数必须重写
class Car{
public:virtual void Drive(){}
};
class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒适" << endl;}
};

抽象类是什么?

一般在虚函数后写上 =0 则把这个虚函数视为纯虚函数.
而把包含纯虚函数的类称为抽象类(也叫接口类),纯虚函数不能实例化出对象,只能靠派生类重写虚函数来进行实例化对象.纯虚函数的存在意味着派生类必须重写纯虚函数,由此体现了接口继承

class Car
{
public:
virtual void Drive() = 0;
};
class Benz :public Car
{
public:virtual void Drive(){cout << "Benz-舒适" << endl;}
};
class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};
void Test()
{
Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

什么是虚表?

在C++中,一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表.

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:int _b = 1;
};

通过观察测试我们发现b对象是8bytes,除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。

// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3
class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}
private:int _b = 1;
};
class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}
private:int _d = 2;
};
int main()
{Base b;Derive d;return 0;
}

通过观察和测试,我们发现了以下几点问题:

  1. 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在部分的另一部分是自己的成员。
  2. 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
  3. 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
  4. 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
  5. 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
  6. 这里还有一个童鞋们很容易混淆的问题:虚函数存在哪的?虚表存在哪的?
    注意虚表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的只是他的指针又存到了虚表中另外对象中存的不是虚表,存的是虚表指针。那么虚表存在哪的呢?实际我们去验证一下会发现vs下是存在代码段
  7. 值得注意的是,不同的类的虚表是不同的,即使派生类没有重写父类的虚函数,那么派生类的虚表和父类的虚表也是不同的
  8. 所有虚函数一定会被放入虚表

多继承中的虚表

当一个派生类继承了两个父类的时候,此时这个派生类就会有两个虚表,分别是两个父类的虚表

class Base1 {
public:virtual void func1() {cout << "Base1::func1" << endl;}virtual void func2() {cout << "Base1::func2" << endl;}
private:int b1;
};
class Base2 {
public:virtual void func1() {cout << "Base2::func1" << endl;}virtual void func2() {cout << "Base2::func2" << endl;}
private:int b2;
};
class Derive : public Base1, public Base2 {
public:virtual void func1() {cout << "Derive::func1" << endl;}virtual void func3() {cout << "Derive::func3" << endl;}
private:int d1;
};
typedef void(*VFPTR) ();
void PrintVTable(VFPTR vTable[])
{cout << " 虚表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d个虚函数地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}
int main()
{Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d+sizeof(Base1)));PrintVTable(vTableb2);return 0;
}

多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中

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

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

相关文章

SpreadJS+vue3练手使用

SpreadJS的练手使用 // 首先在 package.json 这个文件里{"name": "app-admin","private": true,"version": "0.0.0","type": "module","scripts": {"dev": "vite",&quo…

【深度学习笔记】 3_13 丢弃法

注&#xff1a;本文为《动手学深度学习》开源内容&#xff0c;部分标注了个人理解&#xff0c;仅为个人学习记录&#xff0c;无抄袭搬运意图 3.13 丢弃法 除了前一节介绍的权重衰减以外&#xff0c;深度学习模型常常使用丢弃法&#xff08;dropout&#xff09;[1] 来应对过拟合…

阿里面试:最佳线程数,如何确定?

尼恩说在前面 在40岁老架构师 尼恩的读者交流群(50)中&#xff0c;最近有小伙伴拿到了一线互联网企业如得物、阿里、滴滴、极兔、有赞、shein 希音、百度、网易的面试资格&#xff0c;遇到很多很重要的面试题&#xff1a; 如何确定系统的最佳线程数&#xff1f; 小伙伴 没有回…

机器学习深度解析:原理、应用与前景

随着人工智能的迅速发展&#xff0c;机器学习已经成为当今时代最为引人注目的技术之一。它不仅仅是一种技术或工具&#xff0c;更是一种推动社会进步、影响人类生活的重要力量。那么&#xff0c;什么是机器学习&#xff1f;它是如何工作的&#xff1f;又在哪些领域中发挥着不可…

阿里云服务器ECS u1实例性能怎么样?

阿里云服务器ECS u1实例&#xff0c;2核4G&#xff0c;5M固定带宽&#xff0c;80G ESSD Entry盘优惠价格199元一年&#xff0c;性能很不错&#xff0c;CPU采用Intel Xeon Platinum可扩展处理器&#xff0c;购买限制条件为企业客户专享&#xff0c;实名认证信息是企业用户即可&a…

介绍一下我们:久菜盒子工作室

大数据科学团队/全网可搜索的久菜盒子工作室 我们是&#xff1a;985硕博/美国全奖doctor/计算机7年产品负责人/医学大数据公司医学研究员/SCI一区2篇/Nature子刊一篇/中文二区核心一篇/都是我们 主要领域&#xff1a;医学大数据分析/经管数据分析/金融模型/统计数理基础/统计学…

编程笔记 Golang基础 028 结构体与JSON

编程笔记 Golang基础 028 结构体与JSON 一、JSON二、结构体转JSON&#xff08;序列化&#xff09;三、JSON转结构体&#xff08;反序列化&#xff09;小结 结构体与JSON之间的相互转换是现代软件开发中数据处理的基础工具&#xff0c;极大地简化了数据在不同层次、不同组件间的…

spring boot 集成科大讯飞星火认知大模型

一、安装依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/…

Educational Codeforces Round 160 (Rated for Div. 2) D. Array Collapse(笛卡尔树+DP)

原题链接&#xff1a;D. Array Collapse 题目大意&#xff1a; 给你一个长度为 n n n 的排列 p p p &#xff0c;排列的定义为 [ 1 , 2 , 3 , . . , n ] [1,2,3,..,n] [1,2,3,..,n] 中每个数都出现 恰好 一次。 你可以做 任意多次 这样的操作&#xff1a; 选出一个任意长度…

前端导出EXCEL

步骤解析 定义了一个名为 excelDown 的函数&#xff0c;它接受两个参数&#xff1a;res 和 type。res 是包含响应数据的对象&#xff0c;type 是要导出的文件类型。如果 type 未提供&#xff0c;则默认使用 Excel 文件的 MIME 类型。 export const excelDown (res, type) >…

unity导航网格无法烘培到台阶和斜坡

如图是我在b站学Unity导航网格时建的一个示例场景&#xff0c;本场景使用的为棱长1m的立方体&#xff0c;读者可以以此为参照度量其他物体大小。 可见导航网格根本无法烘焙到斜坡和台阶上&#xff0c;为解决问题我做了不少尝试&#xff0c;调整最大坡度和步高都没办法解决问题…

AI新纪元:可能的盈利之道

本文来源于Twitter大神宝玉&#xff08;dotey&#xff09;在聊 Sora 的时候&#xff0c;总结了 Sora 的价值和可能的盈利方向&#xff0c;我把这部分内容单独摘出来再整理一下。现在的生成式 AI 大家应该不陌生&#xff0c;用它总结文章、翻译、写作、画图&#xff0c;当然真正…

搭建私有Git服务器:GitLab部署详解

引言&#xff1a; 为了方便团队协作和代码管理&#xff0c;许多组织选择搭建自己的私有Git服务器。GitLab是一个集成了Git版本控制、项目管理、代码审查等功能的开源平台&#xff0c;是搭建私有Git服务器的理想选择。 目录 引言&#xff1a; 一、准备工作 在开始部署GitLab之…

Dockerfile和jar包不同目录处理

如果Dockerfile的全路径为/srm/myDockerfile/Dockerfile&#xff0c;而JAR文件位于/srm目录下&#xff0c;你可以在Dockerfile中使用相对路径引用JAR文件。以下是如何编写Dockerfile的示例&#xff1a; 假设你的项目结构如下&#xff1a; luaCopy code /srm |-- myDockerfile …

Map集合的遍历方式

遍历Map集合的几种方式 迭代器(Iterator)forlambdaStream 代码示例 package com.haimeng.Array;import java.security.Key; import java.util.HashMap; import java.util.Iterator; import java.util.Map;public class Lambda1 {public static void main(String[] args) {//…

MySQL数据库基础(十五):PyMySQL使用介绍

文章目录 PyMySQL使用介绍 一、为什么要学习PyMySQL 二、安装PyMySQL模块 三、PyMySQL的使用 1、导入 pymysql 包 2、创建连接对象 3、获取游标对象 4、pymysql完成数据的查询操作 5、pymysql完成对数据的增删改 PyMySQL使用介绍 提前安装MySQL数据库&#xff08;可以…

shell脚本介绍及基本功能

目录 1. 什么是shell 2. hello word 2.1 echo 2.2 第一个脚本 3. Bash的基本功能 3.1别名 3.2 常用快捷键 3.3 输入输出 3.4 输出重定向 3.5 多命令执行 3.6 管道符 3.7 通配符和特殊符号 1. 什么是shell Shell 是一个用 C 语言编写的程序&#xff0c;它是用户使用…

数据分析---常见处理逻辑

目录 数据清洗数据转换数据聚合数据筛选增删改查(以查为例)数据清洗 去除重复值:使用DISTINCT关键字去除重复行。//这将返回一个包含所有不重复城市的结果集 SELECT DISTINCT city FROM students;处理缺失值:使用IS NULL或IS NOT NULL判断是否为空值,并使用COALESCE或CASE…

STM32--低功耗模式详解

一、PWR简介 正常模式与睡眠模式耗电是mA级&#xff0c;停机模式与待机模式是uA级。 二、电源框图 供电区域有三处&#xff0c;分别是模拟部分供电&#xff08;VDDA&#xff09;&#xff0c;数字部分供电&#xff0c;包括VDD供电区域和1.8V供电区域&#xff0c;后备供电&…

mysql和redis双写一致性策略分析

mysql和redis双写一致性策略分析 一.什么是双写一致性 当我们更新了mysql中的数据后也可以同时保证redis中的数据同步更新&#xff1b; 数据读取的流程&#xff1a; 1.读取redis,如果value!null,直接返回&#xff1b; 2.如果redis中valuenull&#xff0c;读取mysql中数据对应的…