【C++ 进阶】Cyber霓虹掩体下的代码拟态——【面向对象编程 之 多态】一文带你搞懂C++面向对象编程中的三要素之一————多态!

⚡ CYBER_PROFILE ⚡
/// SYSTEM READY ///


WARNING : DETECTING HIGH ENERGY

🌊 🌉 🌊 心手合一 · 水到渠成

|------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------|
| >>> ACCESS TERMINAL <<< ||
| 🦾 作者主页 | 🔥 C语言核心 |
| 💾 编程百度 | 📡 代码仓库 |


Running Process: 100% | Latency: 0ms


索引与导读

  • [1. 多态的基本概念](#1. 多态的基本概念)
  • [2. 多态的分类](#2. 多态的分类)
      • [A. 编译时多态(静态多态)](#A. 编译时多态(静态多态))
      • [B. 运行时多态(动态多态)](#B. 运行时多态(动态多态))
  • [3. 什么是虚函数?](#3. 什么是虚函数?)
  • [4. 多态的构成条件](#4. 多态的构成条件)
  • [5. 隐藏](#5. 隐藏)
    • 5.1)隐藏的两种基本情况
      • [A. 函数同名,但参数不同](#A. 函数同名,但参数不同)
      • [B. 函数同名,参数也相同,且无 `virtual`](#B. 函数同名,参数也相同,且无 virtual)
    • [5.2)隐藏 VS 重写](#5.2)隐藏 VS 重写)
    • 5.3)代码示例
  • [6. 多态的原理](#6. 多态的原理)
  • [💻结尾--- 核心连接协议](#💻结尾— 核心连接协议)

1. 多态的基本概念

从字面上理解,多态意为"多种状态",即同一个行为在不同的对象上具有不同的表现形式


想象一下"按回车键"这个动作:

  • 如果你正在写文档,按回车是"换行"。
  • 如果你正在浏览网页,按回车是"跳转/搜索"。
  • 如果你正在玩游戏,按回车可能是"打开聊天框"。

同样的指令(按回车),在不同的场景(对象)下产生了不同的结果。


2. 多态的分类

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

在编译阶段就确定了要调用哪个函数。主要通过 函数重载Overloading)实现。

  • 例子 :同一个函数名 add,你可以定义 add(int, int),也可以定义 add(double, double)。编译器会根据你传入的参数类型,自动选择对应的版本

B. 运行时多态(动态多态)

这是通常大家讨论"多态"时所指的核心概念。程序在运行期间才根据对象的实际类型来决定调用哪个函数。

  • 前提条件
    1. 必须有继承关系。
    2. 子类**重写(Override)**父类的虚函数。
    3. 父类指针或引用指向子类对象

3. 什么是虚函数?

🔗Lucy的空间骇客裂缝:虚函数讲解


4. 多态的构成条件

多态是一个继承关系下的类对象,去调用同一函数,产生了不同的行为。比如 Student 继承了 PersonPerson 对象买票全价,Student 对象优惠买票

  • 必须是基类的指针 或者引用调用虚函数
  • 被调用的函数必须是虚函数,并且完成了虚函数重写/覆盖

5. 隐藏

简单来说,隐藏是指:派生类(子类)中定义了与基类(父类)同名的函数或变量,从而导致基类的成员在子类作用域内被屏蔽


5.1)隐藏的两种基本情况

A. 函数同名,但参数不同

无论基类函数是否有 virtual 关键字,只要子类定义了同名但参数不同的函数,基类的同名函数就会被隐藏。

  • 现象:你无法通过子类对象直接调用父类的那个版本,编译器会报错,因为它只在子类里找到了匹配的函数名,但参数对不上。

B. 函数同名,参数也相同,且无 virtual

如果父类函数没有 virtual 关键字(即不是虚函数),而子类定义了一个完全一样的函数。

  • 现象:这不是重写(Override),而是隐藏。如果你用子类指针调用,执行子类版本;如果你用父类指针调用,执行的是父类版本

5.2)隐藏 VS 重写

特性 隐藏(Hiding) 重写(Override)
范围 派生类与基类之间 派生类与基类之间
函数名 必须相同 必须相同
参数列表 可以不同 必须完全相同
virtual 关键字 基类函数无要求 基类函数必须有 virtual
多态性 不支持多态,静态绑定 支持多态,动态绑定

5.3)代码示例

cpp 复制代码
#include <iostream>
using namespace std;

class Base {
public:
    void fun(int a) { 
        cout << "Base::fun(int)" << endl; 
    }
};

class Derived : public Base {
public:
    // 隐藏了 Base::fun(int)
    // 即使参数类型不同,只要名字一样就会触发隐藏
    void fun(string s) { 
        cout << "Derived::fun(string)" << endl; 
    }
};

int main() {
    Derived d;
    d.fun("hello"); // 正常:调用 Derived::fun
    
    // d.fun(10);   // 报错!Base::fun(int) 已被隐藏,编译器在 Derived 中找不到接收 int 的 fun
    
    d.Base::fun(10); // 正确:必须显式指定作用域才能访问被隐藏的成员
    return 0;
}

6. 多态的原理


6.1)虚函数表指针

下面代码的运行原理是什么?

cpp 复制代码
class Base
{
public:
    virtual void Func1()
    {
        cout << "Func1()" << endl;
    }
protected:
    int _b = 1;
    char _ch = 'x';
};

int main()
{
    Base b;
    cout << sizeof(b) << endl;

    return 0;
}

因为Base 中包含了一个虚函数 Func1() ,编译器会为这个类生成一张虚函数表 (vtable)

  • 为了支持多态,每一个 Base 类的对象头部都会多出一个隐藏的指针,叫做 虚函数表指针_vptr)。

  • 这个指针指向虚函数表的起始地址。

  • 大小 : 在 32 位环境下指针占 4 字节,在 64 位环境下占 8 字节


我们来看看类中的数据成员:

  1. int _b:占 4 字节

  2. char _ch:占 1 字节


计算过程(以 32 位环境为例):

  1. vptr4 字节(偏移量 0-3

  2. _b4 字节(偏移量 4-7

  3. _ch1 字节(偏移量 8

  4. 结构体整体对齐 :为了提高 CPU 读取内存的效率,类的大小必须是其最宽基本类型成员(这里是指针或 int,均为 4 字节)大小的整数倍。

    • 目前总大小是 (4 + 4 + 1 = 9) 字节。
    • 向上补齐到 4 的倍数,结果为 12 字节

计算过程(以 64 位环境为例):

  1. vptr8 字节(偏移量 0-7

  2. _b4 字节(偏移量 8-11

  3. _ch1 字节(偏移量 12

  4. 结构体整体对齐 :此时最宽基本类型是指针(8 字节)。

    • 目前总大小是 (8 + 4 + 1 = 13) 字节。
    • 向上补齐到 8 的倍数,结果为 16 字节

6.2)虚函数表 (vtable)

  • 编译器会为每个包含虚函数的类创建一个数组,这个数组里存放的是该类所有虚函数的地址

  • 如果子类重写了父类的虚函数,子类的虚表中对应的函数地址就会被替换成子类自己的函数地址;如果没有重写,则保留父类的地址

cpp 复制代码
class Animal {
public:
    virtual void Speak() { cout << "Animal Speak"; }
    virtual void Eat() { cout << "Animal Eat"; }
};

class Dog : public Animal {
public:
    void Speak() override { cout << "Dog Bark"; } // 重写了 Speak
    // Eat 没重写,直接继承
};

在内存中:

  1. Animal 的虚表:存放的是 &Animal::Speak&Animal::Eat

  2. Dog 的虚表:存放的是 &Dog::Speak(因为重写了)和 &Animal::Eat(因为没重写,直接用父类的)。

当程序运行到这一行时:

cpp 复制代码
Animal* a = new Dog();

对象 a 的内存头部有一个 vptr,它指向的是 Dog 的虚表


6.3)动态绑定与静态绑定

C++这种编译型语言中,绑定 指的是将函数调用与具体的函数代码块连接起来的过程

1. 静态绑定

静态绑定也叫"早绑定" (Early Binding)。

  • 发生时间 :在编译阶段 。编译器根据代码中变量的声明类型(静态类型)来决定调用哪个函数。

  • 适用场景

    • 普通函数调用。
    • 函数重载(Overloading)。
    • 运算符重载。
    • 模板(Templates)。
  • 特点

    • 效率高:编译时就确定了地址,运行时直接跳转,没有任何额外开销。
    • 缺乏灵活性:无法根据运行时的实际情况改变行为。

2. 动态绑定

动态绑定也叫"晚绑定" (Late Binding)。

  • 发生时间 :在程序运行阶段 。程序根据指针或引用所指向的实际对象类型来决定调用哪个函数。

  • 适用场景

    • 虚函数(Virtual Functions)。
    • 当通过父类指针或引用调用虚函数时。
  • 特点

    • 灵活性极高:它是实现多态的基础,允许程序在不修改代码的情况下扩展功能。
    • 轻微性能损失:因为需要查虚函数表(vtable),比直接跳转函数地址要慢一点点(在现代硬件上几乎可以忽略不计)。

核心区别:静态类型 vs 动态类型

cpp 复制代码
Animal* a = new Dog();

💻结尾--- 核心连接协议

警告: 🌠🌠正在接入底层技术矩阵。如果你已成功破解学习中的逻辑断层,请执行以下指令序列以同步数据:🌠🌠


【📡】 建立深度链接: 关注本终端。在赛博丛林中深耕底层架构,从原始代码到进阶协议,同步见证每一次系统升级。

【⚡】 能量过载分发: 执行点赞操作。通过高带宽分发,让优质模组在信息流中高亮显示,赋予知识跨维度的传播力。

【💾】 离线缓存核心: 将本页加入收藏。把这些高频实战逻辑存入你的离线存储器,在遭遇系统崩溃或需要离线检索时,实现瞬时读取。

【💬】 协议加密解密:评论区留下你的散列码。分享你曾遭遇的代码冲突或系统漏洞(那些年踩过的坑),通过交互式编译共同绕过技术陷阱。

【🛰️】 信号频率投票: 通过投票发射你的选择。你的每一次点击都在重新定义矩阵的进化方向,决定下一个被全量拆解的技术节点。



相关推荐
Cloud_Shy61814 小时前
解读《Effective Python 3rd Edition》:从练气到老魔
开发语言·python
雨辰AI14 小时前
MySQL 迁移至达梦 DM9 完整改造指南|99% SQL 零改动
java·开发语言·数据库·sql·mysql·政务
弹简特14 小时前
【Java项目-轻聊】05-AI赋能设计接口文档
java·开发语言
AI行业学习14 小时前
.NET Framework 3.5 SP1 完整离线包(2029.5.29)
开发语言·windows·.net
cany100014 小时前
C++ -- 队列std::queue
开发语言·c++
skywalk816314 小时前
根据言律的语法,能否用racket进行开发呢?主要探讨是否可行。 racket在这里:E:\Program Files\Racket\Racket.exe
开发语言·原型模式
OctShop大型商城源码14 小时前
OctShop对比JAVA商城源码_OctShop大型专业级多用户商城源码
java·开发语言·商城系统·小程序商城·octshop
l1t14 小时前
DeepSeek总结的使用实体-组件-系统和基于存在性处理进行Python编程15-17
开发语言·数据库·python
guslegend14 小时前
AGENT.md,Skill与工程规范
java·开发语言·数据库
jingling55515 小时前
Flutter | Dio网络请求实战
android·开发语言·前端·flutter