C++中的虚函数:初学者为何需要它们?

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

C++中的虚函数:为什么离不开它们?

\n\n

想象一下,你是一位导演,正在拍摄一部关于动物的电影。你的剧本是:“所有动物都会发出声音”。你拍摄了狗的场景——它汪汪叫。拍摄了猫的场景——它喵喵叫。一切都很合理。现在想象一下,你的助手(编译器)给你拿来一盒玩具动物,说:“拍吧!”。你按下按钮,但玩具……一声不吭。为什么?因为你忘了虚函数。

\n\n

在C++编程中,这个问题经常出现。你编写了一个“动物”类,里面有一个方法sound()。然后你创建了继承自“动物”的“狗”和“猫”类。一切似乎都很好,直到你试图通过基类指针操作这些对象。这时你会发现,C++默认调用的是基类的方法,而不是派生类中重写的方法。虚函数一劳永逸地解决了这个问题。

\n\n

在本文中,我们将详细讨论什么是虚函数,为什么需要它们,它们是如何在底层工作的,以及为什么现代C++离不开它们。

\n\n

用简单的话说,什么是虚函数

\n\n

虚函数是一个类的方法,你用关键字virtual标记它。这样的函数可以在派生类中被重写,并且当通过基类的指针或引用调用时,实际调用的是派生类中的版本。

\n\n

听起来很复杂?让我们用一个生活中的例子来说明。想象一个机器人遥控器。你有一个基类“机器人”,其中有一个方法doWork()。有扫地机器人和送货机器人。如果你按下“工作”按钮,应该启动相应的算法:扫地机器人开始打扫,送货机器人开始送包裹。如果没有虚函数,你按下按钮,机器人只会原地不动(调用的是基类的方法)。

\n\n

在C++中,虚函数是多态性的基础。多态性允许你统一处理继承自同一个父类的不同类型对象。

\n\n

没有虚函数的问题:一个直观的例子

\n\n

让我们看看如果不使用virtual会发生什么。

\n\n
#include <iostream>\n#include <vector>\n\nclass Animal {\npublic:\n    void sound() {\n        std::cout << "Some generic animal sound" << std::endl;\n    }\n};\n\nclass Dog : public Animal {\npublic:\n    void sound() {\n        std::cout << "Woof! Woof!" << std::endl;\n    }\n};\n\nclass Cat : public Animal {\npublic:\n    void sound() {\n        std::cout << "Meow!" << std::endl;\n    }\n};\n\nint main() {\n    std::vector<Animal*> animals;\n    animals.push_back(new Dog());\n    animals.push_back(new Cat());\n\n    for (Animal* a : animals) {\n        a->sound(); // 会输出什么?\n    }\n\n    // 清理内存\n    for (Animal* a : animals) delete a;\n\n    return 0;\n}\n
\n\n

运行这段代码。你会看到:

\n
Some generic animal sound\nSome generic animal sound\n
\n\n

为什么?因为编译器查看的是指针的类型(Animal*),而不是实际的对象。它调用了基类的方法。这被称为早期绑定(early binding)——调用哪个方法的决定是在编译阶段做出的。

\n\n

而我们想要的是晚期绑定(late binding)——决定在程序运行时根据对象的实际类型做出。这正是虚函数所提供的。

\n\n

解决方案:添加virtual

\n\n

让我们修改示例,添加关键字virtual。

\n\n
class Animal {\npublic:\n    virtual void sound() {  // 添加了virtual\n        std::cout << "Some generic animal sound" <<                

博客

书籍推荐