当前位置: 首页 > news >正文

【C++】多态与虚函数

目录

  • 🚀前言
  • 🤔多态的定义和分类
    • 💯静态多态(编译期多态)
    • 💯动态多态(运行期多态)
  • 🌟动态多态的实现条件
  • 🦜虚函数实现多态的机制
    • 💯虚函数表是什么?
    • 💯虚函数表的生成与继承规则
    • 💯动态绑定的过程(多态的实际执行逻辑)
    • 💯关于虚函数表的关键细节
  • 🎋纯虚函数与抽象类
    • 💯纯虚函数的语法
    • 💯抽象类的特点
    • 💯代码示例
  • ☘️虚析构函数
    • 💯虚析构的写法
  • 🐍override和final
    • 💯override
    • 💯final
  • ⚙️总结

🚀前言

在这里插入图片描述

大家好!我是 EnigmaCoder

  • 本文介绍C++中的多态,从静态/动态多态、虚函数表、纯虚函数/抽象类到虚析构函数。

🤔多态的定义和分类

多态(Polymorphism)是面向对象三大特性之一,核心是“一个接口,多种形态”,比如同样是“发出叫声”,猫是“喵喵喵”,狗是“汪汪汪”。多态的核心魅力在于让同一行为在不同对象上有不同实现,既提升了代码复用性,又增强了扩展性。

多态可以分为两类:

