1. 虚函数(Virtual Function)
    • 定义:在基类中使用 virtual 关键字声明的成员函数,允许在派生类中被重新定义(覆盖,override)。其目的是实现多态性,即通过基类指针或引用调用函数时,根据对象的实际类型来决定调用哪个类的函数版本。
#include <iostream>class Animal {
public:virtual void speak() {std::cout << "Animal speaks" << std::endl;}
};class Dog : public Animal {
public:void speak() override {std::cout << "Dog barks" << std::endl;}
};class Cat : public Animal {
public:void speak() override {std::cout << "Cat meows" << std::endl;}
};int main() {Animal* animal1 = new Dog();Animal* animal2 = new Cat();animal1->speak();animal2->speak();delete animal1;delete animal2;return 0;
}
  • 在上述代码中,Animal 类中的 speak 函数被声明为虚函数。DogCat 类从 Animal 类派生,并覆盖了 speak 函数。在 main 函数中,通过基类指针调用 speak 函数时,实际调用的是对象所对应的派生类中的函数版本,从而实现了多态性。
  1. 纯虚函数(Pure Virtual Function)
    • 定义:在基类中声明的没有函数体,并且初始化为 0 的虚函数。包含纯虚函数的类称为抽象类,抽象类不能实例化对象,只能作为基类被派生类继承。派生类必须实现纯虚函数,否则派生类也将成为抽象类。
#include <iostream>class Shape {
public:virtual double area() = 0;
};class Circle : public Shape {
public:Circle(double r) : radius(r) {}double area() override {return 3.14159 * radius * radius;}
private:double radius;
};class Rectangle : public Shape {
public:Rectangle(double w, double h) : width(w), height(h) {}double area() override {return width * height;}
private:double width, height;
};int main() {Shape* shape1 = new Circle(5.0);Shape* shape2 = new Rectangle(4.0, 6.0);std::cout << "Circle area: " << shape1->area() << std::endl;std::cout << "Rectangle area: " << shape2->area() << std::endl;delete shape1;delete shape2;return 0;
}
  • Shape 类中的 area 函数是纯虚函数。CircleRectangle 类继承自 Shape 类,并实现了 area 函数。通过这种方式,强制派生类提供自己的 area 计算方法,同时利用基类指针实现多态调用。
  1. 虚函数实现机制

    • 虚函数表(Virtual Table,简称 vtable):当一个类中包含虚函数时,编译器会为该类创建一个虚函数表。虚函数表是一个存储类成员虚函数指针的数组。每个包含虚函数的类都有自己的虚函数表。
    • 虚指针(Virtual Pointer,简称 vptr):每个包含虚函数的对象都包含一个指向其所属类的虚函数表的指针,即虚指针。当对象被创建时,虚指针被初始化,指向该对象所属类的虚函数表。
    • 调用过程:当通过基类指针或引用调用虚函数时,首先根据对象的虚指针找到对应的虚函数表,然后在虚函数表中查找与被调用函数对应的指针,最后通过该指针调用实际的函数。例如,在上述 Animal 类及其派生类的例子中,animal1animal2 对象都有自己的虚指针,分别指向 DogCat 类的虚函数表。当调用 animal1->speak() 时,通过 animal1 的虚指针找到 Dog 类的虚函数表,再从虚函数表中找到 speak 函数的指针并调用。
  2. 虚函数表的细节

    • 布局:虚函数表中的函数指针按照虚函数在类中声明的顺序排列。如果派生类覆盖了基类的虚函数,虚函数表中相应位置的指针会被替换为派生类中该虚函数的实现地址。
    • 多重继承:在多重继承的情况下,一个对象可能有多个虚指针,分别指向不同基类的虚函数表。这是因为不同基类可能有不同的虚函数集合。例如,当一个类从多个包含虚函数的基类派生时,每个基类的虚函数表都需要被正确管理,以确保虚函数调用的正确性。
    • 运行时开销:虚函数机制带来了运行时的额外开销,主要包括存储虚指针的空间开销以及通过虚指针和虚函数表查找函数指针的时间开销。然而,这种开销在大多数情况下是可以接受的,并且为C++ 提供了强大的多态性支持。
  3. 虚函数与纯虚函数

    • 析构函数的虚属性
      • 重要性:当基类指针指向派生类对象,并且通过该指针删除对象时,如果基类析构函数不是虚函数,那么只会调用基类的析构函数,派生类的析构函数不会被调用,这可能导致内存泄漏。例如:
