C++20 标准概述
C++20是C++语言的一次重大更新,引入了多项核心特性以提升开发效率和代码表现力。模块系统(Modules)替代传统头文件机制,改善编译速度与隔离性;概念(Concepts)为模板编程提供类型约束的直观语法;协程(Coroutines)支持异步编程的简化实现;范围库(Ranges)提供声明式数据操作管道;三向比较运算符(<=>)统一比较逻辑;日历与时区扩展()增强时间处理能力。此外,constexpr支持更多运行时计算,结构化绑定可作用于普通数组,原子操作新增等待通知机制。这些特性共同强化了C++在泛型编程、并发处理和代码组织方面的现代化能力。
🧱 一、VsCode配置C++20编译环境
- 下载最新版的 MinGW64 安装并配置 bin 目录到环境变量 path 中
- 配置 task.json,加上参数 -std=c++20
{
"tasks": [
{
"type": "cppbuild",
"label": "C/C++: g++.exe build active file",
"command": "D:\\DevelopApps\\mingw64\\bin\\g++.exe",
"args": [
"-fdiagnostics-color=always",
"-g",
"-std=c++20", // 添加此行
"${file}",
"-o",
"${fileDirname}\\${fileBasenameNoExtension}.exe"
],
"options": {
"cwd": "${fileDirname}"
},
"problemMatcher": [
"$gcc"
],
"group": {
"kind": "build",
"isDefault": true
},
"detail": "Task generated by Debugger."
}
],
"version": "2.0.0"
}
🧱 二、核心语言特性
1. 模块 (Modules)
- 目标: 解决传统头文件(
#include
)机制带来的问题(编译速度慢、宏污染、包含顺序依赖、语义不明确)。 - 核心: 将代码划分为独立的、预编译的模块单元(
.ixx
,.cppm
,.cpp
)。 - 关键字:
module
,export
,import
。 - 优点:
- 显著提升编译速度: 模块接口只编译一次,导入时不需重新解析。
- 强隔离性: 模块内非导出内容对外完全不可见(无宏泄露、无私有声明污染)。
- 消除包含顺序依赖: 导入顺序无关紧要。
- 更清晰的语义: 明确区分接口(
export
)与实现。
- 常考/常用: 是 C++20 最重要的特性之一,未来项目的基础构建方式,面试常问其优点和基本用法。
- 示例
// math.cppm - 模块接口文件 export module math; export int add(int a, int b) { return a + b; } export constexpr double pi = 3.1415926; ``` ```cpp // main.cpp - 主程序 import math; // 导入模块 #include <iostream> int main() { std::cout << "2 + 3 = " << add(2, 3) << std::endl; std::cout << "π ≈ " << pi << std::endl; return 0; } ```
2. 协程 (Coroutines)
- 目标: 简化异步编程、惰性生成器、状态机等的实现。
- 核心: 提供一种可以挂起(
co_await
)和恢复执行的函数。函数状态(局部变量、执行位置)在挂起时自动保存。 - 关键字:
co_await
,co_yield
,co_return
。函数体中使用这些关键字即成为协程。 - 底层: 依赖编译器生成的“协程框架”代码(承诺对象
promise_type
、协程句柄coroutine_handle
)。 - 库支持:
std::generator
(C++23),std::task
(提案中),或使用第三方库(如 cppcoro)。 - 常用/常考: 实现异步 I/O、生成器序列、复杂状态机。是面试高级 C++ 岗位的热点话题,常考基本概念和使用场景。
- 示例
#include <coroutine> #include <iostream> #include <memory> // 生成器协程示例 template<typename T> struct Generator { struct promise_type { T current_value; auto get_return_object() { return Generator{this}; } auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() noexcept { return std::suspend_always{}; } void return_void() {} void unhandled_exception() { std::terminate(); } auto yield_value(T value) { current_value = value; return std::suspend_always{}; } }; using Handle = std::coroutine_handle<promise_type>; Handle coro_handle; explicit Generator(promise_type* p) : coro_handle(Handle::from_promise(*p)) {} ~Generator() { if (coro_handle) coro_handle.destroy(); } T next() { coro_handle.resume(); return coro_handle.promise().current_value; } }; Generator<int> range(int start, int end) { for (int i = start; i <= end; ++i) { co_yield i; // 挂起并返回值 } } int main() { auto gen = range(1, 5); while (true) { int val = gen.next(); std::cout << val << " "; if (val >= 5) break; } return 0; }
3. 概念 (Concepts)
- 目标: 大幅改进模板元编程,为模板参数提供编译时类型约束,使错误信息更清晰,代码可读性更高。
- 核心: 定义一组对类型的要求(约束)。
- 关键字:
concept
,requires
。 - 用法:
- 定义概念:
template <typename T> concept Integral = std::is_integral_v<T>; // 使用类型特性 template <typename T> concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>; // 要求表达式有效且结果可转换 };
- 约束模板参数:
template <Integral T> // 语法糖 T add(T a, T b) { return a + b; } template <typename T> requires Addable<T> // requires 子句 T add(T a, T b) { return a + b; }
- 约束 auto:
Addable auto add(Addable auto a, Addable auto b) { return a + b; }
- 示例
#include <concepts> #include <vector> #include <iostream> // 定义概念 template<typename T> concept Addable = requires(T a, T b) { { a + b } -> std::convertible_to<T>; }; // 约束模板函数 template <Addable T> T sum(T a, T b) { return a + b; } // 约束 auto Addable auto add(Addable auto a, Addable auto b) { return a + b; } int main() { std::cout << sum(3, 4) << std::endl; // 正确:int 满足 Addable std::cout << add(2.5, 3.7) << std::endl; // 正确:double 满足 Addable // sum("a", "b"); // 错误:const char* 不满足 Addable return 0; }
- 定义概念:
- 标准概念库:
<concepts>
头文件提供了大量预定义概念(std::integral
,std::copyable
,std::invocable
等)。 - 常用/常考: 现代模板编程的基石,极大提升泛型代码的健壮性和可读性。必考内容,常要求编写或理解概念约束的模板。
4. 三向比较 (Spaceship Operator, <=>
)
- 目标: 简化用户自定义类型的比较运算符定义。
- 核心: 引入新的运算符
<=>
(spaceship),它在一个操作中返回两个值的所有可能比较关系(小于、等于、大于)。 - 返回值类型:
std::strong_ordering
,std::weak_ordering
,std::partial_ordering
(定义在<compare>
中)。 - 编译器自动生成: 如果类定义了默认的
operator<=>
,编译器会自动生成==
,!=
,<
,<=
,>
,>=
六个运算符(除非显式定义或删除)。struct Point { int x, y; auto operator<=>(const Point&) const = default; // 按成员声明顺序进行字典序比较 };
- 示例
#include <compare> #include <iostream> struct Point { int x, y; auto operator<=>(const Point&) const = default; // 默认比较(按成员字典序) }; int main() { Point p1{1, 2}, p2{1, 3}; std::cout << (p1 < p2) << std::endl; // true std::cout << (p1 == p2) << std::endl; // false return 0; }
- 常用/常考: 极大简化了需要全序或偏序类型的代码编写,是重载运算符相关问题的重点。
5. 指定初始化 (Designated Initializers)
- 目标: 提高聚合类型初始化的清晰度和安全性(类似 C 的指定初始化,但有更严格的 C++ 规则)。
- 核心: 允许在初始化聚合类型(数组、结构体、类 - 如果满足聚合初始化条件)时,显式指定成员名。
- 规则:
- 初始化器必须按类中成员的声明顺序出现(C 不要求,C++ 要求)。
- 不能混合使用指定初始化器和非指定初始化器。
- 不能嵌套指定初始化器(不能用于初始化非聚合子成员)。
- 未显式指定的成员进行值初始化。
- 示例:
struct S { int a; double b; const char* c; }; S s1 = { .a = 1, .b = 2.2, .c = "hello" }; // 正确 S s2 = { .b = 3.3, .a = 2 }; // 错误!顺序必须与声明一致 (a 在 b 前)
- 常用: 提高初始化复杂结构体时代码的可读性和防错能力。
6. constexpr
的增强
- 目标: 让更多代码能在编译期执行。
- 核心允许:
constexpr
虚函数: 虚函数现在可以是constexpr
。constexpr
动态内存分配 (new
/delete
): 在constexpr
函数内部可以使用new
和delete
(分配的内存必须在编译期释放)。constexpr
try
/catch
: 在constexpr
函数中允许try-catch
块(但throw
在constexpr
求值中仍不允许)。constexpr
标准库的更多内容: 大量标准库容器和算法被标记为constexpr
(如std::vector
,std::string
,std::sort
等)。
- 常用/影响: 极大地扩展了编译时计算的可能性,使得更复杂的逻辑和数据结构可以在编译期构造和操作。
7. 立即函数 (consteval
)
- 目标: 确保函数一定在编译期被求值。
- 核心: 用
consteval
关键字声明的函数(称为“立即函数”)。如果调用它的上下文不能在编译期求值,则编译错误。 - 与
constexpr
区别:constexpr
函数可以在编译期或运行期求值;consteval
函数必须在编译期求值。 - 常用: 用于强制编译期计算,保证某些关键操作(如某些元编程、字面量处理)没有运行时开销。
8. using
枚举 (Using-enum Declaration)
- 目标: 简化作用域枚举(
enum class
)成员的访问。 - 核心: 将枚举类的所有成员引入当前作用域。
- 示例:
enum class Color { Red, Green, Blue }; void paint(Color c) { using enum Color; // 引入 Red, Green, Blue 到 paint 函数作用域 switch (c) { case Red: ... // 不需要 Color::Red case Green: ... case Blue: ... } }
- 常用: 减少作用域枚举使用时重复写枚举类名的冗余。
📚 二、标准库特性
1. 范围库 (Ranges Library)
- 目标: 提供处理元素序列(范围)的现代、可组合、惰性求值的算法和视图。
- 核心组件:
- 范围概念: 定义序列的要求(如
std::ranges::range
,std::ranges::view
)。 - 范围算法: 在
<algorithm>
头文件下有对应的std::ranges::
版本(如std::ranges::sort
,std::ranges::find
)。接受范围对象,而非迭代器对。 - 视图 (Views): 惰性求值的适配器,通过管道操作符
|
进行组合,不修改底层数据。- 常用视图:
std::views::filter
,std::views::transform
,std::views::take
,std::views::drop
,std::views::reverse
,std::views::join
等。 - 示例:
std::vector<int> vec = {1, 2, 3, 4, 5}; auto result = vec | std::views::filter([](int i) { return i % 2 == 0; }) // 取偶数 [2, 4] | std::views::transform([](int i) { return i * i; }); // 平方 [4, 16] for (auto i : result) { ... } // 惰性求值,实际计算发生在迭代时
- 常用视图:
- 范围工厂:
std::views::iota
(生成整数序列)。
- 范围概念: 定义序列的要求(如
- 优点: 代码更简洁、可读性更高(类似函数式编程)、惰性求值提升性能(避免中间临时容器)、安全(范围自动管理边界)。
- 常用/常考: 现代 C++ 数据处理的核心库,是替代传统迭代器+算法风格的主力。面试高频考点,常考视图组合和管道操作。
- 示例
#include <ranges> #include <vector> #include <iostream> #include <algorithm> int main() { std::vector<int> vec = {3, 1, 4, 1, 5, 9, 2, 6}; // 视图:过滤偶数并平方 auto result = vec | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * x; }); for (int v : result) { std::cout << v << " "; // 输出:4 16 36 } // 范围排序 std::ranges::sort(vec); return 0; } ```
2. 格式化库 (std::format
)
- 目标: 提供类型安全、可扩展、高性能的文本格式化机制,替代
printf
和复杂的iostreams
拼接。 - 核心:
std::format(format_string, args...)
函数和std::formatter
自定义格式化。 - 语法: 类似 Python 的
str.format()
,使用{}
作为占位符。 - 示例:
#include <format> #include <iostream> int main() { std::string name = "World"; int answer = 42; std::string message = std::format("Hello, {}! The answer is {}.", name, answer); std::cout << message << std::endl; // 输出: Hello, World! The answer is 42. // 直接输出到流 std::cout << std::format("π ≈ {:.2f}", 3.1415926535) << std::endl; // 输出: π ≈ 3.14 }
- 优点: 类型安全、位置和命名参数支持、良好的本地化支持、性能通常优于
iostreams
和sprintf
。 - 常用: 快速成为新的标准输出和字符串构建方式,广泛使用。
3. std::span
- 目标: 提供对连续内存序列(如数组、
std::vector
,std::array
)的非拥有视图(引用),避免传递指针+大小的原始组合。 - 核心: 轻量级,包含指向数据的指针和大小(或范围)。
- 特点: 非拥有、不分配内存、可读写(除非
const
限定)。 - 用途: 安全地传递数组或容器的一部分给函数,作为函数参数或返回值。
- 示例:
#include <span> #include <vector> void process_chunk(std::span<int> data) { for (auto& elem : data) { elem *= 2; // 修改原始数据 } } int main() { std::vector<int> vec = {1, 2, 3, 4, 5}; process_chunk({vec.data() + 1, 3}); // 处理 vec[1] 到 vec[3] (2, 3, 4) // vec 变为 {1, 4, 6, 8, 5} }
- 常用/常考: 提高处理连续序列的 API 安全性和表达力,是现代 C++ API 设计的推荐做法。常考其与原始指针/数组的区别和优点。
4. std::jthread
- 目标: 提供更安全、更方便的线程管理,解决
std::thread
在析构时行为(需要显式join
或detach
)可能导致的资源泄漏或崩溃问题。 - 核心: “Joining Thread”。在析构函数中,如果线程仍可连接(
joinable
),它会自动调用join()
。这意味着在大多数情况下,你不需要手动join
。 - 协作取消: 支持通过
std::stop_token
和std::stop_source
机制请求线程停止(协作式)。 - 示例:
#include <thread> #include <iostream> void worker(std::stop_token stopToken) { while (!stopToken.stop_requested()) { std::cout << "Working...\n"; std::this_thread::sleep_for(std::chrono::seconds(1)); } std::cout << "Stopped by request.\n"; } int main() { { std::jthread jt(worker); // 创建并启动线程 std::this_thread::sleep_for(std::chrono::seconds(3)); } // jt 析构时自动调用 join(),并通过 stop_source 请求停止 return 0; }
- 常用: 推荐用于所有新代码中的线程管理,比
std::thread
更安全。
5. 日历和时区扩展 (<chrono>
)
- 目标: 提供强大的日历日期和时区处理能力。
- 核心类型:
- 日历类型:
std::chrono::year
,std::chrono::month
,std::chrono::day
,std::chrono::year_month_day
,std::chrono::weekday
,std::chrono::month_day
等。 - 时区支持:
std::chrono::time_zone
,std::chrono::zoned_time
。 - 格式化和解析: 与
std::format
集成。
- 日历类型:
- 示例:
#include <chrono> #include <format> #include <iostream> using namespace std::chrono; int main() { auto today = floor<days>(system_clock::now()); // 当前 UTC 日期 year_month_day ymd{today}; // 转换为年-月-日结构 std::cout << std::format("Today is {:%Y-%m-%d}\n", ymd); // 输出格式化的日期 // 处理时区 zoned_time zt{"America/New_York", system_clock::now()}; std::cout << std::format("Current time in New York: {:%T %Z}\n", zt); }
- 常用: 处理日期和时间相关逻辑变得非常方便和强大。
🎯 总结重点 (常考常用)
- 模块 (Modules): 未来构建方式,解决头文件痛点。
- 概念 (Concepts): 模板编程革命,提升泛型代码质量。
- 范围库 (Ranges + Views): 现代数据处理范式,简洁高效安全。
- 协程 (Coroutines): 异步编程/生成器新模式 (理解概念和基本应用)。
- 三向比较 (
<=>
) 和默认比较: 极大简化比较运算符定义。 std::format
: 新的格式化标准,类型安全易用。std::span
: 安全传递连续序列的视图。std::jthread
: 更安全的线程管理。