【C++笔记】多态的原理、单继承和多继承关系的虚函数表、 override 和 final、抽象类、重载、覆盖(重写)、隐藏(重定义)的对比
1.final关键字
引出:设计一个不能被继承的类。有如下方法:
class A
{
private:A(int a=0):_a(a){}
public:static A CreateOBj(int a=0){return A(a);}
protected:int _a;
}
//简介限制,子类构成函数无法调用父类构造函数初始化
//子类的构造函数一定去调用父类的构造函数去初始化那一部分
//而父类的构造函数继承下来后为不可见
class B:public A
{}
int main()
{A aa=;//构造函数被私有化,报错//可以通过如下方法进行A类对象的创建A aa=A::CreateOBj(10);
}
以上的方法虽然可以实现我们的需求,但是会对类进行强制性的封装,所有我们在C++11中还有更好的方法,通过final关键字来实现。
- C++11中final声明表示为最终类,表明该类不能被继承。
- 修饰函数,限制函数不能被重写。
//修饰类
class A final
{
protected:int _a;
};
//直接限制报错,A不可被继承
class B:public A
{};
//修饰函数
class C
{
public:virtual void f() final{cout<<"C::f()"<<endl;}
}
class D:public C
{
public://直接报错virtual void f(){}
}
2. override
override: 检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错。
class Car{
public:virtual void Drive(){}};class Benz :public Car {public:virtual void Drive() override {cout << "Benz-舒适" << endl;}};
3. 重载、覆盖(重写)、隐藏(重定义)的对比
- 重载:
 1、两个函数在同一作用域
 2、函数名/参数相同
- 重写(覆盖):
 1、两个函数分别在基类和派生类的作用域
 2、函数名/参数/返回值都必须相同(协变例外)
 3、两个函数必须是虚函数
- 重定义(隐藏):
 1、两个函数分别在基类和派生类的作用域
 2、函数名相同
 3、两个基类和派生类的同名函数不构成重写就是重定义
4.纯虚函数
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。
class Student
{
public:virtual void show()=0;
};
5.抽象类
5.1概念
包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生类才能实例化出对象。纯虚函数规范了派生类必须重写,另外纯虚函数更体现出了接口继承:本质上强制子类去完成虚函数的重写。
 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;}};
5.2接口继承和实现继承
普通函数的继承是一种实现继承,派生类继承了基类函数,可以使用函数,继承的是函数的实现。虚函数的继承是一种接口继承,派生类继承的是基类虚函数的接口,目的是为了重写,达成多态,继承的是接口。所以如果不实现多态,不要把函数定义成虚函数。
6.多态的原理
6.1认识虚函数表
先来看一道经典的问题,sizeof(Base)是多少?
class Base{public:virtual void Func1(){cout << "Func1()" << endl;}private:int _b = 1;};
很多一部分的答案是4Byte,但实际正确答案是8Byte,这是怎么一回事呢?看Visual Studion下的监视窗口。如下图。
 
 结论:
 1.除了_b成员,还多一个__vfptr放在对象的前面(注意有些平台可能会放到对象的最后面,这个跟平台有关),对象中的这个指针我们叫做虚函数表指针(v代表virtual,f代表function)。
 2.一个含有虚函数的类中都至少都有一个虚函数表指针,因为虚函数的地址要被放到虚函数表中,虚函数表也简称虚表。
 继续深入:如果一个类的继承了该类,那么派生类中这个表放了些什么呢?
// 针对上面的代码我们做出以下改造
// 1.我们增加一个派生类Derive去继承Base
// 2.Derive中重写Func1
// 3.Base再增加一个虚函数Func2和一个普通函数Func3class 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;}
看Visual Studion下的监视窗口。如下图。
 
 通过对监视窗口的分析可以得出如下结论:
