C++ 多态和虚函数

2023-11-17

一. 先搞清override overload overwrite的区别

 

1. overload(重载)(不是多态)

在C++程序中,可以将语义、功能相似的几个函数用同一个名字表示,但参数不同(包括类型、顺序不同),即函数重载。
(1)相同的范围(在同一个类中);
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。

2. override(覆盖)(运行时多态、虚函数)

是指派生类函数覆盖基类函数,特征是:
(1)不同的范围(分别位于派生类与基类);
(2)函数名字相同;
(3)参数相同;
(4)基类函数必须有virtual 关键字。

3. overwrite(重写)(编译时多态)

是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。

 

二. 静态联编和动态联编(运行时多态和编译时多态)

  • 在C++中,多态性的实现和联编这一概念有关。一个源程序经过编译,链接,成为可执行文件的过程是把可执行代码连接在一起的过程。
  • 在编译过程中进行联编被称为静态联编(static binding),编译器生成的能够在程序运行时选择正确的虚方法的代码,被称为动态联编(dynamic binding)。
  • 静态联编也称为编译时多态性,主要通过函数重写实现。动态联编也称为运行时多态性。主要通过继承和虚函数来实现。
  • 编译器对非虚方法使用静态联编,一个父类指针指向一个子类时,静态联编调用的是父类函数。
    编译器对虚方法使用动态联编,运行时程序才确定对象的类型,此时一个父类指针指向一个子类,调用的是子类的函数。

 

三. 动态联编的工作原理(虚函数表)

  • 编译器处理虚函数的方法是:给每个对象添加一个隐藏成员,用于保存一个指向函数地址数组的指针。这个数组称为“虚函数表”。
  • 没有虚函数的C++类,是不会有虚函数表的。
  • 虚函数表(virtual function table,vtbl):存储了为类对象进行声明的虚函数的地址。

1. 虚表工作原理

虚函数表实质是一个指针数组,里面存的是虚函数的函数指针。

调用虚函数的时候,程序将查看存储在对象中的虚表地址(也就是说按照实例所属的类来看的,而不是按照指针类型。同一类共用一张虚表,只是每一个对象都一个指向该虚表的指针)【前几天面试遇到一个面试官,对方坚持说是一个实例里面一张虚表,就感觉很奇怪,后来问了一些大佬,回复是看编译器实现,两种都可能。不过多数还是一个指针,共用一张虚表的。】,然后转向相应的函数地址表(放在类中,一个类只有一张)。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址;如果使用类声明中的第三个虚函数,程序将使用地址为数组中第三个元素的函数。

而这张虚表,是会根据你是否有override、是否有overwrite来决定每个函数指针指向的位置的。

 

2. 举个栗子(看完再回去1理解原理可能更好理解)

  • 当使用了虚函数override时,可以明显看到,两个虚表指针 _vfptr 不一样,同时指向两个函数的位置也不一样(红圈)。
  • 在找函数时候都是找虚表中的 [2],猜测[0]和[1]是默认的构造和析构函数。
#include<iostream>
using namespace std;

class father_test {
public:
	virtual void test() { cout << "test1" << endl; }
};

class son_test :public father_test {
public:
	virtual void test() { cout << "test2" << endl; }

};


int main() {
	son_test son;
	father_test father;
	father_test *p1,*p2;
	p1 = &son;
	p2 = &father;
	p1->test();
	p2->test();
	for (int i = 0; i < 10; i++);

	cout << endl;
	getchar();
	return 0;
}
  • 当使用了vitrual声明,但没有进行override的时候,可以看到还是有两个虚表 _vfptr 的指针,只是这时函数指向的地址一样(红圈)。

#include<iostream>
using namespace std;

class father_test {
public:
	virtual void test() { cout << "test1" << endl; }
};

class son_test :public father_test {
public:
	//virtual void test() { cout << "test2" << endl; }

};


