C++多态

一、多态的核心概念

多态是 C++ 面向对象的三大特性(封装、继承、多态)之一,核心含义是:同一个接口,不同的实现 。简单说就是:调用同一个函数,根据调用的对象不同,执行的函数逻辑也不同。比如:定义一个动物基类,有叫()函数,继承自动物并各自重写叫();当用动物类型的指针 / 引用指向对象,调用叫()时,会执行对应子类的叫(),而不是基类的 ------ 这就是多态的直观体现。

二、多态的实现前提(3 个)

C++ 中运行时多态(核心) 必须满足以下 3 个条件,缺一不可:

  1. 存在继承关系(子类继承基类);
  2. 子类重写 (override)基类的虚函数 (基类函数加virtual关键字);
  3. 通过基类的指针基类的引用调用重写的虚函数(直接用对象调用不会触发多态)。

三、多态的分类

C++ 中的多态分两类,运行时多态(动态多态) 是工作中最常用的核心,编译时多态(静态多态) 是基础特性,先明确区别:

类型 实现方式 确定时机 核心特点
编译时多态 函数重载、运算符重载 程序编译阶段 编译器根据参数匹配确定执行哪个函数
运行时多态 虚函数 + 继承 + 重写 + 指针 / 引用 程序运行阶段 根据实际指向的对象确定执行哪个函数
补充:易混概念区分
  1. 函数重写(override) :子类重写基类的虚函数 ,要求函数名、参数列表、返回值完全一致(协变除外,新手暂不考虑),是运行时多态的核心;
  2. 函数重载(overload):同一作用域下,函数名相同、参数列表(个数 / 类型 / 顺序)不同,与返回值无关,是编译时多态的核心。

四、核心知识点:虚函数

虚函数是实现运行时多态的关键 ,基类中用virtual关键字修饰的函数就是虚函数,规则如下:

  1. 基类声明虚函数后,子类重写该函数时,可省略virtual(编译器会自动识别为虚函数),但建议显式添加,增强代码可读性;
  2. 虚函数可以有函数体(基类可提供默认实现);
  3. 析构函数建议声明为虚函数(避免子类对象析构不彻底,下文示例会讲);
  4. 静态成员函数、内联函数不能是虚函数(静态函数属于类,内联函数编译时展开,均无法满足运行时绑定)。

五、代码示例(从基础到进阶,可直接运行)

示例 1:基础运行时多态(核心)

实现动物基类,子类重写叫()虚函数,通过基类指针调用,触发多态。

cpp

运行

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

// 基类:动物
class Animal {
public:
    // 虚函数:叫(加virtual,为多态做准备)
    virtual void cry() {
        cout << "动物发出叫声" << endl;
    }
    // 虚析构函数:避免子类析构不彻底(重点!)
    virtual ~Animal() {
        cout << "Animal析构" << endl;
    }
};

// 子类:猫(继承Animal)
class Cat : public Animal {
public:
    // 重写基类虚函数(可省略virtual,建议添加)
    virtual void cry() override { // override关键字:显式声明重写,编译器会检查语法,避免写错
        cout << "猫咪:喵喵喵~" << endl;
    }
    ~Cat() {
        cout << "Cat析构" << endl;
    }
};

// 子类:狗(继承Animal)
class Dog : public Animal {
public:
    virtual void cry() override {
        cout << "狗狗:汪汪汪!" << endl;
    }
    ~Dog() {
        cout << "Dog析构" << endl;
    }
};

// 测试函数:接收基类引用,调用cry()
void doCry(Animal &a) { // 基类引用,触发多态
    a.cry();
}

int main() {
    Cat cat;
    Dog dog;

    // 方式1:基类引用调用(推荐)
    doCry(cat); // 输出:猫咪:喵喵喵~
    doCry(dog); // 输出:狗狗:汪汪汪!

    // 方式2:基类指针调用(常用,尤其动态分配对象时)
    Animal *p1 = new Cat();
    Animal *p2 = new Dog();
    p1->cry(); // 输出:猫咪:喵喵喵~
    p2->cry(); // 输出:狗狗:汪汪汪!

    // 释放动态内存(虚析构的作用:会先析构子类,再析构基类)
    delete p1;
    delete p2;

    return 0;
}

关键说明

  • override关键字:C++11 引入,显式告诉编译器这是重写基类的虚函数,若函数名 / 参数写错,编译器会直接报错,避免手写错误;
  • 虚析构函数:如果基类析构函数不加virtual,用基类指针delete子类对象时,只会调用基类析构,子类析构不会执行,造成内存泄漏 ;加virtual后,会先执行子类析构,再执行基类析构,保证析构彻底。
示例 2:编译时多态(函数重载)

最基础的多态,编译器编译时根据参数匹配函数,代码简单直观:

cpp

运行

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

// 函数重载:同一作用域,函数名相同,参数列表不同
int add(int a, int b) {
    return a + b;
}
double add(double a, double b) {
    return a + b;
}
int add(int a, int b, int c) {
    return a + b + c;
}

int main() {
    cout << add(1,2) << endl;       // 调用int版,输出3
    cout << add(1.5,2.5) << endl;   // 调用double版,输出4.0
    cout << add(1,2,3) << endl;     // 调用3个int版,输出6
    return 0;
}
示例 3:纯虚函数与抽象类(工程常用)

