网站首页不被收录,公司怎么注册官方网站,四川建设学习网,网站建设sz886作者#xff1a;billy 版权声明#xff1a;著作权归作者所有#xff0c;商业转载请联系作者获得授权#xff0c;非商业转载请注明出处
前言
C20 是 C 标准中的一个重要版本#xff0c;引入了许多新特性和改进#xff0c;包括模块#xff08;Modules#xff09;、协程…作者billy 版权声明著作权归作者所有商业转载请联系作者获得授权非商业转载请注明出处
前言
C20 是 C 标准中的一个重要版本引入了许多新特性和改进包括模块Modules、协程Coroutines、概念Concepts、三向比较运算符、范围Ranges、日期时间库Date and Time、数字分隔符Digit Separators等等。 有的小伙伴可能会需要参考手册的链接这里给一个网页版的C 参考手册 - 网页版
一. 语言特性
1. 模块Modules
C20 的模块通过引入 “导入import” 和 “导出export” 概念替代传统的头文件机制减少了编译时间避免头文件互相包含的问题使代码组织更清晰提高了封装性。
// example_module.cppm 在 .cppm 文件中定义模块
export module example_module;import string; // 使用 import 代替 #include 来导入 string
export namespace Example
{// 定义一个学生类class Student {public:Student(std::string _name, int _age) : name(_name), age(_age) {}std::string getName() const { return name; }int getAge() const { return age; }private:std::string name;int age;};
}// main.cpp
import example_module; // 导入自定义的模块 example_module
#include iostream
//import iostream; 这里用 import iostream 失败了可能 vs2019 中支持还不够还是用的原始的 #includeint main()
{Example::Student stu(billy, 18);std::cout Student: stu.getName() , stu.getAge() std::endl;return 0;
}在这个例子中example_module.cppm 定义了一个名为 Example 的命名空间并在其中导出了 Student 类。main.cpp 通过 import 语句导入了example_module然后直接使用了 Student。这种清晰的模块边界和导入机制使得代码更加整洁、易于管理和维护。
模块化编程是 C 语言发展的重要一步它解决了长期困扰 C 开发者的编译时间和代码组织问题。虽然在实际应用中可能会遇到一些挑战但通过合理的规划和实践开发者可以充分利用这一特性提升开发效率和代码质量。随着编译器对 C20 标准支持的不断成熟模块化编程将成为现代 C 开发不可或缺的一部分。
2. 协程Coroutines
协程就是一个特殊的函数可以在执行过程中挂起suspend并在稍后恢复resume。你可以暂停执行去做其他事情然后在适当的时候恢复到暂停的位置继续执行。协程让异步编程更接近同步编程风格减少了异步操作的复杂度在游戏脚本、网络通信、高并发服务器等领域有很大作用。
C 提供了三个方法挂起协程co_await co_yield 和 co_return。如果一个函数中存在这三个关键字之一那么它就是一个协程。
co_await用于暂停协程的执行等待一个可等待对象awaitable完成。co_yield用于将一个值返回给协程的调用者并暂停协程的执行。co_return用于从协程中返回一个值并终止协程的执行。
// co_await 和 co_return 示例
#include iostream
#include future
#include thread// 协程函数
std::futureint coroutine()
{// 创建一个异步任务休眠 2 秒后返回 42auto task std::async(std::launch::async, []() {std::this_thread::sleep_for(std::chrono::seconds(2));return 42;});// 使用 co_await 等待异步任务完成int result co_await std::move(task);co_return result;
}int main()
{auto fut coroutine();// 获取协程的结果int value fut.get();std::cout Result: value std::endl;return 0;
}// co_yield 示例
#include iostream
#include coroutine
#include optional// 自定义的协程返回类型包含一个 promise_type
template typename T
struct Generator
{struct promise_type;using handle_type std::coroutine_handlepromise_type;struct promise_type {T value_;std::exception_ptr exception_;Generator get_return_object() {return Generator{handle_type::from_promise(*this)};}std::suspend_always initial_suspend() { return {}; }std::suspend_always final_suspend() noexcept { return {}; }void unhandled_exception() { exception_ std::current_exception(); }template std::convertible_toT Fromstd::suspend_always yield_value(From from) {value_ std::forwardFrom(from);return {};}void return_void() {}};handle_type h_;Generator(handle_type h) : h_(h) {}~Generator() { if (h_) h_.destroy(); }std::optionalT operator()() {if (!h_ || h_.done()) return {};h_.resume();if (h_.promise().exception_)std::rethrow_exception(h_.promise().exception_);return h_.promise().value_;}
};// 生成斐波那契数列的协程
Generatorint fibonacci()
{int a 0, b 1;while (true) {co_yield a;int temp a;a b;b temp b;}
}int main()
{// 调用 fibonacci 协程并通过 Generator 的 operator() 方法获取生成的值auto gen fibonacci();for (int i 0; i 10; i) {auto value gen();if (value) {std::cout *value ;}}std::cout std::endl;return 0;
}3. 概念Concepts
在传统的模板编程中模板参数的类型约束通常依赖于隐式接口即模板代码在实例化时才会检查类型是否满足要求。这可能导致在编译过程中出现复杂且难以理解的错误信息。C20 引入的概念Concepts是一项强大的新特性它为模板编程提供了更强大的类型约束机制使得代码更加清晰、易读并且能够在编译期更早地发现错误。概念通过显式地定义模板参数必须满足的条件使得模板的使用更加安全和可维护。
#include iostream
#include concepts// 定义一个概念检查类型是否为整数类型
template typename T
concept Integral std::is_integral_vT;// 方式一使用 Integral 概念约束模板参数
template Integral T
T add(T a, T b)
{return a b;
}// 方式二使用 requires 子句
template typename T
requires IntegralT
T subtract(T a, T b)
{return a - b;
}int main()
{int x 5, y 3;std::cout Addition: add(x, y) std::endl;std::cout Subtraction: subtract(x, y) std::endl;// 以下代码会导致编译错误因为 double 不满足 Integral 概念// double d1 5.5, d2 3.3;// std::cout Addition: add(d1, d2) std::endl;return 0;
}// 复杂概念约束示例
#include iostream
#include concepts// 定义一个概念检查类型是否为整数类型
template typename T
concept Integral std::is_integral_vT;// 定义一个概念要求类型支持 运算符
template typename T
concept Incrementable requires(T t)
{{ t } - std::same_asT;
};// 定义一个复杂概念要求类型既是整数类型又支持 运算符
template typename T
concept IntegralAndIncrementable IntegralT IncrementableT;// 使用复杂概念约束模板参数
template IntegralAndIncrementable T
void incrementAndPrint(T value)
{value;std::cout Incremented value: value std::endl;
}int main()
{int num 10;incrementAndPrint(num);return 0;
}4. 三向比较运算符
也叫太空船运算符用于比较两个对象返回小于、等于、大于三种结果。类似于 C 的 strcmp 函数返回 -1, 0, 1。
int a 5, b 3;
auto result a b;
// result 0 表示 a b
// result 0 表示 a b
// result 0 表示 a b可以通过下面的代码自动生成 、!、、、、 等运算符它简化了多重比较运算的实现过程。 auto X::operator(const Y) default;
如果对象是结构体则会逐个比较
#include iostream
#include comparestruct Point
{int x, y;// 自动生成所有比较运算符auto operator(const Point) const default;
};int main()
{Point p1 { 1, 2 };Point p2 { 1, 3 };if (p1 p2) {std::cout p1 is less than p2\n;}if (p1 ! p2) {std::cout p1 is not equal to p2\n;}
}5. 范围 for 循环的语句初始化
从 C 17 开始支持 if 和 switch 的语句初始化现在 C20 中也可以在范围循环中进行初始化。
for ( std::vector v {1, 2, 3}; auto e : v )
{ std::cout e std::endl;
}二. 标准库特性
1. Ranges 库
Ranges 库是对标准模板库STL的一个重要扩展它提供了一种现代、简洁的方式来处理序列如数组、容器等上的操作。范围库的主要特点是允许通过组合函数和适配器链接操作使代码更直观和易读。
#include iostream
#include ranges
#include vectorint main()
{std::vectorint numbers { 1, 9, 6, 7, 5, 8, 4, 3, 2, 10, 16, 14, 13, 12, 15, 11 };// 使用 ranges 进行惰性计算auto result numbers| std::ranges::views::filter([](int n) { return n % 2 0; }) // 只保留偶数| std::ranges::views::transform([](int n) { return n * 2; }) // 每个数乘以2| std::ranges::views::take(6) // 获取范围内前6个数| std::ranges::views::drop(2) // 丢弃范围内前2个数| std::ranges::views::reverse; // 反转范围内元素的顺序// 遍历输出结果for (int n : result){std::cout n std::endl;}
}2. 日历和时区库
C20 引入了强大的日历和时区库提供了处理日期、时间、日历系统和时区的功能使得在 C 中进行日期和时间的操作变得更加方便和直观。
std::chrono::year_month_day表示公历中的一个具体日期由年、月、日组成。std::chrono::year_month_weekday表示公历中一个月内的第几个星期几例如 2025 年 2 月的第 2 个星期五。std::chrono::day、std::chrono::month、std::chrono::year分别表示日、月、年的类型。std::chrono::time_zone表示一个时区包含时区的名称、偏移量等信息。std::chrono::zoned_time表示一个带有时区信息的时间点。
// 时间操作示例
#include iostream
#include chronoint main()
{using namespace std::chrono;// 创建一个具体日期year_month_day date 2025y / February / 7d;// 输出日期信息std::cout Year: static_castint(date.year()) std::endl;std::cout Month: static_castunsigned(date.month()) std::endl;std::cout Day: static_castunsigned(date.day()) std::endl;// 检查日期是否有效if (date.ok()) {std::cout The date is valid. std::endl;} else {std::cout The date is invalid. std::endl;}return 0;
}// 时区操作示例
#include iostream
#include chronoint main()
{using namespace std::chrono;// 获取当前系统时钟的时间点auto now system_clock::now();// 获取本地时区const time_zone* local_tz current_zone();// 创建带有时区信息的时间点zoned_time local_time(local_tz, now);// 输出本地时间std::cout Local time: local_time std::endl;// 获取另一个时区例如纽约const time_zone* ny_tz locate_zone(America/New_York);zoned_time ny_time(ny_tz, now);// 输出纽约时间std::cout New York time: ny_time std::endl;return 0;
}3. std::span
在 C20 中引入的 std::span 是一个轻量级、非拥有的视图它可以表示连续的对象序列。它提供了一种安全且高效的方式来处理数组、std::array、std::vector 等连续存储的数据结构而无需复制数据。
主要特点
轻量级std::span 仅包含指向数据的指针和长度信息没有内存所有权因此创建和复制 std::span 的开销非常小。通用性可以与各种连续存储的数据结构如 C 风格数组、std::array、std::vector 等一起使用。安全性std::span 会跟踪所引用数据的长度避免越界访问。
#include iostream
#include span
#include vector
#include arrayint main()
{// 从 std::vector 创建 std::spanstd::vectorint vec {1, 2, 3, 4, 5};std::spanint vecSpan(vec);// 从 std::array 创建 std::spanstd::arrayint, 5 arr {1, 2, 3, 4, 5};std::spanint arrSpan(arr);// 从 C 风格数组创建 std::spanint cArr[] {1, 2, 3, 4, 5};std::spanint cArrSpan(cArr, std::size(cArr));// 获取子视图std::spanint subSpan vecSpan.subspan(1, 3);// 输出子视图中的元素for (int element : subSpan) {std::cout element ;}std::cout std::endl;return 0;
}4. std::jthread
std::jthread 是 C20 标准库中引入的一个新线程类std::jthread 与 std::thread 相比安全性更高std::jthread 会自动处理线程的清理工作避免了可能出现的资源泄漏问题。并且提供了内置的停止机制使得线程的取消操作更加方便和安全。
#include iostream
#include thread
#include chronovoid cancellableThread(std::stop_token st)
{while (!st.stop_requested()) {std::cout Thread is working... std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));}std::cout Thread is stopping. std::endl;
}int main()
{std::jthread jt(cancellableThread);// 模拟一段时间后请求线程停止std::this_thread::sleep_for(std::chrono::seconds(3));jt.request_stop();return 0;
}5. std::format
C20 引入了 std::format 库提供了一种功能强大且灵活的字符串格式化方式。与传统的 printf 和 std::stringstream 方法相比std::format 更加简洁且安全支持类型安全的格式化并且可以更方便地与现代 C 标准库集成。
#include iostream
#include formatint main()
{std::string name billy;int age 18;double pi 3.1415926;std::cout std::format(Name: {}, Age: {}, name, age) std::endl;std::cout std::format(Pi: {:.4f}, pi) std::endl;std::cout std::format({:10}, age) std::endl; // 右对齐宽度为10std::cout std::format({:10}, age) std::endl; // 左对齐宽度为10std::cout std::format({:010}, age) std::endl; // 用0填充宽度为10std::cout std::endl;std::cout std::format(Decimal: {}, age) std::endl;std::cout std::format(Hexadecimal: {:#x}, age) std::endl; // 带有前缀0xstd::cout std::format(Octal: {:#o}, age) std::endl; // 带有前缀0std::cout std::format(Binary: {:#b}, age) std::endl; // 带有前缀0breturn 0;
}// 自定义类型的格式化
#include iostream
#include format
#include stringstruct Point {int x;int y;
};template
struct std::formatterPoint
{constexpr auto parse(std::format_parse_context ctx) { return ctx.begin(); }auto format(const Point p, std::format_context ctx) {return std::format_to(ctx.out(), ({}, {}), p.x, p.y);}
};int main()
{Point p{3, 4};std::string result std::format(The point is {}., p);std::cout result std::endl;return 0;
}6. std::source_location
std::source_location 是 C20 标准库引入的一个新特性它允许在代码中获取当前代码的源文件位置信息包括文件名、行号、列号以及函数名等。这对于调试、日志记录和错误报告等场景非常有用。
// 日志记录
#include iostream
#include source_locationvoid log(const std::string message, const std::source_location location std::source_location::current())
{std::cout File: location.file_name() \n Line: location.line() \n Column: location.column() \n Function: location.function_name() \n Message: message \n;
}int main()
{log(This is a log message.);return 0;
}// 错误处理
#include iostream
#include source_location
#include stdexceptvoid divide(int a, int b, const std::source_location location std::source_location::current())
{if (b 0) {throw std::runtime_error(std::format(Division by zero at {}:{}, location.file_name(), location.line()));}std::cout Result: a / b std::endl;
}int main()
{try {divide(10, 0);} catch (const std::exception e) {std::cerr Exception: e.what() std::endl;}return 0;
}7. std::map 和 std::set 的 contains 函数
在 C20 中std::map 和 std::set 引入了 contains 函数这为判断容器中是否包含某个特定元素提供了更加简洁和直观的方式。在 C20 之前要判断 std::set 或 std::map 中是否包含某个元素或键通常使用 find 函数。虽然 find 函数也能实现相同的功能但 contains 函数的语义更加清晰代码也更加简洁。
#include iostream
#include set
#include map
#include stringint main()
{std::setint mySet { 1, 2, 3, 4, 5 };// 检查集合中是否包含元素 3// if (mySet.find(3) ! mySet.end()) { // find 函数的判断方式if (mySet.contains(3)) {std::cout Set contains 3. std::endl;}else {std::cout Set does not contain 3. std::endl;}std::mapstd::string, int myMap {{apple, 1},{banana, 2},{cherry, 3}};// 检查映射中是否包含键 banana// if (myMap.find(banana) ! myMap.end()) {if (myMap.contains(banana)) {std::cout Map contains key banana. std::endl;}else {std::cout Map does not contain key banana. std::endl;}return 0;
}8. 栅栏barriers、闩锁latches
C20 中引入了 std::latch闩锁和 std::barrier栅栏这两个同步原语它们用于在多线程环境中协调线程的执行确保线程在特定的点上进行同步。
std::latch 是一种一次性的同步原语它允许一个或多个线程等待直到达到预先设定的计数值。一旦计数值达到零所有等待的线程都会被释放并且之后不能再使用这个 std::latch 进行同步。
#include iostream
#include latch
#include thread
#include vector// 模拟一些工作
void worker(std::latch latch)
{std::this_thread::sleep_for(std::chrono::seconds(1));std::cout Worker thread finished its work. std::endl;// 完成工作后减少闩锁的计数将计数值减 1latch.count_down();
}int main()
{const int numThreads 3;// 初始化闩锁预期计数值为 3表示需要等待 3 个线程完成工作std::latch latch(numThreads);std::vectorstd::jthread threads;for (int i 0; i numThreads; i) {threads.emplace_back(worker, std::ref(latch));}// 主线程调用 latch.wait() 阻塞直到计数值变为 0即所有工作线程都完成了工作std::cout Main thread waiting for workers... std::endl;latch.wait();std::cout All workers finished, main thread can continue. std::endl;return 0;
}std::barrier 是一种可重复使用的同步原语它允许一组线程在某个点上进行同步。当所有线程都到达栅栏时会执行一个可选的完成函数然后所有线程继续执行。之后这个 std::barrier 可以再次用于同步。
#include iostream
#include barrier
#include thread
#include vector// 栅栏的完成函数
void onCompletion()
{std::cout All threads reached the barrier, continue... std::endl;
}// 模拟一些工作
void worker(std::barrier barrier)
{std::cout Worker thread starting work. std::endl;std::this_thread::sleep_for(std::chrono::seconds(1));std::cout Worker thread reached the barrier. std::endl;// 到达栅栏并等待其他线程barrier.arrive_and_wait();std::cout Worker thread continues after the barrier. std::endl;
}int main()
{const int numThreads 3;// 初始化栅栏预期线程数量为 3并指定完成函数std::barrier barrier(numThreads, onCompletion);// 启动 3 个工作线程每个线程在完成一部分工作后调用 barrier.arrive_and_wait() 到达栅栏并等待其他线程。// 当所有线程都到达栅栏时会执行完成函数 onCompletion然后所有线程继续执行后续代码。std::vectorstd::jthread threads;for (int i 0; i numThreads; i) {threads.emplace_back(worker, std::ref(barrier));}return 0;
}9. std::future 和 std::promise
std::promise 和 std::future 通常一起使用来实现线程间的同步和数据传递。std::promise 用于存储一个值或异常供后续的 std::future 对象获取而 std::future 则用于异步地获取 std::promise 所存储的值或异常通过这种方式可以方便地在不同线程之间共享数据和状态。
std::future 主要成员函数
get()阻塞当前线程直到关联的 std::promise 设置了值或异常然后返回该值或抛出异常。valid()检查 std::future 对象是否有效即是否与一个 std::promise 或 std::packaged_task 关联。wait()阻塞当前线程直到关联的 std::promise 设置了值或异常但不获取该值。wait_for()在指定的时间内等待关联的 std::promise 设置值或异常如果超时则返回一个状态。wait_until()等待直到指定的时间点如果在该时间点前关联的 std::promise 设置了值或异常则返回。
std::promise 主要成员函数
set_value()设置存储的值一旦调用此函数关联的 std::future 对象就可以安全地获取该值。set_exception()设置存储的异常关联的 std::future 对象在获取值时会抛出该异常。get_future()返回一个与该 std::promise 关联的 std::future 对象用于获取存储的值或异常。
#include iostream
#include future
#include thread
#include vector// 工作线程函数
void multipleWorkers(std::promiseint prom, int id)
{std::this_thread::sleep_for(std::chrono::seconds(id));prom.set_value(id * 10);
}int main()
{// 创建了一个包含多个 std::promise 和 std::future 的向量以及一个线程向量const int numPromises 3;std::vectorstd::promiseint promises(numPromises);std::vectorstd::futureint futures;std::vectorstd::thread threads;// 遍历 std::promise 向量为每个 std::promise 获取对应的 std::future并启动一个工作线程处理该 std::promisefor (int i 0; i numPromises; i) {futures.emplace_back(promises[i].get_future());threads.emplace_back(multipleWorkers, std::ref(promises[i]), i);}// 主线程遍历 std::future 向量调用 get() 方法获取每个 std::future 的结果并输出for (int i 0; i numPromises; i) {int result futures[i].get();std::cout Result from promise i : result std::endl;}// 等待所有工作线程完成for (auto t : threads) {t.join();}return 0;
}10. std::is_constant_evaluated
std::is_constant_evaluated 是 C20 引入的一个函数用于在编译时判断当前代码是否在常量求值上下文中执行。这可以让函数根据不同的上下文执行不同的逻辑。
#include iostream
#include type_traitsconstexpr int compute(int x)
{if (std::is_constant_evaluated()) {// 在编译时执行的逻辑return x * x;} else {// 在运行时执行的逻辑return x x;}
}int main()
{// 编译时计算constexpr int compileTimeResult compute(5);std::cout Compile-time result: compileTimeResult std::endl;// 运行时计算int num 10;int runtimeResult compute(num);std::cout Runtime result: runtimeResult std::endl;return 0;
}三. 其他改进
1. Lambda 表达式的增强
1模板 Lambda 在 C20 之前Lambda 表达式的参数类型必须是具体的类型。C20 引入了模板 Lambda允许在 Lambda 表达式中使用模板参数使得 Lambda 可以处理不同类型的参数。
#include iostreamint main()
{// 模板Lambda使用 auto 关键字auto add [](auto a, auto b) {return a b;};std::cout add(1, 2) std::endl; // 处理整数std::cout add(1.5, 2.5) std::endl; // 处理浮点数return 0;
}2约束 Lambda C20 引入了概念Concepts可以将其应用于 Lambda 表达式对 Lambda 的参数类型进行约束。
#include iostream
#include conceptsint main()
{// 约束Lambda要求参数是整数类型auto printInt []std::integral T(T value) {std::cout value std::endl;};printInt(10); // 可以正常调用// printInt(3.14); // 编译错误因为 3.14 不是整数类型return 0;
}3Lambda 默认构造函数 在 C20 之前lambda 表达式没有默认构造函数这意味着你不能默认初始化一个 lambda 对象。而从 C20 开始无捕获的 lambda 表达式可以默认构造这为代码的编写和使用带来了更多的灵活性。
#include iostream// 定义一个函数接受一个无捕获的 lambda 类型的参数
templatetypename Func
void callFunction(Func func) {func();
}int main()
{// 定义一个无捕获的 lambda 类型using MyLambda void(*)();// C20 中无捕获的 lambda 可以默认构造MyLambda lambda{};// 为 lambda 赋值一个无捕获的 lambda 表达式lambda []() {std::cout Hello, C20 Lambda Default Constructor! std::endl;};// 调用 lambda 表达式lambda();// 也可以将 lambda 传递给模板函数callFunction(lambda);return 0;
}4使用模板形参 C20 允许在 Lambda 表达式中显式使用模板形参这使得 Lambda 更加灵活和强大就像普通的模板函数一样。
#include iostream
#include concepts// 定义一个概念要求类型为算术类型
templatetypename T
concept Arithmetic std::is_arithmetic_vT;int main()
{// 带有模板形参和概念约束的 Lambdaauto arithmeticLambda []Arithmetic T(T value) {std::cout The arithmetic value is: value std::endl;};// 调用 Lambda 处理算术类型的数据arithmeticLambda(10);arithmeticLambda(3.14);// 下面这行代码会编译错误因为字符串不是算术类型// arithmeticLambda(Not an arithmetic type);return 0;
}2. constexpr 改进
1constexpr 动态内存分配 C20 允许在 constexpr 函数中进行动态内存分配和释放这意味着在编译时可以使用 new 和 delete 运算符。不过这些动态分配的内存必须在编译时释放否则会导致编译错误。
#include iostreamconstexpr int* allocateAndFill(int value)
{int* ptr new int(value);return ptr;
}constexpr void deletePtr(int* ptr)
{delete ptr;
}int main()
{// 在编译时分配内存并在编译时访问该内存中的值constexpr int* ptr allocateAndFill(42);constexpr int value *ptr;// 在编译时释放内存deletePtr(ptr);std::cout Value: value std::endl;return 0;
}2constexpr std::vector 和其他标准库容器 C20 允许在 constexpr 上下文中使用一些标准库容器如 std::vector、std::string 等。这使得在编译时可以创建和操作容器。
#include iostream
#include vectorconstexpr std::vectorint createVector()
{std::vectorint vec;vec.push_back(1);vec.push_back(2);vec.push_back(3);return vec;
}int main()
{// 在编译时创建一个 std::vector并遍历该容器输出其中的元素constexpr auto vec createVector();for (const auto value : vec) {std::cout value ;}std::cout std::endl;return 0;
}3constexpr 虚函数调用的限制放宽 在 C20 之前constexpr 函数不能调用虚函数。C20 放宽了这一限制只要虚函数调用在编译时可以解析就可以在 constexpr 函数中进行虚函数调用。
4constexpr std::initializer_list C20 允许 std::initializer_list 在 constexpr 上下文中使用这使得在编译时可以使用初始化列表来初始化对象。
#include iostream
#include initializer_listconstexpr int sum(std::initializer_listint list)
{int result 0;for (int value : list) {result value;}return result;
}int main()
{// 使用初始化列表调用 sum 函数并将结果存储在 constexpr 变量 result 中。constexpr int result sum({1, 2, 3, 4});std::cout Sum: result std::endl;return 0;
}3. using 扩展
在 C11 及以后版本就支持使用 using 定义模板别名C20 进一步增强了模板别名在泛型编程中的灵活性允许使用非类型模板参数。
#include iostream
#include vector
#include array// c11 定义一个模板别名
templatetypename T
using Vec std::vectorT;// c20 定义一个模板别名使用了非类型模板参数 std::size_t
templatetypename T, std::size_t N
using FixedArray std::arrayT, N;int main()
{Vecint vec {1, 2, 3};for (int num : vec) {std::cout num ;}std::cout std::endl;FixedArrayint, 5 arr {1, 2, 3, 4, 5};for (int num : arr) {std::cout num ;}std::cout std::endl;return 0;
}4. 即时函数Immediate Functions
即时函数Immediate Functions是一种特殊类型的函数与常量表达式密切相关使用 consteval 关键字来声明即时函数。
主要特性
即时函数必须在编译时求值如果无法在编译时计算函数的结果编译器会报错。即时函数只能在常量表达式的上下文中调用并且调用的结果也会作为常量表达式使用。即时函数可以进行递归调用但递归调用也必须在编译时能够终止。
// constexpr 函数与 consteval 函数比较示例
#include iostream// constexpr 函数可以在编译时或运行时求值具体取决于调用的上下文。
// 如果调用 constexpr 函数的参数是常量表达式那么函数会在编译时求值否则函数会在运行时求值。
constexpr int add(int a, int b)
{return a b;
}// consteval 即时函数
consteval int multiply(int a, int b)
{return a * b;
}int main()
{// 编译时求值constexpr int compileTimeSum add(2, 3);constexpr int compileTimeProduct multiply(2, 3);int x 4, y 5;// 运行时求值int runtimeSum add(x, y);// 错误不能在运行时上下文调用 consteval 函数// int runtimeProduct multiply(x, y); std::cout Compile-time sum: compileTimeSum std::endl;std::cout Compile-time product: compileTimeProduct std::endl;std::cout Runtime sum: runtimeSum std::endl;return 0;
}5. char8_t 类型
在 C20 之前UTF - 8 编码的字符串通常使用 char 类型来表示。然而char 类型既可以用于表示有符号整数也可以用于表示无符号整数这取决于编译器的实现这就导致了在处理 UTF - 8 字符串时可能会出现一些未定义行为或移植性问题。为了解决这些问题C20 引入了 char8_t 类型它是无符号的专门用于表示 UTF - 8 编码的字符从而提高了代码的安全性和可移植性。
char8_t 的特点
无符号类型char8_t 是无符号类型这意味着它的取值范围是从 0 到 255避免了有符号 char 可能带来的符号扩展问题。与 UTF - 8 编码紧密相关char8_t 类型专门用于处理 UTF - 8 编码的字符和字符串使得代码更加清晰和安全。标准库支持C 标准库提供了对 char8_t 的支持例如 std::u8string 用于表示 UTF - 8 字符串。
#include iostream
#include stringint main()
{// 使用 u8 前缀定义一个 char8_t 字符char8_t ch u8A;// 使用 u8 前缀定义一个 char8_t 字符串const char8_t* str u8Hello, 世界!;// 使用 u8 前缀定义一个 std::u8string 对象std::u8string u8str u8你好C20!;// 注意std::cout 不能直接输出 char8_t 字符串需要将其转换为 char 类型std::cout Character: static_castchar(ch) std::endl;// 输出字符串std::cout String: ;for (const char8_t* p str; *p; p) {std::cout static_castchar(*p);}std::cout std::endl;// 输出 std::u8string 的长度std::cout Length of u8string: u8str.length() std::endl;// 遍历 std::u8string 中的每个字符for (char8_t ch : u8str) {std::cout static_castchar(ch);}std::cout std::endl;return 0;
}6. 智能指针优化
在 C20 中智能指针如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr本身没有语法上的重大改变但 C20 引入的一些新特性可以帮助我们更好地使用和优化智能指针的使用。
1使用consteval和constexpr进行编译时计算 constexpr 和 consteval 可以在编译时执行代码对于智能指针相关的常量初始化和编译时计算非常有用。
#include iostream
#include memory// 编译时函数返回一个 unique_ptr
consteval auto createUniquePtr()
{return std::make_uniqueint(42);
}int main()
{// 在编译时创建 unique_ptrconstexpr auto ptr createUniquePtr();static_assert(*ptr 42);std::cout *ptr std::endl;return 0;
}2使用 std::span 和智能指针结合 将智能指针管理的数组与 std::span 结合使用避免不必要的复制。
#include iostream
#include memory
#include spanvoid printSpan(std::spanint sp)
{for (int num : sp) {std::cout num ;}std::cout std::endl;
}int main()
{auto arr std::make_uniqueint[](5);for (int i 0; i 5; i) {arr[i] i;}// 使用 std::span 包装智能指针管理的数组std::spanint sp(arr.get(), 5);printSpan(sp);return 0;
}3使用 std::jthread 和智能指针 使用智能指针管理 std::jthread 对象确保线程资源的正确释放。
#include iostream
#include memory
#include threadvoid threadFunction()
{std::cout Thread is running. std::endl;
}int main()
{auto threadPtr std::make_uniquestd::jthread(threadFunction);// 当 threadPtr 离开作用域时线程会自动加入return 0;
}4使用 std::atomic 和智能指针 C20 对 std::atomic 进行了改进可以使用 std::atomicstd::shared_ptr实现线程安全的共享指针操作。
#include iostream
#include memory
#include atomic
#include threadstd::atomicstd::shared_ptrint atomicPtr;void writer()
{auto newPtr std::make_sharedint(42);atomicPtr.store(newPtr, std::memory_order_release);
}void reader()
{std::shared_ptrint localPtr atomicPtr.load(std::memory_order_acquire);if (localPtr) {std::cout Read value: *localPtr std::endl;}
}int main()
{std::thread t1(writer);std::thread t2(reader);t1.join();t2.join();return 0;
}5使用概念Concepts来约束智能指针的使用 C20 引入的概念可以用于约束模板参数确保智能指针操作的类型安全。
#include iostream
#include memory
#include concepts// 定义一个概念要求类型可以解引用
template typename T
concept Dereferenceable requires(T t)
{*t;
};// 接受任何可解引用的类型
template Dereferenceable Ptr
void printValue(Ptr ptr)
{std::cout *ptr std::endl;
}int main() {auto ptr std::make_uniqueint(42);printValue(ptr);return 0;
}7. constinit 变量
constinit 是 C20 引入的一个新的变量声明说明符用于确保变量在程序启动时进行常量初始化。与 constexpr 不同constinit 不要求变量本身是常量只要求它的初始化表达式是常量表达式。
#include iostream// 使用 constinit 声明变量虽然 globalValue 不是常量但它的初始化表达式 10 * 2 是常量表达式
constinit int globalValue 10 * 2;int main()
{// 访问 constinit 变量std::cout Global value: globalValue std::endl;// 修改 constinit 变量的值globalValue 30;std::cout Modified global value: globalValue std::endl;return 0;
}8. 哈希查找优化
1自定义哈希函数 对于自定义类型需要提供自定义的哈希函数和相等比较函数。在 C20 中可以使用 std::hash 模板来简化哈希函数的定义。
#include iostream
#include unordered_map
#include string// 自定义类型
struct Person
{std::string name;int age;// 重载相等比较运算符bool operator(const Person other) const {return name other.name age other.age;}
};// 自定义哈希函数
namespace std
{template struct hashPerson {std::size_t operator()(const Person p) const {// 使用 std::hash 组合多个成员的哈希值auto nameHash std::hashstd::string{}(p.name);auto ageHash std::hashint{}(p.age);return nameHash ^ (ageHash 1);}};
} // namespace stdint main()
{std::unordered_mapPerson, std::string personMap {{{ Alice, 25 }, Engineer},{{ Bob, 30 }, Doctor}};Person target { Alice, 25 };auto it personMap.find(target);if (it ! personMap.end()) {std::cout target.name is a it-second std::endl;} else {std::cout Person not found std::endl;}return 0;
}2std::erase_if C20 引入了 std::erase_if 函数可用于删除满足特定条件的元素避免了手动遍历和删除元素时的迭代器失效问题同时也可能间接影响哈希表的性能。
#include iostream
#include unordered_mapint main()
{std::unordered_mapint, std::string myMap {{1, Apple},{2, Banana},{3, Cherry}};// 删除值为 Banana 的元素std::erase_if(myMap, [](const auto pair) {return pair.second Banana;});for (const auto pair : myMap) {std::cout pair.first : pair.second std::endl;}return 0;
}3调整哈希表参数 std::unordered_map 和 std::unordered_set 提供了一些成员函数来调整哈希表的参数如 rehash 和 reserve可以在插入大量元素之前预先分配足够的空间减少哈希冲突的概率。
#include iostream
#include unordered_mapint main()
{std::unordered_mapint, std::string myMap;// 预先分配足够的空间myMap.reserve(100);// 插入元素for (int i 0; i 100; i) {myMap[i] Value std::to_string(i);}// 查找元素auto it myMap.find(50);if (it ! myMap.end()) {std::cout Found: it-second std::endl;} else {std::cout Not found std::endl;}return 0;
}