文章

c++高频总结五

Cpp新特性知道哪些

C++ 自 C++11 开始引入了大量的新特性,每个标准版本都在语言和库的各方面进行了增强。以下是各个 C++ 标准(从 C++11 到 C++23)的重要新特性总结:


C++11(核心特性升级)

1. 语言特性

  • 自动类型推导 (auto): 自动推导变量类型。

    1
    
    auto x = 42;  // x 推导为 int
    
  • 右值引用和移动语义: 用于优化临时对象的资源管理,std::move 可显式调用。

    1
    2
    
    std::string s = "hello";
    std::string t = std::move(s);  // s 的资源转移给 t
    
  • 初始化列表: 方便地对容器或对象进行初始化。

    1
    
    std::vector<int> vec = {1, 2, 3};
    
  • Lambda 表达式: 提供匿名函数支持。

    1
    
    auto add = [](int a, int b) { return a + b; };
    
  • nullptr: 统一的空指针类型,取代 NULL

    1
    
    int* ptr = nullptr;
    
  • 强类型枚举 (enum class): 避免普通枚举类型的作用域污染。

    1
    
    enum class Color { Red, Green, Blue };
    
  • 关键字 constexpr: 支持编译期计算的常量表达式。

    1
    
    constexpr int square(int x) { return x * x; }
    
  • 线程支持: 引入 std::threadstd::mutex 等多线程支持。

    1
    2
    
    std::thread t([] { std::cout << "Hello, thread!"; });
    t.join();
    
  • decltype: 用于获取表达式的类型。

    1
    2
    
    int x = 0;
    decltype(x) y = 42;  // y 的类型是 int
    
  • 静态断言 (static_assert): 编译期检查条件。

    1
    
    static_assert(sizeof(int) == 4, "int size must be 4 bytes");
    

2. 标准库特性

  • 智能指针: 引入 std::shared_ptrstd::unique_ptrstd::weak_ptr

    1
    
    std::shared_ptr<int> p = std::make_shared<int>(10);
    
  • 容器改进

    • std::array(静态数组容器)。
    • std::unordered_mapstd::unordered_set(基于哈希表)。
  • 正则表达式: 提供正则匹配支持。

    1
    
    std::regex re("\\w+");
    

C++14(增强和优化)

1. 语言特性

  • 泛型 Lambda 表达式: Lambda 支持自动类型推导。

    1
    
    auto lambda = [](auto a, auto b) { return a + b; };
    
  • decltype(auto): 根据返回值类型进行推导。

    1
    
    decltype(auto) func() { int x = 42; return (x); }
    
  • 二进制字面值: 提供二进制数支持。

    1
    
    int bin = 0b1010;  // 等于 10
    
  • constexpr 的增强: 支持更复杂的逻辑。

    1
    2
    3
    
    constexpr int factorial(int n) {
        return n <= 1 ? 1 : (n * factorial(n - 1));
    }
    

C++17(简化与增强)

1. 语言特性

  • 结构化绑定: 解构赋值。

    1
    
    auto [a, b] = std::pair<int, int>{1, 2};
    
  • if constexpr: 编译期条件分支。

    1
    2
    3
    
    if constexpr (sizeof(int) == 4) {
        std::cout << "int is 4 bytes";
    }
    
  • 内联变量: 允许在头文件中声明具有唯一定义的变量。

    1
    
    inline int global_value = 42;
    
  • 折叠表达式: 用于参数包的累积操作。

    1
    2
    
    template <typename... Args>
    auto sum(Args... args) { return (... + args); }
    

2. 标准库特性

  • std::optional: 用于存储可能为空的值。

    1
    
    std::optional<int> opt = 42;
    
  • std::variant: 安全的联合类型。

    1
    
    std::variant<int, std::string> var = "Hello";
    
  • std::any: 用于存储任意类型的值。

    1
    
    std::any val = 10;
    
  • 并行算法: 标准算法支持并行执行。

    1
    2
    
    std::vector<int> vec = {1, 2, 3};
    std::sort(std::execution::par, vec.begin(), vec.end());
    

C++20(革命性升级)