int main() {
	son_test son;
	father_test father;
	father_test *p1,*p2;
	p1 = &son;
	p2 = &father;
	p1->test();
	p2->test();
	for (int i = 0; i < 10; i++);

	cout << endl;
	getchar();
	return 0;
}
  • 为了验证上面对[2]的猜测,在test函数前面,多加了一个test2函数。果然可以看到这时调用test变成[3]

#include<iostream>
using namespace std;

class father_test {
public:
	virtual void test2() {}
	virtual void test() { cout << "test1" << endl; }
	father_test(){}
	~father_test(){}
};

class son_test :public father_test {
public:
	virtual void test2(){}
	virtual void test() { cout << "test2" << endl; }
	son_test(){}
	~son_test(){}
};


int main() {
	son_test son;
	father_test father;
	father_test *p1,*p2;
	p1 = &son;
	p2 = &father;
	p1->test();
	p2->test();
	for (int i = 0; i < 10; i++);

	cout << endl;
	getchar();
	return 0;
}
  • 再来看虚表什么时候真正初始化的

运行到①的时候,此时有指针p1和p2,但显示“无法读取内存”

运行到②,p1指针指向一个实例对象,此时可以看到_vfptr了,p2还是没有

运行到③,两个指针的虚表都有了内容。

  • 那么问题又来了,是什么时候有的虚表。指针初始化的时候才有吗?显然不是,根据前面的理论可以知道,其实在对象初始化的时候就有。
    所以回到 son_test son; 这句语句执行完,而father_test father; 还没执行之前
    可看到,对象son里面已经有虚表了,但father的虚表还是一堆问号

 

四. 多重继承时的虚函数表

  • 多重继承情况下,和上面类似。只是有几个父类,就有几个vtbl和_vfptr

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

C++ 多态和虚函数 的相关文章

  • Python3之异常with语句

    高级语法之异常with 语法 with 表达式1 as 变量1 表达式2 as 变量2 语句块 with的作用 使用于对资源进行访问的场合 确保使用过程中不管是否发生异常都会执行必须的 清理 操作 并释放资源 如 文件打开后自动关闭 线程中

