各位未来的自己:当你翻开这篇笔记时,大概率正对着当初写的 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_flagjthread是 “智能员工”:析构时自动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;
	}
}

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