1. 语言特性

  • 模块(Modules): 替代传统头文件的模块化功能。

  • 概念(Concepts): 用于约束模板参数。

    1
    2
    
    template <typename T>
    concept Integral = std::is_integral_v<T>;
    
  • 协程(Coroutines): 支持异步操作。

    1
    
    co_return 42;
    
  • 范围库(Ranges): 提供强大的范围操作。

    1
    2
    
    std::vector<int> v = {1, 2, 3};
    auto res = v | std::views::filter([](int i) { return i % 2 == 0; });
    
  • constexpr 的进一步增强: 支持更多场景,例如动态分配。


C++23(现代化补充)

1. 语言特性

  • 多维数组的支持: 引入 mdspan,更方便处理多维数组。
  • 静默终止的范围 (std::ranges::to): 提供更直观的范围转换。
  • 新语法和优化
    • std::print 提供更简便的格式化输出。
    • constexpr 支持更强大的计算能力。

总结

版本核心特性
C++11Lambda 表达式、右值引用、智能指针、线程支持、autoconstexpr
C++14泛型 Lambda、decltype(auto)、二进制字面值、增强的 constexpr
C++17结构化绑定、if constexprstd::optionalstd::variant、并行算法。
C++20模块、概念、协程、范围库(Ranges)、增强的 constexprstd::format
C++23多维数组支持、新范围特性、更简化的输出函数等。

每个版本的新增特性使 C++ 更加现代化、易用,同时提升了性能和开发效率!

类型转换

在 C++ 中,类型转换是将一种数据类型转换为另一种数据类型的操作。C++ 提供了多种类型转换方式,包括隐式类型转换、显式类型转换(也称为强制类型转换),以及 C++ 特有的四种类型转换运算符(static_castdynamic_castconst_castreinterpret_cast)。

以下是对类型转换的详细介绍:


1. 隐式类型转换(Implicit Type Conversion)

隐式类型转换是编译器自动执行的类型转换,无需程序员显式指定。也称为类型提升类型兼容

规则

  1. 算术类型的转换

    • 小范围类型会提升到大范围类型,例如 char 转换为 int
    • 浮点数和整数混合运算时,整数会转换为浮点数。
    1
    2
    3
    
    int a = 10;
    double b = 3.14;
    double c = a + b;  // a 自动转换为 double
    
  2. 指针的转换

    • 非常量指针可以隐式转换为常量指针。
    • 空指针常量(如 nullptr)可以转换为任意指针类型。
    1
    2
    
    int x = 42;
    const int* p = &x;  // 非常量指针转为常量指针
    

2. 显式类型转换(Explicit Type Conversion)

显式类型转换是通过程序员明确指示编译器进行的类型转换,也称为强制类型转换

C 风格的类型转换

1
(type) expression

这是 C 风格的强制类型转换,灵活但容易导致错误。

例子

1
2
int a = 10;
double b = (double)a;  // C 风格的类型转换

缺点

  • 不易读,难以定位问题。
  • 不区分不同类型的转换意图,容易导致不安全的操作。

3. C++ 类型转换运算符

C++ 提供了四种类型转换运算符,分别针对不同的场景,提供了更明确和更安全的转换方式:


(1) static_cast

  • 用于在类型兼容的情况下进行明确的类型转换。
  • 通常用于基本类型之间的转换、类层次结构中的向上转换(从派生类到基类)等。

语法

1
static_cast<type>(expression)

例子

1
2
3
4
5
6
int a = 10;
double b = static_cast<double>(a);  // 安全的类型转换

class Base {};
class Derived : public Base {};
Base* base = static_cast<Base*>(new Derived());  // 向上转换

特点

  • 编译时完成类型检查,安全性高。
  • 不允许非相关类型之间的转换。

(2) dynamic_cast

  • 用于多态类型(即存在虚函数的类)之间的类型转换。
  • 通常用于向下转换(从基类到派生类),并在运行时进行类型检查。

语法

1
dynamic_cast<type>(expression)

例子

1
2
3
4
5
6
7
8
9
10
11
class Base {
    virtual void func() {}
};

class Derived : public Base {};

