【C++入门】虚函数与多态
文章目录
- 前言
- 虚函数是什么?
- 如何使用虚函数?
- 纯虚函数是什么?
- 虚函数与普通函数的区别
- 虚表
- 虚表是什么?
- 含有虚表的类内存结构图
- 如何找到虚表的地址?
- 示例代码
- 代码解释
- 多态是什么?
- 如何使用多态?
- 为什么要使用多态?
- 多态遇到的所有情况
- 总结
前言
C++ 是一种功能强大的编程语言,以其面向对象编程(OOP)特性而闻名。虚函数和多态是 C++ 面向对象编程中的两个重要概念,它们使得代码更灵活、更具扩展性。本篇文章将介绍虚函数与多态,包括其定义、使用方法及其在实际编程中的作用。
虚函数是什么?
虚函数是一种在基类中使用 virtual
关键字声明的函数,它允许在运行时通过基类指针或引用调用子类的重写版本。这种机制使得 C++ 能够实现多态性。
如何使用虚函数?
在基类中声明虚函数,并在子类中重写该虚函数。基类指针或引用可以指向子类对象,并在运行时调用子类的重写版本。
#include <iostream>class Base {
public:virtual void show() {std::cout << "Base show" << std::endl;}
};class Derived : public Base {
public:void show() override {std::cout << "Derived show" << std::endl;}
};int main() {Base* ptr = new Derived();ptr->show(); // 调用的是 Derived 的 show 方法delete ptr;return 0;
}
纯虚函数是什么?
纯虚函数是一种没有实现的虚函数,必须在派生类中实现。它使用 = 0
语法来声明。声明纯虚函数的类称为抽象类,不能直接实例化。
class AbstractBase {
public:virtual void pureVirtualFunction() = 0; // 纯虚函数
};class ConcreteDerived : public AbstractBase {
public:void pureVirtualFunction() override {std::cout << "ConcreteDerived implementation of pureVirtualFunction" << std::endl;}
};int main() {// AbstractBase obj; // 错误,不能实例化抽象类ConcreteDerived obj;obj.pureVirtualFunction(); // 调用派生类实现的纯虚函数return 0;
}
虚函数与普通函数的区别
-
调用时机:
- 普通函数:在编译时确定调用的函数。
- 虚函数:在运行时确定调用的函数(动态绑定)。
-
多态性:
- 普通函数:不支持多态性。
- 虚函数:支持多态性,可以通过基类指针或引用调用派生类的重写版本。
虚表
虚表是什么?
在 C++ 中,虚表(vtable)是一个由编译器维护的数据结构,用于实现虚函数的动态绑定。当一个类中包含虚函数时,编译器会为这个类创建一个虚表。虚表是一个指针数组,其中每个指针指向该类的虚函数实现。当通过基类指针或引用调用虚函数时,程序会通过虚表找到实际要调用的函数。
每个包含虚函数的类对象都有一个隐藏的指针,称为虚表指针(vptr),它指向该对象所属类的虚表。因此,通过虚表指针,程序可以在运行时动态地确定调用哪个函数。
含有虚表的类内存结构图
有下面这样的两个类:
class Base {
public:virtual void func1();int baseData;
};class Derived : public Base {
public:void func1() override;void func2();int derivedData;
};
它的内存结构图如下
+-----------------+
| Derived object |
+-----------------+
| vptr ---------->+-------------------------+
| derivedData | |
+-----------------+ |
| baseData | |
+-----------------+ ||||+-----------------+ +-----------------+| Virtual Table | | Virtual Table || for Derived | | for Base |+-----------------+ +-----------------+| func1() | | func1() || Derived::func1 | | Base::func1 |+-----------------+ +-----------------+| func2() || Derived::func2 |+-----------------+
如何找到虚表的地址?
找到虚表地址的方法涉及使用一些底层的 C++ 技巧和对内存布局的理解。以下是一个简单的示例代码,用于展示如何找到一个类对象的虚表地址并打印虚表中的内容。
示例代码
#include <iostream>class Base {
public:virtual void func1() {std::cout << "Base::func1" << std::endl;}virtual void func2() {std::cout << "Base::func2" << std::endl;}
};class Derived : public Base {
public:void func1() override {std::cout << "Derived::func1" << std::endl;}void func2() override {std::cout << "Derived::func2" << std::endl;}
};typedef void(*FuncPtr)();void printVTable(FuncPtr* vtable) {std::cout << "Virtual Table Address: " << vtable << std::endl;for (int i = 0; vtable[i] != nullptr; ++i) {std::cout << "Function " << i << " address: " << vtable[i] << std::endl;}
}int main() {Derived obj;// 获取虚表指针的地址FuncPtr* vptr = *reinterpret_cast<FuncPtr**>(&obj);printVTable(vptr);return 0;
}
代码解释
-
类定义:
Base
类中定义了两个虚函数func1
和func2
。Derived
类继承自Base
并重写了这两个虚函数。
-
函数指针类型:
typedef void(*FuncPtr)();
定义了一个函数指针类型FuncPtr
,用于指向虚函数。
-
打印虚表函数:
printVTable
函数接受一个虚表指针数组并打印虚表地址和其中每个函数的地址。
-
main 函数:
- 创建一个
Derived
类对象obj
。 - 使用
reinterpret_cast
将对象的地址转换为虚表指针数组的指针,从而获取虚表地址。 - 调用
printVTable
函数打印虚表内容。
- 创建一个
多态是什么?
多态性是面向对象编程的一种特性,它允许通过基类指针或引用在运行时调用不同子类的重写方法。多态性使得代码更灵活和可扩展。
如何使用多态?
通过基类指针或引用指向子类对象,并调用基类中的虚函数。在运行时,根据实际指向的对象类型调用相应的重写方法。
#include <iostream>class Animal {
public:virtual void sound() {std::cout << "Some generic animal sound" << std::endl;}
};class Dog : public Animal {
public:void sound() override {std::cout << "Bark" << std::endl;}
};class Cat : public Animal {
public:void sound() override {std::cout << "Meow" << std::endl;}
};void makeSound(Animal* animal) {animal->sound(); // 根据实际对象类型调用相应的 sound 方法
}int main() {Dog dog;Cat cat;makeSound(&dog); // 输出 BarkmakeSound(&cat); // 输出 Meowreturn 0;
}
为什么要使用多态?
- 代码复用:多态性允许通过统一的接口调用不同子类的方法,提高代码的复用性和可维护性。
- 灵活性:多态性使得程序在运行时根据实际情况调用不同的方法,提高了代码的灵活性和扩展性。
- 可扩展性:可以在不修改已有代码的情况下添加新的子类,并在运行时通过多态性使用它们。
多态遇到的所有情况
-
通过基类指针或引用调用子类方法:
- 使用基类指针或引用指向子类对象,调用虚函数实现多态性。
-
对象切片问题:
- 如果将子类对象赋值给基类对象,会发生对象切片问题,子类的附加属性和方法会被切掉。
Base baseObj = derivedObj; // 对象切片,丢失 Derived 部分
-
多重继承中的多态:
- C++ 支持多重继承,可以通过虚继承解决多重继承中的菱形继承问题,使多态性依然有效。
-
虚析构函数:
- 在有多态的情况下,基类通常需要一个虚析构函数,以确保通过基类指针删除派生类对象时,正确调用派生类的析构函数。
class Base { public:virtual ~Base() {std::cout << "Base destructor" << std::endl;} };class Derived : public Base { public:~Derived() {std::cout << "Derived destructor" << std::endl;} };int main() {Base* ptr = new Derived();delete ptr; // 正确调用 Derived 的析构函数return 0; }
总结
虚函数和多态是 C++ 中面向对象编程的重要特性,通过虚函数可以实现多态性,使得代码更加灵活和可扩展。虚函数允许在运行时通过基类指针或引用调用子类的重写方法,而纯虚函数则强制派生类实现某些方法,使得抽象类无法实例化。多态性使得程序可以在运行时根据实际对象类型调用相应的方法,提升了代码的复用性、灵活性和可扩展性。然而,在使用多态时,需要注意对象切片问题和虚析构函数的使用,以确保正确的对象管理和方法调用。掌握虚函数和多态的概念和应用,对于提高 C++ 编程水平和开发效率至关重要。
相关文章:

【C++入门】虚函数与多态
文章目录 前言虚函数是什么?如何使用虚函数? 纯虚函数是什么?虚函数与普通函数的区别虚表虚表是什么?含有虚表的类内存结构图如何找到虚表的地址?示例代码代码解释 多态是什么?如何使用多态?为什…...

wpf中轮询显示图片
本文的需求是,在一个文件夹中,放一堆图片的集合,然后在wpf程序中,按照定时的方式,循序显示照片。 全部代码 1.声明一个PictureInfo类 namespace WpfApp1 {public class PictureInfo{public string? FileName { get; …...

CSA笔记9-磁盘管理(2)
分区挂载 挂载:将该文件系统中的内容与指定的目录关联起来,使得你可以通过该目录来访问文件系统中的文件和目录。 mount 命令用来挂载文件系统 #挂载/dev/sda1和/dev/sda2 [rootlocalhost ~]# mkdir test{1..2} [rootlocalhost ~]# ll test1 te…...

Python入门第三课
# 入门第三课 # 关键字 if and or in not in ! car g print(car g) print(car dd) if car ! hh:print("wlcome to here ") age 33 print(age 33) print(age 44) age1 44 if age >0 and age1 > 0:print("nihao") if age >0 or age1 > …...

java计算器,输入公式和对应变量的值
目标:最近想写个东西,本质就是一个计算器,我们可以输入公式(例如:ab),然后把公式的值(a:10,b:20)也输入进去。最后得到结果。核心:这个想法核心部分就是给一个…...

加密货币赋能跨境电商:PayPal供应链金融服务如何引领行业新趋势
跨境电商行业近年来呈现出爆发式增长,随着全球化贸易壁垒的降低和数字经济的快速发展,越来越多的商家和消费者跨越国界进行交易。根据eMarketer的数据,全球跨境电商交易额在2023年已超过4万亿美元,并预计在未来几年内仍将保持两位…...

redis面试(二)List链表数据
list 列表 我们总是说List为列表,其实在真正的数据结构来说,redis是自己基于c语言来实现的双向链表数据结构,主要的逻辑就是每个节点都可以指向下一个节点,这个结构就属于链表数组结构。 每个节点中的属性如下: type…...

SpringDataJPA(三):多表操作,复杂查询
一、Specifications动态查询 有时我们在查询某个实体的时候,给定的条件是不固定的,这时就需要动态构建相应的查询语句,在Spring Data JPA中可以通过JpaSpecificationExecutor接口查询。相比JPQL,其优势是类型安全,更加的面向对象。 import …...

嵌入式硬件面试题集萃:从基础到进阶
基础问题 问题: 解释什么是微控制器,以及它与微处理器的区别。 答案: 微控制器是具有集成内存和输入/输出外设的微型计算机。与通用微处理器相比,微控制器通常用于控制特定应用,而不是执行通用计算任务。 问题: 什么是数字逻辑门,…...

easyui-datebox 只显示月份选择,默认开启月份,隐藏日期选择框
如果你使用 easyui-datebox 并希望隐藏日期选择框,只显示月份选择,可以通过一些自定义代码来实现。虽然 EasyUI 没有直接提供这种功能,但可以通过自定义 formatter 和 parser 方法,以及修改 onShowPanel 事件来实现这个功能。 以下…...

【数据结构】队列(链表实现 + 力扣 + 详解 + 数组实现循环队列 )
Hi~!这里是奋斗的明志,很荣幸您能阅读我的文章,诚请评论指点,欢迎欢迎 ~~ 🌱🌱个人主页:奋斗的明志 🌱🌱所属专栏:数据结构 📚本系列文章为个人学…...

02 Go语言操作MySQL基础教程_20240729 课程笔记
概述 如果您没有Golang的基础,应该学习如下前置课程。 Golang零基础入门Golang面向对象编程Go Web 基础Go语言开发REST API接口_20240728 基础不好的同学每节课的代码最好配合视频进行阅读和学习,如果基础比较扎实,则阅读本教程巩固一下相…...

相交链表 - 力扣(LeetCode)C语言
160. 相交链表 - 力扣(LeetCode) (点击前面链接即可查看题目) 一、题目 给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。 图示两个链表在节点 c1 开始…...

【Python】基础学习技能提升代码样例3:JSON文本处理
对json的处理,无非是编码和解码两部分 编码:将python数据结构转换为json字符串解码: 将json字符串转换为python数据结构 另外,还有.json文件的读写 一、编码 json.dumps(obj, *, skipkeysFalse, ensure_asciiTrue, check_circularTrue, a…...

最新Yiso智云搜索引擎系统源码/开源PHP源码/修复版
源码简介: 最新Yiso智云搜索引擎系统源码/开源PHP源码/修复版。Yiso 是一个性能非常好的搜索引擎,不仅免费开源,还能当作收录网址的平台来用呢!只需要输入关键词,就能轻松找到相关的搜索结果内容。 1、Yiso 用的是自…...

Anconda 快速常用命令简洁版
目的:简单清楚的使用基本的conda 命令 可能需求 查看项目中的虚拟环境及依赖是否满足需求操作新环境来满足项目或者论文的实现 Anconda 常用命令 conda 查看基础命令1. 进入Anaconda 环境2. 查看版本3.查看有哪些虚拟环境4.激活虚拟环境5. 进入虚拟环境查看6. 退出…...

Android 系统启动动画
一、接着我们把 bootanimation.zip 动画文件 预制到 /system/media/ 目录下: 二、目录/system/media/bootanimation.zip PRODUCT_COPY_FILES \$(LOCAL_PATH)/bootanimation.zip:/system/media/bootanimation.zipPRODUCT_ARTIFACT_PATH_REQUIREMENT_WHITELIST \ /…...

解决antd打开modal时页面自动跳到顶部问题
问题原因:antd的样式中有一行,如下样式代码,这行代码导致了在本来有滚动条的页面底部触发modal弹出时,会自动滚动到页面顶部。 html {overflow-y: scroll; } 解决办法:删除这行代码、或者将html的overflow-y属性改成…...

什么是等保测评2.0,等保测评如何定级
在信息化时代,网络安全已成为国家安全的重要组成部分。为了应对日益复杂的网络安全形势,我国推出了网络安全等级保护制度,其中等保测评是评估信息系统安全防护能力的关键环节。本文将深入探讨等保2.0的测评流程和定级标准,以揭示其…...

【嵌入式英语教程--6】C语言中的数组与指针
C语言中的数组与指针 英文原文 Arrays and pointers are fundamental concepts in the C programming language. An array is a collection of elements of the same data type stored in contiguous memory locations. Arrays can be used to store and manipulate sequence…...

RocketMQ 中的同步发送
在现代分布式系统中,消息队列是实现异步通信和解耦的重要组件。Apache RocketMQ 是一款高性能、高吞吐量的分布式消息中间件,广泛应用于电商、金融等领域。本文将详细介绍 RocketMQ 中的同步发送,包括其原理、应用场景、代码示例及注意事项。…...

c语言指针2
文章目录 一、void * 指针二、const关键字1.const修饰变量2.const修饰指针变量2. 1 const放在*的右边2. 2 const放在*的左边2. 3 总结 三、指针的运算3. 1指针的加减运算3. 2 指针 - 指针3. 3 指针的关系运算 四、野指针4. 1 什么叫野指针?4. 1 野指针的成因4.1.1 指…...

十七、openCV教程 图像轮廓
一、图像轮廓 图像轮廓是具有相同颜色或灰度的连续点的曲线.轮廓在形状分析和物体的检测和识别中很有用。 轮廓的作用:.用于图形分析、物体的识别和检测 注意点: 为了检测的准确性,需要先对图像进行二值化或Canny操作。 画轮廓时会修改输入的图像,如…...

基于视觉的语义匹配见多了,那基于雷达的呢?
论文题目: LiDAR-based HD Map Localization using Semantic Generalized ICP with Road Marking Detection 论文作者: Yansong Gong, Xinglian Zhang, Jingyi Feng, Xiao He and Dan Zhang 作者单位:北京驭势科技有限公司 导读ÿ…...

01、爬虫学习入门
爬虫:通过编写程序,来获取获取互联网上的资源 需求:用程序模拟浏览器,输入一个网址,从该网址获取到资源或内容 一、入门程序 #使用urlopen来进行爬取 from urllib.request import urlopen url "http://www.ba…...

我与C语言二周目邂逅vlog——6.文件操作
1. 为什么使⽤⽂件? 如果没有⽂件,我们写的程序的数据是存储在电脑的内存中,如果程序退出,内存回收,数据就丢失 了,等再次运⾏程序,是看不到上次程序的数据的,如果要将数据进⾏持久…...

Hugo 部署与自动更新(Git)
文章目录 Nginx部署Hugonginx.confhugo.conf Hugo自动更新Hugo自动更新流程添加访问令牌添加web hookrust实现自动更新接口 Nginx部署Hugo nginx.conf user nginx; worker_processes auto;error_log /var/log/nginx/error.log notice; pid /var/run/nginx.pid;even…...

HTTP代理揭秘:这些场景你都用对了吗?
HTTP代理是网络中常见的一种工具,可以帮助我们提升网络安全性和隐私保护,优化网络访问速度。本文将详细介绍什么是HTTP代理及其适用的场景。 HTTP代理是介于客户端(如浏览器)和服务器之间的中间服务器。它接收客户端的HTTP请求&a…...

电动汽车充电技术及运营知识问答pdf
电动汽车充电技术及运营知识问答 作者:马银山编著 出版社:北京:中国电力出版社 ISBN:9787512320406 资源大小:16.99MB 目录: http://literalink.top/resource/detail/7181601144102195200 第一章 电动汽车基本知识 1 1-1什么是电动汽车? 11-…...

playbooks 分布式部署 LNMP
1、环境配置 ansible 服务器 192.168.10.10nginx 服务器 192.168.10.20mysql 服务器 192.168.10.21php 服务器 192.168.10.22 2、安装 ansble #192.168.10.10节点 yum install -y epel-release #先安装 epel 源 yum install -y ansible配置主机清单 …...