抽象类因含纯虚函数(=0且无函数体)而不可实例化,派生类须实现全部纯虚函数才可实例化;纯虚函数不可有默认参数、不可为构造函数,析构函数可为虚或纯虚;重写须用override确保签名匹配。

抽象类不能被实例化,否则编译器直接报错
你写 Base b; 或 new Base(),编译器会扔出类似 error: cannot declare variable 'b' to be of abstract type 'Base' 的错误。这不是运行时问题,是编译期拦下来的——因为抽象类里至少有一个 virtual void func() = 0; 这样的纯虚函数,它没实现,编译器不让你“造一个不完整的东西”。
常见误操作:
- 把基类当普通类用,忘了派生类必须重写所有纯虚函数才能实例化
- 派生类只重写了部分纯虚函数,剩下几个漏了,结果还是抽象类
- 把纯虚函数声明写成
virtual void func() = 0 {}(带函数体),语法合法但容易误导——= 0 和 {} 不能共存,否则编译失败
纯虚函数定义必须带 = 0,且不能有函数体
= 0 是唯一标记纯虚函数的语法,不是赋值,也不是初始化。它告诉编译器:“这个函数只负责定义接口,具体谁干、怎么干,子类自己定。”
正确写法:virtual int getValue() const = 0;
错误写法:virtual int getValue() const { return 0; }(这是普通虚函数)
也错误:virtual int getValue() const = 0 {}(语法冲突)
注意点:
立即学习“C++免费学习笔记(深入)”;
- 纯虚函数可以有 const / noexcept / override 等修饰,但不能有默认参数(C++11 起允许,但极不推荐——子类重写时默认参数不继承,容易引发行为不一致)
- 构造函数不能是虚函数,更不能是纯虚;析构函数可以是虚的,也建议是纯虚的(如果真要强制子类实现清理逻辑),但更常见的是
virtual ~Base() = default;或带空实现的虚析构
派生类只有实现全部纯虚函数后,才不再是抽象类
哪怕只差一个没重写,Derived d; 依然编译不过。编译器不看你写了多少,只看“有没有缺口”。
实操建议:
- 在派生类中显式加
override,比如int getValue() const override { return m_val; },能帮编译器检查签名是否完全匹配(返回类型、const、noexcept 等) - 如果基类纯虚函数有多个重载版本(如
void process(int)和void process(double)),子类必须分别重写,少一个都不行 - 别依赖 IDE 自动补全——有些补全只抄函数名和参数,漏掉 const 或返回类型,结果看似重写了,实际是新函数,纯虚函数缺口还在
纯虚函数可以有实现,但调用必须显式通过作用域解析
纯虚函数允许在类外提供定义,比如:
class Base {
public:
virtual void log() const = 0;
};
void Base::log() const { std::cout << "default log\n"; }
这时候,子类可以不重写 log(),但只要没重写,它就还是抽象类;一旦重写,就能调用父类实现:Base::log();。
这种写法少见,但有用场景是:提供通用日志/序列化逻辑,子类按需扩展。不过得小心:
- 不能在构造/析构函数里通过
Base::log()调用——此时虚函数机制未完全建立或已销毁,行为未定义 - 子类重写时若忘记调用父类实现,就丢了默认逻辑;若重复调用,可能多打一次日志
- 多数项目倾向用普通虚函数 + 默认实现,更直白;纯虚函数带实现属于进阶技巧,容易让新人困惑










