C++ 语法速查表 - C++ 常用语法与 STL 容器大全

这套速查给写应用、游戏或库的 C++ 开发者,围绕现代 C++ 组织——智能指针、STL 和 Lambda 正是把 C++ 那著名的"手动生命周期易出错"压下去的关键。内容覆盖类与对象及其访问控制、各场景该选哪个 STL 容器、智能指针对比裸 new/delete、Lambda 捕获、函数与类模板、异常。与平铺的 C++ 特性表不同,条目按你在写哪类结构来分,并点出 RAII 与手动所有权怎么选。读完应能优先用 std::vector/std::map 而非裸数组、用 unique_ptr 而不是裸 new,并写出不泄漏的模板函数或 Lambda。

编程语言·共 44 条命令·最后更新 2026-07-21
cppstl面向对象

典型使用场景

面向写 C++ 时想在安全性和性能之间取得平衡的开发者,尤其是从「会 new/delete」过渡到现代 C++ 工程写法的阶段。本表沿着类与对象、STL 容器、智能指针、Lambda、模板、异常处理、现代特性七条线展开:把手动内存管理换成 unique_ptr / shared_ptr / weak_ptr 三件套,明确什么时候用独占、什么时候需要引用计数、为什么观察者场景要用弱引用避免循环;容器部分给出 vector / list / map / unordered_map / set / stack / queue 的选型对照;最后落到 auto、decltype、constexpr、optional、variant、结构化绑定这些能立刻减少样板代码的特性上。

类与对象 8

class MyClass { public: MyClass(); ~MyClass(); };
类定义
struct MyStruct { int x; int y; };
结构体(默认 public)
MyClass obj;
栈上对象
MyClass *obj = new MyClass();
堆上对象
delete obj;
释放对象
class Derived : public Base { };
继承
virtual void func() override;
虚函数重写
class Abstract { virtual void func() = 0; };
纯虚函数(抽象类)

STL 容器 8

std::vector<int> v = {1, 2, 3};
动态数组
v.push_back(4); v.size(); v[0];
vector 操作
std::list<int> l;
双向链表
std::map<std::string, int> m;
有序映射(红黑树)
std::unordered_map<std::string, int> m;
无序映射(哈希表)
std::set<int> s;
有序集合
std::stack<int> st; std::queue<int> q;
栈与队列
std::pair<int, std::string> p = {1, "one"};
键值对

智能指针 5

std::unique_ptr<int> p = std::make_unique<int>(10);
独占指针
std::shared_ptr<int> p = std::make_shared<int>(10);
共享指针(引用计数)
std::weak_ptr<int> wp = sp;
弱引用(不增加计数)
if (auto locked = wp.lock()) { }
弱引用转强引用
std::unique_ptr<int> p2 = std::move(p);
转移所有权

Lambda 表达式 6

auto fn = []() { return 42; };
无捕获 Lambda
int x = 10; auto fn = [x]() { return x; };
值捕获
auto fn = [&x]() { x++; };
引用捕获
auto fn = [=]() { return x; };
值捕获所有
auto fn = [&]() { x++; };
引用捕获所有
auto fn = [](int a, int b) { return a + b; };
带参数 Lambda

模板 4

template <typename T> T add(T a, T b) { return a + b; }
函数模板
template <typename T> class Stack { };
类模板
template <> void func<int>(int x) { }
模板特化
std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
自定义排序

异常处理 5

try { } catch (const std::exception &e) { }
异常捕获
throw std::runtime_error("error");
抛出异常
catch (...) { }
捕获所有异常
noexcept
声明不抛异常
std::terminate();
终止程序

现代 C++ 特性 8

auto x = 42;
类型推导
decltype(x) y = 10;
类型声明
constexpr int MAX = 100;
编译期常量
std::optional<int> opt = 42;
可选值
std::variant<int, std::string> v = 42;
变体类型
std::any a = 42;
任意类型
for (auto &[key, value] : map) { }
结构化绑定(C++17)
std::string_view sv = "hello";
字符串视图(C++17)

参数矩阵

