本章目标:concepts、ranges、三路比较(太空船运算符)、 协程(入门)、module(概念)、span、consteval、constinit、 jthread、chrono 日期时间格式化。 C++20 被称作"现代 C++ 的成人礼",特性多且重要。
回顾一句话:给模板参数加上"必须满足什么"的约束, 报错信息从迷宫变人话。
template <std::integral T> T twice(T x) { return x * 2; }
旧方式:
std::sort(v.begin(), v.end());
auto it = std::find(v.begin(), v.end(), 5);
新方式(ranges):
#include <ranges>
std::ranges::sort(v); // 直接传容器!
auto r = v | std::views::filter([](int x){ return x % 2 == 0; })
| std::views::transform([](int x){ return x * x; });
ranges 三宝:
经典例子:打印 1..100 中能被 3 整除的数的平方和:
int s = 0;
for (int x : std::views::iota(1, 101)
| std::views::filter([](int n){ return n % 3 == 0; })
| std::views::transform([](int n){ return n * n; })) {
s += x;
}
惰性:视图不真正计算,遍历到哪算到哪,零额外内存。 这就是 C++20 的函数式编程风格。
常用视图速查:
std::views::iota(0, 10) // 0~9 的整数序列
std::views::take(v, 3) // 前 3 个
std::views::drop(v, 2) // 丢掉前 2 个
std::views::filter(v, pred) // 过滤
std::views::transform(v, f) // 映射
std::views::reverse(v) // 反转
std::views::join(...) // 拍平嵌套
#include <compare>
struct Point {
int x, y;
auto operator<=>(const Point&) const = default; // 一键生成
bool operator==(const Point&) const = default;
};
Point a{1, 2}, b{1, 3};
a < b; // 自动支持!< <= > >= == 全都有
人话:`= default` 一个 <=> 就能让所有比较运算符自动生成, 按成员顺序比较(x 优先,再 y)。一个运算符替代六个。 返回值是"强/弱/部分排序"类型,一般用 auto 就不用管。
#include <span>
void sum_of(std::span<const int> data) {
int s = 0;
for (int x : data) s += x;
}
int arr[] = {1, 2, 3};
std::vector<int> v{4, 5};
sum_of(arr); // C 数组也能传
sum_of(v); // vector 也能传
代替"裸指针 + 长度"两个参数。带边界信息,能检查。 类似 string_view 之于 string:不拥有,只观察。
consteval int sq(int n) { return n * n; } // 必须编译期调用
// sq(5) OK;sq(x)(x 是运行期变量)→ 编译错误
constinit int global = 42; // 静态初始化期的常量
// constinit 保证没有"动态初始化顺序问题"(全局变量初始化的坑)
层级关系:
const 运行期也能改不了……(运行时存在)
constexpr 编译期算,运行期也能当普通常量用
consteval 必须编译期算(C++20)
constinit 静态变量初始化必须在编译期(C++20)
协程是"能暂停/恢复的函数"。C++20 引入底层机制,但没有 标准库的封装类型(需要第三方库或自己写)——所以新手了解即可。
特点:
实际开发中通常用第三方库(如 cppcoro、asio)或等标准库 补齐再深度使用。目前知道"它是什么"就够。
C++ 20 引入 module 解决头文件的老问题(重复编译、宏污染)。 但工具链支持还不完全(clang 21 支持不错了):
// math.cppm
export module math;
export int add(int a, int b) { return a + b; }
// main.cpp
import math;
import std; // 引入整个标准库
int main() { return add(1, 2); }
编译(clang 21):
clang++ -std=c++20 --precompile math.cppm -o math.pcm
clang++ -std=c++20 -fmodule-file=math.pcm main.cpp math.pcm -o app
人话:import 取代 #include,更快、更干净。 现状:生态成熟还要时间,了解语法即可,项目里老代码仍是 #include。
#include <thread>
std::jthread t1([](std::stop_token st) { // 可停止
while (!st.stop_requested()) { /* 干活 */ }
});
// t1 析构时自动 join,不用手动 join()(旧 thread 忘了 join 会崩)
jthread 解决两大痛点:忘 join 崩溃、程序退出时线程还在跑。 配合 stop_token 可以优雅地请求线程停止。
#include <chrono>
namespace ch = std::chrono;
auto now = ch::system_clock::now(); // 当前时刻
auto today = ch::floor<ch::days>(now); // 当天零点
auto t = ch::sys_days{2025y / ch::June / 15}; // 构造日期
std::println("{:%Y年%m月%d日 %H:%M:%S}", now); // 直接格式化!
运算:日期加减天数、计算间隔、比较先后,全是类型安全的。
#include <print>
#include <ranges>
#include <numeric>
int main() {
// 1~100 中偶数的平方和(全程函数式,无手写循环)
auto view = std::views::iota(1, 101)
| std::views::filter([](int n) { return n % 2 == 0; })
| std::views::transform([](int n) { return n * n; });
auto sum = std::accumulate(view.begin(), view.end(), 0);
std::println("结果: {}", sum);
}
体会:没有显式循环,数据流用管道描述,bug 少、意图清楚。
练习题