多态 Polymorphism

What

多态使得程序中的同一接口能够应对不同的实现。“一个接口,多种实现”.

Why

多态的主要目的是提高代码的灵活性和可扩展性,使得程序能够以统一的方式处理不同类型的对象,减少代码的重复性,增强代码的可复用性。

静态多态 Compile-time Polymorphism

静态多态发生在编译时期,通常通过函数重载和运算符重载实现。

静态多态在Cpp中包括函数重载 和 模板编程.

函数重载的底层原理

编译器如何选择合适的 同名 函数?这涉及 重载解析 Overload Resolution 和 符号名称修饰 Name Mangling.

#include <iostream>

void print(int x) {
    std::cout << "Integer: " << x << std::endl;
}

void print(double x) {
    std::cout << "Double: " << x << std::endl;
}

int main() {
    print(42);         // 调用 print(int)
    print(3.14);       // 调用 print(double)
    return 0;
}

函数重载解析:编译期根据参数类型选择合适的函数版本,例如
print(42), 参数类型为int,编译器选择调用void print(int).

符号名称修饰: 为区分不同的重载函数,C++编译期会对函数名进行修饰,即在底层中函数名是不一样的。例如编译期可能将print(int)修饰为_Z5printi, 将print(double)修饰为_Z5printd.
确保函数名不存在冲突.

符号名称修饰确保底层中没有同名函数;函数重载解析确保编译期调用适当的函数。

模板编程原理

模板编程的核心原理是泛型编程。主要依赖于模板实例化 -- 编译时的代码生成 和 类型推导。

编译器会根据具体的类型 实例化 不同对应类型的代码,也就是对不同类型生成不同的代码实现。

动态多态 Dynamic Polymorphism

动态多态指的是程序在允许时根据对象的实际类型决定调用哪个方法。
通过虚函数和继承实现。基于 虚函数表vtable 和 虚函数指针vptr实现。

每个包含虚函数的类都有一个表 -- 地址数组 -- 地址指向虚函数.

每个对象都一个虚函数指针, 指向虚函数表 -- 根据虚函数表调用对象对应类的虚函数。

class Base {
public:
    virtual void func() {
        std::cout << "Base func" << std::endl;
    }
};

class Derived : public Base {
public:
    void func() override {
        std::cout << "Derived func" << std::endl;
    }
};

int main() {
    Base* ptr = new Derived();
    ptr->func();  // 调用的是 Derived::func()
}

对象创建:

  • 执行new Derived()时,系统为Derived对象分配内存;
  • 编译器会为对象插入一个vptr(一般在对象内存空间的最前面)
    指向派生类Derived的虚函数表;
  • 这个vptr是编译期插入的,代码中不可见;
  • Derived的构造函数中,会将vptr赋值为Derivedvtable地址(vtable在何时构造?vtable在编译期构造,存储在数据段.rodata或静态存储区. 同一个类的所有对象共享。)

基类指针接收派生类对象

Base* ptr = new Derived();
  • ptr类型为Base,但实际指向Derived对象的内存地址.

调用ptr->func()

  • 编译期通过ptr指向对象内的vptr(此时是Derived对象)
    查找虚函数表;
  • Derived的虚函数表中查找func()地址,进行调用.

如果去掉virtual会怎么样?

class Base {
public:
    void func() { std::cout << "Base func" << std::endl; }
};
class Derived : public Base {
public:
    void func() { std::cout << "Derived func" << std::endl; }
};

没有虚函数,也就没有对应的虚函数表,虚函数指针,因此编译器无法支持运行时多态。此时编译期只根据指针的静态类型(Base *)决定调用哪个函数.

  • 编译阶段,编译期静态绑定Static Binding,决定ptr->func()调用的函数为Base::func()

  • 运行时,编译期已经将ptr->func()翻译为Base::func().

posted @ 2025-04-07 13:59  代码改变头发  阅读(10)  评论(0)    收藏  举报