Skip to content

C++ 高级特性速览

本文档是对 C++ 高级特性的系统总结,覆盖:类与对象、运算符重载、移动语义与智能指针、继承与多态、模板与异常、标准库 STL 六大板块。每部分包含核心知识点、代表性示例和易错点。适合已经掌握 C++ 基础语法的开发者快速回顾。

编译环境建议:C++20 及以上(g++ -std=c++20 或 VS2022 /std:c++20)。


一、类与对象(Classes & Objects)

核心知识点

  • enum / enum class:枚举给整数值命名。现代 C++ 优先用 enum class(类型安全、不污染外层作用域,不会隐式转 int)。
  • struct vs class:唯一区别是默认访问级别——struct 默认 publicclass 默认 private。纯数据的聚合用 struct,有封装需求用 class
  • 访问控制public(谁都能访问)、private(仅成员和 friend)、protected(派生类可见,外部不可见)。数据成员几乎总是 private,通过接口操作。
  • 构造函数:与类同名、无返回类型。默认构造函数、带参构造、委托构造(Date() : Date{...})。
  • 初始化列表Date(int y) : m_year{y} {}。const 成员和引用成员必须用初始化列表;成员按声明顺序初始化,不是列表书写顺序。
  • 析构函数:对象销毁时自动调用,释放资源。析构顺序与构造相反(栈对象后进先出)。
  • this 指针:指向当前对象。链式调用靠 return *this;(返回引用)。
  • const 成员函数int get() const; 承诺不修改对象。const 对象只能调 const 成员函数。getter 一律加 const。
  • static 成员:属于类不属于对象,所有对象共享。inline static int count{};(C++17 类内定义)。static 函数没有 this,只能访问 static 成员。
  • 拷贝 / 移动:默认拷贝是浅拷贝(指针成员共享内存 → double-free)。深拷贝需要自定义拷贝构造/赋值。移动是"偷资源 + 源置空",O(1)。
  • Rule of Five / Zero:需要自定义析构/拷贝时,通常五个特殊成员都要考虑。不管理资源的类一个都不写(Rule of Zero),交给 std::string/std::vector 成员自动管理。

实例

cpp
class BankAccount {
public:
    BankAccount() = default;
    BankAccount(double initial) : m_balance{ initial } {
        if (m_balance < 0) m_balance = 0;   // 构造函数里校验不变量
    }
    double getBalance() const { return m_balance; }   // const getter
    void deposit(double amount) { if (amount >= 0) m_balance += amount; }
    bool withdraw(double amount) {
        if (amount > m_balance) return false;
        m_balance -= amount;
        return true;
    }
private:
    double m_balance{};
};

易错点

  1. class 默认 private,写类忘加 public: 是常见错误。
  2. 声明了带参构造函数后,默认构造函数不再隐式生成,需要就写 = default
  3. const 成员/引用成员必须在初始化列表赋值,不能在函数体里。
  4. 成员按声明顺序初始化,初始化列表顺序不一致会告警且有风险。
  5. 写循环要注意循环体是否真的用了循环变量。

二、运算符重载(Operator Overloading)

核心知识点

  • 本质operator+ 只是名字特殊的函数。a + b 翻译成 operator+(a, b)(非成员)或 a.operator+(b)(成员)。
  • 三条铁律:不能发明新运算符;不能改优先级;不能改操作数个数。
  • 成员 vs 非成员
    • []++/-- 必须是成员。
    • <</>> 必须非成员 + friend(左操作数是流对象)。
    • 混合运算(int + Money)必须非成员,才能让 int 在左边。
    • 比较运算符惯例用非成员 friend,保持对称。
  • 流运算符operator<< 返回 std::ostream& 才能链式 cout << a << b;通常 friend 访问私有成员。
  • 前置 vs 后置 ++++aoperator++()a++operator++(int)(int 哑元)。后置要先存旧值再自增返回,性能略差。
  • 下标operator[] 必须成员,做 const/非 const 双版本(int& vs const int&)。
  • 不能重载::..*?:sizeof

实例

cpp
class Vec2 {
public:
    Vec2(double x, double y) : m_x{x}, m_y{y} {}
    friend Vec2 operator+(const Vec2& a, const Vec2& b);          // 算术
    friend bool operator==(const Vec2& a, const Vec2& b);         // 比较
    friend std::ostream& operator<<(std::ostream& out, const Vec2& v);  // 流
private:
    double m_x{}, m_y{};
};

Vec2 operator+(const Vec2& a, const Vec2& b) { return Vec2{a.m_x+b.m_x, a.m_y+b.m_y}; }
bool operator==(const Vec2& a, const Vec2& b) { return a.m_x==b.m_x && a.m_y==b.m_y; }
std::ostream& operator<<(std::ostream& out, const Vec2& v) {
    out << '(' << v.m_x << ", " << v.m_y << ')';
    return out;
}
// 使用: std::cout << (Vec2{1,2} + Vec2{3,4});   // (4, 6)

