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默认public,class默认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{};
};易错点
class默认 private,写类忘加public:是常见错误。- 声明了带参构造函数后,默认构造函数不再隐式生成,需要就写
= default。 - const 成员/引用成员必须在初始化列表赋值,不能在函数体里。
- 成员按声明顺序初始化,初始化列表顺序不一致会告警且有风险。
- 写循环要注意循环体是否真的用了循环变量。
二、运算符重载(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 后置
++:++a调operator++(),a++调operator++(int)(int 哑元)。后置要先存旧值再自增返回,性能略差。 - 下标:
operator[]必须成员,做 const/非 const 双版本(int&vsconst 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)易错点
<</>>忘记返回流引用,链式失效。- 重载
&&/||会丢失短路求值,别碰。 !=可以直接!(a == b)复用。- 一元负号返回新对象,不修改原对象。
三、移动语义与智能指针(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"));易错点
- 移动后必须把源置空,否则两个析构 double-free。
std::move后别再使用源的资源。- shared_ptr 循环引用会泄漏,用 weak_ptr 打破。
- 传"借用"用裸指针/引用,传"拥有"用智能指针。
四、继承与多态(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易错点
- 多态必须通过引用/指针,按值传基类会切片。
- 重写记得
override,签名错了编译器能拦住。 - 基类析构必须 virtual。
- 构造顺序基类先、析构顺序派生类先。
五、模板与异常(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_error,what()取消息;按引用捕获const std::exception&接所有标准异常。 - 标准异常:
std::invalid_argument、std::out_of_range、std::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';
}易错点
- 模板定义必须在使用处或头文件。
- 捕获多态异常按引用(值捕获会切片)。
catch (...)兜底必须放最后。- 析构函数不要抛异常(会 terminate)。
六、标准库 STL(Standard Library)
核心知识点
- std::string:可变字符串,支持拼接、
find(找不到返回npos)、substr、to_string/stoi数字互转。 - std::string_view:只读视图,不拥有内存,零拷贝。只读参数首选。不能返回指向局部临时对象的 view(悬垂)。
- 容器:
vector:动态数组,默认选择。容量满时翻倍搬移,reserve预分配。array:编译期定长,栈上。map/set:有序,平衡树,O(log n)。[]访问不存在键会插入默认值。unordered_map:哈希,O(1) 平均。list/deque:链表/双端队列,用于特殊插入需求。
- 迭代器:统一访问容器的方式,区间
[begin, end)左闭右开。end()不指向元素。 - 算法(
<algorithm>):sort、find/find_if、count/count_if、transform、max_element、accumulate(<numeric>)等,都吃迭代器区间。 - lambda:就地定义函数。
[捕获](参数){函数体}。捕获按值[x](快照)或按引用[&x]。用于算法谓词和排序规则。 - I/O:
cout/cin格式化(setw、setprecision、fixed)、文件读写(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易错点
- 用到就包含头文件(
<vector>、<algorithm>、<memory>、<string>各管各的)。 find找不到返回end(),用前判!= end()。map用[]读不存在键会插入默认值,只想查用find/contains。list没有下标,用迭代器 +std::next。- lambda 按值捕获是快照,要改外部变量用
[&]。 - range-for 默认值拷贝,要改原元素用
for (auto& x : v)。
总结
| 板块 | 核心 |
|---|---|
| 类与对象 | 封装数据 + 行为,构造函数维护不变量 |
| 运算符重载 | 让自定义类型用起来像内置类型 |
| 移动语义与智能指针 | 所有权高效转移,RAII 自动管理资源 |
| 继承与多态 | is-a 关系 + 虚函数动态绑定 |
| 模板与异常 | 类型参数化 + 错误处理解耦 |
| 标准库 STL | 容器存数据 + 算法处理数据 + lambda 定制规则 |
现代 C++ 的黄金组合:RAII + 智能指针 + 容器/算法 + lambda——写安全、高效、简洁的代码。