Ch 14: C++20/23 新特性
- 理解 C++20 Ranges 库的惰性求值模型
- 掌握 std::format 格式化语法
- 了解 C++20 Modules 的优势
- 熟悉 C++23 的新特性
- 理解三路比较运算符
<=>
14.1 C++20 Ranges 库
Section titled “14.1 C++20 Ranges 库”Ranges 库是 C++20 最重大的库增强之一,改变了我们处理数据的方式:
14.1.1 为什么需要 Ranges
Section titled “14.1.1 为什么需要 Ranges”传统算法的局限性:
#include <vector>#include <algorithm>#include <iostream>
void traditional_algorithm_demo() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 传统方式:需要临时容器 std::vector<int> result; for (int n : numbers) { if (n % 2 == 0) { // 过滤偶数 result.push_back(n * n); // 平方 } }
// 问题: // 1. 需要额外的内存 // 2. 多次遍历需要多个容器 // 3. 代码不直观(循环比算法更明显) // 4. 无法组合操作
for (int n : result) { std::cout << n << " "; }}Ranges 解决方案:
#include <vector>#include <ranges>#include <iostream>
void ranges_demo() { std::vector<int> numbers = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 惰性求值 - 不创建中间容器 auto result = numbers | std::views::filter([](int n) { return n % 2 == 0; }) | std::views::transform([](int n) { return n * n; });
// result 是一个惰性视图,不实际计算 // 只有在迭代时才计算
for (int n : result) { std::cout << n << " "; // 4 16 36 64 100 }}14.1.2 视图组合
Section titled “14.1.2 视图组合”Ranges 的管道语法允许组合多个操作:
#include <vector>#include <ranges>#include <iostream>
void view_composition_demo() { std::vector<int> data = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 组合多个视图 auto result = data | std::views::filter([](int x) { return x % 2 == 0; }) // 偶数 | std::views::transform([](int x) { return x * x; }) // 平方 | std::views::take(3); // 取前3个
// 输出: 4 16 36 for (int n : result) { std::cout << n << " "; }
// views::reverse - 反转 auto reversed = data | std::views::reverse;
// views::drop - 跳过前N个 auto dropped = data | std::views::drop(3);
// views::stride - 步长 auto strided = data | std::views::stride(2);}14.1.3 常用视图工厂
Section titled “14.1.3 常用视图工厂”#include <vector>#include <ranges>#include <iostream>
void view_factories_demo() { // iota - 生成序列 auto nums = std::views::iota(1, 10); // 1, 2, 3, ..., 9 // 注意:iota(1) 表示从1开始,无限序列 // iota(1, 10) 表示 [1, 10) 范围
// single - 单元素视图 auto single_view = std::views::single(42);
// empty - 空视图 auto empty_view = std::views::empty<int>;
// 使用示例:生成斐波那契 auto fibonacci = []() { return std::views::iota(0, 10) | std::views::transform([](int n) { // 简化的斐波那契计算 if (n <= 1) return n; return n * 2; // 不准确,仅示例 }); };
// 范围for循环可以直接使用 for (int i : std::views::iota(1, 6)) { std::cout << i << " "; // 1 2 3 4 5 }}14.1.4 std::ranges 算法
Section titled “14.1.4 std::ranges 算法”C++20 在 std::ranges 命名空间下提供了惰性算法:
#include <vector>#include <ranges>#include <iostream>#include <algorithm>
void ranges_algorithm_demo() { std::vector<int> v = {5, 2, 8, 1, 9, 3};
// 惰性算法(返回视图) auto result = v | std::views::filter([](int x) { return x > 3; });
// 如果需要酂结果,可以使用 to_vector // std::vector<int> filtered = result | std::ranges::to<std::vector>();
// std::ranges::sort(仍然需要酂结果) std::ranges::sort(v); std::cout << "Sorted: "; for (int n : v) std::cout << n << " "; std::cout << std::endl;
// std::ranges::find auto it = std::ranges::find(v, 5); if (it != v.end()) { std::cout << "Found at index: " << std::ranges::distance(v.begin(), it); }
// std::ranges::transform std::vector<int> doubled(v.size()); std::ranges::transform(v, doubled.begin(), [](int x) { return x * 2; });}14.2 std::format
Section titled “14.2 std::format”Python 风格的格式化库,安全且类型化:
14.2.1 基础用法
Section titled “14.2.1 基础用法”#include <format>#include <iostream>#include <string>
void format_demo() { // {} 占位符自动编号 std::string s1 = std::format("Hello, {}!", "World"); std::cout << s1 << std::endl; // Hello, World!
// {0}, {1} 手动编号 std::string s2 = std::format("{1} {0}", "World", "Hello"); std::cout << s2 << std::endl; // Hello World
// 重复使用参数 std::string s3 = std::format("{0} {0} {0}", "repeat"); std::cout << s3 << std::endl; // repeat repeat repeat}14.2.2 格式化规格符
Section titled “14.2.2 格式化规格符”#include <format>#include <iostream>#include <iomanip>
void format_spec_demo() { // 整数格式 std::cout << std::format("{:d}", 42) << std::endl; // 42 std::cout << std::format("{:5d}", 42) << std::endl; // " 42" std::cout << std::format("{:05d}", 42) << std::endl; // "00042" std::cout << std::format("{:+d}", 42) << std::endl; // "+42" std::cout << std::format("{:x}", 255) << std::endl; // "ff" std::cout << std::format("{:X}", 255) << std::endl; // "FF" std::cout << std::format("{:b}", 42) << std::endl; // "101010" std::cout << std::format("{:o}", 42) << std::endl; // "52"
// 浮点数格式 std::cout << std::format("{:f}", 3.14159) << std::endl; // "3.141590" std::cout << std::format("{:.2f}", 3.14159) << std::endl; // "3.14" std::cout << std::format("{:e}", 3.14159) << std::endl; // "3.141590e+00" std::cout << std::format("{:E}", 3.14159) << std::endl; // "3.141590E+00" std::cout << std::format("{:g}", 3.14) << std::endl; // "3.14" std::cout << std::format("{:g}", 0.000001) << std::endl; // "1e-06"
// 字符串格式 std::cout << std::format("{:>10}", "right") << std::endl; // 右对齐 std::cout << std::format("{:<10}", "left") << std::endl; // 左对齐 std::cout << std::format("{:^10}", "center") << std::endl; // 居中 std::cout << std::format("{:10}", "pad") << std::endl; // 默认左对齐}14.2.3 格式化对象
Section titled “14.2.3 格式化对象”#include <format>#include <iostream>#include <chrono>#include <ctime>
void format_custom_demo() { // 格式化时间 auto now = std::chrono::system_clock::now(); std::time_t t = std::chrono::system_clock::to_time_t(now); std::tm* tm = std::localtime(&t);
std::cout << std::format("{:%Y-%m-%d %H:%M:%S}", *tm) << std::endl;
// 格式化指针 int x = 42; std::cout << std::format("{:p}", &x) << std::endl; // 指针地址
// 布尔值 std::cout << std::format("{:d}", true) << std::endl; // "1" std::cout << std::format("{:s}", true) << std::endl; // "true"}14.3 Concepts(详细)
Section titled “14.3 Concepts(详细)”见第13章,这里补充一些实际应用:
#include <concepts>#include <iostream>#include <vector>#include <list>
// 用 Concept 约束模板参数template<std::ranges::random_access_range R>void sort_range(R& r) { std::ranges::sort(r);}
// random_access_range 要求:// - begin(), end() 返回随机访问迭代器// - 支持 O(1) 的下标访问// - std::vector 满足,std::list 不满足
void concept_application_demo() { std::vector<int> v = {3, 1, 4, 1, 5, 9}; sort_range(v); // OK
std::list<int> lst = {3, 1, 4, 1, 5, 9}; // sort_range(lst); // 编译错误:list 是双向迭代器,不是随机访问}14.4 C++20 Modules
Section titled “14.4 C++20 Modules”Modules 是 C++20 最重要的新特性之一,目标是取代头文件:
14.4.1 为什么需要 Modules
Section titled “14.4.1 为什么需要 Modules”传统头文件的缺点:
#ifndef OLD_HEADER_H#define OLD_HEADER_H
// 每次包含都重新解析整个文件// 宏定义可能冲突// 编译时间随包含层次指数增长// 没有封装可言(内部实现暴露)
class OldStyle { int internal_state_; // 对包含者可见};
#endif14.4.2 Modules 语法
Section titled “14.4.2 Modules 语法”module; // 模块单元声明
module my_module; // 模块名称
// 导出接口export module my_module;
export int add(int a, int b) { return a + b;}
export class Widget {public: void do_something();private: int private_data_; // 对导入者不可见};
// 实现(在同一个文件或单独文件)void Widget::do_something() { // 实现细节}14.4.3 使用 Modules
Section titled “14.4.3 使用 Modules”import my_module; // 导入模块
int main() { int result = add(1, 2); std::cout << "Result: " << result << std::endl;
Widget w; w.do_something(); // 可以调用导出的方法 // w.private_data_; // 错误:不可访问 return 0;}14.4.4 模块与命名空间
Section titled “14.4.4 模块与命名空间”export module math_utils;
export namespace math { double PI = 3.14159265358979;
export double circle_area(double radius) { return PI * radius * radius; }
namespace internal { // 内部辅助函数,不导出 double辅助函数() { /* ... */ } }}
// main.cppimport math_utils;
int main() { double area = math::circle_area(5.0); std::cout << "Area: " << area << std::endl;
// math::internal::辅助函数(); // 错误:不可访问 return 0;}14.5 三路比较运算符 <=>
Section titled “14.5 三路比较运算符 <=>”C++20 引入了宇宙飞船运算符 <=>:
14.5.1 基础用法
Section titled “14.5.1 基础用法”#include <compare>#include <iostream>
void spaceship_demo() { int a = 1, b = 2;
// <=> 返回强排序类型 auto result = a <=> b;
if (result < 0) { std::cout << "a < b" << std::endl; } else if (result == 0) { std::cout << "a == b" << std::endl; } else { std::cout << "a > b" << std::endl; }
// 也可以用于其他类型 std::string s1 = "apple", s2 = "banana"; auto cmp = s1 <=> s2; // 字典序比较}14.5.2 自动生成比较运算符
Section titled “14.5.2 自动生成比较运算符”#include <compare>#include <iostream>
class Point {public: int x, y;
// C++20 自动生成 ==, !=, <, >, <=, >= auto operator<=>(const Point&) const = default;};
void point_comparison_demo() { Point p1{1, 2}, p2{1, 2}, p3{2, 1};
std::cout << std::boolalpha; std::cout << "p1 == p2: " << (p1 == p2) << std::endl; // true std::cout << "p1 != p3: " << (p1 != p3) << std::endl; // true std::cout << "p1 < p3: " << (p1 < p3) << std::endl; // true}14.6 C++23 新特性
Section titled “14.6 C++23 新特性”14.6.1 std::expected
Section titled “14.6.1 std::expected”类似 Rust 的 Result 类型:
#include <expected>#include <iostream>#include <string>
std::expected<int, std::string> parse_int(const std::string& s) { try { return std::stoi(s); } catch (...) { return std::unexpected("Invalid integer: " + s); }}
void expected_demo() { auto r1 = parse_int("42"); if (r1) { std::cout << "Parsed: " << *r1 << std::endl; }
auto r2 = parse_int("abc"); if (!r2) { std::cout << "Error: " << r2.error() << std::endl; }
// transform 和 and_then auto doubled = r1.transform([](int x) { return x * 2; });}14.6.2 std::ranges::to
Section titled “14.6.2 std::ranges::to”将范围收集到容器:
#include <vector>#include <ranges>#include <iostream>
void ranges_to_demo() { auto result = std::views::iota(1, 6) | std::views::filter([](int x) { return x % 2 == 0; }) | std::views::transform([](int x) { return x * x; }) | std::ranges::to<std::vector<int>>();
// result = {4, 16}}14.6.3 auto(x) 强制推导
Section titled “14.6.3 auto(x) 强制推导”#include <iostream>
void auto_deduction_demo() { int x = 42; int& rx = x; const int& crx = x;
// auto 通常会移除引用和 const auto a1 = rx; // int,不是 int& auto a2 = crx; // int,不是 const int&
// C++23: auto(x) 保持相同类型 auto a3 = auto(x); // int auto a4 = auto(rx); // int&(保持引用) auto a5 = auto(crx); // const int&(保持 const)}14.6.4 C++23 其他特性
Section titled “14.6.4 C++23 其他特性”// 1. import std 导入整个标准库(C++23)import std;import std.io;
int main() { std::println("Hello, {}!", "World"); // 类似 Python print}
// 2. stacktrace(C++23)#include <stacktrace>void foo() { std::cout << std::stacktrace::current() << std::endl;}
// 3. std::optional::transform 和 and_then 已稳定
// 4. std::flat_map / std::flat_set(C++23)// 类似 Python dict,但键是有序的14.7 特性检测宏
Section titled “14.7 特性检测宏”检测编译器支持:
#include <iostream>#include <version>
// 检测 C++ 版本#if __cplusplus >= 202002L #include <ranges> constexpr bool cpp20_or_later = true;#else constexpr bool cpp20_or_later = false;#endif
// 使用 <version> 检查特性#ifdef __cpp_lib_ranges // Ranges 库 // 使用 Ranges#endif
#ifdef __cpp_lib_format // std::format // 使用 std::format#endif- C++20 Ranges 库提供惰性求值的视图组合
- 管道操作符
|组合多个视图 std::format提供类型安全的格式化- Concepts 约束模板参数,让错误信息更清晰
- Modules 目标是取代头文件,减少编译依赖
<=>运算符自动生成所有比较函数- C++23 带来
std::expected、std::flat_map等新特性 - 使用
<version>和__cpp_lib_*宏检测特性支持
下章预告:ch15 项目迁移策略。