易错点

  1. <</>> 忘记返回流引用,链式失效。
  2. 重载 &&/|| 会丢失短路求值,别碰。
  3. != 可以直接 !(a == b) 复用。
  4. 一元负号返回新对象,不修改原对象。

三、移动语义与智能指针(Move Semantics & Smart Pointers)

核心知识点

  • 左值 vs 右值:左值有名字、可寻址;右值是临时值、快用完。右值可以"偷资源"。
  • 移动构造/赋值IntBuffer(IntBuffer&& other)——偷指针 + 把源置空(nullptr)。不分配内存,O(1)。必须标 noexcept(vector 扩容依赖)。
  • std::move:不真正移动,只是把左值转成右值引用,让重载解析选移动版本。移动后源对象"有效但未指定"。
  • RAII:资源在构造获取、析构释放。即使异常/提前 return,析构也保证执行,资源不泄漏。这是 C++ 资源管理的基石。
  • std::unique_ptr:独占所有权,不可拷贝、可移动。make_unique 创建。
  • std::shared_ptr:共享所有权,引用计数,最后一个离开时释放。make_shared 创建。
  • std::weak_ptr:只观察不拥有,不参与计数。lock() 尝试提升为 shared_ptr。用于打破循环引用。
  • 经验:能用 unique_ptr 就不用 shared_ptr;用 make_* 优先于裸 new;别用两个智能指针托管同一裸指针。

实例

cpp
#include <memory>
#include <vector>

class IntBuffer {
public:
    explicit IntBuffer(int size) : m_size{size}, m_data{new int[size]{}} {}
    ~IntBuffer() { delete[] m_data; }
    IntBuffer(IntBuffer&& other) noexcept : m_size{other.m_size}, m_data{other.m_data} {
        other.m_data = nullptr;   // 源置空,防 double-free
        other.m_size = 0;
    }
private:
    int m_size{};
    int* m_data{};
};

// 智能指针
auto p{ std::make_unique<int>(42) };                    // 独占
auto s{ std::make_shared<std::string>("hi") };          // 共享,计数
std::weak_ptr<std::string> w{ s };                      // 观察
std::vector<std::unique_ptr<Monster>> monsters;         // 容器装多态对象
monsters.push_back(std::make_unique<Monster>("Dragon"));

易错点

  1. 移动后必须把源置空,否则两个析构 double-free。
  2. std::move 后别再使用源的资源。
  3. shared_ptr 循环引用会泄漏,用 weak_ptr 打破。
  4. 传"借用"用裸指针/引用,传"拥有"用智能指针。

四、继承与多态(Inheritance & Polymorphism)

核心知识点

  • 继承class Dog : public Animal 表达 is-a 关系。public 继承最常用。
  • 访问级别:基类 private 成员派生类永远不能直接访问;protected 派生类可见、外部不可见。
  • 构造顺序:从最基类到最派生类(Base → Mid → Leaf);析构完全相反。像盖楼/拆楼。
  • 基类构造初始化:基类无默认构造时,派生类必须 Derived(...) : Base{...} 显式初始化基类部分。
  • 虚函数:基类函数标 virtual,调用时动态绑定到真实类型。派生类重写用 override(编译器检查签名)。
  • 多态:通过基类引用/指针调用虚函数,走真实类型。必须传引用/指针,按值传会切片
  • 虚析构:类有虚函数时,基类析构必须 virtual,否则通过基类指针 delete 派生类会泄漏。
  • 纯虚函数与抽象类virtual void area() const = 0; 让类变抽象,不能实例化,强迫派生类实现。抽象基类定义"接口契约"。
  • is-a vs has-a:B 是 A 吗?是 → 继承;不是(只是拥有/使用)→ 组合/聚合。

实例

cpp
class Animal {
public:
    virtual void speak() const = 0;   // 纯虚 → 抽象类
    virtual ~Animal() = default;      // 虚析构
};
class Dog : public Animal {
public:
    void speak() const override { std::cout << "woof\n"; }
};
class Cat : public Animal {
public:
    void speak() const override { std::cout << "meow\n"; }
};

void makeSpeak(const Animal& a) { a.speak(); }   // 多态:必须传引用

Dog d; Cat c;
makeSpeak(d);   // woof
makeSpeak(c);   // meow

易错点

  1. 多态必须通过引用/指针,按值传基类会切片。
  2. 重写记得 override,签名错了编译器能拦住。
  3. 基类析构必须 virtual。
  4. 构造顺序基类先、析构顺序派生类先。

五、模板与异常(Templates & Exceptions)