随机推荐

  • 如何判断是否适合学编程?

    能问出这个问题的人 肯定是心里有点想学习编程 但是又担心自己不适合学习编程 学不会反而浪费时间 那么 如何判断自己是否适合学习编程呢 可以从如下几个方面入手 1 明确自己为什么要学编程 因为不同的编程语言适用的领域不一样 比如我认识一个做互
  • 使用COLMAP进行三维重构

    提示 文章写完后 目录可以自动生成 如何生成可参考右边的帮助文档 文章目录 前言 一 COLMAP是什么 二 数据准备 2 1 数据采集 2 2 数据组织 三 重构 3 1 提取图像特征 3 2 特征点匹配 3 3 稀疏重建 SfM 3 4
  • QT开发遇到的问题(1)——程序循环执行的问题

    我之前一直直接用C 开发工程 有需求需要跨平台开发 前期使用时候感觉还可以 转到工程应用时候 就遇到好多坑 今天就遇到个大坑 在开发时候需要不断循环一块代码来实现某种功能 QT不像C 那种 这个更加专业 下面我对这个问题进行详细说明分析 问
  • ResultSet详解

    结果集 ResultSet 是数据中查询结果返回的一种对象 可以说结果集是一个存储查询结果的对象 但是结果集并不仅仅具有存储的功能 他同时还具有操纵数据的功能 可能完成对数据的更新等 结果集读取数据的方法主要是getXXX 他的参数可以是整
  • docker 入门指南

    docker Docker is an open platform for developing shipping and running applications Docker enables you to separate your a
  • Matlab零基础入门

    前言 本篇是随笔 一段时间没用Matlab 简单复习了下 都是入门知识 零基础可读 文章目录 1 初步认识界面和命名 2 数据类型和矩阵 3 元胞数组和结构体 3 1 元胞数组 3 2 eye 3 3 3 magic 3 4 结构体 4 矩
  • RNA-seq——学习路线、学习经验、实战项目、学习总结

    1 参考课程和博客 B站 RNA seq转录组数据分析入门实战 生信技能树 转录组测序数据分析 简书 RNA seq 1 用conda安装RNA seq所需要的工具 简书 RNA seq 2 1 原始数据下载的几种方法 简书 RNA seq
  • python接口自动化(三)--如何设计接口测试用例(详解)

    简介 上篇我们已经介绍了什么是接口测试和接口测试的意义 在开始接口测试之前 我们来想一下 如何进行接口测试的准备工作 或者说 接口测试的流程是什么 有些人就很好奇 接口测试要流程干嘛 不就是拿着接口文档直接利用接口 测试工具测试嘛 其实 如
  • 开发EduSoho v8.7.10 本地播放视频超时或者快进后网络错误导致视频下载中途失败。鉴权播放次数问题

    EduSoho v8 7 10 本地播放视频超时或者快进后网络错误导致视频下载中途失败 鉴权播放次数问题 文件路径 src AppBundle Twig WebExtension php protected function makeTok
  • CFileDialog 多文件选择注意事项

    当选择文件数量比较多的时候 发现CFileDialog返回文件名并不完整 翻阅MSDN发现文件名长度是有限制的 解决思路 CFileDialog dlgOpen TRUE T txt NULL OFN HIDEREADONLY OFN RE
  • 【转】游戏汉化之Tile全格式解读 by 阿一

    最近在破解一些图片的格式 并想导出PNG 不过老是记不住bpp的格式 转载之 方便查看 做些锚记 标准1BPP NDS 1BPP 标准2BPP VB 2BPP NGP 2BPP NES 2BPP 1BPP 1BPP GB 2BPP 1BPP
  • SpringCloud2架构图

    先来个简洁版 1 外部或者内部的非Spring Cloud项目都统一通过API网关 Zuul 来访问内部服务 zuul是对外暴露的唯一接口相当于路由的是controller的请求 2 网关接收到请求后 从注册中心 Eureka 获取可用服务
  • Unity泛光效果消失问题

    关于Unity泛光效果消失问题解决过程 问题描述 第一次尝试解决 第二次尝试解决 第三次尝试解决 问题描述 之前一直在做的一个项目 在一次想要添加UI泛光效果失败后 发现项目中已有的泛光效果也消失了 第一次尝试解决 因为问题是在添加插件Po
  • linux服务器编译报错:DSO missing from command line原因及解决办法

    报错信息提示包含以下两行 undefined reference to symbol libfastrtps so 1 error adding symbols DSO missing from command line 原因 提示说符号没
  • SpringMVC异常处理

    为了统一处理代码运行过程中出现的异常 给用户一个更友好的异常界面 需要引入springMVC的异常处理功能 为了演示这个功能 本文实现一个比较常用的需求 将所有的异常归为两类 一类是程序员自己创建的异常类 另一类是系统或框架定义的异常类 程
  • junit如何测试没有返回值的方法

    方法里总有些操作 只要测试结果对就可以了 没有必要说非要有返回值 马士兵
  • 深入理解 SQL 中的 Grouping Sets 语句

    前言 SQL 中 Group By 语句大家都很熟悉 根据指定的规则对数据进行分组 常常和聚合函数一起使用 比如 考虑有表 dealer 表中数据如下 id Int city String car model String quantity
  • Linux系统下ping命令报错 name or service not know

    问题描述 CentOS 但是当执行ping命令的时候 提示name or service not known 解决方法 1 添加DNS服务器 1 vi etc resolv conf 进入编辑模式 增加如下两行内容 分别是首选DNS服务器和
  • logback--进阶--05--自定义Appenders

    logback 进阶 05 自定义Appenders 代码位置 https gitee com DanShenGuiZu learnDemo tree master logback learn 1 介绍 1 1 继承关系图 可以看到Appe
  • C++ 多态和虚函数

    一 先搞清override overload overwrite的区别 1 overload 重载 不是多态 在C 程序中 可以将语义 功能相似的几个函数用同一个名字表示 但参数不同 包括类型 顺序不同 即函数重载 1 相同的范围 在同一个