七米网站建设推广优化,前端个人介绍网站模板下载,聚成网络网站建设,大学生网页设计个人主页个人博客地址: https://cxx001.gitee.io 前言
自C11这个大版本更新以来#xff0c;后来陆续有两次小版本迭代C14、C17#xff0c;它们主要是对C11的补充扩展#xff0c;并没有增加太多大的特性。
而这次的C20#xff0c;和当年C11一样#xff0c;又是一次重大更新#… 个人博客地址: https://cxx001.gitee.io 前言
自C11这个大版本更新以来后来陆续有两次小版本迭代C14、C17它们主要是对C11的补充扩展并没有增加太多大的特性。
而这次的C20和当年C11一样又是一次重大更新有人甚至说这是一门新语言。
1. 新增关键字
concept 用于约束模板参数的类型范围从而限制模板的实例化范围。
#include iostreamtemplate typename T
concept Arithmetic std::is_arithmetic_vT; // 约束参数T只能是算术类型template Arithmetic T
T add(T a, T b)
{return a b;
}int main()
{int result add(5, 10); // 正确int 是算术类型//double result add(Hello, World); // 错误字符串不是算术类型return 0;
}一些常用的类型约束还有
std::is_integralT::value检查类型 T 是否为整数类型包括有符号和无符号整数。
std::is_floating_pointT::value检查类型 T 是否为浮点类型。
std::is_pointerT::value检查类型 T 是否为指针类型。
std::is_arrayT::value检查类型 T 是否为数组类型。
std::is_classT::value检查类型 T 是否为类类型。
std::is_enumT::value检查类型 T 是否为枚举类型。
std::is_functionT::value检查类型 T 是否为函数类型。
std::is_sameT, U::value检查类型 T 是否与类型 U 相同。requires 用于在concept中定义更复杂的约束条件。通过使用 requires 关键字可以指定更多的条件以进一步限制模板参数的属性。
template typename T
concept Incrementable requires(T a) {{ a } - std::same_asT; // 要求 a 可以递增并返回 Tstd::is_arithmetic_vT; // 要求 T 是算术类型
};template Incrementable T
T increment(T value) {value;return value;
}int main() {int result increment(5); // 正确int 可以递增// double result increment(3.14); // 错误double 不可递增return 0;
}constinit 用于指定一个对象必须以静态初始化方式进行初始化提高程序的性能和可预测性。
在 C 中对象的初始化可以分为两种方式静态初始化和动态初始化。静态初始化是指在程序启动时或者在第一次使用之前由编译器自动完成的初始化过程。动态初始化是指在运行时通过代码执行来完成的初始化过程。
使用 constinit 关键字可以确保对象以静态初始化方式进行初始化从而避免了动态初始化的开销和潜在的不确定性。
constinit int x 42; // 编译器将确保 x 以静态初始化方式进行初始化。注意constinit 关键字只能用于具有静态存储期的对象例如全局变量、静态变量或者在命名空间作用域内定义的变量。它不能用于局部变量或者动态分配的对象。
consteval 用于声明一个函数必须在编译时进行求值编译器将在编译时执行该函数并将结果替换到调用点。以提供更高的性能和优化机会。
consteval int square(int x) {return x * x;
}注意与C11的constexpr区别上面示例用这两个效果一样都可以实现在编译期求值。不过constexpr 用于声明可以在编译时求值的常量表达式或函数而consteval 关键字只能用于函数声明不能用于变量或其他语句。 co_await 、co_return、co_yield 这些是C20引入协程编程新增的关键字协程编程是一种轻量级的并发编程机制**它允许函数在执行过程中暂停和恢复。**在后面会详细介绍。 char8_t 用于表示 UTF-8 编码的字符。char8_t 类型的大小为一个字节8位与 UTF-8 编码方式一致。可以使用 char8_t 类型的指针来遍历和操作 UTF-8 字符串而无需进行字符集转换或编码处理。更加方便了。
#include iostreamint main() {const char8_t* utf8String u8Hello, 世界!;std::cout UTF-8 String: reinterpret_castconst char*(utf8String) std::endl;return 0;
}import、module 这是C20引入的又一重大特性模块。后面详细介绍。(和js的模块机制很像~)
2. 模块
在C20中终于引入了模块化编程的概念这和各脚本语言中的模块概念很类似。模块化编程旨在提供更好的代码组织、封装和可重用性。
模块化编程通过使用module关键字来定义模块将相关的实体变量、函数、类等封装在一个单独的单元中并使用import关键字在其他模块中导入所需的实体。
// vs2022中添加模块文件math.ixx
export module math; // 创建模块mathexport int add(int a, int b)
{return a b;
}export int subtract(int a, int b)
{return a - b;
}// main.cpp
#include iostreamimport math; // 导入模块int main()
{int result add(5, 3);std::cout result;return 0;
}使用模块化编程可以带来许多优势例如
更好的代码组织模块将相关的实体封装在一起使代码结构更清晰易于维护和理解。更好的封装模块可以选择性地导出实体控制对外部的可见性提供更好的封装性。更快的编译速度由于模块只包含所需的实体而不是整个头文件编译速度可能会更快。避免头文件的预处理器宏模块不需要使用预处理器宏来处理头文件的重复包含和条件编译。
3. 新增ranges标准库组件
C20引入了一种新的标准库组件称为Ranges范围用于简化和增强对序列包括容器、数组、迭代器等的操作和处理。
Ranges的设计目标是通过使用管道操作符|和函数式编程的风格提供一种更现代、更可读、更易于组合的方式来操作序列。使用Ranges你可以将多个操作链接在一起形成一个连续的操作链每个操作都是对序列进行转换、筛选、排序等操作。
#include iostream
#include ranges
#include vectorint main()
{std::vectorint numbers { 1, 2, 3, 4, 5 };// 首先对numbers数组中每个元素*2变为{2, 4, 6, 8, 10},然后筛选被3整除的元素。// 整个操作流程使用管道符|连接。auto result numbers | std::views::transform([](int n) { return n * 2; })| std::views::filter([](int n) { return n % 3 0; });for (int n : result) {std::cout n ;}// 输出: 6return 0;
}需要注意的是使用Ranges需要包含头文件ranges并使用命名空间std::views和std::ranges来访问Ranges的操作。
下面列出Ranges的一些常用的操作 转换序列使用std::views::transform将序列中的元素进行转换例如将整数序列转换为字符串序列或进行数值计算。 筛选元素使用std::views::filter根据特定条件筛选序列中的元素例如筛选出满足某个谓词的元素。 排序序列使用std::views::sort对序列进行排序操作可以根据元素的某个属性进行排序。 分组元素使用std::views::group_by将序列中的元素按照某个条件进行分组例如按照元素的奇偶性进行分组。 聚合操作使用std::views::reduce或std::views::transform_reduce对序列中的元素进行聚合操作例如计算序列的总和、平均值等。 切片操作使用std::views::take、std::views::drop或std::views::slice对序列进行切片操作例如获取前几个元素或跳过前几个元素。 迭代操作使用std::ranges::for_each对序列中的每个元素进行迭代操作例如打印序列中的所有元素。 查找元素使用std::ranges::find或std::ranges::find_if在序列中查找特定的元素或满足某个条件的元素。 合并序列使用std::views::concat将多个序列合并为一个序列方便进行统一的操作。 转换为容器使用std::ranges::to将序列转换为特定类型的容器例如将范围视图转换为std::vector或std::list。
注意std::views::xx和std::ranges::xx区别std::views下的操作是懒操作在用到时才执行并且不会修改原序列返回一个操作后的新视图。而std::ranges下的操作是立即执行操作修改原序列不返回任何值。
4. 协程
这特性又是一大干货哈终于是引入标准了。也是很多脚本语言常用特性感觉越往后发展C编码门槛会越来越低哈会和写脚本一样丝滑~~
协程是一种轻量级的并发编程机制本质是一个特殊函数只是它允许函数在执行过程中暂停和恢复。协程通常用于处理异步操作例如网络请求、文件读写等。
主要关键字
co_await co_await 用于在协程内部暂停执行通常用于等待异步操作完成。当协程遇到 co_await 表达式时它将被暂停并在异步操作完成后恢复执行。 co_yield co_yield 用于生成值并将其发送给调用者。它通常用于生成序列中的下一个值。当协程遇到 co_yield 表达式时它会生成值并将其返回给调用者然后暂停协程的执行以便在需要时继续生成更多值。 co_return co_return 用于结束协程的执行并返回一个值。它通常用于返回协程的最终结果。当协程遇到 co_return 表达式时它将结束执行并将指定的值返回给调用者。
#include coroutine
#include iostream
#include stdexcept
#include thread//! coro_ret 协程函数的返回值内部定义promise_type承诺对象
template typename T
struct coro_ret {struct promise_type;using handle_type std::coroutine_handlepromise_type;//! 协程句柄handle_type coro_handle_;coro_ret(handle_type h): coro_handle_(h){}coro_ret(const coro_ret) delete;coro_ret(coro_ret s): coro_handle_(s.coro_){s.coro_handle_ nullptr;}~coro_ret(){//! 自行销毁if (coro_handle_)coro_handle_.destroy();}coro_ret operator(const coro_ret) delete;coro_ret operator(coro_ret s){coro_handle_ s.coro_handle_;s.coro_handle_ nullptr;return *this;}//! 恢复协程返回是否结束bool move_next(){coro_handle_.resume();return coro_handle_.done();}//! 通过promise获取数据返回值T get(){return coro_handle_.promise().return_data_;}//! promise_type就是承诺对象承诺对象用于协程内外交流struct promise_type {promise_type() default;~promise_type() default;//! 生成协程返回值auto get_return_object(){return coro_retT { handle_type::from_promise(*this) };}//! 注意这个函数,返回的就是awaiter//! 如果返回std::suspend_never{}就不挂起//! 返回std::suspend_always{} 挂起//! 当然你也可以返回其他awaiterauto initial_suspend(){// return std::suspend_never{};return std::suspend_always {};}//! co_return 后这个函数会被调用void return_value(T v){return_data_ v;return;}//!auto yield_value(T v){std::cout yield_value invoked. std::endl;return_data_ v;return std::suspend_always {};}//! 在协程最后退出后调用的接口。//! 若 final_suspend 返回 std::suspend_always 则需要用户自行调用//! handle.destroy() 进行销毁但注意final_suspend被调用时协程已经结束//! 返回std::suspend_always并不会挂起协程实测 VSC 2022auto final_suspend() noexcept{std::cout final_suspend invoked. std::endl;return std::suspend_always {};}//void unhandled_exception(){std::exit(1);}// 返回值T return_data_;};
};// 这就是一个协程函数
coro_retint coroutine_7in7out()
{// 进入协程看initial_suspend返回std::suspend_always{};会有一次挂起std::cout Coroutine co_await std::suspend_never std::endl;// co_await std::suspend_never{} 不会挂起co_await std::suspend_never {};std::cout Coroutine co_await std::suspend_always std::endl;co_await std::suspend_always {};std::cout Coroutine stage 1 ,co_yield std::endl;co_yield 101;std::cout Coroutine stage 2 ,co_yield std::endl;co_yield 202;std::cout Coroutine stage 3 ,co_yield std::endl;co_yield 303;std::cout Coroutine stage end, co_return std::endl;co_return 808;
}int main(int argc, char* argv[])
{bool done false;std::cout Start coroutine_7in7out ()\n;// 调用协程,得到返回值c_r后面使用这个返回值来管理协程。auto c_r coroutine_7in7out();// 第一次停止因为initial_suspend 返回的是suspend_always// 此时没有进入Stage 1std::cout Coroutine (done ? is done : isnt done ) ret c_r.get() std::endl;done c_r.move_next();// 此时是co_await std::suspend_always{}std::cout Coroutine (done ? is done : isnt done ) ret c_r.get() std::endl;done c_r.move_next();// 此时打印Stage 1std::cout Coroutine (done ? is done : isnt done ) ret c_r.get() std::endl;done c_r.move_next();std::cout Coroutine (done ? is done : isnt done ) ret c_r.get() std::endl;done c_r.move_next();std::cout Coroutine (done ? is done : isnt done ) ret c_r.get() std::endl;done c_r.move_next();std::cout Coroutine (done ? is done : isnt done ) ret c_r.get() std::endl;return 0;
}Start coroutine_7in7out ()
Coroutine isnt done ret 0
Coroutine co_await std::suspend_never
Coroutine co_await std::suspend_always
Coroutine isnt done ret 0
Coroutine stage 1 ,co_yield
yield_value invoked.
Coroutine isnt done ret 101
Coroutine stage 2 ,co_yield
yield_value invoked.
Coroutine isnt done ret 202
Coroutine stage 3 ,co_yield
yield_value invoked.
Coroutine isnt done ret 303
Coroutine stage end, co_return
final_suspend invoked.
Coroutine is done ret 8085. Lambda表达式更新
就是进行了些小扩展具体细节这个不介绍了本质就是一个匿名函数。
6. constexpr常量表达式更新
也是增强了限制更少了具体细节这个也不介绍了本质就是常量化编译期求值。
7.原子(Atomic)智能指针
C20引入了原子智能指针这是对C原子操作的一种扩展。原子智能指针允许你在多线程环境中安全地共享和操作智能指针。std::atomic原子智能指针可以保证对智能指针的操作是线程安全的并且可以防止数据竞争。
#include atomic
#include iostream
#include memory
#include thread
#include vector// 共享的数据结构
struct Data {int value;Data(int val): value(val){}
};// 全局共享的std::shared_ptr使用std::atomic包装
std::atomicstd::shared_ptrData sharedData;// 线程函数从共享的std::shared_ptr中读取数据
void worker(int threadId)
{// 从原子std::shared_ptr中加载数据std::shared_ptrData localPtr std::atomic_load(sharedData);// 使用本地副本进行操作std::cout Thread threadId read value: localPtr-value std::endl;
}int main()
{// 初始化共享的std::shared_ptrsharedData.store(std::make_sharedData(42));// 创建线程池std::vectorstd::thread threads;for (int i 0; i 4; i) {threads.emplace_back(worker, i);}// 等待所有线程完成for (std::thread thread : threads) {thread.join();}return 0;
}Thread 2 read value: 42Thread
Thread 1 read value: 0 read value: 42
42
Thread 3 read value: 42由于我们使用原子操作来加载std::shared_ptr多个线程可以同时访问和操作它而不会发生竞态条件。所以打印的结果是随机的。
8.原子引用类型std::atomic_ref
C20 中引入的一种引用类型用于支持原子操作。它允许您在不使用指针的情况下对共享数据进行原子操作。std::atomic_ref 可以用于提供线程安全的访问和修改共享变量的能力。
#include atomic
#include iostream
#include threadint main()
{int sharedValue 0;std::atomic_refint atomicSharedValue(sharedValue);std::thread thread1([]() {for (int i 0; i 100000; i) {atomicSharedValue.fetch_add(1, std::memory_order_relaxed);}});std::thread thread2([]() {for (int i 0; i 100000; i) {atomicSharedValue.fetch_add(1, std::memory_order_relaxed);}});thread1.join();thread2.join();std::cout Final shared value: sharedValue std::endl;return 0;
}Final shared value: 2000009. 自动合流和可中断的线程
线程自动合流是指在线程执行完毕后程序自动等待线程的结束并回收线程资源而无需显式调用线程的 join() 方法。这使得线程的合流变得更加便捷和安全避免了忘记合流或手动合流时出现的问题。
#include chrono
#include iostream
#include threadvoid threadFunction()
{std::this_thread::sleep_for(std::chrono::seconds(2));std::cout Thread is done. std::endl;
}int main()
{std::jthread myThread(threadFunction);std::cout Main thread is doing some work. std::endl;// 不需要手动调用 join()std::jthread 会自动合流// 线程会在这里自动合流std::cout Main thread is done. std::endl;return 0;
}Main thread is doing some work.
Main thread is done.
Thread is done.可中断线程是指线程具有一种机制允许它在执行过程中被其他线程或外部事件中断然后执行特定的操作。这使得可以更灵活地控制线程的执行。以前一般都是用一个全局变量来外部控制线程中断容易出错也不灵活。
#include iostream
#include thread
#include stop_token
#include chronovoid worker(std::stop_token token) {while (!token.stop_requested()) {// 执行工作std::this_thread::sleep_for(std::chrono::seconds(1));}std::cout Thread is interrupted. std::endl;
}int main() {std::jthread t(worker);// 主线程等待一段时间后请求线程停止std::this_thread::sleep_for(std::chrono::seconds(3));t.request_stop();t.join(); // 阻塞等待线程返回后往下执行std::cout Main thread is done. std::endl;return 0;
}Thread is interrupted.
Main thread is done.10. 新的同步库
C20引入了新的同步库该库提供了多种同步工具以帮助开发人员编写并发和多线程代码。
下面是一些C20同步库的主要组件
std::latchstd::latch是一个计数器它允许您等待某个事件发生。当计数器归零时所有等待的线程都将被唤醒。使用std::latch::wait()等待计数器归零。
#include iostream
#include latch
#include threadvoid worker(std::latch lt, int id)
{std::this_thread::sleep_for(std::chrono::seconds(2));std::cout Thread id is done. std::endl;lt.count_down(); // 减少计数器
}int main()
{const int numThreads 3;std::latch latch(numThreads);for (int i 0; i numThreads; i) {std::thread(worker, std::ref(latch), i).detach();}// 等待所有线程完成即当计数器为0时才往下执行latch.wait();std::cout All threads are done. std::endl;return 0;
}Thread 1 is done.
Thread 0 is done.Thread 2 is done.All threads are done.std::latch 可以用于控制一组线程的同步点等待它们都完成后再进行下一步操作。这对于需要等待多个线程完成某项任务后再继续的情况非常有用。 std::barrierstd::barrier是一个同步原语它允许一组线程相互等待直到所有线程都到达某个点。只有当所有线程都到达屏障点时这些线程才能继续执行。 与 std::latch 不同std::barrier 允许线程多次参与同步即在达到同步点后线程可以再次加入到下一轮同步中。
#include barrier
#include iostream
#include thread
#include vectorconst int numThreads 3;
std::barrier barrier(numThreads);void worker(int id)
{std::cout Thread id is ready. std::endl;barrier.arrive_and_wait(); // 等待所有线程达到同步点std::cout Thread id continues. std::endl;barrier.arrive_and_wait(); // 再次等待所有线程达到同步点std::cout Thread id is done. std::endl;
}int main()
{std::vectorstd::thread threads;for (int i 0; i numThreads; i) {threads.emplace_back(worker, i);}for (std::thread thread : threads) {thread.join();}std::cout All threads are done. std::endl;return 0;
}Thread 1 is ready.Thread 2 is ready.Thread 0 is ready.
Thread 0Thread continues.Thread
2 continues.1continues.
Thread Thread 1Thread 0 is done. is done.
2 is done.All threads are done.在这个示例中我们使用了两次同步点。std::barrier 允许线程多次参与同步适用于需要多轮协作的情况例如迭代式的并行计算或其他需要多次同步的场景。在每个同步点所有线程都会等待直到所有线程都到达同步点后才会继续执行。这可以用于更复杂的多线程协作任务。
std::promise 和 std::futurestd::promise 和 std::future 提供了一种在不同线程之间传递数据的方式。std::promise 允许您在一个线程中设置一个值或异常而 std::future 可以在另一个线程中获取该值或异常。您可以使用 std::promise::set_value() 设置值使用 std::promise::set_exception() 设置异常并使用 std::future::get() 获取值。
#include iostream
#include thread
#include futurevoid do_work(std::promiseint p) {int result 42; // 执行一些工作并产生一个结果p.set_value(result); // 设置结果
}int main() {std::promiseint p;std::futureint f p.get_future();std::thread(do_work, std::ref(p)).detach();int result f.get(); // 阻塞等待并获取结果std::cout Result: result std::endl;return 0;
}Result: 42这在多线程之间通信和协作非常有用。
std::semaphorestd::semaphore 是一种信号量可以用于控制并发访问共享资源的线程确保同时只有有限数量的线程可以访问共享资源从而避免竞争条件和提高多线程程序的性能。
#include iostream
#include semaphore
#include thread
#include vectorstd::counting_semaphore10 sem(2); // 创建一个初始计数为2的信号量, 10是最大值void worker(int id)
{sem.acquire(); // 获取资源如果没有资源则等待std::cout Thread id acquired resource. std::endl;// 模拟工作std::this_thread::sleep_for(std::chrono::seconds(2));sem.release(); // 释放资源std::cout Thread id released resource. std::endl;
}int main()
{std::vectorstd::thread threads;for (int i 0; i 5; i) {threads.emplace_back(worker, i);}for (std::thread thread : threads) {thread.join();}return 0;
}Thread 1 acquired resource.Thread
0 acquired resource.
Thread Thread 4 acquired resource.1 released resource.ThreadThread 0 released resource.2acquired resource.
Thread Thread 3 acquired resource.2 released resource.Thread 4released resource.Thread 3 released resource.11. 其它更新
1. 指定初始化
它允许您在初始化复合数据结构如结构体和数组时为特定的成员或元素提供初始化值而不必按顺序初始化所有成员或元素。并且可以直接在声明时初始化。
#include iostream
#include vectorstruct Point {int x 1;int y 2;int z 3;
};int main()
{Point p1 { .x 10, .y 20 };Point p2 { .y 70, .z 80 };std::cout p1: x p1.x y p1.y z p1.z std::endl;std::cout p2: x p2.x y p2.y z p2.z std::endl;return 0;
}p1: x10 y20 z3
p2: x1 y70 z802. 航天飞机操作符
也叫三路比较运算符用于比较两个对象的关系返回值是std::strong_ordering类型该类型包括三个可能的值std::strong_ordering::less、std::strong_ordering::equal和std::strong_ordering::greater分别表示左操作数小于、等于或大于右操作数。
#include iostream
#include compareint main() {int a 5;int b 7;std::strong_ordering result a b;if (result std::strong_ordering::less) {std::cout a is less than b std::endl;} else if (result std::strong_ordering::equal) {std::cout a is equal to b std::endl;} else if (result std::strong_ordering::greater) {std::cout a is greater than b std::endl;}return 0;
}三路比较运算符对比传统的, , 更加方便简洁内部实现了3种关系比较不需要分别一个个手动判断了。例如std::strong_ordering 类型的结果可以直接用于排序算法对于自定义类型排序只需要重载operator运算符一次然后可以轻松地进行比较操作。
#include algorithm
#include compare
#include iostream
#include vectorstruct Person {std::string name;int age;// 按年龄大小排序std::sort内部自动按这个排序算法auto operator(const Person other) const{return age other.age; // 如果要按从大到小反过来就行了 other.age age}
};int main()
{std::vectorPerson people {{ Alice, 30 },{ Bob, 25 },{ Charlie, 35 },{ David, 28 }};// 使用 std::sort 对容器进行排序std::sort(people.begin(), people.end());// 输出排序后的结果for (const auto person : people) {std::cout person.name - person.age std::endl;}return 0;
}Bob - 25
David - 28
Alice - 30
Charlie - 353.范围for循环支持初始化语句
在前面C17中我们讲了if-switch支持初始化语句现在for循环语句遍历容器时也支持初始化语句了。
#include iostream
#include vectorint main()
{std::vectorint numbers { 1, 2, 3, 4, 5 };// 使用 C20 范围 for 循环的初始化语句for (int i 0; int value : numbers) {std::cout Value at index i : value std::endl;i; // 初始化语句中声明的 i 变量用于记录索引}return 0;
}Value at index 0: 1
Value at index 1: 2
Value at index 2: 3
Value at index 3: 4
Value at index 4: 54. 非类型模板形参支持字符串
这意味着您可以在模板中使用字符串作为模板参数以实现更灵活的泛型编程。
#include iostream
#include stringtemplate const char* str
void printString() {std::cout str std::endl;
}int main() {constexpr const char* message Hello, World!;printStringmessage(); // 以字符串作为模板参数return 0;
}注在vs2022上编译不过可能是这个特性在当前编译器上还没有完美支持。
5. [[likely]], [[unlikely]]标记
它们是用于标记代码分支的建议性属性以帮助编译器优化执行路径。这些属性的主要目的是告诉编译器哪些分支更可能被执行以便它可以更好地进行优化。
[[likely]]属性用于标记代码分支表示这个分支更可能被执行。这有助于编译器在生成机器代码时对这个分支进行更好的优化。[[unlikely]]属性用于标记代码分支表示这个分支更不可能被执行。这有助于编译器避免在生成机器代码时对这个分支进行过多的优化以提高性能。
if (condition) [[likely]] {// 告诉编译器这个分支很可能会执行// 在性能敏感的代码路径上使用
} else [[unlikely]] {// 告诉编译器这个分支不太可能会执行// 在不太可能执行的代码路径上使用
}这些属性是可选的编译器可以根据它们来进行代码优化但不是必需的。它们主要用于提高性能并且在性能敏感的代码路径上使用得最多以确保编译器对这些路径进行更好的优化。
请注意这些属性的效果取决于编译器的实现不同的编译器可能会有不同的优化策略。因此在使用这些属性时最好进行性能测试以确保它们对代码的性能产生了预期的影响。
6. 日历和时区功能
C20引入了标准库中的chrono头文件的重大更新其中包括了对日历和时区的新功能。这些功能使得在C中处理日期、时间和时区更加容易和灵活。
以下是C20中日历和时区功能的主要亮点
日历支持C20引入了std::chrono::year_month_day、std::chrono::year_month和std::chrono::weekday等类型以更方便地表示日期和时间。这些类型可以帮助您执行日期算术操作如添加或减去日期、计算两个日期之间的时间间隔等。格式化和解析C20引入了std::format函数和std::chrono::from_stream函数用于方便地格式化和解析日期和时间。这些函数可以将日期和时间以不同的格式进行字符串表示并将字符串解析为日期和时间对象。时区支持C20引入了std::chrono::zoned_time类型用于表示带有时区信息的日期和时间。这使得在不同的时区之间进行转换和比较变得更容易。此外C20还引入了std::chrono::current_zone()函数用于获取当前的本地时区。std::chrono::sys_time类型std::chrono::sys_time是一个系统级别的时钟用于表示与特定时钟不相关的时间点。这对于在不同时钟之间进行时间计算和比较非常有用。时钟和时钟间隔C20引入了std::chrono::leap时钟用于处理闰秒以及std::chrono::den时钟间隔用于表示任意固定时钟周期。这些时钟和时钟间隔类型增加了时间计算的灵活性。时区数据库C20的std::chrono库中包含了一个时区数据库其中包含了大量的时区信息允许您轻松地执行与时区相关的操作。
这些功能的引入使得C在日期、时间和时区处理方面变得更加强大和标准化。使用这些功能您可以更容易地处理不同时区的时间、执行日期算术操作、进行时间间隔计算等。
7. std::span
这是一个用于表示一段连续内存的非拥有式、轻量级的容器。std::span的目的是提供对现有内存的安全引用而不进行内存分配或拷贝。通过它来引用数组传递使用就不会有越界风险它内部自动实现了边界检查。它配合STL容器一起使用更加灵活。
#include iostream
#include vector
#include spanint main() {std::vectorint data {1, 2, 3, 4, 5};std::spanint span(data.data(), data.size());// 使用 std::span 遍历数据for (int value : span) {std::cout value ;}std::cout std::endl;// 修改数据span 反映了原始数据的更改span[2] 99;// 再次遍历数据看看修改后的结果for (int value : span) {std::cout value ;}std::cout std::endl;return 0;
}1 2 3 4 5
1 2 99 4 58. 新增特性测试宏
C20引入了一组特性测试宏用于在代码中检测编译器是否支持特定的C20功能或库。
以下是一些常用的C20特性测试宏 __cpp_concepts用于测试是否支持C20的概念Concepts特性。 #ifdef __cpp_concepts
// 在支持概念的编译器中编写的代码
#endif__cpp_consteval用于测试是否支持C20的consteval特性该特性允许在编译时执行函数。 #ifdef __cpp_consteval
// 在支持 consteval 的编译器中编写的代码
#endif__cpp_modules用于测试是否支持C20的模块Modules特性。 #ifdef __cpp_modules
// 在支持模块的编译器中编写的代码
#endif__cpp_ranges用于测试是否支持C20的范围Ranges库特性。 #ifdef __cpp_ranges
// 在支持范围库的编译器中编写的代码
#endif__cpp_coroutines用于测试是否支持C20的协程Coroutines特性。 #ifdef __cpp_coroutines
// 在支持协程的编译器中编写的代码
#endif__cpp_lib_concepts、__cpp_lib_consteval、__cpp_lib_modules、__cpp_lib_ranges、__cpp_lib_coroutines等用于测试编译器是否支持C20库中的相关功能。 #ifdef __cpp_lib_ranges
// 在支持范围库的编译器中编写的代码
#endif9. using可以为enum类型取别名
它允许您为枚举类型定义更具有意义的别名这可以帮助提高代码的可读性和可维护性。
#include iostream// 定义一个枚举类
enum class Color {Red,Green,Blue
};int main() {// 使用 using 引用枚举类类型using ColorType Color;ColorType color Color::Red;// 使用引用的别名来声明变量if (color ColorType::Red) {std::cout Color is Red std::endl;} else {std::cout Color is not Red std::endl;}return 0;
}10. std::format格式化库
类似于C语言中的printf函数或Python中的str.format()方法。std::format库的目标是提供一种类型安全、可扩展和国际化友好的方式来构建格式化字符串。
使用{}作为占位符并且支持添加格式说明符。
#include iostream
#include formatint main() {int age 30;std::string name Alice;double pi 3.14159265359;// 使用 std::format 格式化字符串std::string formatted std::format(Name: {}, Age: {}, Pi: {:.2f}, name, age, pi);std::cout formatted std::endl;return 0;
}11. 增加数学常量
在C20中标准库引入了一组常见的数学常量这些常量定义在numbers头文件中。这些数学常量是通过std::numbers命名空间提供的可以用于进行数学计算例如π、自然对数的底数e等。
以下是一些C20中引入的数学常量示例 π圆周率可以使用std::numbers::pi访问。 double pi std::numbers::pi;e自然对数的底数可以使用std::numbers::e访问。 double e std::numbers::e;黄金比例可以使用std::numbers::phi访问。 double phi std::numbers::phi;平方根2可以使用std::numbers::sqrt2访问。 double sqrt2 std::numbers::sqrt2;平方根3可以使用std::numbers::sqrt3访问。 double sqrt3 std::numbers::sqrt3;自然对数的2可以使用std::numbers::ln2访问。 double ln2 std::numbers::ln2;自然对数的10可以使用std::numbers::ln10访问。 double ln10 std::numbers::ln10;这些数学常量使得在C中执行常见的数学计算更加方便和可读。它们以类型安全的方式提供了数学常数避免了传统的宏定义或手动输入常数值的问题。
12. std::source_location
它允许在代码中获取当前文件的名称、当前行号、当前列号以及调用点的函数名称。这对于调试和日志记录非常有用可以帮助定位代码中的问题。
std::source_location定义在source_location头文件中并提供了以下常用成员函数
file_name()返回当前源文件的名称。line()返回当前源文件的行号。column()返回当前源文件的列号。function_name()返回调用点的函数名称。
#include iostream
#include source_locationvoid printSourceLocation(const std::source_location loc std::source_location::current()) {std::cout File: loc.file_name() Line: loc.line() Column: loc.column() Function: loc.function_name() std::endl;
}int main() {printSourceLocation();return 0;
}File: C:\Users\cxx\Desktop\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp Line: 18 Column: 5 Function: main13. [[nodiscard(reason)]]
在C17和C20中可以使用[[nodiscard]]属性来告诉编译器应该注意忽略函数的返回值。C20进一步扩展了这个特性允许您提供一个可选的字符串参数以解释为什么返回值应该被注意忽略。如果返回值没有被使用编译则会给出这个警告信息。
#include iostream[[nodiscard(Please check the return value for error handling)]] int divide(int a, int b)
{if (b 0) {// 返回值应该用于错误处理return -1;}return a / b;
}int main()
{int result divide(10, 2); // 正确使用返回值std::cout Result: result std::endl;divide(10, 0); // 忽略返回值但编译器会发出警告return 0;
}已启动重新生成...
1------ 已启动全部重新生成: 项目: ConsoleApplication1, 配置: Debug x64 ------
1正在扫描源以查找模块依赖项...
1math.ixx
1正在编译...
1math.ixx
1ConsoleApplication1.cpp
1C:\Users\cxx\Desktop\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp(20,11): warning C4858: 正在放弃返回值: Please check the return value for error handling
1ConsoleApplication1.vcxproj - C:\Users\cxx\Desktop\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe
1已完成生成项目“ConsoleApplication1.vcxproj”的操作。“全部重新生成”: 1 成功0 失败0已跳过 重新生成 开始于 3:04 PM并花费了 01.026 秒 14. 增加循环移位计算位中0和1数量等功能
这些功能是通过C20的标准库中的bit头文件引入的。
循环左移和循环右移std::rotl用于执行循环左移操作std::rotr用于执行循环右移操作。注意普通左移2位会将位移出并丢弃而循环左移2位会将位重新循环回来保持位数不变即将移除的最高位的位数添加回数的最低位。
#include iostream
#include bitint main() {unsigned int value 0b1100; // 二进制表示 1100// 循环左移两位unsigned int result_left std::rotl(value, 2); // 结果为 0b0011二进制表示 0011// 循环右移两位unsigned int result_right std::rotr(value, 2); // 结果为 0b0011二进制表示 0011std::cout Left Rotation: result_left std::endl;std::cout Right Rotation: result_right std::endl;return 0;
}计算位中0和1的数量std::countr_zero用于计算从右侧开始的连续0的数量std::countr_one用于计算从右侧开始的连续1的数量。
#include iostream
#include bitint main() {unsigned int value 0b11001100; // 二进制表示 11001100int zeros std::countr_zero(value); // 连续0的数量为 2int ones std::countr_one(value); // 连续1的数量为 2std::cout Count of Zeros: zeros std::endl;std::cout Count of Ones: ones std::endl;return 0;
}以上就是C20的主要新增特性了当然还一些细节扩展那些就待使用时再查阅相关文档了。总结下来C11主要是引入了类型自动推导的auto智能指针。C20主要是模块、协程、原子操作。总体发展线路可以看出C编码风格在向脚本语言靠拢了。