纯虚函数必须在声明末尾写=0且无函数体,含纯虚函数的类为抽象类不可实例化,但可定义指针或引用实现多态;派生类未重写所有纯虚函数仍为抽象类;纯虚函数可有默认实现,但调用需显式作用域限定。

纯虚函数的定义语法必须带 = 0
在 C++ 类中声明纯虚函数,核心是函数声明末尾加上 = 0,且该函数不能有函数体(哪怕写成 {} 也会编译失败)。它只出现在类定义内部,用于表明该函数必须由派生类实现。
常见错误包括:
- 漏掉
= 0,结果变成普通虚函数,基类可实例化,失去抽象约束 - 在类内写了函数体,如
virtual void foo() = 0 { },编译器直接报错 - 把
= 0写在函数声明之外,比如放在注释后或换行后,会被视为语法错误
正确写法示例:
class Shape {
public:
virtual double area() const = 0; // ✅ 纯虚函数
virtual ~Shape() = default;
};
含纯虚函数的类自动成为抽象类
只要一个类中至少有一个纯虚函数,该类就不可实例化——即使其他所有函数都已实现。这不是靠关键字 abstract 控制(C++ 没这关键字),而是语言规则强制的。
使用场景上要注意:
立即学习“C++免费学习笔记(深入)”;
- 不能对抽象类调用
new Shape或声明Shape s;,否则编译失败,错误信息类似:error: cannot declare variable 's' to be of abstract type 'Shape' - 可以定义指向抽象类的指针或引用,这是多态调用的基础,例如
Shape* p = new Circle; - 派生类若未重写全部纯虚函数,它本身仍是抽象类,也无法实例化
纯虚函数可以有默认实现,但调用需显式限定
纯虚函数允许在类外提供定义(即函数体),但这不改变其“纯虚”性质,也不影响抽象类判定。这种写法常用于提供通用逻辑,同时强制接口契约。
关键点在于:派生类中若要调用该默认实现,必须显式通过作用域解析符调用,例如 Base::func();否则仅靠 func() 会触发纯虚调用崩溃(运行时 undefined behavior)。
示例:
class Base {
public:
virtual void log() const = 0;
};
void Base::log() const { std::cout << "default log\n"; } // ✅ 合法定义
class Derived : public Base {
public:
void log() const override {
Base::log(); // ✅ 必须这样调用默认实现
// log(); // ❌ 错误:递归调用自身,非预期行为
}
};
接口设计中慎用数据成员和非虚析构函数
把抽象类当接口用时(类似 Java 的 interface),容易忽略两个隐性陷阱:
- 抽象类中定义了数据成员,会导致所有派生类对象大小增加,且破坏“零开销抽象”原则;接口应尽量只含纯虚函数
- 析构函数不是虚函数,会导致
delete指向派生类对象的基类指针时,派生类析构函数不被调用——内存泄漏或资源未释放
务必为抽象基类声明虚析构函数,哪怕它是空的:
class Interface {
public:
virtual ~Interface() = default; // ✅ 推荐:= default 更清晰
virtual void do_something() = 0;
};
如果基类析构函数是 protected 且 non-virtual,也可能掩盖资源管理问题,这类细节在大型继承体系里往往到上线才暴露。











