C++20 新特性学习笔记:从 “test0X 乱码“ 到 “一看就懂“ 的救赎指南
各位未来的自己:当你翻开这篇笔记时,大概率正对着当初写的 test05 ()、test08 () 一脸懵 —— 咱当初为了省事儿,函数名起得跟 “密码本” 似的,注释更是惜字如金,连个 “这测啥” 都没写。现在我把这些 “加密代码” 解密,改成 “人话” 函数名,加了比代码还长的注释,还顺便吐槽了当初踩过的坑,主打一个 “以后复习不骂街,看一眼就知道咋回事”!
一、先总览:咱的代码里藏了哪些 C++20 新特性?
先列个 “特性清单”,省得你翻半天不知道重点,对着代码发呆:
模块(Modules)、三向比较运算符(<=>,外号 “太空船”)、范围库(Ranges,容器操作快捷键)、日期和时区库(终于能好好算日期了)、格式化库(std::format,输出界的排版大师)、跨度(std::span,连续内存的万能视图)、并发增强(jthread/stop_token,智能线程)、数学常数库(std::numbers,π/e 直接拿)、概念(Concept,模板的门禁系统)、协程(Coroutines,异步编程的暂停播放)。
下面逐个拆解,每个特性都配 “改名后代码 + 大白话注释 + 当初踩过的坑”,保证你看一遍就回忆起来。
二、逐个攻破:C++20 新特性详解(附改名代码)
1. 模块(Modules):给 #include 发 “下岗通知书”
核心作用:
以前用#include像 “复印店打工”—— 用个函数得把整个头文件复制过来,编译慢还容易冲突。模块是 “代码界的云共享文档”:一份代码存云端,谁用直接import,改一处全项目生效,编译速度从 “自行车” 飙到 “高铁”。
当初的坑:
- 直接导入模块分区(比如
import mymoduletest.impl),忘了分区是 “主模块的子文件”,外部不能直接导; - 模块名重复,导致导入时冲突;
- 忘了加
export,想暴露的函数外部看不见。
改名后代码(原 test05-test08→TestModuleFunctionCall)
cpp
运行
// 1. 标准库模块导入:代替#include <iostream>,避免文本拷贝(C++20新姿势)
import <iostream>;
// 2. 自定义模块导入:主模块+分区(impl=实现细节,只能主模块内部导)
import mymoduletest; // 主模块:对外暴露公开接口(func1、func2、var1等)
import mymoduletest.impl; // 模块分区:隐藏实现细节(func4、helloFunc),主模块导入后外部才能用
import Module1; // 另一个自定义模块:功能拆分,互不干扰
import Module1.modulepart; // 模块子分区:更细粒度拆分(testfenqu函数)
import 三向比较运算符; // 特性模块:专门测试太空船运算符
import 范围ranges; // 特性模块:专门测试Ranges库
import 日期和时区; // 特性模块:专门测试日期库
using namespace std;
// 原test05-test08整合:明确测试模块内函数调用,再也不用猜test0X是啥
void TestModuleFunctionCall() {
cout << "=== 测试模块:调用各模块函数 ===" << endl;
// 调用mymoduletest主模块的函数
func1(); // 输出:hello c++(主模块export的函数)
func2(); // 输出:func2()(主模块export的函数)
cout << "主模块导出的变量var1:" << var1 << endl; // 输出:100(主模块export的变量)
// 调用mymoduletest.impl分区的函数(主模块已导入分区,外部才能用)
func4(); // 输出:func4()(分区export的函数)
helloFunc(); // 输出:helloFunc()(分区export的函数)
testimpl(); // 输出:testmpl()(分区export的函数)
// 调用Module1主模块和子分区的函数
MyFunc(); // 输出:MyFunc()(Module1主模块的函数)
testfenqu(); // 输出:testfenqu()(Module1.modulepart子分区的函数)
// 当初踩过的坑:这里不能直接调用func11()!因为func11()没加export,是主模块内部函数
// func11(); // 编译报错:未声明的标识符(外部看不见内部函数)
}
// -------------------------- 模块1:mymoduletest(主模块) --------------------------
export module mymoduletest; // 声明主模块:必须加";",相当于给模块起名字
import <iostream>;
using namespace std;
// 1. 直接export单个函数:外部能调用
export void func1() {
cout << "mymoduletest主模块:func1() - hello c++" << endl;
}
export void func2() {
cout << "mymoduletest主模块:func2() - func2()" << endl;
}
// 2. export代码块:批量暴露变量和函数,更整洁
export {
int var1 = 100; // 导出全局变量:外部能访问,模块内安全无冲突
void func3() { // 导出函数:外部能调用
cout << "mymoduletest主模块:func3() - func3()" << endl;
}
};
// 3. 非export函数:仅模块内部可用,外部看不见(封装性拉满)
void func11() {
cout << "mymoduletest主模块:func11() - 内部辅助函数(外部调用不到)" << endl;
}
// -------------------------- 模块2:mymoduletest.impl(模块分区) --------------------------
export module mymoduletest.impl; // 声明分区:属于mymoduletest的"子文件",不能单独被外部导入
import <iostream>;
using namespace std;
// 实现主模块需要的函数:加export后,主模块导入分区就能用
export void func4() {
cout << "mymoduletest.impl分区:func4() - func4()" << endl;
}
export void helloFunc() {
cout << "mymoduletest.impl分区:helloFunc() - helloFunc()" << endl;
}
export void testimpl() {
cout << "mymoduletest.impl分区:testimpl() - testmpl()" << endl; // 当初的拼写错,先保留提醒自己
}
// -------------------------- 模块3:Module1(带子分区) --------------------------
export module Module1.modulepart; // Module1的子分区:专门放分区函数
import <iostream>;
using namespace std;
export void testfenqu() {
cout << "Module1.modulepart子分区:testfenqu() - 测试模块分区功能" << endl;
}
export module Module1; // Module1主模块
import <iostream>;
using namespace std;
export void MyFunc() {
cout << "Module1主模块:MyFunc() - MyFunc()" << endl;
}
复习重点:
- 模块分区(如
mymoduletest.impl)只能被主模块导入(import :impl),外部不能直接导; - 想让外部用的内容必须加
export,没加的都是内部隐私; - 编译模块时要先编主模块,再编依赖它的代码,不然会报 “找不到模块”。
2. 三向比较运算符(<=>):“一次重载,搞定所有比较”
核心作用:
以前比大小像 “菜市场反复问价”—— 想让自定义类型支持</>/==,得手写 6 个运算符重载,手都写麻。“太空船” 运算符是 “万能裁判”:一次重载搞定所有比较,返回值直接告诉你 “大于、小于还是等于”,省事儿到飞起。
当初的坑:
- 判断返回值时写成
(num1 <=> num2 == 0),优先级错了(应该先算<=>,再和 0 比); - 自定义类型用
<=>后,忘了它能自动生成其他运算符,还手动写==。
改名后代码(原 test01→TestSpaceshipOperatorDemo)
cpp
运行
export module 三向比较运算符;
import <iostream>;
using namespace std;
// 原test01→改名:明确测试太空船运算符,再也不用猜test01是啥
void TestSpaceshipOperatorDemo() {
cout << "\n=== 测试三向比较运算符(太空船 <=>) ===" << endl;
// 场景1:基础类型比较(int)
int num1 = 200, num2 = 100;
// <=>返回三种结果:>0(num1大)、<0(num2大)、==0(相等)
auto compare_result = num1 <=> num2; // 核心:一次比较全搞定
// 当初踩过的坑:这里不能写(num1 <=> num2 == 0)!优先级:==比<=>高,会先算num2==0
if (compare_result < 0) {
cout << "num1(" << num1 << ") < num2(" << num2 << ")" << endl;
} else if (compare_result > 0) {
cout << "num1(" << num1 << ") > num2(" << num2 << ")" << endl; // 这里执行
} else { // 等价于 compare_result == 0
cout << "num1(" << num1 << ") == num2(" << num2 << ")" << endl;
}
// 场景2:自定义类型比较(比如“奶茶按甜度比”)
class MilkTea {
private:
int sweetness; // 甜度(0-100)
public:
MilkTea(int s) : sweetness(s) {}
// 核心:一次重载<=>,所有比较关系自动生效!
auto operator<=>(const MilkTea& other) const {
return sweetness <=> other.sweetness; // 按甜度比较
}
};
MilkTea taro(70); // 芋泥奶茶(70%甜,齁)
MilkTea oolong(50); // 乌龙奶茶(50%甜,刚好)
if (taro > oolong) { // 自动生成>,不用手动写!
cout << "芋泥奶茶比乌龙奶茶甜(像喝糖浆)" << endl;
}
}
复习重点:
<=>返回值支持>0/<0/==0判断,别搞反优先级;- 自定义类型重载
<=>后,编译器自动生成==/</>/<=/>=/!=,不用手写; - 想自定义
==逻辑(比如忽略大小写),可以单独写operator==,编译器会优先用。
3. 范围库(Ranges):容器操作的 “快捷键”
核心作用:
以前操作容器像 “用老年机”—— 遍历要写begin()/end(),筛选要传迭代器,麻烦得很。Ranges 是 “容器快捷键”:直接对容器 “喊话”,支持链式操作(过滤、转换、跳过),还不复制数据(视图零成本)。
当初的坑:
- 忘了
views::filter/views::transform是视图,修改原容器会影响视图结果; - 用
ranges::sort时还传begin()/end(),白瞎了 Ranges 的便捷性。
改名后代码(原 test02→TestRangesChainOperation)
cpp
运行
export module 范围ranges;
import <vector>;
import <ranges>;
import <iostream>;
using namespace std;
// 原test02→改名:明确测试Ranges链式操作,再也不用猜test02是啥
void TestRangesChainOperation() {
cout << "\n=== 测试Ranges库:链式操作(过滤/转换/反转) ===" << endl;
// 场景1:生成0~9,筛选偶数,再平方(基础视图用法)
auto ints = views::iota(0, 10); // 视图:生成0-9的序列,不占额外内存
auto is_even = [](int i) { return i % 2 == 0; }; // 筛选偶数
auto square = [](int i) { return i * i; }; // 平方
cout << "0~9筛选偶数并平方:";
// 链式操作:生成→过滤→平方→遍历(不用写迭代器!)
for (int i : ints | views::filter(is_even) | views::transform(square)) {
cout << i << " "; // 输出:0 4 16 36 64
}
cout << endl;
// 场景2:vector进阶操作(跳过→过滤→乘3→反转)
vector<int> scores = { 10, 2, 20, 5, 30, 8 }; // 原始分数
cout << "原始分数:";
for (int s : scores) cout << s << " ";
cout << endl;
// 当初踩过的坑:这里的chain是视图,修改scores会影响chain!
auto processed_scores = scores
| views::drop(2) // 步骤1:跳过前2个→[20,5,30,8]
| views::filter([](int n) { // 步骤2:筛选偶数→[20,30,8]
return n % 2 == 0;
})
| views::transform([](int n) { // 步骤3:乘3→[60,90,24]
return n * 3;
})
| views::reverse; // 步骤4:反转→[24,90,60]
cout << "处理后(跳过2→偶数→乘3→反转):";
for (int s : processed_scores) {
cout << s << " "; // 输出:24 90 60
}
cout << endl;
// 场景3:Ranges算法(不用传迭代器!)
ranges::sort(scores); // 直接对容器排序,比std::sort(scores.begin(), scores.end())简洁
cout << "排序后分数:";
ranges::for_each(scores, [](int s) { cout << s << " "; }); // 遍历也不用迭代器
cout << endl;
}
复习重点:
- 视图(
views::xxx)不复制数据,修改原容器会同步影响视图; - 链式操作用
|(管道符),像 “流水线” 一样清晰; - Ranges 算法(如
ranges::sort)直接传容器,不用写begin()/end()。
4. 日期和时区库:终于能和 “时间刺客” 说拜拜
核心作用:
以前算日期像 “用算盘算日历”—— 想创建 “2024 年 10 月 1 日”,得手动算闰年、每月天数;转时区得自己加 8 小时,算错了直接社死(把 “今天开会” 搞成 “昨天开会”)。C++20 日期库是 “智能日历”:自带时区和日历计算,直接创建年月日,省心到哭。
当初的坑:
- 月份枚举拼错(比如把
September写成Sept); - 用
year_month_day时忘了加std::chrono命名空间,编译报错; - 不知道
y/m/d后缀,手动写year(2024)嫌麻烦。
改名后代码(原 test03→TestDateTimeConstruction)
cpp
运行
export module 日期和时区;
import <chrono>; // 日期库核心头文件,必须加!
import <iostream>;
using namespace std;
using namespace std::chrono; // 简化命名空间,不用每次写std::chrono
// 原test03→改名:明确测试日期创建,再也不用猜test03是啥
void TestDateTimeConstruction() {
cout << "\n=== 测试日期库:创建年月日(告别手算) ===" << endl;
// 场景1:单独创建年/月/日(两种写法,任君选择)
year y1 = year{ 2025 }; // 写法1:显式构造
year y2 = 2019y; // 写法2:后缀y,简洁(C++20新语法,当初觉得超酷)
month m1 = month(12); // 写法1:数字(1=1月,12=12月)
month m2 = September; // 写法2:枚举(直观,避免数字写错,当初拼错过Sept→September)
day d1 = day{ 18 }; // 写法1:显式构造
day d2 = 18d; // 写法2:后缀d,简洁
// 场景2:组合成“年月日”(三种常用写法,推荐第二种!)
year_month_day date1{ 2022y, month(10), 21d }; // 数字月+后缀日
year_month_day date2 = 2022y / July / 12d; // 除法语法(超直观,像写日期一样)
year_month_day date3 = { Monday[3] / July / 2022 }; // 高级:2022年7月第3个周一
// 场景3:直接打印日期(C++20支持year_month_day直接输出,不用自己拼接字符串)
cout << "日期1(2022年10月21日):" << date1 << endl; // 输出:2022-10-21
cout << "日期2(2022年7月12日):" << date2 << endl; // 输出:2022-07-12
cout << "日期3(2022年7月第3个周一):" << date3 << endl; // 输出:2022-07-18
// 进阶:计算两个日期差(比如距离国庆还有多久,再也不用手算!)
year_month_day national_day{ 2024y, October, 1d };
auto days_diff = (national_day - date2).count(); // 计算天数差
cout << "2022年7月12日距离2024年国庆还有:" << days_diff << "天" << endl;
}
复习重点:
- 年 / 月 / 日后缀:
y(年)、m(月,少用,容易和变量名混)、d(日); - 月份枚举:
January~December,首字母大写,别拼错; - 计算日期差用
-,返回days类型,加.count()拿具体天数。
5. 跨度(std::span):连续内存的 “万能视图”
核心作用:
以前传数组像 “递快递没写地址”—— 得传指针 + 长度(void func(int* arr, int len)),容易越界;传 vector 得复制,浪费内存。span是 “连续内存的快照”:不复制数据,还知道长度,安全又高效,传数组 /vector/ 部分内存都能用。
当初的坑:
- 用
span管理栈内存(比如span<int> s(&x, 1)),栈变量销毁后 span 变 “悬垂视图”; - 忘了
span不复制数据,修改 span 里的元素会改原内存。
补充代码(原代码没写,新增 TestSpanBasic)
cpp
运行
export module 跨度;
import <span>;
import <vector>;
import <iostream>;
using namespace std;
// 新增函数:测试span的万能视图功能
void TestSpanBasic() {
cout << "\n=== 测试跨度(span):连续内存的万能视图 ===" << endl;
// 场景1:传原生数组(不用写指针+长度,span自动知道大小)
int arr[] = { 1, 2, 3, 4 };
span<int> arr_span(arr); // 直接用数组初始化span
cout << "原生数组span(长度:" << arr_span.size() << "):";
for (int x : arr_span) cout << x << " "; // 输出:1 2 3 4
cout << endl;
// 场景2:传vector(不用传begin()/end(),直接传vector)
vector<int> vec = { 5, 6, 7, 8, 9 };
span<int> vec_span(vec);
cout << "vector span(长度:" << vec_span.size() << "):";
for (int x : vec_span) cout << x << " "; // 输出:5 6 7 8 9
cout << endl;
// 场景3:传数组的一部分(比如前2个元素,当初觉得超灵活)
int big_arr[] = { 10, 20, 30, 40, 50 };
span<int> part_span(big_arr, 2); // 从big_arr开始,取2个元素
cout << "数组前2个元素span:";
for (int x : part_span) cout << x << " "; // 输出:10 20
cout << endl;
// 当初踩过的坑:修改span会改原内存!span是视图,不是副本
part_span[0] = 100; // 修改span的第一个元素
cout << "修改span后,原数组big_arr[0]:" << big_arr[0] << endl; // 输出:100(原数组被改了)
}
复习重点:
span不复制数据,只是 “视图”,修改 span 会同步改原内存;- 别用
span管理临时变量(比如函数里的局部数组),变量销毁后 span 失效; - 函数参数用
span比用 “指针 + 长度” 安全,比用vector高效。
6. 格式化库(std::format):输出界的 “排版大师”
核心作用:
以前打印像 “写作文没标点”——printf怕类型错(% d 填字符串直接崩),cout拼接像打补丁(<<用得手抽筋)。std::format是 “Word 排版”:模板字符串清晰,类型安全,还支持对齐、补零,比前两者都香。
当初的坑:
- 占位符顺序错了(比如
format("{} {}", a, b)写成format("{} {}", b, a)); - 忘了包含
<format>头文件,编译报 “找不到 std::format”; - 不知道
{:.1f}是保留 1 位小数,手动拼接.0嫌麻烦。
补充代码(原代码没写,新增 TestFormatUsage)
cpp
运行
export module 格式化;
import <format>;
import <iostream>;
import <string>;
using namespace std;
// 新增函数:测试格式化库的便捷性
void TestFormatUsage() {
cout << "\n=== 测试格式化库(std::format):输出不抽筋 ===" << endl;
// 场景1:基础格式化(占位符{},按顺序传参,比cout简洁)
string feature = "格式化库";
int version = 20;
double score = 9.9; // 满意度(少0.1怕它骄傲)
string basic_str = format("C++{}的{}满意度:{:.1f}分", version, feature, score);
cout << basic_str << endl; // 输出:C++20的格式化库满意度:9.9分
// 场景2:进阶格式化(补零、对齐、进制,比printf安全)
cout << "数字补零(5位):" << format("{:05d}", 123) << endl; // 输出:00123
cout << "十六进制(小写):" << format("{:x}", 255) << endl; // 输出:ff
cout << "右对齐(10位):" << format("{:>10s}", "hello") << endl; // 输出: hello(前面5个空格)
// 场景3:嵌套格式化(当初觉得超秀)
string nested_str = format("今日推荐:{}(版本{}),评分{}",
format("C++{}新特性", version), // 嵌套format
version,
score);
cout << nested_str << endl; // 输出:今日推荐:C++20新特性(版本20),评分9.9
}
复习重点:
- 必须包含
<format>头文件,不然编译器不认识std::format; - 占位符支持格式控制:
{:0nd}补零、{:.nf}保留 n 位小数、{:x}十六进制; - 类型安全:传错类型会编译报错,不像
printf会崩。
7. 其他必学特性(原代码没写,但必须会)
7.1 并发增强(jthread/stop_token):智能线程 “不加班”
核心作用:
以前用std::thread像 “出门忘锁门”—— 忘了join()会崩溃;想中途停线程,得自己写atomic<bool> stop_flag。jthread是 “智能员工”:析构时自动join(),还支持stop_token中途 “下班”,省心又安全。
补充代码(TestJthreadStopToken)
cpp
运行
export module 并发;
import <thread>;
import <stop_token>;
import <iostream>;
using namespace std;
// 线程任务:循环工作,收到停止请求就下班
void WorkTask(stop_token stop_flag) {
int count = 0;
while (!stop_flag.stop_requested()) { // 检查是否要下班
cout << "工作中... 计数:" << ++count << endl;
this_thread::sleep_for(1s); // 模拟工作1秒
}
cout << "收到停止请求,下班!" << endl;
}
// 测试智能线程
void TestJthreadStopToken() {
cout << "\n=== 测试并发(jthread/stop_token):智能线程不加班 ===" << endl;
// 创建jthread:自动传递stop_token,不用手动管理
jthread work_thread(WorkTask);
// 主线程:3秒后让工作线程下班
this_thread::sleep_for(3s);
work_thread.request_stop(); // 发送“下班通知”
// 当初踩过的坑:不用写work_thread.join()!jthread析构时自动join()
}
复习重点:
jthread析构时自动join(),再也不怕忘写导致崩溃;stop_token是 “协作式中断”:线程自己检查是否要停,不会强制杀死;- 别在
jthread析构后还调用request_stop(),线程已经没了。
7.2 数学常数库(std::numbers):π/e “直接拿”
核心作用:
以前算圆周率像 “背小数点”—— 要么手写3.1415926(精度不够),要么查头文件(麻烦)。std::numbers里全是高精度数学常数,直接用,精度拉满。
补充代码(TestNumbersLibrary)
cpp
运行
export module 数学常数;
import <numbers>;
import <iostream>;
using namespace std;
// 测试数学常数库
void TestNumbersLibrary() {
cout << "\n=== 测试数学常数(std::numbers):π/e直接拿 ===" << endl;
cout << "圆周率π:" << numbers::pi << endl; // 输出:3.141592653589793
cout << "自然常数e:" << numbers::e << endl; // 输出:2.718281828459045
cout << "黄金比例φ:" << numbers::phi << endl; // 输出:1.618033988749895
cout << "根号2:" << numbers::sqrt2 << endl; // 输出:1.4142135623730951
}
复习重点:
- 必须包含
<numbers>头文件; - 常数是模板变量,支持不同精度(比如
numbers::pi_v<float>是 float 版 π); - 不用自己定义常数,避免手写错误(比如把 π 写成 3.1415927,精度不够)。
7.3 概念(Concept):模板的 “门禁系统”
核心作用:
以前写模板像 “没门禁的小区”—— 传入错误类型会报一堆 “天书级” 错误(比如把int传给需要迭代器的模板)。Concept是 “门禁”:提前规定 “必须是什么类型”,错了直接说 “你不符合要求”,调试效率翻倍。
补充代码(TestConceptConstraint)
cpp
运行
export module 概念;
import <type_traits>;
import <iostream>;
using namespace std;
// 定义Concept:必须是“可转换为string”的类型(当初想测的功能)
template <typename T>
concept ConvertibleToString = requires(T a) {
{ a }->convertible_to<string>; // 检查a能否转换为string
};
// 模板函数:只接受“可转换为string”的类型(用Concept当门禁)
template <ConvertibleToString T>
string ToString(T val) {
return string(val); // 安全转换为string
}
// 测试Concept
void TestConceptConstraint() {
cout << "\n=== 测试概念(Concept):模板的门禁系统 ===" << endl;
// 正确:const char*能转换为string,符合门禁
cout << "字符串常量转换:" << ToString("hello C++20") << endl; // 输出:hello C++20
// 正确:string本身能转换为string
string s = "test";
cout << "string转换:" << ToString(s) << endl; // 输出:test
// 当初踩过的坑:int不能转换为string,编译直接报错(不会出现一堆乱码)
// int num = 100;
// cout << ToString(num) << endl; // 报错:int不满足ConvertibleToString
}
复习重点:
- Concept 语法:
template <typename T> concept 名字 = requires(...) { 约束条件 };; - 用 Concept 约束模板参数(
template <Concept T>),错了编译报错; - 约束条件用
requires表达式,比如检查是否有某个成员函数、能否转换为某类型。
7.4 协程(Coroutines):异步编程的 “暂停播放”
核心作用:
以前写异步像 “看视频不能暂停”—— 用回调函数嵌套成 “回调地狱”,逻辑乱成麻。协程能 “暂停” 执行,保存状态,需要时再 “继续”,像看视频暂停去接电话,回来接着看,进度不丢,异步逻辑超清晰。
补充代码(TestCoroutineGenerator)
cpp
运行
export module 协程;
import <coroutine>;
import <iostream>;
using namespace std;
// 简单协程生成器:生成从0到max的整数(C++20协程基础用法)
template <typename T>
struct Generator {
// 协程 promise:管理协程状态和返回值
struct promise_type {
T current_val; // 协程每次返回的值
Generator get_return_object() {
return Generator{ coroutine_handle<promise_type>::from_promise(*this) };
}
suspend_always initial_suspend() { return {}; } // 初始暂停,等待resume
suspend_always final_suspend() noexcept { return {}; }
void unhandled_exception() { terminate(); }
// 核心:co_yield时调用,保存当前值并暂停
suspend_always yield_value(T val) {
current_val = val;
return {};
}
void return_void() {}
};
coroutine_handle<promise_type> handle; // 协程句柄,管理协程生命周期
~Generator() { if (handle) handle.destroy(); } // 释放协程资源
// 迭代器:支持for循环遍历
bool MoveNext() {
handle.resume(); // 恢复协程执行
return !handle.done(); // 判断是否执行完
}
T CurrentVal() { return handle.promise().current_val; }
};
// 协程函数:生成0到max的整数
Generator<int> CountFrom0To(int max) {
for (int i = 0; i <= max; ++i) {
co_yield i; // 暂停,返回i,下次resume从这里继续(当初觉得这行超神奇)
}
}
// 测试协程
void TestCoroutineGenerator() {
cout << "\n=== 测试协程:生成0~5(暂停播放) ===" << endl;
auto generator = CountFrom0To(5);
while (generator.MoveNext()) {
cout << generator.CurrentVal() << " "; // 输出:0 1 2 3 4 5
}
cout << endl;
}
复习重点:
- 协程需要
promise_type:管理状态、返回值、暂停 / 恢复逻辑; co_yield:暂停协程并返回值,下次resume从下一行开始;- 协程不直接返回值,返回 “生成器” 或 “任务” 对象(比如上面的
Generator)。
三、复习速查表(怕忘就看这个!)
| 特性 | 核心作用 | 关键语法 / 避坑点 |
|---|---|---|
| 模块(Modules) | 替代 #include,编译快、无冲突 | 1. 分区只能主模块导入;2. 外部用的内容加 export;3. 先编译主模块再编译依赖代码 |
| 三向比较(<=>) | 一次重载搞定所有比较 | 1. 返回值 > 0/<0/==0 判断;2. 自动生成 ==/<>/<=/>=;3. 别写错优先级(先 <=> 再比 0) |
| 范围库(Ranges) | 容器操作快捷键,链式操作 | 1. 视图不复制数据,改原容器影响视图;2. 算法直接传容器,不用 begin ()/end () |
| 日期库 | 智能日历,支持时区和日期计算 | 1. 年 / 日后缀 y/d;2. 月份枚举别拼错;3. 计算差用 -,加.count () 拿天数 |
| 跨度(span) | 连续内存万能视图,安全高效 | 1. 不复制数据,改 span 影响原内存;2. 别管理临时变量,避免悬垂 |
| 格式化(format) | 输出排版,类型安全 | 1. 必须包含<format>;2. 占位符支持格式控制({:05d} 补零);3. 类型错编译报错 |
| 并发(jthread) | 智能线程,自动 join,支持中断 | 1. 析构自动 join,不用手动写;2. stop_token 是协作式,线程自己检查停止请求 |
| 数学常数(numbers) | 高精度常数直接拿 | 1. 包含<numbers>;2. 支持不同精度(pi_v<float>);3. 不用手写常数 |
| 概念(Concept) | 模板门禁,约束参数类型 | 1. 语法:template <T> concept 名 = requires (...);2. 约束模板参数用<Concept T> |
| 协程(Coroutines) | 异步暂停播放,避免回调地狱 | 1. 需要 promise_type 管理状态;2. co_yield 暂停返回值;3. 返回生成器 / 任务对象 |
四、结尾:以后复习别再 “猜代码” 了!
这篇笔记把当初的test0X全改成了 “看得懂” 的函数名(比如TestSpaceshipOperatorDemo),加了比代码还长的注释,还把踩过的坑(比如模块分区直接导入、span 改原内存)标出来了。
C++20 新特性看着多,但核心都是 “让代码更简洁、更安全、更少加班”—— 模块少写#include,太空船少写 5 个比较函数,Ranges 少写迭代器,日期库少算天数… 记住这些 “偷懒又安全” 的原则,复习起来会超轻松。
下次再翻开这篇笔记,对着速查表看特性,对着代码看用法,不用再对着 “密码式” 函数名发呆啦!
下面附上我学习时写的源码,需要的可以自己拿来练习,可以自由修改练习,欢迎一同交流学习。
import <iostream>;//建议使用import导入库 c++新特性
using namespace std;
//导入模块
import mymoduletest;//导入模块
import mymoduletest.impl;
import Module1;
import mymo1;
import Module1.modulepart;
import 三向比较运算符;
import test;
import test.impl;
//import 范围ranges;
import 日期和时区;
import 格式化;
int main()
{
string s1 = "hello C++";
//MyFunc666();
//std::cout << "Hello World!\n";
//func1();
//func2();
//cout << var1 << endl;
//func4();
//helloFunc();
//MyFunc();
//testfenqu();
//testimpl();
//test01();
//test02();
//test03();
//test04();
test05();
test06();
test07();
test08();
}
import <iostream>;
using namespace std;
export module mymoduletest;//模块声明 注意需要加“;”
//import mymoduletest.impl;
//声明函数
export void func1() {
cout << "hello c++" << endl;
}
export void func2() {
cout << "func2()" << endl;
//func1();
}
export{
int var1 = 100;
void func3() {
cout << "func3()" << endl;
}
//声明函数func4()
//void func4();
//void helloFunc();
//void testimpl();
}
export void func11() {
cout << "func11()" << endl;
}
import <iostream>;
using namespace std;
export module mymoduletest.impl;
//实现函数func4()
export void func4() {
cout << "func4()" << endl;
}
export void helloFunc() {
cout << "helloFunc()" << endl;
}
export void testimpl() {
cout << "testmpl()" << endl;
}
export module Module1.modulepart;//为模块Module1声明接口分区
import <iostream>;
using namespace std;
export void testfenqu() {
cout << "testfenqu()" << endl;
}
import <iostream>;
using namespace std;
export module Module1;
export void MyFunc() {
cout << "MyFunc()" << endl;
}
export module mymo1;
//export static void func1() {} bug代码
//export static int num = 100; bug代码
//namespace {
// export class ABC{};
//}
export module test.impl;
import <iostream>;
using namespace std;
export void MyFunc666() {
cout << "MyFunc()实现" << endl;
}
export module test;
//export void MyFunc666();
export module 范围ranges;
import <vector>;
import <ranges>;
import <iostream>;
using namespace std;
/*
std::views::filter(过滤)和
std::views::transform(转换)
都是 “视图适配器”—— 它们接收一个范围,返回一个新的视图(转换后的范围)。
*/
export void test02() {
auto ints = views::iota(0, 10);//生成0~9
auto even = [](int i) {
return i % 2 == 0;
};
auto square = [](int i) {
return i * i;
};
for (int i : ints | views::filter(even) | views::transform(square)) {
cout << i << " ";
}
cout << endl;
vector<int> nums = { 10,2,20,5,30,8 };
//操作链:跳过前两个元素-》取偶数=》乘3=》反转=》打印
auto chain = nums
| views::drop(2) //跳过前2个元素
| views::filter([](int n) {return n % 2 == 0; }) //取偶数
| views::transform([](int n) {return n * 3; }) //乘3
| views::reverse; //反转
//遍历输出
for (int n : chain) {
cout << n << " ";
}
cout << endl;
}
export module 范围ranges;
import <vector>;
import <ranges>;
import <iostream>;
using namespace std;
/*
std::views::filter(过滤)和
std::views::transform(转换)
都是 “视图适配器”—— 它们接收一个范围,返回一个新的视图(转换后的范围)。
*/
export void test02() {
auto ints = views::iota(0, 10);//生成0~9
auto even = [](int i) {
return i % 2 == 0;
};
auto square = [](int i) {
return i * i;
};
for (int i : ints | views::filter(even) | views::transform(square)) {
cout << i << " ";
}
cout << endl;
vector<int> nums = { 10,2,20,5,30,8 };
//操作链:跳过前两个元素-》取偶数=》乘3=》反转=》打印
auto chain = nums
| views::drop(2) //跳过前2个元素
| views::filter([](int n) {return n % 2 == 0; }) //取偶数
| views::transform([](int n) {return n * 3; }) //乘3
| views::reverse; //反转
//遍历输出
for (int n : chain) {
cout << n << " ";
}
cout << endl;
}
export module 模板;
import <iostream>;
import <string>;
using namespace std;
export void MyFunc();
export;
#include <chrono>
using namespace std::chrono;
export module 日期和时区;
//import <chrono>;
import <iostream>;
using namespace std;
export void test03() {
//creating a year 创建年
auto y1 = year{ 2025 };
year y2 = 2019y;
//创建月
auto m1 = month(12);
month m2 = September;
//创建日
auto d1 = day{ 18 };
day d2 = 18d;
//创建年月日
year_month_day date1{ 2022y,month(10),21d};
auto date2 = 2022y / July / 12d;
chrono::year_month_day date3 = { Monday[3] / July / 2022 };
cout << date1 << endl;
cout << date2 << endl;
cout << date3 << endl;
cout << date3 << endl;
}
export module 三向比较运算符;
import <iostream>;
using namespace std;
export void test01() {
int num1 = 200, num2 = 100;
if ((num1 <=> num2) < 0) {
cout << "num1<num2" << endl;
}
else if ((num1 <=> num2) > 0) {
cout << "num1>num2" << endl;
}
else if((num1<=>num2==0)){
cout << "num1==num2" << endl;
}
}
更多推荐


所有评论(0)