Base* base = new Derived();
Derived* derived = dynamic_cast<Derived*>(base);  // 向下转换
if (derived) {
    // 转换成功
}

特点

  • 运行时检查类型的合法性,不合法时返回 nullptr(指针)或抛出异常(引用)。
  • 必须用于包含虚函数的类。

(3) const_cast

  • 用于移除或添加对象的 const 限定。
  • 常用于需要修改常量对象时(但需谨慎使用)。

语法

1
const_cast<type>(expression)

例子

1
2
3
const int a = 10;
int* p = const_cast<int*>(&a);  // 移除 const 限定
*p = 20;  // 未定义行为(如果 a 原本是常量)

特点

  • 只能改变对象的 constvolatile 限定符,不影响其他类型转换。
  • 修改真正的常量对象会导致未定义行为。

(4) reinterpret_cast

  • 用于将指针或引用类型转换为完全不相关的类型。
  • 这是最低层次、最危险的类型转换。

语法

1
reinterpret_cast<type>(expression)

例子

1
2
3
int a = 10;
void* p = reinterpret_cast<void*>(&a);  // 转为 void*
int* p2 = reinterpret_cast<int*>(p);   // 再转换回 int*

特点

  • 没有类型检查,不保证结果的可用性。
  • 主要用于操作底层数据结构,例如与硬件相关的编程。

4. 类型转换的优先级

类型转换按以下优先级进行:

  1. C++ 类型转换运算符(如 static_cast)优先于 C 风格的强制类型转换。
  2. 隐式类型转换优先于显式类型转换。

5. 使用场景

转换类型使用场景
static_cast基本类型转换、类层次结构中安全的向上转换。
dynamic_cast类层次结构中带运行时检查的向下转换,通常用于多态场景。
const_cast移除或添加 const 限定,通常用于修正 API 的限制。
reinterpret_cast指针或引用的底层转换,通常用于低级编程或与 C 接口交互。

6. 注意事项

  1. 避免滥用类型转换
    • 使用类型转换可能隐藏代码中的设计缺陷。优先考虑重构代码,而非频繁使用类型转换。
  2. 谨慎使用 reinterpret_castconst_cast
    • 它们不进行类型检查,可能导致未定义行为。
  3. 优先使用 C++ 风格的类型转换
    • C 风格的类型转换可能不安全,也难以阅读。
  4. dynamic_cast 需要虚函数
    • 如果类中没有虚函数,dynamic_cast 将无法使用。

总结

转换方式类型检查转换时间用途
隐式转换自动编译时类型兼容时的自动转换。
C 风格转换编译时不推荐,容易引入隐患。
static_cast编译时安全的显式转换,用于兼容类型之间的转换。
dynamic_cast运行时多态类型之间的向下转换,带运行时检查。
const_cast编译时添加或移除 constvolatile
reinterpret_cast编译时最低级别的转换,用于底层操作。

选择合适的类型转换方式,可以提高代码的安全性和可维护性!

RAII基于什么实现的

RAII(Resource Acquisition Is Initialization,资源获取即初始化)是一种 C++ 编程中广泛使用的资源管理惯用法。它的核心思想是将资源的生命周期绑定到对象的生命周期,通过对象的构造函数获取资源,在对象的析构函数中释放资源。

RAII 的实现基于以下两个核心特性:


1. 构造函数和析构函数

RAII 的核心依赖于 C++ 的构造函数和析构函数的机制:

  • 构造函数:在对象创建时自动调用,用于初始化对象并获取所需资源(如内存、文件句柄、互斥锁等)。
  • 析构函数:在对象销毁时自动调用,用于释放对象占用的资源。

例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>
#include <fstream>

class FileRAII {
public:
    FileRAII(const std::string& filename) {
        file.open(filename);
        if (!file.is_open()) {
            throw std::runtime_error("Failed to open file");
        }
    }

    ~FileRAII() {
        if (file.is_open()) {
            file.close();
        }
    }

private:
    std::ofstream file;
};

int main() {
    try {
        FileRAII fileManager("example.txt");
        // 文件在此作用域内被管理
    } // fileManager 的析构函数会在这里关闭文件
    catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }
    return 0;
}

