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. 编译生成的代码:对生成的普通代码执行常规编译。
编译期绑定
  • 同一模板函数,为不同模板参数实例化出的多个函数,拥有完全不同的函数签名和函数地址
  • 调用时,编译器通过重载决议模板参数匹配静态地选择调用哪个实例化版本。
  • 所有决策在编译期完成,无运行时开销
示例
template<typenameT> T max(T a, T b){return a > b ? a : b;}intmain(){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)}

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

intmax_int(int a,int b){return a > b ? a : b;}doublemax_double(double a,double b){return a > b ? a : b;}charmax_char(char a,char b){return a > b ? a : b;}

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

运行时多态

虚函数相关

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

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

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

virtualvoidbreathe()=0;

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

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

静态绑定与动态绑定

静态绑定(早绑定)
  • 编译阶段就确定了函数调用的目标地址。
  • 依据指针/引用的静态类型决定调用哪个函数。
  • 例(无虚函数)
classAnimal{public:voidbreathe(){ cout <<"animal breathe"<< endl;}};classFish:publicAnimal{public:voidbreathe(){ cout <<"fish bubble"<< endl;}}; Fish fh; Animal* p =&fh;// 静态类型为 Animal* p->breathe();// 输出 "animal breathe"

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

动态绑定(晚绑定)
  • 运行阶段根据对象的实际类型动态决定调用的函数。
  • 要求:通过基类指针或引用调用虚函数
  • 例(有虚函数)
classAnimal{public:virtualvoidbreathe(){ cout <<"animal breathe"<< endl;}};classFish:publicAnimal{public:voidbreathe()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.ZEEKLOG.net/pzhw520hchy/article/details/51850289?fromshare=blogdetail&sharetype=blogdetail&sharerId=51850289&sharerefer=PC&sharesource=qq_60536076&sharefrom=from_link

Read more

【Linux】进程概念(六):地址空间核心机制

【Linux】进程概念(六):地址空间核心机制

引言 在计算机科学的世界里,最精妙的魔法往往隐藏在最基础的机制之中。当我们编写一个简单的printf("Hello World")时,背后正上演着一场关于内存管理的交响乐。进程地址空间、页表、缺页中断——这些看似深奥的概念,实则是现代操作系统的智慧结晶,它们共同构筑了一个让每个进程都"自以为"独占整个计算机内存的完美幻境。理解这套机制,不仅是掌握操作系统原理的关键,更是窥见计算机系统设计美学的窗口。 目录 一、程序地址空间 1.1 核心概念 一个N位的系统,其指针地址的位宽即为N比特,理论可寻址空间为 ( 2^N ) 字节。在内存布局图中,地址通常用十六进制表示。每1个十六进制数对应4个二进制位(比特)。因为一个字节(8比特位)可以用2个十六进制位数完整表示 1. 32位环境 * 地址位宽为 32比特。 * 一个完整的地址需要用 ( 32 / 4 = 8

By Ne0inhk
Flutter 三方库 cli_config 鸿蒙开发者脚手架工具链编译级配置拦截适配剖析:无缝桥接强类型验证及命令行参数安全审计矩阵、极大限度赋能终端极客工程-适配鸿蒙 HarmonyOS ohos

Flutter 三方库 cli_config 鸿蒙开发者脚手架工具链编译级配置拦截适配剖析:无缝桥接强类型验证及命令行参数安全审计矩阵、极大限度赋能终端极客工程-适配鸿蒙 HarmonyOS ohos

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 cli_config 鸿蒙开发者脚手架工具链编译级配置拦截适配剖析:无缝桥接强类型验证及命令行参数安全审计矩阵、极大限度赋能终端极客工程环境管控力 在鸿蒙应用的自定义自动化构建脚本、鸿蒙 SDK 扩展插件或命令行离线工具的开发中,如何优雅、稳健地解析复杂的环境配置与用户参数?cli_config 库(由 Dart 官方团队维护)提供了一套基于级联优先级(Cascade Priority)的配置治理方案。本文将详解该库在 OpenHarmony 上的适配要点。 前言 什么是 cli_config?它不仅能解析传统的命令行位置参数,还能自动识别环境变量(Environment Variables)和特定的配置文件(如 YAML)。它最大的特色在于引入了“配置源优先级”系统。在鸿蒙操作系统强调的“极致工程纪律”和“CI/CD

By Ne0inhk
Flutter 三方库 common_locale_data 的鸿蒙化适配指南 - 实现具备全球化区域元数据与多语言辅助能力的底层数据池、支持端侧国际化业务的精细化治理实战

Flutter 三方库 common_locale_data 的鸿蒙化适配指南 - 实现具备全球化区域元数据与多语言辅助能力的底层数据池、支持端侧国际化业务的精细化治理实战

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.ZEEKLOG.net Flutter 三方库 common_locale_data 的鸿蒙化适配指南 - 实现具备全球化区域元数据与多语言辅助能力的底层数据池、支持端侧国际化业务的精细化治理实战 前言 在进行 Flutter for OpenHarmony 开发时,当我们的鸿蒙应用需要支持全球化(i18n)并涉及到复杂的地区逻辑(如:展示国家旗帜映射、获取特定地区的货币符号、或者根据 IP 解析所属大洲)时,散落在各处的硬编码数据会让维护成本剧增。common_locale_data 是一款专注于提供极致详尽、符合 ISO 标准的核心区域元数据仓库。本文将探讨如何在鸿蒙端构建稳健、专业的全球化数据底座。 一、原直观解析 / 概念介绍 1.1 基础原理 该库通过对 Unicode CLDR(Common

By Ne0inhk
【Linux】别再被命令行劝退!Linux 26个救命指令,从小白到运维高手

【Linux】别再被命令行劝退!Linux 26个救命指令,从小白到运维高手

🔥个人主页:爱和冰阔乐 📚专栏传送门:《数据结构与算法》 、C++ 、 🐶学习方向:C++方向学习爱好者 ⭐人生格言:得知坦然 ,失之淡然 🏠博主简介 文章目录 * 前言 * 一、Linux下的基本指令 * 1 .clear指令 * 2. ls / ll 指令 * 3. pwd 指令 * 4. cd 指令 * 5. touch 指令 * 6. mkdir 指令 * 7. rmdir 指令 && rm 指令 * 8. man 指令 * 9. cp 指令 * 10. echo 指令

By Ne0inhk