在C++面向对象编程中,虚函数与纯虚函数是实现多态的两个核心机制,但它们在语法形式、使用约束和设计意图上存在明显不同。虚函数允许基类提供一份可被子类覆盖的默认实现,而纯虚函数则只声明接口、不提供实现,强制子类必须完成具体逻辑。很多人在设计基类时容易混淆二者,导致抽象层次不清晰或者出现无法实例化的类。下文将从语法定义、内存模型与设计场景三个维度展开说明。

语法定义与基本使用方式
虚函数的声明是在基类成员函数前加上 virtual 关键字,并且可以提供函数体。派生类可以使用 override 关键字重写该函数,也可以完全不重写,此时通过基类指针调用的依然是基类的版本。这种机制非常适合那些基类已经具备通用逻辑、但允许子类按需调整行为的场景。例如动物基类中的发声函数,大部分动物有默认叫声,少数特殊动物才需要重写。
纯虚函数的写法是在声明后加上 = 0,并且基类通常不给出函数实现(C++11之后也允许提供定义但极少用)。一旦类中存在纯虚函数,这个类就变成抽象类,编译器禁止创建其实例。派生类必须对所有纯虚函数给出实现,否则它自身也会成为抽象类。下面是一段对比代码:
#include <iostream>
using namespace std;
class Animal {
public:
virtual void eat() {
cout << "Animal eats food" << endl;
}
virtual void sound() = 0; // 纯虚函数
};
class Dog : public Animal {
public:
void sound() override {
cout << "Dog barks" << endl;
}
};
int main() {
Dog d;
d.eat(); // 调用基类虚函数
d.sound(); // 调用派生类实现的纯虚函数
return 0;
}
从上面代码可以看出,eat 是虚函数,Dog 没有重写也照常使用;sound 是纯虚函数,Dog 必须实现才能编译通过。如果尝试写 Animal a; 这样的语句,编译器会直接报错,因为 Animal 含有纯虚函数。
底层内存模型与调用过程
无论是虚函数还是纯虚函数,编译器都会为包含它们的类生成虚函数表(vtable)。每个对象实例内部都有一个隐藏的虚表指针(vptr),指向所属类的虚表。虚表中按顺序存放各个虚函数的地址。对于虚函数,表中存放的是基类实现地址(若未被重写);对于纯虚函数,表中通常存放一个指向抽象错误处理的占位地址,有些编译器会放 NULL 或特定报错函数,目的是在误调用时抛出链接或运行期错误。
当通过基类指针调用函数时,程序先通过 vptr 找到虚表,再取出对应槽位的函数地址进行间接调用,这就是动态绑定的本质。虚函数和纯虚函数在这个过程中没有任何性能差异,开销都仅是一次指针间接寻址。值得注意的是,纯虚函数并不额外占用对象内存,它只是约束了类的实例化能力和派生类的实现义务。下面的代码展示了通过基类指针触发多态:
#include <iostream>
using namespace std;
class Shape {
public:
virtual double area() = 0;
virtual ~Shape() {}
};
class Circle : public Shape {
double r;
public:
Circle(double x) : r(x) {}
double area() override {
return 3.14 * r * r;
}
};
int main() {
Shape* s = new Circle(2.0);
cout << s->area() << endl; // 动态绑定到 Circle::area
delete s;
return 0;
}
这段例子里 Shape 只有纯虚函数 area,保证了所有图形都必须能计算面积,但具体算法由 Circle 等子类决定。虚表机制让 s->area() 在运行期准确跳到 Circle 的实现,而不依赖编译期类型。这种统一的接口调度正是大型系统解耦的基础。
设计场景与选型建议
在选择虚函数还是纯虚函数时,核心判断标准是:基类是否应该提供默认行为。如果某些逻辑在基类层面已经具有通用性,且绝大多数派生类不需要改变,那么用虚函数可以减少重复代码。例如日志类的 format 虚函数,默认输出文本格式,个别子类可重写支持 JSON。这样既不强制所有子类写一遍相同代码,又保留了扩展能力。
反之,如果基类仅仅定义规范、完全不知道具体实现细节,就应该使用纯虚函数构成抽象接口。比如支付网关的 PayChannel 基类,不同渠道的扣款流程差异巨大,基类给默认实现反而会造成误导或错误调用。此时纯虚函数能编译期强制子类落地实现,避免遗漏。混合使用也是常见做法:一个基类既有虚函数提供公用能力,又有纯虚函数约定必选接口,从而兼顾复用与约束。
另外需要注意,含有纯虚函数的类析构函数通常也要声明为虚函数,否则通过基类指针删除派生类对象时可能漏掉子类资源释放。虽然析构函数可以是纯虚的,但必须给出定义,否则链接失败。合理搭配二者,才能构建出既灵活又安全的继承体系,降低后期维护成本。
virtual_functionpure_virtual_functionC++_polymorphism修改时间:2026-08-17 21:34:30