在上述例子中:

  • 构造函数:在对象创建时打开文件。
  • 析构函数:在对象销毁时自动关闭文件,即使发生异常也会自动释放资源。

2. 栈对象的生命周期管理

RAII 的资源管理依赖于 C++ 栈对象的作用域规则:

  • 当栈对象超出其作用域时,编译器会自动调用该对象的析构函数。
  • 这种自动销毁机制确保资源总能被正确释放,即使在异常情况下也是如此。

例子

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <mutex>

std::mutex mtx;

void criticalSection() {
    std::lock_guard<std::mutex> lock(mtx);  // RAII 管理锁
    // 代码在此区域内操作临界区
    // 离开作用域时 lock_guard 会自动释放锁
    std::cout << "In critical section" << std::endl;
}

在这个例子中,std::lock_guard 是一个 RAII 类型,它在构造时加锁,在析构时自动解锁。


RAII 的核心特性

  1. 自动化资源管理: RAII 通过构造函数和析构函数自动管理资源的获取和释放,减少手动管理资源的负担。
  2. 异常安全性: 即使在异常情况下,RAII 对象的析构函数仍会被调用,确保资源不会泄露。
  3. 作用域绑定: RAII 将资源的生命周期绑定到对象的作用域,从而实现资源的自动释放。

RAII 的实现依赖哪些语言特性

  1. 构造函数和析构函数
    • 构造函数用于获取资源。
    • 析构函数用于释放资源。
  2. 栈对象的作用域规则: 栈对象超出作用域时,析构函数会被自动调用。
  3. 异常处理机制: RAII 能有效处理异常情况下的资源释放问题,因为析构函数始终会被调用。
  4. 封装: RAII 通常通过类封装资源及其管理逻辑。

RAII 的典型应用场景

  1. 智能指针

    • std::unique_ptr:独占管理动态内存。
    • std::shared_ptr:共享管理动态内存。
    • std::weak_ptr:避免循环引用。
    1
    
    std::unique_ptr<int> ptr(new int(10));  // 自动释放内存
    
  2. 文件管理: 自动打开和关闭文件。

  3. 锁管理: 使用 std::lock_guardstd::unique_lock 管理多线程互斥锁。

  4. 网络资源: 自动管理网络连接的打开和关闭。

  5. 数据库连接: 管理数据库连接的生命周期。


RAII 的优点

  1. 资源安全: 自动释放资源,减少内存泄漏和资源泄露。
  2. 代码简洁: 不需要显式调用资源释放函数,代码更直观。
  3. 异常安全: 即使发生异常,资源也能被安全释放。
  4. 与 STL 配合良好: RAII 机制与现代 C++ 标准库中的容器和算法高度兼容。

RAII 的局限性

  1. 不适合非作用域生命周期的资源: 如果资源的生命周期超出作用域范围,RAII 可能无法有效管理。
  2. 需要小心封装: 不恰当的封装可能导致析构函数误释放资源。
  3. 过于依赖对象销毁的时间点: 在某些情况下(如性能要求),依赖作用域可能导致资源释放的时机不够灵活。

RAII 与现代 C++ 的结合

现代 C++(尤其是 C++11 和之后的标准)为 RAII 提供了更强大的支持,如:

  • 智能指针std::unique_ptr, std::shared_ptr)。
  • 锁管理类std::lock_guard, std::unique_lock)。
  • 容器类std::vector, std::string 等)。

这些工具都充分利用了 RAII 的思想,将资源的管理变得更加简单和安全。


总结

RAII 的实现依赖于 构造函数和析构函数 的自动调用机制,以及 C++ 对象的作用域规则。它是一种极为重要的编程惯用法,确保资源在异常或正常情况下都能被正确管理,在现代 C++ 中得到了广泛应用和支持。

手撕:Unique_ptr,控制权转移(移动语义)

以下是一个手写实现 std::unique_ptr 的例子,包含核心功能(如控制权转移和移动语义),以帮助理解其工作原理。


Unique_ptr 的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#include <iostream>
#include <utility> // std::move

