乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • C++ 学习路径
  • 第1章 C++概述与开发环境

    • C++ 语言概述与编译模型
    • 第一个 C++ 程序与规范
    • 开发环境配置
    • 预处理指令详解
  • 第2章 基本语法与类型系统

    • 变量与基本类型
    • 类型转换
    • 枚举类型
    • 引用与指针
    • 数组与 stdarray
    • 字符串与原始字符串字面量
    • const 与 constexpr
    • nullptr 与空指针
    • 类型推导 auto 与 decltype
    • 基于范围的 for 循环
    • 值类别全面解析
    • char16_t 与 char32_t
    • static_assert 编译期断言
  • 第3章 函数与重载

    • 函数声明与定义
    • 函数重载
    • 默认参数与内联函数
    • Lambda 表达式
    • 函数对象与 stdfunction
    • 后置返回类型与 noexcept
  • 第4章 类与对象

    • 类的基本定义
    • 构造函数与析构函数
    • 拷贝控制
    • 移动构造函数与移动赋值
    • 列表初始化与类内初始化器
    • 静态成员与嵌套类
    • 友元
    • =default 与 =delete
  • 第5章 继承与多态

    • 继承基础
    • 虚函数与多态
    • 虚函数表与动态绑定原理
    • 虚析构函数
    • 抽象类与纯虚函数
    • 多重继承与虚继承
    • 继承构造函数
  • 第6章 运算符重载

    • 运算符重载基础
    • 算术与关系运算符重载
    • 赋值与移动运算符重载
    • 特殊运算符重载
  • 第7章 模板与泛型编程

    • 函数模板
    • 类模板
    • 模板特化与偏特化
    • 可变参数模板
    • 别名模板与模板模板参数
    • SFINAE 与类型萃取
    • 依赖名与 typename/template 关键字
  • 第8章 异常处理

    • 异常处理机制
    • noexcept 与异常安全
    • RAII 与异常安全实践
  • 第9章 内存管理与智能指针

    • 动态内存与内存分区
    • RAII 惯用法
    • unique_ptr
    • shared_ptr 与 weak_ptr
    • 内存管理最佳实践
  • 第10章 右值引用与移动语义

    • 右值引用与值类别深度解析
    • std::move 原理与使用
    • 完美转发与 std::forward
    • 移动语义性能对比与最佳实践
  • 第11章 STL容器

    • vector 深度剖析
    • deque 内部机制
    • list 与 forward_list
    • map 与 set 深度解析
    • unordered 容器与哈希原理
    • array 与 tuple
    • 容器适配器
    • 容器选择全景指南
  • 第12章 STL算法与迭代器

    • 迭代器体系全解
    • 非变异算法
    • 变异算法
    • 排序与二分算法
    • Lambda 与算法组合
    • std::random 随机数库
    • 自定义迭代器开发
  • 第13章 IO流与文件

    • 标准 IO 流
    • 格式化输出控制
    • 文件流操作
    • 字符串流
    • std::regex 正则表达式
  • 第14章 并发与多线程

    • thread 基础与线程管理
    • mutex 与 lock_guard
    • unique_lock 与 condition_variable
    • thread_local 线程局部存储
    • atomic 与内存序
    • future 与 async 异步编程
    • std::chrono 时间库
  • 第15章 现代C++新特性

    • 从 C++11 到 C++20 演进路线
    • C++14 关键新特性
    • C++17 关键新特性
    • C++20 核心特性速览
  • 第16章 面试考点与最佳实践

    • C++ 综合最佳实践清单
    • 高频面试题精讲
    • 多线程面试题与实战
    • 内存管理常见陷阱与排查
  • 附录

    • C++ 核心知识点
    • C++ 专业术语

抽象类与纯虚函数

定义与作用

纯虚函数是通过 = 0 声明的虚函数,它强制派生类必须提供实现:

class Shape {
public:
    virtual double area() const = 0;  // 纯虚函数
};

抽象类是包含至少一个纯虚函数的类。抽象类不能直接实例化——它只定义接口,具体实现交给派生类。抽象类解决了"基类本身不合理但需要统一接口"的编程痛点。

核心原理

抽象类的设计层次

纯虚析构函数的特殊性

