虚函数和纯虚函数的区别是什么?

来源:苹果APP网作者:过客头衔:草根站长
导读:本期聚焦于过客创作的《虚函数和纯虚函数的区别是什么?》,敬请观看详情。基类指针调用派生类方法时行为为何不同,根源在虚函数与纯虚函数的设计差异。虚函数提供默认实现,允许派生类选择重写或不重写,支撑运行时多态。纯虚函数则强制派生类必须给出具体实现,否则该类仍为抽象类无法实例化。二者在内存布局上都依赖虚表指针,但纯虚函数对应的虚表项常指向抽象占位逻辑。理解这两类函数在接口约束与代码复用上的不同定位,能帮助设计更合理的类继承体系,避免把本该由子类实现的细节错误地写进基类。

在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

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。