// 定义 Unique_ptr 模板类
template <typename T>
class Unique_ptr {
private:
    T* ptr; // 原始指针,管理资源

public:
    // 构造函数:接收一个指针
    explicit Unique_ptr(T* p = nullptr) : ptr(p) {}

    // 禁用拷贝构造函数
    Unique_ptr(const Unique_ptr& other) = delete;

    // 禁用拷贝赋值运算符
    Unique_ptr& operator=(const Unique_ptr& other) = delete;

    // 移动构造函数:转移所有权
    Unique_ptr(Unique_ptr&& other) noexcept : ptr(other.ptr) {
        other.ptr = nullptr; // 清空原对象的指针,避免重复释放
    }

    // 移动赋值运算符:转移所有权
    Unique_ptr& operator=(Unique_ptr&& other) noexcept {
        if (this != &other) {
            delete ptr;        // 释放当前持有的资源
            ptr = other.ptr;   // 转移指针
            other.ptr = nullptr; // 清空原对象的指针
        }
        return *this;
    }

    // 析构函数:释放资源
    ~Unique_ptr() {
        delete ptr;
    }

    // 重载 `*` 运算符:解引用
    T& operator*() const {
        return *ptr;
    }

    // 重载 `->` 运算符:访问成员
    T* operator->() const {
        return ptr;
    }

    // 获取指针
    T* get() const {
        return ptr;
    }

    // 释放控制权并返回指针
    T* release() {
        T* temp = ptr;
        ptr = nullptr; // 清空当前指针
        return temp;
    }

    // 重置指针
    void reset(T* p = nullptr) {
        delete ptr;  // 删除旧的资源
        ptr = p;     // 设置新的资源
    }
};

功能实现说明

1. 控制权转移(移动语义)

  • 移动构造函数:允许通过 std::move 将资源从一个 Unique_ptr 转移到另一个对象。
  • 移动赋值运算符:实现资源的转移,同时释放当前对象持有的资源。
  • 关键点:
    • 转移所有权后,原对象的指针必须置为 nullptr,以防止重复释放资源。

2. 资源管理

  • 构造函数:通过一个原始指针初始化 Unique_ptr
  • 析构函数:在 Unique_ptr 对象销毁时自动释放资源。
  • resetrelease
    • reset 用于替换管理的资源。
    • release 用于放弃对资源的管理并返回原始指针。

测试代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
int main() {
    // 创建一个 Unique_ptr 管理整数
    Unique_ptr<int> uptr1(new int(42));
    std::cout << "Value: " << *uptr1 << std::endl;

    // 转移控制权
    Unique_ptr<int> uptr2 = std::move(uptr1);
    if (!uptr1.get()) {
        std::cout << "uptr1 is empty after move." << std::endl;
    }
    std::cout << "uptr2 value: " << *uptr2 << std::endl;

    // 使用 release() 放弃资源管理
    int* rawPtr = uptr2.release();
    if (!uptr2.get()) {
        std::cout << "uptr2 is empty after release." << std::endl;
    }
    std::cout << "Raw pointer value: " << *rawPtr << std::endl;

    // 手动释放原始指针资源
    delete rawPtr;

    // 使用 reset() 管理新资源
    uptr2.reset(new int(88));
    std::cout << "uptr2 new value: " << *uptr2 << std::endl;

    return 0;
}

测试结果

1
2
3
4
5
6
Value: 42
uptr1 is empty after move.
uptr2 value: 42
uptr2 is empty after release.
Raw pointer value: 42
uptr2 new value: 88

关键点总结

  1. 禁止拷贝
    • Unique_ptr 不允许拷贝(删除拷贝构造和赋值运算符)。
    • 通过移动语义实现控制权的转移。
  2. 自动资源释放
    • Unique_ptr 销毁时,析构函数自动释放资源,避免内存泄漏。
  3. 安全管理
    • 原指针在转移或释放后被置为 nullptr,避免重复释放。
  4. 功能扩展
    • 支持 release()reset(),以更灵活地管理资源。

这就是一个简化版的 Unique_ptr 实现,展示了其核心功能及其与移动语义的结合!

