路线图 · 持续更新
现代 C++ 路线图:C++11 → C++23
大多数入门教材教的是 2003 年的 C++。而 2026 年的真实代码不是那样。这是一份该按什么顺序补上现代特性的清单 —— 每一项都给你旧写法、新写法,以及为什么重要。
说明:浏览器练习场只覆盖教学核心(不支持 std::string、vector 和 C++11 之后的特性),下面的代码片段请在桌面编译器上跑 —— 见编译安装指南。
第一阶 —— C++11/14:基线(先学这些)
这些不是"进阶内容"。近十年的编译器全都支持,专业代码库全都在用。如果一个教程不教这些,它教的是博物馆展品。
1. auto —— 让编译器替你说出类型
// 旧
std::vector<std::string>::iterator it = names.begin();
// 现代
auto it = names.begin(); // 类型自动推导,但依然是静态类型
为什么:类型名又长又吵;auto 还能在你换容器时自动保持正确。它仍是静态类型 —— 运行时什么都不会发生。
2. 范围 for —— 不用再跟下标搏斗
// 旧
for (size_t i = 0; i < v.size(); ++i) sum += v[i];
// 现代
for (int x : v) sum += x;
// 只读、不拷贝 —— 你会到处见到的写法
for (const auto& name : names) cout << name << '\n';
为什么:没有差一错误,没有下标变量。const auto& 应该是你的默认选择:不拷贝,也不会被误改。
3. 智能指针 —— 别再手写 new/delete
// 旧 —— 容易漏、异常不安全
Widget* w = new Widget();
... // 这里一抛异常,w 就泄漏了
delete w;
// 现代 —— 所有权写在类型里
auto w = std::make_unique<Widget>(); // 唯一所有者,自动释放
auto s = std::make_shared<Widget>(); // 共享所有权(少用)
为什么:一行写完 RAII。堆对象默认用 unique_ptr;所有权确实需要共享时才用 shared_ptr。
4. lambda —— 把"行为"当参数传
// 按自定义规则排序,不必另写一个具名函数
std::sort(people.begin(), people.end(),
[](const Person& a, const Person& b) { return a.age < b.age; });
// 按值捕获 [x] 或按引用捕获 [&x]
int threshold = 30;
auto over = [threshold](int age) { return age > threshold; };
为什么:lambda 让"传一个小函数"变成惯用法,也解放了现代算法库。2011 年以后写的代码库处处是它。
5. nullptr、enum class、constexpr
char* p = nullptr; // 不是 0,不是 NULL —— 类型安全
enum class Color { Red, Green }; // 有作用域:Color::Red,不会隐式转 int
constexpr int kMax = 100; // 编译期就算出来
第二阶 —— C++17:体验改良
6. 结构化绑定
std::map<std::string, int> ages;
for (const auto& [name, age] : ages) { // 直接拆开 pair
cout << name << " = " << age << '\n';
}
7. std::optional —— 把"可能没有值"写进类型
std::optional<int> parse(std::string_view s); // 可能失败
if (auto n = parse(input)) { use(*n); } // 不用 -1 这种哨兵值,不用 bool 出参
为什么:取代了魔法返回值和输出参数。"可能失败"这件事现在在函数签名里就能看见。
8. std::string_view
void log(std::string_view msg); // 不拷贝,字面量和 string 都能传
第三阶 —— C++20:重头戏("现代"真正开始的地方)
9. concepts —— 编译器能理解的约束
template <typename T>
requires std::integral<T>
T twice(T x) { return x * 2; }
// 报错变成人话:"T 不满足 integral"
// 而不是几页模板实例化噪音
为什么:这是 C++20 的头号特性。它把模板错误从不可读变成可检查的要求。
10. ranges —— 用管道代替循环体
#include <ranges>
auto result = v
| std::views::filter([](int x) { return x % 2 == 0; })
| std::views::transform([](int x) { return x * x; })
| std::views::take(5); // 惰性:迭代到才计算
为什么:读起来就像题目本身。惰性求值意味着不产生临时 vector。这是范围 for 之后最大的写法变革。
11. modules —— 取代 #include 的文本粘贴
// math.cppm
export module math;
export int add(int a, int b) { return a + b; }
// main.cpp
import math; // 没有头文件卫士,不用重复解析整个世界
为什么:编译更快,模块边界真实存在。2026 年工具链支持仍不均衡 —— 要知道它,但还别处处依赖它。
12. 协程、std::format、span、consteval
std::cout << std::format("{} + {} = {}", a, b, a + b); // 类型安全的 printf
void process(std::span<int> data); // 数组视图,不退化
consteval int square(int x) { return x * x; } // 必须编译期执行
第四阶 —— C++23 及以后:正在落地的
std::expected<T, E>—— 既不用异常、也不至于像 optional 那样丢掉原因的报错方式:返回类型里带着失败理由。std::print/std::println——format终于有了一行调用的入口。std::mdspan—— 在一维内存上做多维视图(搞数值计算的人喊了很多年)。- deducing
this—— 显式对象参数,CRTP 和 mixin 写起来干净很多。 - 值得盯:反射(目标 C++26)、契约、模式匹配 —— 这三个会改变日常代码的读法。
具体怎么过一遍
- 别线性读完。先把第一阶练到手上 —— 把你自己以前写的旧式循环或排序重写一遍。
- 用
-std=c++20 -Wall编译(或c++23)。所谓"现代 C++",有一半只是"别再写 C++03"。 - 一道题配一个特性。我们的 36 道题刻意只用 C++ 核心;拿一道出来用
std::vector+ 范围 for +auto重写,当作练习。 - 读真实代码。挑一个现代开源 C++ 项目,grep 一下
auto&、unique_ptr、std::ranges—— 看真实代码库到底怎么用。