💯静态多态(编译期多态)

  • 实现方式:函数重载、运算符重载
  • 特点:编译阶段就确定函数的调用地址(静态绑定)
  • 注意:如果是多继承中出现同名成员,需要用作用域::区分(比如Father1::func()

💯动态多态(运行期多态)

  • 实现方式:虚函数(virtual)+ 继承 + 重写
  • 特点:运行阶段才确定函数的调用地址(动态绑定)
  • 这是C++多态的核心,其底层依赖「虚函数表」实现

🌟动态多态的实现条件

想要实现动态多态,必须同时满足3个条件:

  1. 有继承关系:子类继承父类
  2. 子类重写父类的虚函数:重写要求:函数名、返回值类型、参数列表完全一致
  3. 父类指针/引用指向子类对象:通过父类的指针或引用调用重写的函数

🦜虚函数实现多态的机制

动态多态能“运行时选函数”,全靠**虚函数表(Virtual Table,简称vtable)虚指针(Virtual Pointer,简称vptr)**的配合,这是C++多态的底层核心机制。

💯虚函数表是什么?

  • 本质:一个全局只读的函数地址数组,属于(而非对象),每个包含虚函数的类都会生成唯一的虚函数表。
  • 内容:存储该类所有虚函数的地址,顺序与虚函数在类中的声明顺序一致。
  • 虚指针(vptr):每个包含虚函数的类的对象,都会隐含一个vptr成员(位于对象内存的最前端),用来指向所属类的虚函数表。

💯虚函数表的生成与继承规则

当类之间存在继承+虚函数重写时,虚函数表会发生如下变化:

  • 父类有虚函数:父类会生成自己的虚函数表,比如Animal类的虚函数表中存Animal::speak()的地址。
  • 子类继承父类
    • 若子类未重写父类虚函数:子类的虚函数表会直接继承父类的虚函数地址。
    • 若子类重写了父类虚函数:子类的虚函数表中,对应位置的函数地址会被替换为子类重写后的函数地址(比如Cat类的虚函数表中,speak()的地址会变成Cat::speak())。

💯动态绑定的过程(多态的实际执行逻辑)

以“动物叫”为例,当执行Animal* p = new Cat(); p->speak();时:

  1. 程序通过p(父类指针)找到所指对象的vptr(此时vptrCat对象的,指向Cat类的虚函数表)。
  2. Cat的虚函数表中,找到speak()对应的地址(即Cat::speak())。
  3. 调用该地址对应的函数,最终输出“小猫喵喵喵”。

举个直观的例子:虚函数表的变化

// 父类:Animal(含虚函数)
class Animal {
public:virtual void speak() { cout << "动物叫" << endl; }virtual void eat() { cout << "动物进食" << endl; }
};// 子类:Cat(重写speak,继承eat)
class Cat : public Animal {
public:void speak() override { cout << "小猫喵喵喵" << endl; }
};
  • Animal类的虚函数表

    位置函数地址
    0Animal::speak()
    1Animal::eat()
  • Cat类的虚函数表

    位置函数地址
    0Cat::speak()
    1Animal::eat()

💯关于虚函数表的关键细节

  • 每个类的vtable全局唯一:不管创建多少个对象,同一个类的所有对象共享同一个虚函数表。
  • vptr在构造函数中初始化:对象创建时,构造函数会把vptr指向当前类的虚函数表。
  • 对象的内存开销:包含虚函数的对象会多一个vptr的大小(32位系统占4字节,64位占8字节)。

🎋纯虚函数与抽象类

如果父类的虚函数不需要具体实现(只是给子类留接口),可以声明为纯虚函数,包含纯虚函数的类称为抽象类

💯纯虚函数的语法

virtual 返回值类型 函数名(参数列表) = 0;

比如动物类的speak()可以写成纯虚函数:

class Animal {
public:// 纯虚函数:没有函数体,强制子类实现virtual void speak() = 0;
};

💯抽象类的特点

  • 不能实例化对象(比如Animal a;会报错)
  • 子类必须全部实现父类的纯虚函数,否则子类也会变成抽象类
  • 可以定义抽象类的指针/引用,用来指向子类对象(这也是多态的常用方式)

💯代码示例

#include <iostream>
using namespace std;// 抽象类:Animal(包含纯虚函数,无法实例化)
class Animal {
public:// 纯虚函数1:动物叫声(仅定义接口,无实现)virtual void speak() = 0;// 纯虚函数2:动物进食(仅定义接口,无实现)virtual void eat() = 0;// 虚析构:确保子类析构被调用(避免内存泄漏)virtual ~Animal() {}
};// 子类1:Cat(必须实现Animal的所有纯虚函数,否则是抽象类)
class Cat : public Animal {
public:// 实现纯虚函数speak:猫的叫声void speak() override {cout << "小猫:喵喵喵~" << endl;}// 实现纯虚函数eat:猫的进食行为void eat() override {cout << "小猫:爱吃小鱼干~" << endl;}// 子类析构(会被虚析构触发)~Cat() override {cout << "Cat对象被销毁" << endl;}
};// 子类2:Dog(同样实现所有纯虚函数)
class Dog : public Animal {
public:void speak() override {cout << "小狗:汪汪汪~" << endl;}void eat() override {cout << "小狗:爱吃肉骨头~" << endl;}~Dog() override {cout << "Dog对象被销毁" << endl;}
};// 错误示例:Bird类(仅实现了一个纯虚函数,因此是抽象类,无法实例化)
class Bird : public Animal {
public:void speak() override {cout << "小鸟:叽叽叽~" << endl;}// 未实现eat(),因此Bird是抽象类
};int main() {// 1. 抽象类不能实例化(以下代码会报错)// Animal animal; // 编译错误:Animal是抽象类// 2. 用抽象类指针指向子类对象(实现多态)Animal* animal1 = new Cat();Animal* animal2 = new Dog();// 3. 调用函数:运行时绑定到子类实现animal1->speak(); // 输出:小猫:喵喵喵~animal1->eat();   // 输出:小猫:爱吃小鱼干~animal2->speak(); // 输出:小狗:汪汪汪~animal2->eat();   // 输出:小狗:爱吃肉骨头~// 4. 释放资源:虚析构会触发子类析构delete animal1; // 输出:Cat对象被销毁delete animal2; // 输出:Dog对象被销毁// 5. 抽象类Bird无法实例化(以下代码会报错)// Bird bird; // 编译错误:Bird是抽象类return 0;
}

☘️虚析构函数

当用父类指针指向子类对象时,如果父类的析构函数不是虚函数,delete父类指针时只会调用父类的析构函数,不会调用子类的析构函数,导致子类的资源无法释放(内存泄漏)。

💯虚析构的写法

class Animal {
public:virtual ~Animal() { // 虚析构cout << "Animal析构" << endl;}
};class Cat : public Animal {
public:~Cat() override {cout << "Cat析构" << endl;}
};// 调用时:
Animal* p = new Cat();
delete p; // 会先调用Cat析构,再调用Animal析构(通过虚函数表实现)

🐍override和final

C++11提供了overridefinal两个关键字,用来增强多态的安全性。

💯override

在子类重写的函数后加override,编译器会检查是否真的重写了父类的虚函数(比如函数名/参数写错了会报错):

class Animal {
public:virtual void speak(int a) {}
};class Cat : public Animal {
public:// 错误:父类speak是带int参数的,这里没参数,override会触发编译错误void speak() override {}
};

💯final

  • 修饰类:该类不能被继承(比如class Animal final {};
  • 修饰虚函数:该函数不能被子类重写(比如virtual void speak() final {}

⚙️总结

多态是C++面向对象的灵魂,其底层逻辑可以概括为:

  • 静态多态:编译期绑定,依赖函数重载
  • 动态多态:运行期绑定,依赖「虚函数表+虚指针」,核心是“子类虚函数表替换重写函数地址”
  • 抽象类+纯虚函数:定义接口规范,强制子类实现
  • 虚析构:通过虚函数表保证子类析构被调用,避免内存泄漏
http://www.dtcms.com/a/585092.html

相关文章:

  • 洛谷 P9847 [ICPC 2021 Nanjing R] Crystalfly
  • X光机AI系统实现轮胎缺陷识别准确率超97%
  • Depth Anything with Any Prior解读
  • Vue2 学习记录--语法部分
  • bluetoothctl命令
  • 泰安做网站多少钱什么网站做ppt
  • 备案 网站负责人 法人今天重大新闻头条新闻军事
  • Android16 EDLA HDMI OUT投屏默认通过设置
  • flink1.20.2环境部署和实验-2
  • TCP滑动窗口:网络世界的“智能流量阀门”
  • TCP全连接队列与tcpdump抓包
  • 感知机:乳腺癌分类实现 K 均值聚类:从零实现
  • 【Linux】Linux 地址空间 + 页表映射的概念解析
  • 【Linux篇】System V IPC详解:共享内存、消息队列与信号量
  • GLM4.6多工具协同开发实践:AI构建智能任务管理系统的完整指南
  • LangChain v1.0 快速入门
  • 云南网站建设找天软东莞网站建设什么价格便宜
  • AI Agent设计模式 Day 4:ReWOO模式:推理而不观察的高效模式
  • 38.华为云存储类服务核心配置
  • 使用 SQLAlchemy 操作单表:以 SQLite 用户表为例的完整实战指南
  • 新余教育网站建设企业网站赏析
  • Flink CDC 从 Definition 到可落地 YAML
  • 深入理解C语言字符串复制:从基础实现到优雅设计
  • SQL注入之堆叠及waf绕过注入(安全狗)
  • 微信小程序开发案例 | 极简清单小程序(下)
  • 37.华为云网络类云服务
  • Java设计模式精讲---04原型模式
  • 有哪些网站是可以做免费推广的做视频网站要多大的服务器
  • 线代强化NO1|行列式及矩阵
  • Shelly智能模块:家居科技革新之选