只要在基类中声明至少一个形如virtual void func() = 0;的纯虚函数,该类即为抽象类,不可实例化;派生类若未实现所有纯虚函数,自身也成抽象类;抽象类可含非纯虚函数、数据成员及构造函数。

纯虚函数怎么写才能让类变成抽象类
只要在基类中声明至少一个纯虚函数,这个类就自动成为抽象类,无法直接实例化。关键不是写了多少个,而是有没有 virtual void func() = 0; 这种带 = 0 的声明。
常见错误是漏掉 = 0,只写 virtual void func(); —— 这只是普通虚函数,类仍可实例化,起不到抽象约束作用。
多个纯虚函数可以共存,比如:
class Drawable {
public:
virtual void draw() = 0;
virtual void resize(int w, int h) = 0;
virtual ~Drawable() = default; // 析构函数建议也设为虚
};注意:= 0 必须紧贴在参数列表后、分号前,中间不能有空格或换行干扰(某些老编译器对格式敏感)。
立即学习“C++免费学习笔记(深入)”;
派生类不重写纯虚函数会怎样
如果派生类没实现基类所有纯虚函数,它自己也会变成抽象类,仍然不能 new 出对象。编译器不会报错,但链接时或运行时调用未实现函数会崩溃。
典型现象:
-
error: cannot declare variable 'b' to be of abstract type 'B'(GCC/Clang 编译时报错) - VS 可能报
C2259: cannot instantiate abstract class - 即使编译通过,运行时调用未重写的纯虚函数会触发
pure virtual function call终止程序
实操建议:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 用
override显式标注重写函数,让编译器检查签名是否匹配 - 确保参数类型、const 限定符、返回值(协变除外)完全一致
- 不要忽略 const 成员函数的重写:基类是
virtual void get() const = 0;,派生类也必须写void get() const override
抽象类里能不能有非纯虚函数和数据成员
完全可以。抽象类不是“空接口”,它可以混用纯虚函数、普通虚函数、非虚成员函数,甚至 public/private 数据成员。
例如:
class Animal {
protected:
std::string name_;
public:
Animal(const std::string& n) : name_(n) {}
virtual void speak() = 0; // 纯虚:强制子类实现
virtual void sleep() { // 普通虚函数:提供默认行为
std::cout << name_ << " is sleeping.\n";
}
void eat() { // 非虚函数:固定逻辑
std::cout << name_ << " is eating.\n";
}
};这种设计很常见:抽象类封装共性(如 name_、eat()),把差异点(如 speak())留给子类决定。
容易踩的坑:
- 在构造函数或析构函数中调用纯虚函数——行为未定义,多数情况下直接 crash
- 把纯虚函数声明为
static或template—— C++ 不允许,语法错误 - 误以为抽象类不能有构造函数——它完全可以有,且常用于初始化共用成员
为什么析构函数要声明为 virtual
如果用基类指针管理派生类对象(比如 Animal* p = new Dog;),而基类析构函数不是 virtual,那么 delete p; 只会调用基类析构函数,派生类部分内存泄漏,资源不释放。
正确做法是:只要类设计为多态基类(含纯虚函数),其析构函数必须是 virtual,哪怕它是空实现:
class Drawable {
public:
virtual void draw() = 0;
virtual ~Drawable() = default; // 推荐用 = default;也可写 {},但必须 virtual
};这个细节极容易被忽略,尤其在小型 demo 中看不出问题,但一到复杂对象生命周期管理阶段就会暴露。
补充一点:C++11 起支持 = default,比手动写空花括号更安全,避免隐式生成非虚析构函数。

















