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() 能"自动"找到正确实现的原因!

相关推荐
卡提西亚2 小时前
一本通网站1130:找第一个只出现一次的字符
数据结构·c++·笔记·算法·一本通
敲上瘾2 小时前
C++ ODB ORM 完全指南:从入门到实战应用
linux·数据库·c++·oracle·db
一匹电信狗3 小时前
【C++11】右值引用+移动语义+完美转发
服务器·c++·算法·leetcode·小程序·stl·visual studio
草莓熊Lotso3 小时前
《算法闯关指南:优选算法--位运算》--36.两个整数之和,37.只出现一次的数字 ||
开发语言·c++·算法
小年糕是糕手4 小时前
【数据结构】常见的排序算法 -- 选择排序
linux·数据结构·c++·算法·leetcode·蓝桥杯·排序算法
huangyuchi.4 小时前
【Linux网络】Socket编程实战,基于UDP协议的Dict Server
linux·网络·c++·udp·c·socket
yunhuibin6 小时前
无锁化编程——c++内存序使用
c++
zzzyyy5388 小时前
C++之vector容器
开发语言·c++
uotqwkn89469s9 小时前
如果Visual Studio不支持C++14,应该如何解决?
c++·ide·visual studio