C++ 多态原理深入理解

多态

引言

多态(Polymorphism)是面向对象程序设计的核心特性,指同一接口,多种实现。C++中的多态主要分为两类:

  1. 编译时多态(静态多态)

    • 通过函数重载 和模板实现
    • 函数调用在编译期确定(早期绑定/静态联编)
    • 基于参数类型或个数区分同名函数
  2. 运行时多态(动态多态)

    • 通过继承 和虚函数实现
    • 函数调用在运行期确定(晚绑定/动态联编)
    • 根据对象的实际类型调用对应函数

静态多态

函数重载

函数重载允许在同一作用域 中声明多个同名函数 ,但这些函数的参数列表必须不同。

参数列表不同的具体体现:

  • 参数个数不同
  • 参数类型不同
  • 参数顺序不同

注意: 不能仅通过返回值类型来区分重载函数。

实现原理

C++通过函数名修饰机制支持函数重载,而C语言不支持此特性。

编译过程概述:

  • 预编译:将头文件中的函数声明拷贝到源文件中,避免编译时找不到函数定义。
  • 编译 :进行语法分析,并符号汇总,生成符号表。
  • 汇编 :生成函数名到函数地址的映射,便于后续调用时定位函数定义位置。
  • 链接:将多个目标文件的符号表汇总合并。

函数名修饰的核心流程:

  • 在编译和汇编阶段 ,编译器根据函数名、参数类型等信息对函数名进行修饰,生成唯一的符号名。
  • 不同参数列表的同名函数会被修饰成不同的符号,从而在符号表中区分。
  • 示例(GCC编译器):
    • int sum(int a, int b) → _Z3sumii
    • double sum(double a, double b) → _Z3sumdd
  • 修饰规则通常包含:
    • 前缀(如_Z)
    • 函数名长度 + 函数名
    • 参数类型首字母(int→i, double→d, 类类型→类名等)

模板

模板(template)是 C++ 实现静态多态(编译时多态)的核心机制之一。

其本质是编译器根据模板参数在编译期生成具体代码,从而实现"同一套代码逻辑,适配多种不同类型"的多态行为。

核心原理

模板不是代码,而是代码生成器

  • 函数模板 或 类模板 本身不会被编译成任何可直接执行的代码。
  • 它只是一个蓝图 ,告诉编译器:当遇到对特定模板参数的使用时,请按此蓝图生成一份具体的代码。

模板实例化

  • 当程序使用模板并提供具体类型参数 时,编译器会执行模板实例化。
  • 实例化过程:
    1. 模板参数推导 :根据函数实参或显式指定的类型,确定模板参数 T 的具体类型。
    2. 模板代码替换 :将模板定义中所有 T 替换为实际类型,生成一份完整的普通函数或普通类。
    3. 编译生成的代码:对生成的普通代码执行常规编译。
编译期绑定
  • 对同一模板函数 ,为不同模板参数实例化出的多个函数,拥有完全不同的函数签名和函数地址。
  • 调用时,编译器通过重载决议 或模板参数匹配 ,静态地选择调用哪个实例化版本。
  • 所有决策在编译期完成,无运行时开销。
示例
cpp 复制代码
template<typename T>
T max(T a, T b) {
    return a > b ? a : b;
}

int main() {
    int    i = max(3, 5);        // (1) 实例化为 int max(int, int)
    double d = max(3.14, 2.71);  // (2) 实例化为 double max(double, double)
    char   c = max('a', 'z');    // (3) 实例化为 char max(char, char)
}

编译器实际生成的代码(概念性):

cpp 复制代码
int max_int(int a, int b) { return a > b ? a : b; }
double max_double(double a, double b) { return a > b ? a : b; }
char max_char(char a, char b) { return a > b ? a : b; }

多态体现 :同一 max 名称,对三种不同类型生成三个不同函数,调用时根据参数类型静态绑定到对应版本。

运行时多态

虚函数相关

用关键字 virtual 修饰的非静态成员函数 称为虚函数。一旦基类将某个函数声明为 virtual,所有派生类中与该函数同名、同参数列表、同返回值 的函数自动成为虚函数。

派生类重新定义基类的虚函数称为重写(override) 。
作用 :通过基类指针或引用调用该函数时,实际执行的是对象所属派生类的版本。