参数作用示例
class MyClass类定义,默认成员私有class MyClass { public: MyClass(); ~MyClass(); };
struct MyStruct结构体,成员默认公开struct MyStruct { int x; int y; };
MyClass obj;在栈上创建对象,作用域结束自动析构MyClass obj;
new MyClass()在堆上创建对象,必须配对释放MyClass *obj = new MyClass();
delete obj;释放堆上对象,指针置空避免悬垂delete obj;
class Derived : public Base公有继承class Derived : public Base { };
virtual void func() override;重写基类虚函数并标注校验virtual void func() override;
virtual void func() = 0;纯虚函数,使类成为抽象基类class Abstract { virtual void func() = 0; };
std::vector<int>连续内存动态数组,随机访问 O(1)std::vector<int> v = {1, 2, 3};
v.push_back(4) / v.size() / v[0]vector 的尾部追加、长度与下标访问v.push_back(4); v.size(); v[0];
std::list<int>双向链表,中间插删不移动元素std::list<int> l;
std::map<K, V>有序映射(红黑树),键按序排列std::map<std::string, int> m;
std::unordered_map<K, V>无序映射(哈希表),查找平均更快但无序std::unordered_map<std::string, int> m;
std::set<T>有序去重集合std::set<int> s;
std::stack<T> / std::queue<T>栈与队列的适配器std::stack<int> st; std::queue<int> q;
std::pair<A, B>键值对,常与结构化绑定搭配std::pair<int, std::string> p = {1, "one"};
std::make_unique<T>(v)构造独占指针,是推荐的创建方式std::unique_ptr<int> p = std::make_unique<int>(10);
std::make_shared<T>(v)构造共享指针,单次分配提升效率std::shared_ptr<int> p = std::make_shared<int>(10);
std::weak_ptr<T> wp = sp;弱引用,不增加引用计数std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock())把弱引用临时提升为强引用,失败说明对象已销毁if (auto locked = wp.lock()) { }
std::move(p)转移所有权,让源指针置空std::unique_ptr<int> p2 = std::move(p);
[]() { return 42; }无捕获 Lambdaauto fn = []() { return 42; };
[x] / [&x]按值捕获 / 按引用捕获指定变量auto fn = [&x]() { x++; };
[=] / [&]按值 / 按引用捕获所有外层变量auto fn = [&]() { x++; };
template <typename T> T add(T, T)函数模板,泛型求值template <typename T> T add(T a, T b) { return a + b; }
template <typename T> class Stack类模板template <typename T> class Stack { };
template <> ... 特化为特定类型提供专门实现template <> void func<int>(int x) { }
std::sort(begin, end, cmp)排序并可传自定义比较函数std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });
try / catch (const std::exception &e)捕获标准异常并读取错误信息try { } catch (const std::exception &e) { }
throw std::runtime_error("error")抛出带信息的异常throw std::runtime_error("error");
catch (...)捕获所有异常,作为兜底分支catch (...) { }
noexcept声明函数不会抛异常noexcept
auto x = 42;类型推导,省去显式类型auto x = 42;
decltype(x) y = 10;按表达式类型声明新变量decltype(x) y = 10;
constexpr int MAX = 100;编译期常量constexpr int MAX = 100;
std::optional<int>可含可不含的值,替代裸指针表达「可能没有」std::optional<int> opt = 42;
std::variant<int, std::string>编译期确定的多类型联合体std::variant<int, std::string> v = 42;
std::any a = 42;可存放任意类型,代价是类型检查在运行时std::any a = 42;
for (auto &[key, value] : map)结构化绑定遍历映射,key/value 直接可用for (auto &[key, value] : map) { }
std::string_view sv = "hello"字符串视图,不持有内存,零拷贝std::string_view sv = "hello";

易错点与避坑指南

现象delete 之后又访问指针,程序偶发崩溃或读出乱码。

原因释放堆对象后没有把指针置空,形成了悬垂指针;更隐蔽的是两个指针指向同一块内存被重复释放。

处置优先改用 std::unique_ptr 接管所有权;若必须手动管理,释放后立即置空,并保证每个 new 恰好对应一次 delete。

现象两个对象互相持有 shared_ptr,程序结束时泄漏且两者都还在。

原因shared_ptr 的循环引用会让引用计数永远归不了零,对象无法被销毁。

处置把其中一侧改成 std::weak_ptr,观察者持有弱引用;访问时用 if (auto locked = wp.lock()) 临时提升。

现象按基类指针删除派生类对象时,析构函数没有被调用,出现资源泄漏。

原因基类析构函数不是虚函数,delete 基类指针不会走派生类的析构逻辑。

处置给打算以基类多态方式管理的那个类把析构函数声明为 virtual。

现象循环里频繁 push_back 导致性能突然下降。

原因vector 扩容需要重新分配并拷贝/移动全部元素,缺少预留容量时每次增长都有额外开销。

处置在已知规模时先 reserve 好容量,或改用 std::list 这类中间插删不移动元素的容器。