- 派生类对象d中也有一个虚表指针,d对象由两部分构成,一部分是父类继承下来的成员,虚表指针也就是存在的部分,另一部分是自己的成员。
- 基类b对象和派生类d对象虚表是不一样的,这里我们发现Func1完成了重写,所以d的虚表中存的是重写的Derive::Func1,所以虚函数的重写也叫作覆盖,覆盖就是指虚表中虚函数的覆盖。重写是语法的叫法,覆盖是原理层的叫法。
- 另外Func2继承下来后是虚函数,所以放进了虚表,Func3也继承下来了,但是不是虚函数,所以不会放进虚表。
- 虚函数表本质是一个存虚函数指针的指针数组,一般情况这个数组最后面放了一个nullptr。
- 总结一下派生类的虚表生成:a.先将基类中的虚表内容拷贝一份到派生类虚表中 b.如果派生类重写了基类中某个虚函数,用派生类自己的虚函数覆盖虚表中基类的虚函数 c.派生类自己新增加的虚函数按其在派生类中的声明次序增加到派生类虚表的最后。
- 这里还有一个很容易混淆的问题:虚函数存在哪的?虚表存在哪的? 答:虚函数存在虚表,虚表存在对象中。注意上面的回答的错的。 但是很多同学都是这样深以为然的。注意:虚函数表存的是虚函数指针,不是虚函数,虚函数和普通函数一样的,都是存在代码段的,只是他的指针又存到了虚函数表中。另外对象中存的不是虚函数表,存的是虚函数表指针。那么虚表存在哪的呢?代码段! 感兴趣的同学可以用如下代码去打印一下虚表的地址,来验证一下。
typedef void(*VF_PTR)();//void PrintVFTable(VF_PTR table[])
// 打印虚函数表中内容
void PrintVFTable(VF_PTR* table)
{for (int i = 0; table[i] != nullptr; ++i){printf("vft[%d]:%p->", i, table[i]);VF_PTR f = table[i];f();}cout << endl << endl;
}
int main()
{Base b;PrintVFTable((VF_PTR*)(*(void**)&b));Derive d;PrintVFTable((VF_PTR*)(*(void**)&d));return 0;
}
7.在函数调用过程中,对象先找到虚函数表指针,通过该指针找到虚函数表,再在虚函数表中找到对应的函数指针,通过函数指针找到所需要的哪个函数。如下图所示。
 
6.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 Mike;Func(&Mike);Student Johnson;Func(&Johnson);return 0;}

- 观察上图的红色箭头我们看到,p是指向mike对象时,p->BuyTicket在mike的虚表中找到虚函数是Person::BuyTicket。
- 观察上图的蓝色箭头我们看到,p是指向johnson对象时,p->BuyTicket在johson的虚表中找到虚函数是Student::BuyTicket。
- 这样就实现出了不同对象去完成同一行为时,展现出不同的形态。
- 反过来思考我们要达到多态,有两个条件,一个是虚函数覆盖,一个是对象的指针或引用调用虚函数。反思一下为什么?单纯的对象的话所有的对象的虚函数表都是相同的,利用指针或者引用可以看作指向对象当中父类那一部分的成员或者指针。覆盖使其不同的虚表中,指向的虚函数不同,从而完成多种状态
- 看出满足多态以后的函数调用,不是在编译时确定的,是运行起来以后到对象的中取找的。不满足多态的函数调用时编译时确认好的。
 !!!一图搞懂多态的原理
  
7.多继承中的虚函数表
 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;}

 分析上述图片可以得出如下结论:
 1、多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中
 2、多继承时,子类重写了Base1和Base2虚函数func1,但是虚函数表中重写的func1的地址却不一样,但是没关系,他们最终还是调到同一个函数。这是因为存在偏移量的问题。
相关文章:
 
【C++笔记】多态的原理、单继承和多继承关系的虚函数表、 override 和 final、抽象类、重载、覆盖(重写)、隐藏(重定义)的对比
1.final关键字 引出:设计一个不能被继承的类。有如下方法: class A { private:A(int a0):_a(a){} public:static A CreateOBj(int a0){return A(a);} protected:int _a; } //简介限制,子类构成函数无法调用父类构造函数初始化 //子类的构造…...
 
