C++中的虚函数:为什么离不开它们?
\n\n想象一下,你是一位导演,正在拍摄一部关于动物的电影。你的剧本是:“所有动物都会发出声音”。你拍摄了狗的场景——它汪汪叫。拍摄了猫的场景——它喵喵叫。一切都很合理。现在想象一下,你的助手(编译器)给你拿来一盒玩具动物,说:“拍吧!”。你按下按钮,但玩具……一声不吭。为什么?因为你忘了虚函数。
\n\n在C++编程中,这个问题经常出现。你编写了一个“动物”类,里面有一个方法sound()。然后你创建了继承自“动物”的“狗”和“猫”类。一切似乎都很好,直到你试图通过基类指针操作这些对象。这时你会发现,C++默认调用的是基类的方法,而不是派生类中重写的方法。虚函数一劳永逸地解决了这个问题。
在本文中,我们将详细讨论什么是虚函数,为什么需要它们,它们是如何在底层工作的,以及为什么现代C++离不开它们。
\n\n用简单的话说,什么是虚函数
\n\n虚函数是一个类的方法,你用关键字virtual标记它。这样的函数可以在派生类中被重写,并且当通过基类的指针或引用调用时,实际调用的是派生类中的版本。
听起来很复杂?让我们用一个生活中的例子来说明。想象一个机器人遥控器。你有一个基类“机器人”,其中有一个方法doWork()。有扫地机器人和送货机器人。如果你按下“工作”按钮,应该启动相应的算法:扫地机器人开始打扫,送货机器人开始送包裹。如果没有虚函数,你按下按钮,机器人只会原地不动(调用的是基类的方法)。
在C++中,虚函数是多态性的基础。多态性允许你统一处理继承自同一个父类的不同类型对象。
\n\n没有虚函数的问题:一个直观的例子
\n\n让我们看看如果不使用virtual会发生什么。
#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运行这段代码。你会看到:
\nSome generic animal sound\nSome generic animal sound\n\n\n为什么?因为编译器查看的是指针的类型(Animal*),而不是实际的对象。它调用了基类的方法。这被称为早期绑定(early binding)——调用哪个方法的决定是在编译阶段做出的。
而我们想要的是晚期绑定(late binding)——决定在程序运行时根据对象的实际类型做出。这正是虚函数所提供的。
\n\n解决方案:添加virtual
\n\n让我们修改示例,添加关键字virtual。
class Animal {\npublic:\n virtual void sound() { // 添加了virtual\n std::cout << "Some generic animal sound" <<