如果基类的虚函数没有实际意义 ,不需要提供函数体,可声明为纯虚函数 ,格式:virtual 函数返回值 函数名(参数) = 0;。包含纯虚函数 的类称为抽象类,特点:

  1. 抽象类不能实例化对象 (不能直接Animal a;);
  2. 子类必须重写抽象类的所有纯虚函数,否则子类也会成为抽象类,无法实例化;
  3. 抽象类的核心作用:作为接口规范,约束子类必须实现指定的函数,是工程中设计基类的常用方式。

代码示例

cpp

运行

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

// 抽象类:包含纯虚函数
class Shape {
public:
    // 纯虚函数:计算面积(基类无具体实现,由子类实现)
    virtual double getArea() = 0;
    // 纯虚函数:计算周长
    virtual double getPeri() = 0;
    virtual ~Shape() {}
};

// 子类:圆形(必须实现所有纯虚函数)
class Circle : public Shape {
private:
    double r; // 半径
public:
    Circle(double r) : r(r) {}
    virtual double getArea() override {
        return 3.14 * r * r;
    }
    virtual double getPeri() override {
        return 2 * 3.14 * r;
    }
};

// 子类:矩形
class Rect : public Shape {
private:
    double w, h; // 宽、高
public:
    Rect(double w, double h) : w(w), h(h) {}
    virtual double getArea() override {
        return w * h;
    }
    virtual double getPeri() override {
        return 2 * (w + h);
    }
};

// 测试函数:基类指针调用,触发多态
void show(Shape *s) {
    cout << "面积:" << s->getArea() << endl;
    cout << "周长:" << s->getPeri() << endl;
}

int main() {
    // Shape s; // 错误:抽象类不能实例化
    Shape *c = new Circle(2); // 圆形,半径2
    Shape *r = new Rect(3,4); // 矩形,3*4
    show(c); // 输出圆形的面积和周长
    show(r); // 输出矩形的面积和周长

    delete c;
    delete r;
    return 0;
}

运行结果

plaintext

复制代码
面积:12.56
周长:12.56
面积:12
周长:14

六、运行时多态的底层原理:虚函数表(vtable)

新手可以不用深入实现,但要知道核心逻辑,帮助理解多态的本质:

  1. 当类中有虚函数 时,编译器会为该类生成一个虚函数表(vtable) ------ 本质是一个指针数组,存储了该类所有虚函数的地址;
  2. 该类的每个对象,都会隐含一个虚表指针(vptr)(通常是对象的第一个成员),指向所属类的虚函数表;
  3. 子类继承基类时,会复制基类的虚函数表;如果子类重写了基类的虚函数,会替换虚表中对应函数的地址;
  4. 程序运行时,通过基类指针 / 引用调用虚函数时,会根据对象实际的虚表指针 ,找到对应类的虚函数表,执行表中存储的函数地址 ------ 这就是运行时绑定的本质。

简单说:虚表存函数地址,虚表指针指向实际的虚表,运行时通过虚表指针找真正要执行的函数

七、多态的核心优势

  1. 提高代码可扩展性 :新增子类时,无需修改原有调用虚函数的代码,直接继承基类并重写虚函数即可,符合开闭原则(对扩展开放,对修改关闭);
  2. 提高代码可读性和维护性:通过基类指针 / 引用统一调用,代码更简洁,无需针对每个子类写单独的调用逻辑;
  3. 实现接口解耦:基类作为接口,调用方只需关注基类的接口,无需关注子类的具体实现,降低代码耦合度。

总结(核心关键点)

  1. C++ 多态分运行时(动态,核心)编译时(静态) ,运行时多态由虚函数 + 继承 + 重写 + 基类指针 / 引用实现;
  2. 虚函数是运行时多态的关键,基类析构函数建议声明为虚函数,避免子类析构不彻底;
  3. 纯虚函数无体,包含纯虚函数的类是抽象类,不能实例化,子类必须重写其所有纯虚函数;
  4. 多态的核心优势是高可扩展性、低耦合,是面向对象编程中实现 "接口统一,实现各异" 的核心手段;
  5. 底层通过虚函数表(vtable)虚表指针(vptr) 实现运行时绑定,根据对象实际类型执行对应函数。
相关推荐
WJX_KOI2 小时前
保姆级教程:Apache Seatunnel CDC(standalone 模式)部署 MySQL CDC、PostgreSQL CDC 及使用方法
java·大数据·mysql·postgresql·big data·etl
今儿敲了吗2 小时前
11| 子集
c++·笔记·算法
阿猿收手吧!2 小时前
【C++】无锁原子栈:CAS实现线程安全
开发语言·c++·安全
今天你TLE了吗2 小时前
JVM学习笔记:第一章——JVM&Java体系结构
java·jvm·笔记·学习
蒹葭玉树2 小时前
【C++上岸】C++常见面试题目--操作系统篇(第三十期)
c++·面试·risc-v
毕设源码-朱学姐2 小时前
【开题答辩全过程】以 基于JAVAweb的影视创作论坛系统为例,包含答辩的问题和答案
java
葡萄城技术团队2 小时前
GcExcel V9.0 新特性解密:VALUETOTEXT/ARRAYTOTEXT 双函数,让数据文本转换更精准高效
java
凤年徐2 小时前
容器适配器深度解析:从STL的stack、queue到优先队列的底层实现
开发语言·c++·算法
她说..2 小时前
策略模式+工厂模式实现订单校验功能
java·spring boot·java-ee·简单工厂模式·策略模式