【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…...

接口测试中缓存处理策略
在接口测试中,缓存处理策略是一个关键环节,直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性,避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明: 一、缓存处理的核…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻
在如今就业市场竞争日益激烈的背景下,越来越多的求职者将目光投向了日本及中日双语岗位。但是,一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧?面对生疏的日语交流环境,即便提前恶补了…...

C++_核心编程_多态案例二-制作饮品
#include <iostream> #include <string> using namespace std;/*制作饮品的大致流程为:煮水 - 冲泡 - 倒入杯中 - 加入辅料 利用多态技术实现本案例,提供抽象制作饮品基类,提供子类制作咖啡和茶叶*//*基类*/ class AbstractDr…...
【Java学习笔记】Arrays类
Arrays 类 1. 导入包:import java.util.Arrays 2. 常用方法一览表 方法描述Arrays.toString()返回数组的字符串形式Arrays.sort()排序(自然排序和定制排序)Arrays.binarySearch()通过二分搜索法进行查找(前提:数组是…...
前端倒计时误差!
提示:记录工作中遇到的需求及解决办法 文章目录 前言一、误差从何而来?二、五大解决方案1. 动态校准法(基础版)2. Web Worker 计时3. 服务器时间同步4. Performance API 高精度计时5. 页面可见性API优化三、生产环境最佳实践四、终极解决方案架构前言 前几天听说公司某个项…...

【HarmonyOS 5.0】DevEco Testing:鸿蒙应用质量保障的终极武器
——全方位测试解决方案与代码实战 一、工具定位与核心能力 DevEco Testing是HarmonyOS官方推出的一体化测试平台,覆盖应用全生命周期测试需求,主要提供五大核心能力: 测试类型检测目标关键指标功能体验基…...

(二)TensorRT-LLM | 模型导出(v0.20.0rc3)
0. 概述 上一节 对安装和使用有个基本介绍。根据这个 issue 的描述,后续 TensorRT-LLM 团队可能更专注于更新和维护 pytorch backend。但 tensorrt backend 作为先前一直开发的工作,其中包含了大量可以学习的地方。本文主要看看它导出模型的部分&#x…...

YSYX学习记录(八)
C语言,练习0: 先创建一个文件夹,我用的是物理机: 安装build-essential 练习1: 我注释掉了 #include <stdio.h> 出现下面错误 在你的文本编辑器中打开ex1文件,随机修改或删除一部分,之后…...

相机从app启动流程
一、流程框架图 二、具体流程分析 1、得到cameralist和对应的静态信息 目录如下: 重点代码分析: 启动相机前,先要通过getCameraIdList获取camera的个数以及id,然后可以通过getCameraCharacteristics获取对应id camera的capabilities(静态信息)进行一些openCamera前的…...
大模型多显卡多服务器并行计算方法与实践指南
一、分布式训练概述 大规模语言模型的训练通常需要分布式计算技术,以解决单机资源不足的问题。分布式训练主要分为两种模式: 数据并行:将数据分片到不同设备,每个设备拥有完整的模型副本 模型并行:将模型分割到不同设备,每个设备处理部分模型计算 现代大模型训练通常结合…...