派生类析构时总是调用基类析构函数。如果基类析构函数是纯虚的,必须提供函数体(否则链接错误):

class AbstractBase {
public:
    virtual ~AbstractBase() = 0;  // 纯虚析构声明
};
AbstractBase::~AbstractBase() = default;  // 必须在类外提供定义

完整示例

示例一:支付系统接口设计

场景说明:设计一个在线支付系统,不同支付方式共享统一接口。

#include <iostream>
#include <memory>
#include <vector>
#include <string>

// 抽象接口:定义支付协议
class PaymentMethod {
public:
    virtual ~PaymentMethod() = default;

    // 纯虚函数:每个支付方式必须实现
    virtual bool pay(double amount) = 0;
    virtual std::string getName() const = 0;
    virtual double getBalance() const = 0;

    // 非纯虚函数:提供默认实现
    virtual void printReceipt(double amount) const {
        std::cout << "===== 支付凭据 =====" << std::endl;
        std::cout << "支付方式: " << getName() << std::endl;
        std::cout << "金额: ¥" << amount << std::endl;
        std::cout << "余额: ¥" << getBalance() << std::endl;
    }
};

class CreditCard : public PaymentMethod {
public:
    CreditCard(std::string card_no, double limit)
        : card_no_(std::move(card_no)), limit_(limit), balance_(limit) {}

    bool pay(double amount) override {
        if (amount <= 0 || amount > balance_) return false;
        balance_ -= amount;
        return true;
    }

    std::string getName() const override {
        return "信用卡 (" + card_no_.substr(card_no_.size() - 4) + ")";
    }

    double getBalance() const override { return balance_; }

private:
    std::string card_no_;
    double limit_;
    double balance_;
};

class Alipay : public PaymentMethod {
public:
    explicit Alipay(double balance) : balance_(balance) {}

    bool pay(double amount) override {
        if (amount <= 0 || amount > balance_) return false;
        balance_ -= amount;
        return true;
    }

    std::string getName() const override { return "支付宝"; }

    double getBalance() const override { return balance_; }

private:
    double balance_;
};

class WeChatPay : public PaymentMethod {
public:
    explicit WeChatPay(double balance) : balance_(balance) {}

    bool pay(double amount) override {
        if (amount <= 0 || amount > balance_) return false;
        balance_ -= amount;
        return true;
    }

    std::string getName() const override { return "微信支付"; }

    double getBalance() const override { return balance_; }

private:
    double balance_;
};

// 订单处理系统——只依赖抽象接口
class OrderProcessor {
public:
    void addPaymentMethod(std::unique_ptr<PaymentMethod> method) {
        methods_.push_back(std::move(method));
    }

    bool processOrder(double amount, size_t method_index) {
        if (method_index >= methods_.size()) {
            std::cerr << "无效的支付方式索引" << std::endl;
            return false;
        }
        auto& method = methods_[method_index];
        std::cout << "尝试使用 " << method->getName() << " 支付 ¥" << amount << std::endl;
        if (method->pay(amount)) {
            method->printReceipt(amount);
            return true;
        }
        std::cerr << "支付失败!余额不足。" << std::endl;
        return false;
    }

private:
    std::vector<std::unique_ptr<PaymentMethod>> methods_;
};

int main() {
    OrderProcessor processor;

    processor.addPaymentMethod(std::make_unique<CreditCard>("6222-1234-5678-9012", 10000));
    processor.addPaymentMethod(std::make_unique<Alipay>(5000));
    processor.addPaymentMethod(std::make_unique<WeChatPay>(3000));

    processor.processOrder(199.99, 0);  // 信用卡
    processor.processOrder(8000, 1);    // 支付宝(余额不足)
    processor.processOrder(150.00, 2);  // 微信支付

    return 0;
}

预期输出:

尝试使用 信用卡 (9012) 支付 ¥199.99
===== 支付凭据 =====
支付方式: 信用卡 (9012)
金额: ¥199.99
余额: ¥9800.01
尝试使用 支付宝 支付 ¥8000
支付失败!余额不足。
尝试使用 微信支付 支付 ¥150
===== 支付凭据 =====
支付方式: 微信支付
金额: ¥150
余额: ¥2850