现象对同一个字符串做大量拼接,程序越来越慢。

原因每次 + 都会构造新字符串并拷贝已有内容,复杂度随长度增长累积。

处置改用 std::string_view 传递只读片段不复制,需要拼接时集中构造而不是循环内反复相加。

现象Lambda 里修改外部变量的修改没生效,或者捕获后调用时变量已析构。

原因按值捕获拿到的是当时的副本,之后外层变化不可见;按引用捕获则可能在对象销毁后留下悬垂引用。

处置确认语义需求:要快照用 [x],要联动用 [&x] 并保证被捕获对象的生命周期长于 Lambda。

现象多态调用时发现子类方法没被调到,退回了基类实现。

原因虚函数被隐藏而非重写(派生类里同名函数没有 override 且签名不同),或通过非多态方式(值传参、模板推导)调用。

处置在重写处加 override 让编译器校验签名;调用处用基类引用或指针以触发动态派发。

排障路径

  1. 1确认异常究竟从哪里抛出

    try { } catch (const std::exception &e) { }

    e.what() 会给出异常携带的信息,可据此定位是哪种标准异常。

  2. 2观察弱引用指向的对象是否已被销毁

    if (auto locked = wp.lock()) { }

    lock() 成功说明对象还活着,返回空说明引用计数已归零,不要再解引用。

  3. 3验证共享指针的所有权是否已转移

    std::unique_ptr<int> p2 = std::move(p);

    转移后 p 处于空状态,再使用 p 会得到空指针解引用。

  4. 4打印容器内容与长度确认数据正确性

    v.push_back(4); v.size(); v[0]

    先确认写入与读取路径无误,再怀疑排序或比较逻辑。

  5. 5用自定义比较函数验证排序行为

    std::sort(v.begin(), v.end(), [](int a, int b) { return a > b; });

    比较函数必须构成严格弱序,返回 a > b 得到降序结果。

  6. 6确认被捕获的变量在 Lambda 生命周期内有效

    auto fn = [&x]() { x++; };

    若 Lambda 被交给异步任务或长期保存,引用捕获容易在作用域结束后失效。

命令示例

RAII:make_unique 管理生命周期,move 转移所有权