#include <iostream>class Base {
public:~Base() {std::cout << "Base destructor" << std::endl;}
};class Derived : public Base {
public:~Derived() {std::cout << "Derived destructor" << std::endl;}
};int main() {Base* basePtr = new Derived();delete basePtr;return 0;
}
 - **输出**:只会输出 “Base destructor”。但如果将 `Base` 类的析构函数声明为虚函数 `virtual ~Base()`,则会先调用 `Derived` 类的析构函数,再调用 `Base` 类的析构函数,确保资源正确释放。
  • 纯虚析构函数:纯虚析构函数是一种特殊情况,一个类可以有纯虚析构函数,但必须在类外提供函数体。例如:
class AbstractClass {
public:virtual ~AbstractClass() = 0;
};AbstractClass::~AbstractClass() {std::cout << "AbstractClass destructor" << std::endl;
}class ConcreteClass : public AbstractClass {
public:~ConcreteClass() override {std::cout << "ConcreteClass destructor" << std::endl;}
};
  • 虚函数与模板:模板元编程中,虚函数的使用需要特别注意。模板是在编译期进行实例化的,而虚函数是运行时多态的基础。当模板类与虚函数结合时,由于模板的实例化机制,可能会导致一些不易察觉的问题。例如,模板类中的虚函数可能不会像预期那样在派生类中被正确覆盖,因为模板的实例化是基于不同的模板参数,每个实例化可能会有不同的虚函数表布局。
  1. 虚函数实现机制与虚函数表
    • 虚函数表与动态绑定:动态绑定是指在运行时根据对象的实际类型来确定调用哪个虚函数的过程。虚函数表是实现动态绑定的关键。编译器在编译时生成虚函数表,运行时通过虚指针和虚函数表进行函数调用。但在一些优化场景下,如在编译期能够确定对象的实际类型(例如通过 constexpr 条件判断等),编译器可能会进行静态绑定,直接调用相应的函数,而不通过虚函数表机制,以提高效率。
    • 虚函数表与内存对齐:由于虚指针的存在,对象的内存布局可能会受到影响。虚指针的大小通常与机器的指针大小相同(例如在 64 位系统中为 8 字节)。为了满足内存对齐的要求,对象的大小可能会增加。例如,一个类只有一个 int 成员变量(通常 4 字节),但如果它包含虚函数,加上 8 字节的虚指针,并且按照 8 字节对齐,对象的大小就会变为 8 字节,而不是 4 + 8 = 12 字节(因为内存对齐会补齐)。
    • 虚函数表的维护与继承层次:在复杂的继承层次结构中,虚函数表的维护变得更加复杂。当有多层继承和虚函数的覆盖时,编译器需要确保虚函数表的一致性。例如,在菱形继承(一个派生类从两个基类继承,而这两个基类又从同一个基类继承)的情况下,虚函数表的布局需要精心设计,以避免二义性和重复调用等问题。C++ 的虚继承机制就是为了解决这类问题,它通过引入虚基类指针等方式,确保虚函数表在复杂继承结构中的正确维护。

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

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

相关文章

uniapp微信小程序-登录页面验证码的实现(springboot+vue前后端分离)EasyCaptcha验证码 超详细

一、项目技术栈登录页面暂时涉及到的技术栈如下:前端 Vue2 Element UI Axios&#xff0c;后端 Spring Boot 2 MyBatis MySQL Redis EasyCaptcha JWT Maven后端使用IntelliJ IDEA 2024.3.5 前端使用 HBuilder X 和 微信开发者工具二、实现功能及效果图过期管理验证码有…

