C++ 虚函数与多态详解

C++ 虚函数与多态详解

这是 C++ 面向对象编程中最核心的特性 :虚函数(Virtual Function)和多态(Polymorphism)。让我详细解释:


🎯 核心概念:运行时多态

1. 基础语法结构

cpp 复制代码
// 接口类(抽象基类)
class ImageEditorProcessorListener {
public:
    // 纯虚函数 (= 0 表示必须被子类实现)
    virtual void onGetImageList(const std::string& id, std::string list, std::string host) = 0;
};

// 实现类1
class ImageEditorManager : public ImageEditorProcessorListener {
public:
    // override 关键字表示重写父类虚函数
    void onGetImageList(const std::string& id, std::string list, std::string host) override {
        // 具体实现...
    }
};

// 实现类2
class ImageListener : public IImageEditorListener {
public:
    void onGetImageList(const std::string& id, std::string list, std::string host) override {
        // 另一个实现...
    }
};

🔍 为什么会跳转?关键在于指针/引用的实际类型

第一次跳转:从 ImageEditorProcessor 到 ImageEditorManager

cpp 复制代码
// 在 ImageEditorProcessor.h 中
private:
    std::shared_ptr<ImageEditorProcessorListener> mImageEditorProcessorListener;
    //              ↑ 声明类型是基类指针
cpp 复制代码
// 在 ImageEditorManager.h 的 init() 方法中
void init() {
    imageEditorProcessor = std::make_shared<ImageEditorProcessor>();
    imageEditorProcessor->init();
    // 关键代码:传入 ImageEditorManager 的 shared_ptr
    imageEditorProcessor->setFileProcessorListener(shared_from_this());
    //                                              ↑ 实际类型是 ImageEditorManager
}
cpp 复制代码
// setFileProcessorListener 的实现
void setFileProcessorListener(std::shared_ptr<ImageEditorProcessorListener> listener) {
    mImageEditorProcessorListener = listener;  
    // 现在 mImageEditorProcessorListener 指向的是 ImageEditorManager 对象
}

关键点:

  • mImageEditorProcessorListener 的声明类型 是 ImageEditorProcessorListener*(基类指针)
  • 但它的实际指向 是 ImageEditorManager 对象(派生类对象)

虚函数表(VTable)机制

当调用 mImageEditorProcessorListener->onGetImageList(...) 时:

markdown 复制代码
编译器的处理流程:
1. 检查 mImageEditorProcessorListener 指向的对象的 vtable(虚函数表)
2. 在 vtable 中查找 onGetImageList 的实际实现地址
3. 发现实际对象是 ImageEditorManager
4. 跳转到 ImageEditorManager::onGetImageList()

虚函数表示意图:

cpp 复制代码
ImageEditorProcessorListener 的 vtable:
├─ onGetImageList → [纯虚函数,无实现]
├─ onPreviewImage → [纯虚函数,无实现]
└─ ...

ImageEditorManager 的 vtable:
├─ onGetImageList → 指向 ImageEditorManager::onGetImageList 的地址
├─ onPreviewImage → 指向 ImageEditorManager::onPreviewImage 的地址
└─ ...

🔄 第二次跳转:从 ImageEditorManager 到 ImageListener

cpp 复制代码
// ImageEditorManager.h
void onGetImageList(const std::string& id, std::string imageList, std::string host) override {
    std::lock_guard<std::mutex> lock(fileListenerMutex);
    
    // 遍历监听器列表
    for (const auto& listener : imageEditorListenerList) {
        //           ↑ listener 的声明类型是 IImageEditorListener*
        //             实际类型是 ImageListener*
        
        listener->onGetImageList(id, imageList, host);
        // 再次触发虚函数机制,跳转到 ImageListener::onGetImageList()
    }
}
cpp 复制代码
// 在某处注册监听器
private:
    std::vector<std::shared_ptr<IImageEditorListener>> imageEditorListenerList;
    //                          ↑ 基类指针容器
cpp 复制代码
// 添加监听器时(实际是 ImageListener 对象)
imageEditorManager->addFileListener(std::make_shared<ImageListener>());
//                                   ↑ 派生类对象,但存储为基类指针

📚 核心语法知识点总结

1. 虚函数(virtual)