auto p = std::make_unique<int>(10); std::unique_ptr<int> p2 = std::move(p); if (!p) { // p 已经为空所有权,析构不会二次释放 }

unique_ptr 离开作用域自动 delete,异常路径也不会漏;std::move 之后原指针为空。函数返回 unique_ptr 时不能写拷贝,必须靠移动或直接 return(现代编译器做了 NRVO)。

shared_ptr / weak_ptr 打破循环引用

std::shared_ptr<int> sp = std::make_shared<int>(10); std::weak_ptr<int> wp = sp; if (auto locked = wp.lock()) { // locked 是有效的 shared_ptr,作用域内 sp 不会归零 }

shared_ptr 之间互相引用会让计数永远降不到 0,节点与它持有的数据一起泄漏;weak_ptr 不参与计数,lock() 在对象已析构时返回空指针。

Lambda 值捕获避免悬垂,引用捕获改动原变量

int x = 10; auto byValue = [x]() { return x; }; auto byRef = [&x]() { x++; }; byRef(); int y = byValue();

值捕获把当时的 x 拷进闭包,之后改 x 不影响它;引用捕获存的是地址,若被捕获的局部变量先出作用域,闭包再执行就是悬垂引用。

结构化绑定遍历 map,配合 optional 处理缺失

std::map<std::string, int> m = {{"a", 1}}; for (auto &[key, value] : m) { value += 1; } std::optional<int> opt = find(3); if (opt.has_value()) { use(*opt); }

auto & 让循环直接改 map 里的值;auto 按值会拷贝一份,改动丢失。optional 用 * 解包,直接参与算术会编译失败,避免了「忘记判空」的静默错误。

异常:按类型捕获并保留 message

try { throw std::runtime_error("error"); } catch (const std::exception &e) { log(e.what()); } catch (...) { log("unknown"); }

按 const 引用捕获基类才能拿到 what() 里的内容;漏写 catch 块时异常会一路传播到 main 并触发 std::terminate,程序直接退出且没有可读堆栈。

常见坑与注意事项

  • 悬垂指针最常见的来源是引用捕获与临时对象:函数返回 [x](按值捕获才安全),或把 vector 元素的引用/指针存到外部后继续 push_back 触发扩容,旧内存立即失效。
  • 迭代器与引用会在容器变更后失效:for 循环里 erase 当前元素后迭代器已经无效,继续 ++ 是未定义行为;range-for 中 push_back 会让遍历的区间与实际存储错位。
  • 裸 new 遇到异常就泄漏:初始化中途抛出时对应的 delete 永远不会执行;类成员优先用 unique_ptr,只有确实需要共享所有权时才上 shared_ptr。
  • 两个 shared_ptr 互相持有会形成引用环,引用计数永远回不到 0,valgrind 里表现为 "still reachable" 且内存持续上涨;环上必须有一个是 weak_ptr。
  • 通过基类指针 delete 派生对象,若基类析构函数不是 virtual 就是未定义行为:派生成员不析构、资源泄漏,部分运行时会直接崩。
  • delete 与 delete[] 混用、重复 delete、以及对栈对象用 delete 都会造成堆破坏:症状是程序在毫不相关的位置崩,valgrind 的 invalid free 是唯一线索。

提示

  • 优先使用智能指针,避免手动 new/delete。
  • STL 容器选择:频繁随机访问用 vector,频繁插入删除用 list,键值查找用 unordered_map。
  • Lambda 表达式适合短小的回调函数,复杂逻辑建议用普通函数或函数对象。

常见问题

shared_ptr 循环引用为什么会导致内存泄漏?

shared_ptr 靠引用计数管理生命周期,对象销毁时计数归零即释放。问题在于计数是成对增减的:A 持有一个指向 B 的 shared_ptr,B 又持有一个指向 A 的 shared_ptr,于是两者互相把对方的计数加一,谁都不会先归零,即便外部所有 shared_ptr 都被销毁,这两个对象仍然存活,对应的堆内存永远回收不了。解法是打断环:其中一侧改用 std::weak_ptr<int> wp = sp,它不增加计数,再通过 if (auto locked = wp.lock()) { } 在真正要用时临时提升为强引用,从而在环断开后正常析构。

按引用捕获和按值捕获,在 Lambda 里改 x 有什么区别?

[x] 是值捕获:Lambda 内部拿到的是 x 在定义那一刻的副本,改这个副本不会影响外层的 x。 [&x] 是引用捕获:内部和外层操作同一个对象,写 [&x]() { x++ } 会真的把外层 x 加一;代价是如果 Lambda 活得比 x 还久(比如被异步保存、存进容器再返回),就会引用到失效对象。实践上的取舍是:短小的同步回调用引用捕获省拷贝,逃逸出去或生命周期不可控的回调优先值捕获,需要写外层状态时用 [&],只读就用 [=]。

C++17 的结构化绑定替代了哪些手写循环?

过去遍历 std::map 要写 for (const auto &p : m) { auto key = p.first; auto value = p.second; },C++17 之后可以写成 for (auto &[key, value] : map) { },直接得到键和值两个变量,且引用形式可以就地修改。pair 同样适用:for (const auto &[id, name] : users)。它不只是少写两行,而是让「解构」这件事在语法上成立,意图更明确。注意 auto & 是引用绑定,写入会影响容器元素;只读遍历用 const auto &。

什么时候该用 unordered_map,什么时候该用 map?

核心差别是底层结构与代价。std::map 是红黑树,键有序,查找、插入、删除都是 O(log n),而且天然支持按序遍历——需要「取最大键」「输出有序结果」时 map 是唯一选择。std::unordered_map 是哈希表,平均 O(1),没有顺序保证,遍历顺序由哈希分布决定,纯查找场景更快。实际写代码时先问一句:这个映射需不需要有序遍历或范围查询?需要就 map,不需要且键数量大、查找密集就 unordered_map。此外键是对象时 hash 成本高,收益会被削弱。

纯虚函数、override 和 noexcept 分别解决什么问题?

virtual void func() = 0 是纯虚函数声明,它让类成为抽象类、无法实例化,但派生类必须实现它,于是「接口约束」被编译器强制执行;配套的虚析构函数保证通过基类指针删除派生对象时不泄漏。override 是给重写用的标记:函数签名一旦和基类不一致,编译器立刻报错,避免「拼错参数类型导致悄悄变成重载」的经典 bug。noexcept 声明函数不会抛异常,配合 std::vector 等容器在扩容时选择更快的移动路径;一旦违反承诺会触发 std::terminate() 直接终止程序。

官方参考来源

下方为命令对应的官方权威文档,供你核对最新用法与深入查阅。

由 巧匠 维护

公开更新于 2026年7月21日,内容持续校对官方文档。

联系我们

命令或描述有误?提交反馈、商务合作或产品建议都可发送邮件给我们。

联系我们