【Java】HashMap的详细介绍

目录 一.HashMap 1.基本概念 2.底层数据结构&#xff1a; 3.HashCode和equals方法 为什么重写HashCode方法&#xff1f; 为什么重新equals方法&#xff1f; 4.put操作 1.初始化和数组检查 2.计算索引并检查桶是否为空 3.桶不为null&#xff0c;处理哈希冲突 4.判断链…

nifi 增量处理组件

在Apache NiFi中&#xff0c;QueryDatabaseTable 是一个常用的处理器&#xff0c;主要用于从关系型数据库表中增量查询数据&#xff0c;特别适合需要定期抽取新增或更新数据的场景&#xff08;如数据同步、ETL流程&#xff09;。它的核心功能是通过跟踪指定列的最大值&#xff…

【数据可视化-90】2023 年城镇居民人均收入可视化分析:Python + pyecharts打造炫酷暗黑主题大屏

&#x1f9d1; 博主简介&#xff1a;曾任某智慧城市类企业算法总监&#xff0c;目前在美国市场的物流公司从事高级算法工程师一职&#xff0c;深耕人工智能领域&#xff0c;精通python数据挖掘、可视化、机器学习等&#xff0c;发表过AI相关的专利并多次在AI类比赛中获奖。CSDN…

Multiverse模型:突破多任务处理和硬件效率瓶颈的AI创新(上)

随着人工智能技术的快速发展&#xff0c;多模态模型成为了当前研究的热点。多模态模型的核心思想是能够同时处理和理解来自不同模态&#xff08;如文本、图像、音频等&#xff09;的数据&#xff0c;从而为模型提供更加全面的语境理解和更强的泛化能力。 杨新宇&#xff0c;卡…

OpenCV 高斯模糊降噪

# 高斯模糊处理(降噪) # 参数1: 原始图像 # 参数2: 高斯核尺寸(宽,高&#xff0c;必须为正奇数) # 其他模糊方法: # - cv.blur(): 均值模糊 # - cv.medianBlur(): 中值模糊 # - cv.bilateralFilter(): 双边滤波 blur cv.GaussianBlur(img, (7,7), cv…

常见通信协议详解:TCP、UDP、HTTP/HTTPS、WebSocket 与 RPC

在现代网络通信中&#xff0c;各种协议扮演着至关重要的角色&#xff0c;它们决定了数据如何在网络中传输、控制其可靠性、实时性与适用场景。对于开发者而言&#xff0c;理解这些常见的通信协议&#xff0c;不仅有助于更好地设计系统架构&#xff0c;还能在面对不同业务需求时…

深入解析MPLS网络中的路由器角色

一、 MPLS概述&#xff1a;标签交换的艺术 在深入角色之前&#xff0c;我们首先要理解MPLS的核心思想。传统IP路由是逐跳进行的&#xff0c;每一台路由器都需要对数据包的目的IP地址进行复杂的路由表查找&#xff08;最长匹配原则&#xff09;&#xff0c;这在网络核心层会造成…

AI的拜师学艺,模型蒸馏技术

AI的拜师学艺&#xff0c;模型蒸馏技术什么是模型蒸馏&#xff0c;模型蒸馏是一种高效的模型压缩与知识转移方法&#xff0c;通过将大型教师模型的知识精炼至小型学生模型&#xff0c;让学生模型模仿教师模型的行为和内化其知识&#xff0c;在保持模型性能的同时降低资源消耗。…

Python爬虫从入门到精通(理论与实践)

目录 1. 爬虫的魅力:从好奇心到数据宝藏 1.1 爬虫的基本流程 1.2 准备你的工具箱 2. 第一个爬虫:抓取网页标题和链接 2.1 代码实战:用requests和BeautifulSoup 2.2 代码解析 2.3 遇到问题怎么办? 3. 进阶爬取:结构化数据抓取 3.1 分析网页结构 3.2 代码实战:抓取…

