解码多态、虚函数——动态行为扩展

news/2025/12/9 21:10:37/文章来源:https://www.cnblogs.com/YouEmbedded/p/19318845

函数绑定机制

函数绑定是将函数调用与具体实现建立关联的过程,分为静态绑定和动态绑定两种核心方式,是实现多态的基础。

静态绑定(早绑定)

  • 定义:程序编译阶段就完成函数地址的绑定,编译器根据调用时的参数类型、个数等直接确定要执行的函数。
  • 特点
    • 编译期确定调用目标,运行时无额外开销;
    • 核心实现方式为函数重载(普通函数、类成员函数、运算符重载);
    • 绑定结果固定,无法动态改变。
  • 示例(函数重载)
/*** 整型加法函数* @brief 实现两个整型数的加法运算* @param a 第一个整型加数,参与加法运算的整数* @param b 第二个整型加数,参与加法运算的整数* @return int 两个整数相加的结果*/
int add(int a, int b) { return a + b; }/*** 浮点型加法函数* @brief 实现两个双精度浮点型数的加法运算* @param a 第一个浮点型加数,参与加法运算的双精度浮点数* @param b 第二个浮点型加数,参与加法运算的双精度浮点数* @return double 两个双精度浮点数相加的结果* @note 与整型add函数构成重载,编译器根据实参类型选择调用版本,属于静态绑定*/
double add(double a, double b) { return a + b; }

动态绑定(晚绑定)

  • 定义:函数地址的绑定延迟到程序运行阶段,根据对象的实际类型确定要执行的函数。
  • 特点
    • 运行期确定调用目标,支持 “一个接口,多种实现”;
    • 核心实现方式为虚函数 + 类的继承体系;
    • 有少量运行时开销(需查找虚函数表),但能实现灵活的运行时多态。
  • 静态绑定与动态绑定对比