核心知识点

  • 函数模板template <typename T> T max(T a, T b)——一份代码通吃所有类型。实例化时才编译,T 必须支持用到的操作。
  • 类模板template <typename T> class Box,实例化写 Box<int>。类外成员定义要 template <typename T> + Box<T>::
  • auto 返回类型:不同类型参数用 auto add(T a, U b) 推导返回类型。
  • 非类型参数template <typename T, int N>,N 是编译期常量,std::array 的原理。
  • 模板特化template <> 对特定类型给特殊实现。
  • 模板必须放头文件:模板是"生成函数的图纸",定义在使用处或头文件,否则链接错误。
  • 异常机制throw 抛异常、try/catch 接住。异常沿调用栈向上传播,沿途局部对象自动析构(栈展开)。
  • 自定义异常:继承 std::runtime_errorwhat() 取消息;按引用捕获 const std::exception& 接所有标准异常。
  • 标准异常std::invalid_argumentstd::out_of_rangestd::runtime_error 等(<stdexcept>)。
  • 异常安全:先检查后修改(withdraw 先判余额再扣款),保证抛异常时状态不被破坏。

实例

cpp
// 模板
template <typename T>
class Pair {
public:
    Pair(T first, T second) : m_first{first}, m_second{second} {}
    T first() const { return m_first; }
private:
    T m_first{};
    T m_second{};
};
Pair<int, double> p{ 42, 3.14 };

// 异常
class MathError : public std::runtime_error {
public:
    MathError(const std::string& msg) : std::runtime_error{msg} {}
};
double divide(double a, double b) {
    if (b == 0.0) throw MathError{ "division by zero" };
    return a / b;
}
try {
    std::cout << divide(5.0, 0.0);
} catch (const std::exception& e) {
    std::cerr << "错误: " << e.what() << '\n';
}

易错点

  1. 模板定义必须在使用处或头文件。
  2. 捕获多态异常按引用(值捕获会切片)。
  3. catch (...) 兜底必须放最后。
  4. 析构函数不要抛异常(会 terminate)。

六、标准库 STL(Standard Library)

核心知识点

  • std::string:可变字符串,支持拼接、find(找不到返回 npos)、substrto_string/stoi 数字互转。
  • std::string_view:只读视图,不拥有内存,零拷贝。只读参数首选。不能返回指向局部临时对象的 view(悬垂)。
  • 容器
    • vector:动态数组,默认选择。容量满时翻倍搬移,reserve 预分配。
    • array:编译期定长,栈上。
    • map/set:有序,平衡树,O(log n)。[] 访问不存在键会插入默认值。
    • unordered_map:哈希,O(1) 平均。
    • list/deque:链表/双端队列,用于特殊插入需求。
  • 迭代器:统一访问容器的方式,区间 [begin, end) 左闭右开。end() 不指向元素。
  • 算法<algorithm>):sortfind/find_ifcount/count_iftransformmax_elementaccumulate<numeric>)等,都吃迭代器区间。
  • lambda:就地定义函数。[捕获](参数){函数体}。捕获按值 [x](快照)或按引用 [&x]。用于算法谓词和排序规则。
  • I/Ocout/cin 格式化(setwsetprecisionfixed)、文件读写(ifstream/ofstream)、stringstream(字符串 ↔ 数据解析)。

实例

cpp
#include <algorithm>
#include <numeric>
#include <string>
#include <vector>

std::vector<int> nums{ 5, 3, 8, 1, 9, 2, 7 };
std::sort(nums.begin(), nums.end());   // 升序
auto it{ std::find(nums.begin(), nums.end(), 8) };       // 查找
int sum{ std::accumulate(nums.begin(), nums.end(), 0) }; // 求和

// lambda 自定义规则
auto firstEven{ std::find_if(nums.begin(), nums.end(),
                             [](int x) { return x % 2 == 0; }) };

// 单词统计(map + istringstream)
std::string line{ "the quick brown fox the" };
std::istringstream iss{ line };
std::map<std::string, int> freq;
std::string word;
while (iss >> word) ++freq[word];   // the:2 quick:1 brown:1 fox:1

易错点

  1. 用到就包含头文件(<vector><algorithm><memory><string> 各管各的)。
  2. find 找不到返回 end(),用前判 != end()
  3. map[] 读不存在键会插入默认值,只想查用 find/contains
  4. list 没有下标,用迭代器 + std::next
  5. lambda 按值捕获是快照,要改外部变量用 [&]
  6. range-for 默认值拷贝,要改原元素用 for (auto& x : v)

总结

板块核心
类与对象封装数据 + 行为,构造函数维护不变量
运算符重载让自定义类型用起来像内置类型
移动语义与智能指针所有权高效转移,RAII 自动管理资源
继承与多态is-a 关系 + 虚函数动态绑定
模板与异常类型参数化 + 错误处理解耦
标准库 STL容器存数据 + 算法处理数据 + lambda 定制规则

现代 C++ 的黄金组合:RAII + 智能指针 + 容器/算法 + lambda——写安全、高效、简洁的代码。