【DDIA】第三部分:衍生数据

1. 章节介绍 本章节是《设计数据密集型应用》的第三部分&#xff0c;聚焦于多数据系统集成问题。前两部分探讨了分布式数据库的基础内容&#xff0c;但假设应用仅用一种数据库&#xff0c;而现实中大型应用常需组合多种数据组件。本部分旨在研究不同数据系统集成时的问题&#…

Spring配置线程池开启异步任务

一、单纯使用Async注解。1、Async注解在使用时&#xff0c;如果不指定线程池的名称&#xff0c;则使用Spring默认的线程池&#xff0c;Spring默认的线程池为SimpleAsyncTaskExecutor。2、方法上一旦标记了这个Async注解&#xff0c;当其它线程调用这个方法时&#xff0c;就会开…

AI数据仓库优化数据管理

内容概要AI数据仓库代表了现代企业数据管理的重大演进&#xff0c;它超越了传统数据仓库的范畴。其核心在于利用人工智能技术&#xff0c;特别是机器学习和深度学习算法&#xff0c;来智能化地处理从多源数据整合到最终价值提取的全过程。这种新型仓库不仅能高效地统一存储来自…

SpringMVC(详细版从入门到精通)未完

SpringMVC介绍 MVC模型 MVC全称Model View Controller,是一种设计创建Web应用程序的模式。这三个单词分别代表Web应用程序的三个部分: Model(模型):指数据模型。用于存储数据以及处理用户请求的业务逻辑。在Web应用中,JavaBean对象,业务模型等都属于Model。 View(视图…

vue3运行机制同tkinter做类比

把刚才“Vue3 盖别墅”的故事&#xff0c;和 Python 的 tkinter 做一个“一一对应”的翻译&#xff0c;你就能瞬间明白两件事的异同。 为了直观&#xff0c;用同一栋房子比喻&#xff1a; Vue3 的“网页” ⇄ tkinter 的“桌面窗口”浏览器 ⇄ Python 解释器 Tcl/Tk 引擎 下面…

Fastadmin后台列表导出到表格

html中添加按钮<a href"javascript:;" class"btn btn-success btn-export" title"{:__(导出数据)}" ><i class"fa fa-cloud-download"></i> {:__(导出数据)}</a>对应的js添加代码处理点击事件&#xff0c;添加…

Nginx反向代理与缓存实现

1. Nginx反向代理核心配置解析 1.1 反向代理基础配置结构 Nginx反向代理的基础配置结构主要包括server块和location块的配置。一个典型的反向代理配置示例如下&#xff1a; server {listen 80;server_name example.com;location / {proxy_pass http://backend_servers;proxy_se…

第2节 如何计算神经网络的参数:AI入门核心逻辑详解

🎯 核心目标:找到最佳w和b! 上期咱们聊了神经网络就是复杂的"线性变换+激活函数套娃",今天的重头戏就是:怎么算出让模型完美拟合数据的w(权重)和b(偏置)!先从最简单的线性函数说起,一步步揭开神秘面纱 那么如何计算w和b呢?首先明确我们需要的w和b能够让…

AutoSar AP平台功能组并行运行原理

在 AUTOSAR Adaptive Platform&#xff08;AP&#xff09;中&#xff0c;同一个机器上可以同时运行多个功能组&#xff08;Function Groups&#xff09;&#xff0c;即使是在单核CPU环境下。其调度机制与进程调度既相似又存在关键差异&#xff0c;具体实现如下&#xff1a;功能…

linux服务器查看某个服务启动,运行的时间

一 查看服务启动运行时间1.1 查看启动时间查看启动时间&#xff08;精确到秒&#xff09;&#xff1a;ps -p <PID> -o lstart例子如下&#xff1a;ps -p 1234 -o lstart1.2 查询运行时长ps -p <PID> -o etimeps -p 1234 -o etime1.3 总结