第十二章 C++20 新特性详解

← 返回目录

本章目标:concepts、ranges、三路比较(太空船运算符)、 协程(入门)、module(概念)、span、consteval、constinit、 jthread、chrono 日期时间格式化。 C++20 被称作"现代 C++ 的成人礼",特性多且重要。

12.1 concepts:模板的"说明书"(已在第十章讲过)

回顾一句话:给模板参数加上"必须满足什么"的约束, 报错信息从迷宫变人话。

template <std::integral T> T twice(T x) { return x * 2; }

12.2 ranges:容器直接用算法(重大改进)

旧方式:

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. ranges 算法:sort、find、count、transform 都接受容器整体
  2. 视图(views):filter 过滤、transform 转换、take 取前 n 个
  3. 管道 |:把视图串起来,像流水线一样

经典例子:打印 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(...)       // 拍平嵌套

12.3 三路比较运算符 <=>(太空船运算符)

#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 就不用管。

12.4 std::span:安全的数组视图

#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:不拥有,只观察。

12.5 consteval / constinit:编译期更严格

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)

12.6 协程 coroutines:异步的新写法(入门)

协程是"能暂停/恢复的函数"。C++20 引入底层机制,但没有 标准库的封装类型(需要第三方库或自己写)——所以新手了解即可。

特点:

实际开发中通常用第三方库(如 cppcoro、asio)或等标准库 补齐再深度使用。目前知道"它是什么"就够。

12.7 module(模块系统:取代头文件,预览)

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。

12.8 std::jthread:能自动 join 的线程(入门)

#include <thread>
std::jthread t1([](std::stop_token st) {    // 可停止
    while (!st.stop_requested()) { /* 干活 */ }
});
// t1 析构时自动 join,不用手动 join()(旧 thread 忘了 join 会崩)

jthread 解决两大痛点:忘 join 崩溃、程序退出时线程还在跑。 配合 stop_token 可以优雅地请求线程停止。

12.9 chrono 日期时间(C++20 全面升级)

#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); // 直接格式化!

运算:日期加减天数、计算间隔、比较先后,全是类型安全的。

12.10 其他 C++20 小特性

12.11 C++20 实战示例:一份完整的现代代码

#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 少、意图清楚。

本章小结

练习题


  1. 用 ranges 打印 1..1000 中所有回文数(数字翻转相等)。
  2. 用 views 实现:取 vector 前 5 个,平方,求和,一次写完。
  3. 用 <=> 给 Student(姓名+分数)定义比较,直接 sort。
  4. 用 span 写一个 sum 函数,接受 C 数组和 vector 都行。
  5. 用 chrono 打印"从今天起 100 天后的日期"。