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

相关推荐
龙山云仓1 小时前
No152:AI中国故事-对话祖冲之——圆周率与AI精度:数学直觉与极限探索
大数据·开发语言·人工智能·python·机器学习
琅琊榜首20201 小时前
AI+Python实操指南:用编程赋能高质量网络小说创作
开发语言·人工智能·python
tbRNA1 小时前
C++ string类
开发语言·c++
ccLianLian1 小时前
算法基础·C++常用操作
开发语言·数据结构·c++
柒儿吖1 小时前
基于 lycium 在 OpenHarmony 上交叉编译 komrad36-CRC 完整实践
c++·c#·harmonyos
草莓熊Lotso1 小时前
Linux 程序地址空间深度解析:虚拟地址背后的真相
java·linux·运维·服务器·开发语言·c++·人工智能
郝学胜-神的一滴2 小时前
使用Linux命名管道(FIFO)实现无血缘关系进程间通信
linux·服务器·开发语言·c++·程序人生
Baihai_IDP2 小时前
Prompt caching 技术是如何实现 1 折的推理成本优化的?
人工智能·面试·llm
HAPPY酷2 小时前
std::pair` 与 `std::map` 基础
开发语言·c++·算法