cpp 复制代码
class Base {
public:
    virtual void func() { }  // 虚函数
};
  • 使用 virtual 关键字声明
  • 允许派生类重写(override)
  • 支持运行时多态

2. 纯虚函数(= 0)

cpp 复制代码
class Interface {
public:
    virtual void func() = 0;  // 纯虚函数
};
  • 使用 = 0 语法
  • 必须被派生类实现
  • 包含纯虚函数的类是抽象类,不能实例化

3. override 关键字(C++11)

cpp 复制代码
class Derived : public Base {
public:
    void func() override { }  // 明确表示重写父类虚函数
};
  • 编译器会检查是否真的重写了父类虚函数
  • 防止拼写错误或参数不匹配

4. 多态的三个条件

✅ 必须满足:

  1. 有继承关系
  2. 子类重写父类的虚函数
  3. 通过基类指针或引用调用虚函数
cpp 复制代码
Base* ptr = new Derived();  // 基类指针指向派生类对象
ptr->func();                 // 调用的是 Derived::func(),而非 Base::func()

🎬 完整执行流程图

css 复制代码
调用点:
mImageEditorProcessorListener->onGetImageList(deviceId, listStr, host)
    ↓
[运行时检查] mImageEditorProcessorListener 指向什么对象?
    ↓
指向 ImageEditorManager 对象
    ↓
通过 vtable 跳转到 ImageEditorManager::onGetImageList()
    ↓
for (listener : imageEditorListenerList) {
    ↓
    [运行时检查] listener 指向什么对象?
    ↓
    指向 ImageListener 对象
    ↓
    通过 vtable 跳转到 ImageListener::onGetImageList()
    ↓
    执行具体业务逻辑(下载图片、更新UI等)
}

💡 为什么这样设计?

优点:

  1. 解耦 :ImageEditorProcessor 不需要知道具体的实现类
  2. 扩展性:可以轻松添加新的监听器实现
  3. 灵活性:运行时决定调用哪个实现
  4. 设计模式 :符合观察者模式 和依赖倒置原则

类比:

arduino 复制代码
就像打电话:
- 你拨的是"客服热线"(基类指针)
- 但接电话的是"小李"或"小王"(具体实现)
- 你不需要知道是谁接的,只需要知道能提供服务

🔬 验证示例代码

cpp 复制代码
#include <iostream>
#include <memory>

class Animal {
public:
    virtual void speak() = 0;  // 纯虚函数
};

class Dog : public Animal {
public:
    void speak() override { std::cout << "汪汪!\n"; }
};

class Cat : public Animal {
public:
    void speak() override { std::cout << "喵喵!\n"; }
};

int main() {
    std::shared_ptr<Animal> animal1 = std::make_shared<Dog>();
    std::shared_ptr<Animal> animal2 = std::make_shared<Cat>();
    
    animal1->speak();  // 输出: 汪汪! (调用 Dog::speak)
    animal2->speak();  // 输出: 喵喵! (调用 Cat::speak)
    
    // 虽然都是 Animal 指针,但调用的是各自的实现
    return 0;
}

这就是为什么 mImageEditorProcessorListener->onGetImageList() 能"自动"找到正确实现的原因!

相关推荐
倒头就睡的小比特5 天前
算法竞赛C++常用的STL
c++·算法
weilx12345 天前
C++笔记-文件IO-<fcntl.h>
c++
Smileyqp沛沛6 天前
前端?C++ ?较大差异基础罗列
c++·基础·前端转c++
C语言小火车6 天前
C/C++ 为什么需要编译器?
开发语言·c++
旖旎夜光6 天前
力控面试题 01.01: 判定字符是否唯一(位运算) —— 题解
c++·学习·算法·leetcode·力控
吞下星星的少年·-·6 天前
C++ 萌新语法入门篇
c++·算法比赛
霍霍的袁6 天前
【C++】map 和 set 的使用 | 从用法到底层
开发语言·c++·学习·visual studio
another heaven6 天前
【算法/C++ MD5算法能否逆解码?原理、C++实现与同类哈希算法对比】
c++·算法·哈希算法
程序猿编码6 天前
告别改源码适配模型:纯 C++ 可配置 LLM 推理引擎,全格式全结构兼容
c++·大模型·llm·推理引擎
此生决int6 天前
深入理解C++系列(20)——C++11(下)
开发语言·c++