逐段分析:

  • PaymentMethod 是纯抽象接口——pay()、getName()、getBalance() 都是纯虚函数
  • printReceipt() 是非纯虚函数——提供默认实现,子类可选择覆盖
  • OrderProcessor 只依赖 PaymentMethod*——不关心具体支付方式
  • 新增支付方式无需修改 OrderProcessor 代码——符合开闭原则
  • 三种支付方式各自实现 pay() 的业务逻辑——多态的核心价值

示例二:纯虚析构函数的必要性

#include <iostream>
#include <memory>

class IResource {
public:
    // 纯虚析构:强制类为抽象 + 保证正确析构
    virtual ~IResource() = 0;

    virtual void release() = 0;
    virtual const char* name() const = 0;
};

// 必须在类外提供纯虚析构的定义
IResource::~IResource() {
    std::cout << "IResource 析构" << std::endl;
}

class FileResource : public IResource {
public:
    explicit FileResource(const char* path) : path_(path) {
        std::cout << "打开文件: " << path_ << std::endl;
    }

    ~FileResource() override {
        std::cout << "关闭文件: " << path_ << std::endl;
    }

    void release() override {
        std::cout << "释放文件资源: " << path_ << std::endl;
    }

    const char* name() const override { return path_; }

private:
    const char* path_;
};

int main() {
    std::cout << "===== 纯虚析构保证正确释放 =====" << std::endl;
    {
        std::unique_ptr<IResource> res =
            std::make_unique<FileResource>("data.txt");
        std::cout << "资源名: " << res->name() << std::endl;
        res->release();
    }  // unique_ptr 正确调用 FileResource::~FileResource → IResource::~IResource

    return 0;
}

预期输出:

===== 纯虚析构保证正确释放 =====
打开文件: data.txt
资源名: data.txt
释放文件资源: data.txt
关闭文件: data.txt
IResource 析构

逐段分析:

  • 纯虚析构 virtual ~IResource() = 0; 让 IResource 成为抽象类(不能实例化)
  • 但析构函数必须有函数体——派生类析构时隐式调用基类析构,链接器需要找到实现
  • IResource::~IResource() = default; 在类外提供定义——解决这一矛盾
  • 纯虚析构确保通过基类指针 delete 时正确调用派生类析构函数

易错场景与面试考点

易错场景

场景错误表现正确做法
纯虚析构忘了提供函数体链接错误类外 Abstract::~Abstract() {}
派生类继承抽象类但未实现所有纯虚函数派生类也是抽象类检查是否遗漏 override 实现
抽象类中定义数据成员设计异味抽象接口不应有状态,用中间抽象类
纯虚函数在构造函数中调用未定义行为构造函数中不调用虚函数
= 0 只放在声明,定义时重复语法错误= 0 仅出现在声明处

常见面试问题

  1. 抽象类和普通类的区别?为什么需要抽象类?——抽象类不能被实例化,至少含一个纯虚函数。用于定义接口规范,强制派生类实现特定方法,是面向接口编程的基础。

  2. 纯虚析构函数的使用场景和注意事项?——当希望类是抽象类但没有其他合适的纯虚函数时使用。必须在类外提供函数体(Abstract::~Abstract() {}),否则链接错误。

  3. 纯虚函数可以有实现吗?——可以有。virtual void f() = 0; 后仍可提供 Base::f() { ... },派生类通过 Base::f() 显式调用。但纯虚函数有实现的场景非常罕见。

  4. 抽象类能定义构造函数吗?——可以。抽象类构造函数会被派生类构造时调用,用于初始化抽象类的成员。但抽象类本身不能直接实例化。

  5. 接口类(Interface)和抽象类的区别?——接口类只包含纯虚函数和虚析构,无数据成员、无具体实现。抽象类可以有数据成员、非虚函数、部分实现。

小结

  • = 0 声明纯虚函数,使类成为抽象类(不可实例化)
  • 抽象类定义接口规范,派生类必须实现所有纯虚函数
  • 纯虚析构必须有类外定义——它在派生类析构时被调用
  • 接口类 = 全部纯虚 + 无数据成员 + 虚析构
  • 面向接口编程降低耦合——调用者只依赖抽象而非具体实现
上一页
虚析构函数
下一页
多重继承与虚继承