手撕:类继承,堆栈上分别代码实现多态

以下是关于 类继承多态 的代码实现,分别展示在 上如何实现多态。


1. 基本概念

  • 继承:通过基类(父类)定义通用接口或功能,派生类(子类)可以扩展或重写这些功能。
  • 多态:使用基类指针或引用调用子类的实现,行为取决于运行时的对象类型。

多态的实现依赖于以下两个关键点:

  1. 虚函数(virtual:在基类中定义虚函数,允许派生类重写。
  2. 运行时类型信息(RTTI):通过虚表(vtable)实现动态绑定,确保调用的函数与对象实际类型匹配。

2. 在堆上实现多态

在堆上创建对象,并通过基类指针操作,实现多态。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#include <iostream>
#include <memory> // 使用智能指针,防止内存泄漏

// 基类
class Shape {
public:
    virtual void draw() const { // 虚函数
        std::cout << "Drawing Shape" << std::endl;
    }
    virtual ~Shape() = default; // 虚析构函数,确保子类析构函数被正确调用
};

// 派生类:圆形
class Circle : public Shape {
public:
    void draw() const override { // 重写虚函数
        std::cout << "Drawing Circle" << std::endl;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
public:
    void draw() const override { // 重写虚函数
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

int main() {
    // 在堆上分配对象
    std::unique_ptr<Shape> shape1 = std::make_unique<Circle>();
    std::unique_ptr<Shape> shape2 = std::make_unique<Rectangle>();

    // 调用虚函数,基类指针调用子类的实现
    shape1->draw(); // 输出:Drawing Circle
    shape2->draw(); // 输出:Drawing Rectangle

    return 0;
}

关键点

  1. 使用 virtual 定义虚函数,支持运行时动态绑定。
  2. 使用智能指针(如 std::unique_ptr),自动管理堆内存,避免内存泄漏。
  3. 虚析构函数保证派生类的析构函数被正确调用。

3. 在栈上实现多态

在栈上创建对象,利用基类引用实现多态。

代码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
#include <iostream>

// 基类
class Shape {
public:
    virtual void draw() const { // 虚函数
        std::cout << "Drawing Shape" << std::endl;
    }
    virtual ~Shape() = default; // 虚析构函数
};

// 派生类:圆形
class Circle : public Shape {
public:
    void draw() const override { // 重写虚函数
        std::cout << "Drawing Circle" << std::endl;
    }
};

// 派生类:矩形
class Rectangle : public Shape {
public:
    void draw() const override { // 重写虚函数
        std::cout << "Drawing Rectangle" << std::endl;
    }
};

int main() {
    // 在栈上创建派生类对象
    Circle circle;
    Rectangle rectangle;

    // 使用基类引用实现多态
    Shape& shape1 = circle;
    Shape& shape2 = rectangle;

    // 调用虚函数,基类引用调用子类的实现
    shape1.draw(); // 输出:Drawing Circle
    shape2.draw(); // 输出:Drawing Rectangle

    return 0;
}

关键点

  1. 使用基类引用(Shape&)操作派生类对象,实现多态。
  2. 栈上的对象在作用域结束时自动销毁,无需手动释放。

4. 区别总结

特性堆上实现多态栈上实现多态
内存管理动态分配,需要手动或智能指针管理资源。自动分配,由作用域管理资源。
使用成本内存分配和释放有一定开销。无额外开销,自动管理更高效。
适用场景对象生命周期复杂或大小动态确定时使用。对象生命周期确定且大小固定时使用。
安全性可能会导致内存泄漏(使用智能指针避免)。无需担心内存泄漏。
灵活性可以动态决定创建的对象类型。类型必须在编译时确定。

5. 总结

  • 堆上多态适合复杂场景,例如动态确定对象类型或对象需要在作用域之外存在。
  • 栈上多态适合简单场景,例如对象生命周期短且确定。

两种方式都依赖 C++ 的 虚函数机制动态绑定 实现多态。在实际开发中,根据具体需求选择合适的实现方式即可。

本文由作者按照 CC BY 4.0 进行授权