类型 绑定时间 实现方式 核心特点 运行开销
静态绑定 编译阶段 普通函数 / 非虚成员函数调用
函数重载
运算符重载
模板实例化
编译期根据变量 / 指针的声明类型确定调用目标,结果不可动态改变,属于 “早绑定” 无额外开销(直接确定调用地址)
动态绑定 运行阶段 虚函数(virtual
类的继承
基类指针 / 引用指向派生类对象
运行期根据指针 / 引用指向的实际对象类型确定调用目标,属于 “晚绑定” 微小开销(查虚函数表 vtable)

多态机制

多态是 C++ 面向对象核心特性,核心思想是 “同一操作作用于不同对象时,产生不同的行为”,分为编译期多态(静态多态,如函数重载)和运行期多态(动态多态,如虚函数)。

核心概念

  • 定义:同一函数调用语句,根据操作对象的实际类型不同,执行不同的函数实现。
  • 运行期多态实现三要素(缺一不可)
    • 继承关系:存在基类和派生类,且为 public 继承(保证基类接口可被访问);
    • 虚函数重写:基类声明虚函数,派生类重写该函数(函数签名需完全一致);
    • 基类指针 / 引用:通过基类指针或引用调用虚函数,触发动态绑定。

虚函数机制

虚函数通过virtual关键字声明,是实现运行期多态的核心,派生类可重写基类虚函数。

核心代码示例

#include <iostream>
using namespace std;/*** 基类:Person(人类)* @brief 定义人类购票接口,声明虚函数Buy_ticket* @note 虚函数为派生类重写提供基础,是动态绑定的核心*/
class Person {
public:/*** 购票函数(虚函数)* @brief 基类默认实现:成人购买全价票* @return void 无返回值* @note virtual关键字标记后,函数参与虚函数表绑定*/virtual void Buy_ticket() {cout << "成人全价票" << endl;}
};/*** 派生类:Student(学生类),公有继承Person* @brief 重写基类虚函数,实现学生购票逻辑*/
class Student : public Person {
public:/*** 购票函数(重写基类虚函数)* @brief 派生类实现:学生购买半价票* @return void 无返回值* @note override关键字(C++11)显式声明重写,编译器检查重写正确性,避免函数签名错误*/void Buy_ticket() override {cout << "学生半价票" << endl;}
};/*** 派生类:Baby(婴儿类),公有继承Person* @brief 重写基类虚函数,实现婴儿购票逻辑*/
class Baby : public Person {
public:/*** 购票函数(重写基类虚函数)* @brief 派生类实现:婴儿免票* @return void 无返回值* @note 函数签名(函数名、参数、const属性)必须与基类完全一致,否则不构成重写*/void Buy_ticket() override {cout << "婴儿免票" << endl; // 修正原语法错误:移除多余左括号}
};/*** 购票处理函数* @brief 接收Person类引用,调用购票接口,体现多态特性* @param a Person类的引用,可接收Person/Student/Baby对象(基类引用指向派生类对象)* @return void 无返回值* @note 核心:基类引用接收派生类对象,调用虚函数时触发动态绑定*/
void take(Person& a) {a.Buy_ticket(); // 运行时根据a绑定的实际对象类型,调用对应Buy_ticket函数
}

内存机制

编译器为每个含虚函数的类生成虚函数表(vtable),表中存储该类所有虚函数的地址;每个该类的对象内存开头会有虚指针(vfptr),指向所属类的虚函数表。

Person对象         Student对象          Baby对象
+---------------+  +---------------+  +---------------+
|   vfptr       |  |   vfptr       |  |   vfptr       |  // 虚指针,指向各自虚函数表
+---------------+  +---------------+  +---------------+
| 成员变量      |  | 成员变量       |  | 成员变量      |  // 类的普通成员变量
| ...           |  | (扩展部分)     |  | ...          |
+---------------+  +---------------+  +---------------+↓                   ↓                   ↓
Person虚函数表       Student虚函数表     Baby虚函数表
[&Person::Buy_ticket] [&Student::Buy_ticket] [&Baby::Buy_ticket]

多态实现三要素(详细拆解)

  • 正确继承关系:必须用 public 继承,private/protected 继承会导致基类 public 接口不可访问,无法通过基类指针 / 引用调用派生类重写函数;
  • 严格虚函数重写
    • 函数签名完全一致:函数名、参数类型 / 个数 / 顺序、const/volatile 属性必须相同;
    • 协变返回值例外:基类虚函数返回基类指针 / 引用,派生类可返回派生类指针 / 引用(如基类返回Person*,派生类返回Student*);
    • 推荐用override:显式声明重写,避免手误导致函数签名不一致;
  • 调用关键方式:必须通过基类指针 / 引用调用虚函数,直接用派生类对象调用会触发静态绑定,无法体现多态。

动态绑定过程

/*** 主函数:演示多态的动态绑定过程* @brief 创建不同类型对象,通过take函数调用购票接口,观察多态行为* @return int 程序运行状态码,0表示正常结束*/
int main() {Person p;    // 基类对象Student s;   // 派生类Student对象Baby b;      // 派生类Baby对象take(p);     // 基类引用绑定基类对象,调用Person::Buy_ticket,输出"成人全价票"take(s);     // 基类引用绑定Student对象,调用Student::Buy_ticket,输出"学生半价票"take(b);     // 基类引用绑定Baby对象,调用Baby::Buy_ticket,输出"婴儿免票"return 0;
}

关键实现原理

  • 虚函数表(vtable):编译器为每个含虚函数的类生成的 “函数地址表”,派生类重写基类虚函数时,会替换表中对应位置的函数地址;
  • 虚指针(vfptr):对象内存开头的指针,对象创建时初始化,指向所属类的虚函数表;
  • 动态绑定本质:运行时通过对象的虚指针找到虚函数表,根据函数偏移量找到实际函数地址并执行。

典型内存访问流程

执行a.Buy_ticket()(a 为 Person&,绑定派生类对象)时:

graph LR A[函数调用a.Buy_ticket] --> B[查找对象vfptr] B --> C[访问虚函数表] C --> D[定位函数偏移量] D --> E[调用实际函数]

final 关键字(禁止继承 / 重写)

final关键字用于限制类的继承和虚函数的重写,核心作用是 “锁定” 类或函数的行为,避免不必要的派生 / 重写破坏原有逻辑,提升代码安全性。

final 修饰类:禁止继承

final修饰的类无法作为基类,任何尝试继承该类的代码都会编译报错,适用于逻辑已完善、无需扩展的类(如工具类、核心业务类)。

/*** final修饰类:FinalPerson(最终人类)* @brief 被final修饰,禁止任何类继承该类* @note final关键字写在类名后,格式:class 类名 final { ... };*/
class FinalPerson final {
public:virtual void Buy_ticket() {cout << "成人全价票(该类禁止被继承)" << endl;}
};// 错误:无法继承被final修饰的类,编译报错
// class FinalStudent : public FinalPerson {
// public:
//     void Buy_ticket() override {}
// };

final 修饰虚函数:禁止重写

final修饰的虚函数,派生类无法重写该函数,锁定函数的实现逻辑,适用于虚函数实现为最终版本、不允许修改的场景。

/*** 基类:LimitPerson(受限人类)* @brief 虚函数Buy_ticket被final修饰,禁止派生类重写*/
class LimitPerson {
public:/*** 购票函数(final修饰虚函数)* @brief 禁止派生类重写该函数,锁定实现逻辑* @return void 无返回值* @note final关键字写在函数参数列表后,格式:virtual 返回值 函数名(参数) final;*/virtual void Buy_ticket() final {cout << "成人全价票(此函数禁止重写)" << endl;}
};/*** 派生类:LimitStudent,公有继承LimitPerson* @brief 尝试重写final修饰的虚函数,编译报错*/
class LimitStudent : public LimitPerson {
public:// 错误:无法重写被final修饰的虚函数,编译报错// void Buy_ticket() override {//     cout << "学生半价票" << endl;// }
};

final 使用场景

  • 修饰类:类的逻辑已固化,无需派生扩展(如基础工具类、核心算法类),避免派生类随意修改核心逻辑;
  • 修饰函数:虚函数的实现是最优 / 唯一版本,不允许派生类篡改(如核心业务规则函数),锁定函数行为。

纯虚函数与抽象类

纯虚函数

纯虚函数是特殊的虚函数,声明时加= 0,只有声明无默认实现,强制派生类重写。

/*** 基类:Shape(形状类)* @brief 定义形状面积计算接口,声明纯虚函数area* @note 纯虚函数强制派生类实现具体的面积计算逻辑*/
class Shape {
public:/*** 面积计算函数(纯虚函数)* @brief 声明形状面积计算接口,无默认实现* @return double 形状的面积值* @note 纯虚函数格式:virtual 返回值 函数名(参数) = 0;*/virtual double area() = 0;
};

抽象类特性

含纯虚函数的类称为抽象类,核心特性:

  • 不能实例化对象:抽象类只有接口无完整实现,如Shape s;会编译报错;
  • 可定义指针 / 引用:抽象类指针 / 引用可指向派生类对象,实现多态;
  • 派生类必须实现所有纯虚函数:未实现则派生类也为抽象类,无法实例化;
  • 作用:定义统一接口规范,强制派生类遵循,实现接口隔离。

示例(派生类实现纯虚函数)

/*** 派生类:Circle(圆形类),公有继承Shape* @brief 实现Shape的纯虚函数,计算圆形面积*/
class Circle : public Shape {double radius; // 圆形半径,私有成员变量
public:/*** 构造函数:初始化圆形半径* @brief 创建Circle对象时设置半径* @param r 圆形半径,非负双精度浮点数*/Circle(double r) : radius(r) {}/*** 面积计算函数(重写纯虚函数)* @brief 实现圆形面积计算:π * 半径²* @return double 圆形面积值* @note 必须实现该函数,否则Circle仍为抽象类,无法实例化*/double area() override {return 3.14 * radius * radius;}
};/*** 测试抽象类与纯虚函数* @brief 创建Circle对象,通过Shape指针调用area函数,体现多态* @return int 程序运行状态码,0表示正常结束*/
int main() {// Shape s; // 错误:抽象类不能实例化对象Shape* shape_ptr = new Circle(2.0); // 抽象类指针指向派生类对象cout << "圆形面积:" << shape_ptr->area() << endl; // 输出:12.56delete shape_ptr; // 释放内存return 0;
}

虚析构函数

虚析构函数用于解决 “通过基类指针删除派生类对象时,派生类析构函数未调用” 的问题,避免资源泄漏。

问题背景(未用虚析构的弊端)

派生类含动态资源(如 new 的数组)时,若基类析构非虚,通过基类指针删除派生类对象只会调用基类析构,派生类资源无法释放,引发内存泄漏。

/*** 基类:Base* @brief 普通析构函数(非虚),演示未用虚析构的问题*/
class Base {
public:/*** 析构函数(非虚)* @brief 基类析构,输出提示信息* @return void 无返回值*/~Base() { cout << "Base析构" << endl; }
};/*** 派生类:Derived,公有继承Base* @brief 含动态分配资源,演示资源泄漏问题*/
class Derived : public Base {int* arr; // 动态分配的整型数组,需手动释放
public:/*** 构造函数:初始化动态数组* @brief 创建Derived对象时分配100个int的内存*/Derived() { arr = new int[100]; cout << "Derived:动态数组已分配" << endl;}/*** 析构函数:释放动态数组* @brief 释放arr指向的内存,输出提示信息* @return void 无返回值* @note 基类析构非虚时,此函数不会被调用,导致内存泄漏*/~Derived() {delete[] arr; // 释放动态数组cout << "Derived析构:动态数组已释放" << endl;}
};/*** 主函数:演示未用虚析构的内存泄漏* @brief 通过Base指针创建/删除Derived对象,观察析构调用情况* @return int 程序运行状态码,0表示正常结束*/
int main() {Base* obj = new Derived(); // 基类指针指向派生类对象delete obj; // 仅调用Base析构,Derived析构未执行,内存泄漏return 0;
}

运行结果

Derived:动态数组已分配
Base析构

虚析构函数

将基类析构声明为virtual,析构函数参与动态绑定,删除派生类对象时先调用派生类析构,再调用基类析构。

/*** 基类:Base* @brief 虚析构函数,解决派生类资源泄漏问题*/
class Base {
public:/*** 析构函数(虚析构)* @brief 基类虚析构,输出提示信息* @return void 无返回值* @note 派生类析构自动继承虚属性,无需显式加virtual*/virtual ~Base() { cout << "Base析构" << endl; }
};/*** 派生类:Derived,公有继承Base* @brief 含动态资源,演示虚析构的作用*/
class Derived : public Base {int* arr; // 动态分配的整型数组
public:Derived() { arr = new int[100]; cout << "Derived:动态数组已分配" << endl;}/*** 析构函数:释放动态数组* @brief 释放arr指向的内存,输出提示信息* @return void 无返回值* @note override可选,派生类析构自动为虚函数*/~Derived() override {delete[] arr; // 释放动态数组cout << "Derived析构:动态数组已释放" << endl;}
};/*** 主函数:演示虚析构的作用* @brief 通过Base指针删除Derived对象,观察析构调用顺序* @return int 程序运行状态码,0表示正常结束*/
int main() {Base* obj = new Derived(); delete obj; // 先调用Derived析构,再调用Base析构return 0;
}

运行结果

Derived:动态数组已分配
Derived析构:动态数组已释放
Base析构

纯虚析构

纯虚析构是将析构函数声明为纯虚函数,需提供实现(纯虚函数的特例)。

/*** 抽象基类:AbstractBase* @brief 含纯虚析构,演示纯虚析构的使用* @note 纯虚析构的类是抽象类,无法实例化*/
class AbstractBase {
public:/*** 析构函数(纯虚析构)* @brief 声明为纯虚析构,必须提供实现* @return void 无返回值* @note 格式:virtual ~类名() = 0; 实现需写在类外*/virtual ~AbstractBase() = 0;
};/*** 纯虚析构的实现* @brief 纯虚析构必须提供实现,否则链接报错* @note 派生类析构会自动调用基类析构,无实现会导致链接错误*/
AbstractBase::~AbstractBase() {cout << "AbstractBase纯虚析构实现" << endl;
}/*** 派生类:ConcreteDerived,公有继承AbstractBase* @brief 实现抽象基类接口,演示纯虚析构的使用*/
class ConcreteDerived : public AbstractBase {int* data; // 动态分配的资源
public:ConcreteDerived() { data = new int[50]; }~ConcreteDerived() override {delete[] data;cout << "ConcreteDerived析构:data数组已释放" << endl;}
};/*** 主函数:演示纯虚析构的使用* @brief 创建ConcreteDerived对象,通过AbstractBase指针删除* @return int 程序运行状态码,0表示正常结束*/
int main() {// AbstractBase ab; // 错误:抽象类不能实例化AbstractBase* ptr = new ConcreteDerived();delete ptr; // 先调用ConcreteDerived析构,再调用AbstractBase纯虚析构return 0;
}

运行结果

ConcreteDerived析构:data数组已释放
AbstractBase纯虚析构实现

关键总结表

概念 核心要点
虚函数 virtual 声明,支持动态绑定;
派生类重写需函数签名一致,推荐 override;
存储在虚函数表,通过虚指针调用
final 关键字 修饰类:禁止该类被继承;
修饰虚函数:禁止派生类重写该函数;
C++11 新增,提升代码安全性
抽象类 含纯虚函数,无法实例化;
可定义指针 / 引用实现多态;
派生类必须实现所有纯虚函数,否则仍为抽象类
虚析构函数 解决基类指针删除派生类对象的资源泄漏;
基类析构声明 virtual,派生类析构自动为虚;
调用顺序:先派生类后基类
纯虚析构 格式:virtual ~ 类名 () = 0;
必须提供实现;
使类成为抽象类,保证析构正常
动态多态 三要素:public 继承、虚函数重写、基类指针 / 引用指向派生类对象;
运行时通过虚函数表确定调用函数
静态绑定 编译期确定调用,如函数重载;
无运行时开销,绑定结果固定

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

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

相关文章

超全树链剖分模板

#include<bits/stdc++.h> #define ls (rt << 1) // 左儿子 #define rs (rt << 1 | 1) // 右儿子 using namespace std;const int N = 1e5 + 5; // 最大节点数 const int M = 2e5 + 5; …

2025托福培训机构选择指南:精准匹配你的提分需求

2025托福培训机构选择指南:精准匹配你的提分需求面对众多的托福培训机构,很多考生都在思考同一个问题:如何找到真正适合自己的备考伙伴?一个明智的选择,并非盲目追随广告或模糊的排名,而是始于对自身“目标分数、…

python 类的repr函数

class Human:def __init__(self,name,age):self.name,self.age=name,agedef __str__(self):return f{self.age}岁的{self.name}def __repr__(self):return Human(+repr(self.name)+,+repr(self.age)+)h1=Human("张…

2025托福辅导机构优选指南:从口碑到提分的全方位攻略

2025托福辅导机构优选指南:从口碑到提分的全方位攻略(一)无老师托福:高端封闭班代表性品牌 推荐指数:★★★★★ 作为上海托福培训高端封闭班领域的标杆品牌之一,无老师托福拥有14年留学教育积淀,专注出国考培与…

51单片机:数码管

数码管简介数码管每段其本质就是个LED灯,只需要控制特定的LED灯亮就能显示数据。普中开发版所使用的是两个并在一起共阴极连接的“4位数码管”,可以同时显示8个数字。数码管的显示可以分成静态显示和动态显示,这里先…

江西过碳酸钠生产厂、浙江过碳酸钠生产厂名单精选

2025年全球过碳酸钠产能预计突破120万吨,这种被称为“固体双氧水”的高效氧化剂,因环保特性在多领域需求激增。从日化洗涤的去污漂白,到纺织印染的固色处理,再到污水处理中的污染物降解,其应用场景持续拓宽。江西…

江西成膜助剂生产厂、浙江成膜助剂生产厂家精选名单

在环保政策持续收紧与水性涂料市场蓬勃发展的双重驱动下,成膜助剂作为涂料工业的“关键助剂”,其市场需求在2025年迎来新的增长拐点。成膜助剂通过降低乳液最低成膜温度,改善漆膜耐候性与可擦洗性,成为水性涂料生产…

使用VSCode开发ESP32单片机基于MicroPython-12.8

Vscode搭建环境,最好是分隔开,增加一个新的ESP32的配置文件。安装插件,Python和RT-Thread MicroPython。 安装RT-Thread MicroPython后,记得将命令行默认打开更改为powershell新建工程创建新的MicroPython工程创建…

DBLens 连接数怎么限制?免费 3 个,订阅随便加

DBLens 连接数怎么限制?免费 3 个,订阅随便加 如果你平时在 Linux / macOS / Windows 上做 MySQL(或 MariaDB)开发、运维、测试,DBLens 可能已经进入你的候选清单:原生体验顺滑、功能聚焦在高频开发场景,并把 A…

过碳酸钠选购指南:优质厂家推荐及欧盟标准供应商盘点

过碳酸钠作为高效环保的氧系漂白剂,广泛应用于洗涤、纺织、水处理等多个领域。2025年,随着市场需求的持续攀升,消费者和企业采购者对过碳酸钠的质量要求愈发严格,本文将聚焦这些关键需求,为大家详细介绍过碳酸钠及…

轮询相关算法

普通轮询 n:请求的编号 x:服务器数量 i:请求的服务器编号 i = n % x 加权轮询 最大公约数算法(Weighted Round-Robin, WRR) 随着每一轮遍历,降低“门槛”(Current Weight),只有权重大于等于当前门槛的服务器才能…

数据仓库和数据集市之ODS、CDM、ADS、DWD、DWS - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

成膜助剂代理商有哪些?成膜助剂全攻略:成膜助剂进口CIF价格供应商

在涂料、胶粘剂等行业绿色转型的浪潮中,成膜助剂作为保障涂膜连续性与稳定性的核心原料,其供应品质、渠道稳定性及价格透明度直接影响下游产业发展。2025年,随着新国标对VOCs排放、沸点等指标的升级要求,市场对优质…

过碳酸钠供应商大全:实力厂家、制造商及优质批发商推荐指南

过碳酸钠作为兼具漂白、杀菌功能的环保型化工原料,在洗涤、纺织、水处理等领域应用愈发广泛。2025年,随着环保政策收紧与下游需求升级,优质过碳酸钠供应商、制造商及批发商的选择成为采购核心命题。本文将聚焦行业标…

完整教程:读后感:《解析极限编程:拥抱变化》

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

托福备考黄金期,如何精准锁定高性价比机构?

托福备考黄金期,如何精准锁定高性价比机构?一、托福备考黄金期,如何精准锁定高性价比机构? 在留学竞争白热化的 2025 年,托福成绩已成为海外名校的核心筛选指标。数据显示,国内托福考生平均备考周期缩短至 3-6 个…

华为fusion-compute-8.x安装

华为FusionCompute 8.x部署文档存储工程师编辑于 2023年04月06日 05:07 收录于文集虚拟化 13篇 一、IP地址规划 二、安装CNA 1.启动安装镜像 2.进入配置界面 3.编辑安装盘设置,选择安装磁盘,设置swap分区大小。 …

2025年12月广州番禺佛山网站建设,营销网站建设,网站建设推广公司品牌推荐,定制能力与交付效率三维测评

引言在 2025 年 12 月的广州番禺及佛山地区,网站建设、营销网站建设以及网站建设推广行业竞争激烈。为了帮助企业客户在众多公司中挑选出最适合的合作伙伴,我们依据国内相关行业协会测评权威数据和白皮书内容,从定制…

2025托福培训机构怎么选?6大高性价比机构测评+避坑指南

2025托福培训机构怎么选?6大高性价比机构测评+避坑指南一、2025 托福备考新趋势:选对机构是提分关键 在如今这个全球化进程不断加速的时代,托福成绩的重要性愈发凸显,它不仅是留学申请时不可或缺的敲门砖,更是职场…

2025雅思一对一机构推荐排行榜:精准提分攻略,考研必看!

2025雅思一对一机构推荐排行榜:精准提分攻略,考研必看!一、雅思备考痛点解析:为什么选择一对一机构? 在雅思备考的漫漫征途上,许多同学都历经坎坷。有的同学选择自学,在浩如烟海的学习资料中艰难摸索,没有清晰…