纯虚函数 :在虚函数声明末尾添加 = 0,如:

cpp 复制代码
virtual void breathe() = 0;

纯虚函数表示该函数在当前类中没有实现,要求派生类必须提供实现。

注:纯虚函数也可以提供函数体,但需要定义在类外;通常仅用于特殊场景(如析构函数)。

静态绑定与动态绑定

静态绑定(早绑定)
  • 编译阶段就确定了函数调用的目标地址。
  • 依据指针/引用的静态类型决定调用哪个函数。
  • 例(无虚函数):
cpp 复制代码
class Animal {
public:
    void breathe() { cout << "animal breathe" << endl; }
};
class Fish : public Animal {
public:
    void breathe() { cout << "fish bubble" << endl; }
};

Fish fh;
Animal* p = &fh;   // 静态类型为 Animal*
p->breathe();      // 输出 "animal breathe"

原因 :p 被声明为 Animal*,编译器按此类型直接绑定 Animal::breathe()。

动态绑定(晚绑定)
  • 运行阶段根据对象的实际类型动态决定调用的函数。
  • 要求:通过基类指针或引用调用虚函数。
  • 例(有虚函数):
cpp 复制代码
class Animal {
public:
    virtual void breathe() { cout << "animal breathe" << endl; }
};
class Fish : public Animal {
public:
    void breathe() override { cout << "fish bubble" << endl; }
};

Fish fh;
Animal* p = &fh;   // 静态类型 Animal*,实际指向 Fish 对象
p->breathe();      // 输出 "fish bubble"
动态绑定原理

虚函数表(vtable)与虚指针(vptr)

动态绑定的底层依赖虚函数表(Virtual Table) 和虚指针(Virtual Pointer)。

虚函数表(vtable)
  • 每个包含虚函数的类(或从包含虚函数的类派生的类)都有一个虚函数表。
  • 本质 :一个一维数组 ,按声明顺序存放该类所有虚函数的地址(包括继承自基类的虚函数和自身新增的虚函数)。
  • 继承时的虚表内容 :
    • 若派生类重写 了基类的虚函数,则虚表中对应位置的地址替换为派生类函数地址。
    • 若派生类新增虚函数,则将其地址添加在虚表末尾。
    • 若派生类未重写 基类虚函数,则虚表中保存基类该函数的地址。
虚指针(vptr)
  • 每个含有虚函数的类的对象 中,编译器会隐式插入一个指针 ,称为 vptr。
  • vptr 指向该对象所属类的虚函数表。
  • 同一类的所有对象共享同一个虚表,但每个对象拥有自己独立的 vptr(指向同一虚表)。

!NOTE\] \[\[为什么每个对象拥有自己独立的 vptr ?\]

参考文章:

https://blog.csdn.net/pzhw520hchy/article/details/51850289?fromshare=blogdetail\&sharetype=blogdetail\&sharerId=51850289\&sharerefer=PC\&sharesource=qq_60536076\&sharefrom=from_link

相关推荐
寻林写彡3 分钟前
从技术报告到面试入口:安全岗位简历如何用问题、关键技术与量化结果重写项目经历
面试·开源
源图客9 分钟前
Python环境安装与PyCharm的第一个项目
开发语言·python·pycharm
longlongzihan10 分钟前
从LeetCode 19. 删除链表的倒数第 N 个结点深入理解“哨兵节点”
c++·算法·leetcode·链表
zx_7414848118 分钟前
PyQt5 环境搭建保姆级教程:PyCharm + QtDesigner + PyUIC 完整配置
开发语言·qt·pycharm·pyqt
爱吃提升36 分钟前
Dev-C++ 5.11 完整版快捷键大全(信奥/初学适用)
c语言·c++
Soari39 分钟前
交换机转发模式
开发语言·网络·php
李剑一1 小时前
AI翻译都这么强了,为什么程序员还是躲不开英语?假如你想用上GPT6,我劝你还是早点背单词吧!
面试·产品·创业
deviant-ART1 小时前
Java 并发编程:深入理解 synchronized 的使用与原理
java·开发语言·后端
kimnoic1 小时前
Python中使用封装类还是函数以及它们的区别
开发语言·python
代码什么用1 小时前
Java Web 21-22
java·开发语言·web