安装thinkphp6并使用多应用模式,解决提示路由不存在解决办法
1. 安装稳定版tp框架 composer create-project topthink/think tptp是安装完成的目录名称 ,可以根据自己需要修改。 如果你之前已经安装过,那么切换到你的应用根目录下面,然后执行下面的命令进行更新: composer update topthin…...
 
FPGA笔试
1、FPGA结构一般分为三部分:可编程逻辑块(CLB)、可编程I/O模块和可编程内部连线。 2 CPLD的内部连线为连续式布线互连结构,任意一对输入、输出端之间的延时是固定 ;FPGA的内部连线为分段式布线互连结构,各…...
 
Pytorch:cat、stack、squeeze、unsqueeze的用法
Pytorch:cat、stack、squeeze、unsqueeze的用法 torch.cat 在指定原有维度上链接传入的张量,所有传入的张量都必须是相同形状 torch.cat(tensors, dim0, *, outNone) → Tensor tensor:相同形状的tensor dim:链接张量的维度,不能超过传入张…...
聊聊HttpClient的RedirectStrategy
序 本文主要研究一下HttpClient的RedirectStrategy RedirectStrategy org/apache/http/client/RedirectStrategy.java public interface RedirectStrategy {/*** Determines if a request should be redirected to a new location* given the response from the target ser…...
【1day】复现宏景OA KhFieldTree接口 SQL注入漏洞
注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞描述 二、资产测绘 三、漏洞复现 四、漏洞修复 一、漏洞描述 宏景OA是一款基于...
 
同为科技TOWE智能PDU引领数据中心机房远控用电安全高效
随着数据中心的环境变得更加动态和复杂,许多数据中心都在对数据中心管理人员施加压力,要求提高可用性,同时降低成本,提升效率。新一代高密度服务器和网络设备的投入使用,增加了对更高密度机架的需求,并对整…...
支付成功后给指定人员发送微信公众号消息
支付成功后给指定人员(导购)发送微信公众号消息 微信openid已录入数据库表 调用后台接口发送消息接口调用代码如下: //----add by grj 20231017 start //订单支付成功发送微信公众号消息$.ajax({url:http://www.menggu100.com:7077/strutsJsp…...
 
漏洞复现--安恒明御安全网关文件上传
免责声明: 文章中涉及的漏洞均已修复,敏感信息均已做打码处理,文章仅做经验分享用途,切勿当真,未授权的攻击属于非法行为!文章中敏感信息均已做多层打马处理。传播、利用本文章所提供的信息而造成的任何直…...
 
简单的对称加密
异或 异或算法的好处便是数A和数B异或后,把结果再和数A异或便可得到B,或者和数B异或可重新得到数据A。利用异或的这个特性可简单实现数据的加密和解密算法。 恺撒密码 恺撒密码的替换方法是通过排列明文和密文字母表,密文字母表示通过将明…...
 
vue源码笔记之——响应系统
vue是一种声明式范式编程,使用vue者只需要告诉其想要什么结果,无需关心具体实现(vue内部做了,底层是利用命令式范式) 1. reactive为什么只能操作对象,对于基本数据类型,需要用ref? …...
 
Android Studio Giraffe | 2022.3.1
Android Gradle 插件和 Android Studio 兼容性 Android Studio 构建系统以 Gradle 为基础,并且 Android Gradle 插件 (AGP) 添加了几项专用于构建 Android 应用的功能。下表列出了各个 Android Studio 版本所需的 AGP 版本。 如果您的项目不受某个特定版本的 Andr…...
 
Spring Boot 3.0 已经就绪,您准备好了么?
Java 微服务开发框架王者 Spring 2014 年的 4 月,Spring Boot 1.0.0 正式发布。距离 1.0 版本的发布已经过去了 9 年多的时间,如今 Spring Boot 已经被 Java 开发者广泛使用,正如 JRebel 的 2022 年开发者生产力报告中提到的那样,…...
 
5+非肿瘤分析,分型+WGCNA+机器学习筛选相关基因
今天给同学们分享一篇非肿瘤分型机器学习WGCNA实验的生信文章“Identification of diagnostic markers related to oxidative stress and inflammatory response in diabetic kidney disease by machine learning algorithms: Evidence from human transcriptomic data and mou…...
算法课作业2 OJ for Divide and Conquer
https://vjudge.net/contest/581947 A - Ultra-QuickSort 题意 每次给n个无序的数,互不重复,问最少需要多少次必要的交换操作使n个数有序。 思路 看一眼想到逆序数,然后验证了逆序数的个数符合样例,但想了一个3 2 1的话实际上…...
 
申请全国400电话的步骤及注意事项
导语:随着企业的发展,越来越多的公司开始意识到全国400电话的重要性。本文将介绍申请全国400电话的步骤及注意事项,帮助企业顺利办理相关手续。 一、了解全国400电话的概念和优势 全国400电话是一种统一的客服热线号码,以“400”…...
C++ 的设计模式之 工厂方法加单例
在下面的示例中,我将演示如何创建一个工厂类,该工厂类能够生成四个不同类型的单例对象,每个单例对象都通过单独的工厂方法进行创建。 #include <iostream> #include <mutex>// Singleton base class class Singleton { protecte…...
 
Deploy、Service与Ingress
Deployment 自愈 介绍:控制Pod,使Pod拥有多副本,自愈,扩缩容等能力 # 清除所有Pod,比较下面两个命令有何不同效果? kubectl run mynginx --imagenginxkubectl create deployment mytomcat --imagetomcat:8.5.68 # 自…...
 
定制化推送+精细化运营,Mobpush助力《迷你世界》用户留存率提升23%
随着智能设备的市场下沉,手游市场迎来了爆发式增长,《迷你世界》作为一款于2015年推出的手游,一经问世就饱受欢迎。上线短短三年,迷你世界在应用商店下载量已经高达2亿次,周下载量两千万,稳居第一名&#x…...
 
深度学习零基础教程
代码运行软件安装: anaconda:一个管理环境的软件–>https://blog.csdn.net/scorn_/article/details/106591160(可选装) pycharm:一个深度学习运行环境–>https://blog.csdn.net/scorn_/article/details/106591160…...
 
龙虎榜——20250610
上证指数放量收阴线,个股多数下跌,盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型,指数短线有调整的需求,大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的:御银股份、雄帝科技 驱动…...
 
最新SpringBoot+SpringCloud+Nacos微服务框架分享
文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的,根据Excel列的需求预估的工时直接打骨折,不要问我为什么,主要…...
 
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...
 
如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...
 
C/C++ 中附加包含目录、附加库目录与附加依赖项详解
在 C/C 编程的编译和链接过程中,附加包含目录、附加库目录和附加依赖项是三个至关重要的设置,它们相互配合,确保程序能够正确引用外部资源并顺利构建。虽然在学习过程中,这些概念容易让人混淆,但深入理解它们的作用和联…...
深入浅出Diffusion模型:从原理到实践的全方位教程
I. 引言:生成式AI的黎明 – Diffusion模型是什么? 近年来,生成式人工智能(Generative AI)领域取得了爆炸性的进展,模型能够根据简单的文本提示创作出逼真的图像、连贯的文本,乃至更多令人惊叹的…...
 
macOS 终端智能代理检测
🧠 终端智能代理检测:自动判断是否需要设置代理访问 GitHub 在开发中,使用 GitHub 是非常常见的需求。但有时候我们会发现某些命令失败、插件无法更新,例如: fatal: unable to access https://github.com/ohmyzsh/oh…...
 
DeepSeek越强,Kimi越慌?
被DeepSeek吊打的Kimi,还有多少人在用? 去年,月之暗面创始人杨植麟别提有多风光了。90后清华学霸,国产大模型六小虎之一,手握十几亿美金的融资。旗下的AI助手Kimi烧钱如流水,单月光是投流就花费2个亿。 疯…...
 
ZYNQ学习记录FPGA(二)Verilog语言
一、Verilog简介 1.1 HDL(Hardware Description language) 在解释HDL之前,先来了解一下数字系统设计的流程:逻辑设计 -> 电路实现 -> 系统验证。 逻辑设计又称前端,在这个过程中就需要用到HDL,正文…...
