前言 自C++11这个大版本更新以来,后来陆续有两次小版本迭代C++14、C++17,它们主要是对C++11的补充扩展,并没有增加太多大的特性。
而这次的C++20,和当年C++11一样,又是一次重大更新,有人甚至说这是一门新语言。
1. 新增关键字
concept 用于约束模板参数的类型范围,从而限制模板的实例化范围。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> template <typename T>concept Arithmetic = std::is_arithmetic_v<T>; template <Arithmetic T>T add (T a, T b) { return a + b; } int main () { int result = add (5 , 10 ); return 0 ; }
一些常用的类型约束还有:
1 2 3 4 5 6 7 8 std::is_integral<T>::value:检查类型 T 是否为整数类型(包括有符号和无符号整数)。 std::is_floating_point<T>::value:检查类型 T 是否为浮点类型。 std::is_pointer<T>::value:检查类型 T 是否为指针类型。 std::is_array<T>::value:检查类型 T 是否为数组类型。 std::is_class<T>::value:检查类型 T 是否为类类型。 std::is_enum<T>::value:检查类型 T 是否为枚举类型。 std::is_function<T>::value:检查类型 T 是否为函数类型。 std::is_same<T, U>::value:检查类型 T 是否与类型 U 相同。
requires 用于在concept中定义更复杂的约束条件。通过使用 requires 关键字,可以指定更多的条件,以进一步限制模板参数的属性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 template <typename T>concept Incrementable = requires (T a) { { ++a } -> std::same_as<T&>; std::is_arithmetic_v<T>; }; template <Incrementable T>T increment (T value) { ++value; return value; } int main () { int result = increment (5 ); return 0 ; }
constinit 用于指定一个对象必须以静态初始化方式进行初始化,提高程序的性能和可预测性。
在 C++ 中,对象的初始化可以分为两种方式:静态初始化和动态初始化。静态初始化是指在程序启动时或者在第一次使用之前,由编译器自动完成的初始化过程 。动态初始化是指在运行时通过代码执行来完成的初始化过程 。
使用 constinit 关键字可以确保对象以静态初始化方式进行初始化,从而避免了动态初始化的开销和潜在的不确定性。
注意,constinit 关键字只能用于具有静态存储期的对象,例如全局变量、静态变量或者在命名空间作用域内定义的变量。它不能用于局部变量或者动态分配的对象。
consteval 用于声明一个函数必须在编译时进行求值,编译器将在编译时执行该函数,并将结果替换到调用点。以提供更高的性能和优化机会。
1 2 3 consteval int square (int x) { return x * x; }
注意与C++11的constexpr区别,上面示例用这两个效果一样,都可以实现在编译期求值。不过constexpr 用于声明可以在编译时求值的常量表达式或函数,而consteval 关键字只能用于函数声明,不能用于变量或其他语句。
co_await 、co_return、co_yield 这些是C++20引入协程编程新增的关键字,协程编程是一种轻量级的并发编程机制,它允许函数在执行过程中暂停和恢复。 在后面会详细介绍。
char8_t 用于表示 UTF-8 编码的字符。char8_t 类型的大小为一个字节(8位),与 UTF-8 编码方式一致。可以使用 char8_t 类型的指针来遍历和操作 UTF-8 字符串,而无需进行字符集转换或编码处理。更加方便了。
1 2 3 4 5 6 7 8 #include <iostream> int main () { const char8_t * utf8String = u8"Hello, 世界!" ; std::cout << "UTF-8 String: " << reinterpret_cast <const char *>(utf8String) << std::endl; return 0 ; }
import、module 这是C++20引入的又一重大特性,模块。后面详细介绍。(和js的模块机制很像~)
2. 模块 在C++20中,终于引入了模块化编程的概念,这和各脚本语言中的模块概念很类似。模块化编程旨在提供更好的代码组织、封装和可重用性。
模块化编程通过使用module关键字来定义模块,将相关的实体(变量、函数、类等)封装在一个单独的单元中,并使用import关键字在其他模块中导入所需的实体。
1 2 3 4 5 6 7 8 9 10 11 12 export module math; export int add (int a, int b) { return a + b; } export int subtract (int a, int b) { return a - b; }
1 2 3 4 5 6 7 8 9 10 11 #include <iostream> import math; int main () { int result = add (5 , 3 ); std::cout << result; return 0 ; }
使用模块化编程可以带来许多优势,例如:
更好的代码组织:模块将相关的实体封装在一起,使代码结构更清晰,易于维护和理解。
更好的封装:模块可以选择性地导出实体,控制对外部的可见性,提供更好的封装性。
更快的编译速度:由于模块只包含所需的实体,而不是整个头文件,编译速度可能会更快。
避免头文件的预处理器宏:模块不需要使用预处理器宏来处理头文件的重复包含和条件编译。
3. 新增ranges标准库组件 C++20引入了一种新的标准库组件,称为Ranges(范围),用于简化和增强对序列(包括容器、数组、迭代器等)的操作和处理。
Ranges的设计目标是通过使用管道操作符(|)和函数式编程的风格,提供一种更现代、更可读、更易于组合的方式来操作序列。使用Ranges,你可以将多个操作链接在一起,形成一个连续的操作链,每个操作都是对序列进行转换、筛选、排序等操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <ranges> #include <vector> int main () { std::vector<int > numbers = { 1 , 2 , 3 , 4 , 5 }; 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 << " " ; } return 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 表达式时,它将结束执行,并将指定的值返回给调用者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 #include <coroutine> #include <iostream> #include <stdexcept> #include <thread> template <typename T>struct coro_ret { struct promise_type ; using handle_type = std::coroutine_handle<promise_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 (); } T get () { return coro_handle_.promise ().return_data_; } struct promise_type { promise_type () = default ; ~promise_type () = default ; auto get_return_object () { return coro_ret<T> { handle_type::from_promise (*this ) }; } auto initial_suspend () { return std::suspend_always {}; } 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 {}; } auto final_suspend () noexcept { std::cout << "final_suspend invoked." << std::endl; return std::suspend_always {}; } void unhandled_exception () { std::exit (1 ); } T return_data_; }; }; coro_ret<int > coroutine_7in7out () { std::cout << "Coroutine co_await std::suspend_never" << std::endl; 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" ; auto c_r = coroutine_7in7out (); std::cout << "Coroutine " << (done ? "is done " : "isn't done " ) << "ret =" << c_r.get () << std::endl; done = c_r.move_next (); std::cout << "Coroutine " << (done ? "is done " : "isn't done " ) << "ret =" << c_r.get () << std::endl; done = c_r.move_next (); std::cout << "Coroutine " << (done ? "is done " : "isn't done " ) << "ret =" << c_r.get () << std::endl; done = c_r.move_next (); std::cout << "Coroutine " << (done ? "is done " : "isn't done " ) << "ret =" << c_r.get () << std::endl; done = c_r.move_next (); std::cout << "Coroutine " << (done ? "is done " : "isn't done " ) << "ret =" << c_r.get () << std::endl; done = c_r.move_next (); std::cout << "Coroutine " << (done ? "is done " : "isn't done " ) << "ret =" << c_r.get () << std::endl; return 0 ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Start coroutine_7in7out () Coroutine isn't done ret =0 Coroutine co_await std::suspend_never Coroutine co_await std::suspend_always Coroutine isn't done ret =0 Coroutine stage 1 ,co_yield yield_value invoked. Coroutine isn't done ret =101 Coroutine stage 2 ,co_yield yield_value invoked. Coroutine isn't done ret =202 Coroutine stage 3 ,co_yield yield_value invoked. Coroutine isn't done ret =303 Coroutine stage end, co_return final_suspend invoked. Coroutine is done ret =808
5. Lambda表达式更新 就是进行了些小扩展,具体细节这个不介绍了,本质就是一个匿名函数。
6. constexpr常量表达式更新 也是增强了,限制更少了,具体细节这个也不介绍了,本质就是常量化,编译期求值。
7.原子(Atomic)智能指针 C++20引入了原子智能指针,这是对C++原子操作的一种扩展。原子智能指针允许你在多线程环境中安全地共享和操作智能指针。std::atomic原子智能指针可以保证对智能指针的操作是线程安全的,并且可以防止数据竞争。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <atomic> #include <iostream> #include <memory> #include <thread> #include <vector> struct Data { int value; Data (int val) : value (val) { } }; std::atomic<std::shared_ptr<Data>> sharedData; void worker (int threadId) { std::shared_ptr<Data> localPtr = std::atomic_load (&sharedData); std::cout << "Thread " << threadId << " read value: " << localPtr->value << std::endl; } int main () { sharedData.store (std::make_shared <Data>(42 )); std::vector<std::thread> threads; for (int i = 0 ; i < 4 ; ++i) { threads.emplace_back (worker, i); } for (std::thread& thread : threads) { thread.join (); } return 0 ; }
1 2 3 4 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 C++20 中引入的一种引用类型,用于支持原子操作。它允许您在不使用指针的情况下对共享数据进行原子操作。std::atomic_ref 可以用于提供线程安全的访问和修改共享变量的能力。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 #include <atomic> #include <iostream> #include <thread> int main () { int sharedValue = 0 ; std::atomic_ref<int > 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 ; }
1 Final shared value: 200000
9. 自动合流和可中断的线程
线程自动合流是指在线程执行完毕后,程序自动等待线程的结束并回收线程资源,而无需显式调用线程的 join() 方法。这使得线程的合流变得更加便捷和安全,避免了忘记合流或手动合流时出现的问题。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 #include <chrono> #include <iostream> #include <thread> void 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; std::cout << "Main thread is done." << std::endl; return 0 ; }
1 2 3 Main thread is doing some work. Main thread is done. Thread is done.
可中断线程是指线程具有一种机制,允许它在执行过程中被其他线程或外部事件中断,然后执行特定的操作。这使得可以更灵活地控制线程的执行。以前一般都是用一个全局变量来外部控制线程中断,容易出错,也不灵活。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 #include <iostream> #include <thread> #include <stop_token> #include <chrono> void 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 ; }
1 2 Thread is interrupted. Main thread is done.
10. 新的同步库 C++20引入了新的同步库,该库提供了多种同步工具,以帮助开发人员编写并发和多线程代码。
下面是一些C++20同步库的主要组件:
**std::latch**:std::latch是一个计数器,它允许您等待某个事件发生。当计数器归零时,所有等待的线程都将被唤醒。使用std::latch::wait()等待计数器归零。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 #include <iostream> #include <latch> #include <thread> void 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 (); } latch.wait (); std::cout << "All threads are done. " << std::endl; return 0 ; }
1 2 3 4 Thread 1 is done. Thread 0 is done.Thread 2 is done. All threads are done.
std::latch 可以用于控制一组线程的同步点,等待它们都完成后再进行下一步操作。这对于需要等待多个线程完成某项任务后再继续的情况非常有用。
std::barrier:std::barrier是一个同步原语,它允许一组线程相互等待,直到所有线程都到达某个点。只有当所有线程都到达 屏障点 时,这些线程才能继续执行。
与 std::latch 不同,std::barrier 允许线程多次参与同步,即在达到同步点后,线程可以再次加入到下一轮同步中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #include <barrier> #include <iostream> #include <thread> #include <vector> const 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::vector<std::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 ; }
1 2 3 4 5 6 7 8 9 10 Thread 1 is ready.Thread 2 is ready. Thread 0 is ready. Thread 0Thread continues.Thread 2 continues.1 continues. Thread Thread 1Thread 0 is done. is done. 2 is done. All threads are done.
在这个示例中,我们使用了两次同步点。std::barrier 允许线程多次参与同步,适用于需要多轮协作的情况,例如迭代式的并行计算或其他需要多次同步的场景。在每个同步点,所有线程都会等待,直到所有线程都到达同步点后才会继续执行。这可以用于更复杂的多线程协作任务。
**std::promise 和 std::future**:std::promise 和 std::future 提供了一种在不同线程之间传递数据的方式。std::promise 允许您在一个线程中设置一个值或异常,而 std::future 可以在另一个线程中获取该值或异常。您可以使用 std::promise::set_value() 设置值,使用 std::promise::set_exception() 设置异常,并使用 std::future::get() 获取值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <iostream> #include <thread> #include <future> void do_work (std::promise<int >& p) { int result = 42 ; p.set_value (result); } int main () { std::promise<int > p; std::future<int > f = p.get_future (); std::thread (do_work, std::ref (p)).detach (); int result = f.get (); std::cout << "Result: " << result << std::endl; return 0 ; }
这在多线程之间通信和协作非常有用。
**std::semaphore**:std::semaphore 是一种信号量,可以用于控制并发访问共享资源的线程,确保同时只有有限数量的线程可以访问共享资源,从而避免竞争条件和提高多线程程序的性能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include <iostream> #include <semaphore> #include <thread> #include <vector> std::counting_semaphore<10> sem (2 ) ; 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::vector<std::thread> threads; for (int i = 0 ; i < 5 ; ++i) { threads.emplace_back (worker, i); } for (std::thread& thread : threads) { thread.join (); } return 0 ; }
1 2 3 4 5 6 7 8 9 10 Thread 1 acquired resource.Thread 0 acquired resource. Thread Thread 4 acquired resource.1 released resource.Thread Thread 0 released resource.2 acquired resource. Thread Thread 3 acquired resource.2 released resource.Thread 4 released resource. Thread 3 released resource.
11. 其它更新 1. 指定初始化 它允许您在初始化复合数据结构(如结构体和数组)时,为特定的成员或元素提供初始化值,而不必按顺序初始化所有成员或元素。并且可以直接在声明时初始化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <vector> struct 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 ; }
1 2 p1: x=10 y=20 z=3 p2: x=1 y=70 z=80
2. 航天飞机操作符<=> 也叫三路比较运算符,用于比较两个对象的关系,返回值是std::strong_ordering类型,该类型包括三个可能的值,std::strong_ordering::less、std::strong_ordering::equal和std::strong_ordering::greater,分别表示左操作数小于、等于或大于右操作数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 #include <iostream> #include <compare> int 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<=>运算符一次,然后可以轻松地进行比较操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 #include <algorithm> #include <compare> #include <iostream> #include <vector> struct Person { std::string name; int age; auto operator <=>(const Person& other) const { return age <=> other.age; } }; int main () { std::vector<Person> people = { { "Alice" , 30 }, { "Bob" , 25 }, { "Charlie" , 35 }, { "David" , 28 } }; std::sort (people.begin (), people.end ()); for (const auto & person : people) { std::cout << person.name << " - " << person.age << std::endl; } return 0 ; }
1 2 3 4 Bob - 25 David - 28 Alice - 30 Charlie - 35
3.范围for循环支持初始化语句 在前面C++17中我们讲了if-switch支持初始化语句,现在for循环语句遍历容器时也支持初始化语句了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <vector> int main () { std::vector<int > numbers = { 1 , 2 , 3 , 4 , 5 }; for (int i = 0 ; int value : numbers) { std::cout << "Value at index " << i << ": " << value << std::endl; ++i; } return 0 ; }
1 2 3 4 5 Value at index 0: 1 Value at index 1: 2 Value at index 2: 3 Value at index 3: 4 Value at index 4: 5
4. 非类型模板形参支持字符串 这意味着您可以在模板中使用字符串作为模板参数,以实现更灵活的泛型编程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <string> template <const char * str>void printString () { std::cout << str << std::endl; } int main () { constexpr const char * message = "Hello, World!" ; printString <message>(); return 0 ; }
注:在vs2022上编译不过,可能是这个特性在当前编译器上还没有完美支持。
5. [[likely]], [[unlikely]]标记 它们是用于标记代码分支的建议性属性,以帮助编译器优化执行路径。这些属性的主要目的是告诉编译器哪些分支更可能被执行,以便它可以更好地进行优化。
[[likely]]属性:用于标记代码分支,表示这个分支更可能被执行。这有助于编译器在生成机器代码时对这个分支进行更好的优化。
[[unlikely]]属性:用于标记代码分支,表示这个分支更不可能被执行。这有助于编译器避免在生成机器代码时对这个分支进行过多的优化,以提高性能。
1 2 3 4 5 6 7 if (condition) [[likely]] { } else [[unlikely]] { }
这些属性是可选的,编译器可以根据它们来进行代码优化,但不是必需的。它们主要用于提高性能,并且在性能敏感的代码路径上使用得最多 ,以确保编译器对这些路径进行更好的优化。
请注意,这些属性的效果取决于编译器的实现,不同的编译器可能会有不同的优化策略。因此,在使用这些属性时,最好进行性能测试,以确保它们对代码的性能产生了预期的影响。
6. 日历和时区功能 C++20引入了标准库中的<chrono>头文件的重大更新,其中包括了对日历和时区的新功能。这些功能使得在C++中处理日期、时间和时区更加容易和灵活。
以下是C++20中日历和时区功能的主要亮点:
日历支持 :C++20引入了std::chrono::year_month_day、std::chrono::year_month和std::chrono::weekday等类型,以更方便地表示日期和时间。这些类型可以帮助您执行日期算术操作,如添加或减去日期、计算两个日期之间的时间间隔等。
格式化和解析 :C++20引入了std::format函数和std::chrono::from_stream函数,用于方便地格式化和解析日期和时间。这些函数可以将日期和时间以不同的格式进行字符串表示,并将字符串解析为日期和时间对象。
时区支持 :C++20引入了std::chrono::zoned_time类型,用于表示带有时区信息的日期和时间。这使得在不同的时区之间进行转换和比较变得更容易。此外,C++20还引入了std::chrono::current_zone()函数,用于获取当前的本地时区。
std::chrono::sys_time类型 :std::chrono::sys_time是一个系统级别的时钟,用于表示与特定时钟不相关的时间点。这对于在不同时钟之间进行时间计算和比较非常有用。
时钟和时钟间隔 :C++20引入了std::chrono::leap时钟,用于处理闰秒,以及std::chrono::den时钟间隔,用于表示任意固定时钟周期。这些时钟和时钟间隔类型增加了时间计算的灵活性。
时区数据库 :C++20的std::chrono库中包含了一个时区数据库,其中包含了大量的时区信息,允许您轻松地执行与时区相关的操作。
这些功能的引入使得C++在日期、时间和时区处理方面变得更加强大和标准化。使用这些功能,您可以更容易地处理不同时区的时间、执行日期算术操作、进行时间间隔计算等。
7. std::span 这是一个用于表示一段连续内存的非拥有式、轻量级的容器。std::span的目的是提供对现有内存的安全引用 ,而不进行内存分配或拷贝。通过它来引用数组传递使用就不会有越界风险,它内部自动实现了边界检查。它配合STL容器一起使用,更加灵活。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 #include <iostream> #include <vector> #include <span> int main () { std::vector<int > data = {1 , 2 , 3 , 4 , 5 }; std::span<int > span (data.data(), data.size()) ; for (int value : span) { std::cout << value << " " ; } std::cout << std::endl; span[2 ] = 99 ; for (int value : span) { std::cout << value << " " ; } std::cout << std::endl; return 0 ; }
8. 新增特性测试宏 C++20引入了一组特性测试宏,用于在代码中检测编译器是否支持特定的C++20功能或库。
以下是一些常用的C++20特性测试宏:
__cpp_concepts:用于测试是否支持C++20的概念(Concepts)特性。
1 2 3 #ifdef __cpp_concepts #endif
__cpp_consteval:用于测试是否支持C++20的consteval特性,该特性允许在编译时执行函数。
1 2 3 #ifdef __cpp_consteval #endif
__cpp_modules:用于测试是否支持C++20的模块(Modules)特性。
1 2 3 #ifdef __cpp_modules #endif
__cpp_ranges:用于测试是否支持C++20的范围(Ranges)库特性。
1 2 3 #ifdef __cpp_ranges #endif
__cpp_coroutines:用于测试是否支持C++20的协程(Coroutines)特性。
1 2 3 #ifdef __cpp_coroutines #endif
__cpp_lib_concepts、__cpp_lib_consteval、__cpp_lib_modules、__cpp_lib_ranges、__cpp_lib_coroutines等:用于测试编译器是否支持C++20库中的相关功能。
1 2 3 #ifdef __cpp_lib_ranges #endif
9. using可以为enum类型取别名 它允许您为枚举类型定义更具有意义的别名,这可以帮助提高代码的可读性和可维护性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 #include <iostream> enum class Color { Red, Green, Blue }; int main () { 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 ; }
类似于C语言中的printf函数或Python中的str.format()方法。std::format库的目标是提供一种类型安全、可扩展和国际化友好的方式来构建格式化字符串。
使用{}作为占位符,并且支持添加格式说明符。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <iostream> #include <format> int main () { int age = 30 ; std::string name = "Alice" ; double pi = 3.14159265359 ; std::string formatted = std::format("Name: {}, Age: {}, Pi: {:.2f}" , name, age, pi); std::cout << formatted << std::endl; return 0 ; }
11. 增加数学常量 在C++20中,标准库引入了一组常见的数学常量,这些常量定义在<numbers>头文件中。这些数学常量是通过std::numbers命名空间提供的,可以用于进行数学计算,例如π、自然对数的底数e等。
以下是一些C++20中引入的数学常量示例:
π(圆周率):可以使用std::numbers::pi访问。
1 double pi = std::numbers::pi;
e(自然对数的底数):可以使用std::numbers::e访问。
1 double e = std::numbers::e;
黄金比例:可以使用std::numbers::phi访问。
1 double phi = std::numbers::phi;
平方根2:可以使用std::numbers::sqrt2访问。
1 double sqrt2 = std::numbers::sqrt2;
平方根3:可以使用std::numbers::sqrt3访问。
1 double sqrt3 = std::numbers::sqrt3;
自然对数的2:可以使用std::numbers::ln2访问。
1 double ln2 = std::numbers::ln2;
自然对数的10:可以使用std::numbers::ln10访问。
1 double ln10 = std::numbers::ln10;
这些数学常量使得在C++中执行常见的数学计算更加方便和可读。它们以类型安全的方式提供了数学常数,避免了传统的宏定义或手动输入常数值的问题。
12. std::source_location 它允许在代码中获取当前文件的名称、当前行号、当前列号以及调用点的函数名称。这对于调试和日志记录非常有用,可以帮助定位代码中的问题。
std::source_location定义在<source_location>头文件中,并提供了以下常用成员函数:
file_name():返回当前源文件的名称。
line():返回当前源文件的行号。
column():返回当前源文件的列号。
function_name():返回调用点的函数名称。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 #include <iostream> #include <source_location> void 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 ; }
1 File: C:\Users\cxx\Desktop\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp Line: 18 Column: 5 Function: main
13. [[nodiscard(reason)]] 在C++17和C++20中,可以使用[[nodiscard]]属性来告诉编译器应该注意忽略函数的返回值。C++20进一步扩展了这个特性,允许您提供一个可选的字符串参数,以解释为什么返回值应该被注意忽略。如果返回值没有被使用,编译则会给出这个警告信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #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 2 3 4 5 6 7 8 9 10 11 12 已启动重新生成... 1>------ 已启动全部重新生成: 项目: ConsoleApplication1, 配置: Debug x64 ------ 1>正在扫描源以查找模块依赖项... 1>math.ixx 1>正在编译... 1>math.ixx 1>ConsoleApplication1.cpp 1>C:\Users\cxx\Desktop\ConsoleApplication1\ConsoleApplication1\ConsoleApplication1.cpp(20,11): warning C4858: 正在放弃返回值: Please check the return value for error handling 1>ConsoleApplication1.vcxproj -> C:\Users\cxx\Desktop\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe 1>已完成生成项目“ConsoleApplication1.vcxproj”的操作。 ========== “全部重新生成”: 1 成功,0 失败,0已跳过 ========== ========= 重新生成 开始于 3:04 PM,并花费了 01.026 秒 ==========
14. 增加循环移位,计算位中0和1数量等功能 这些功能是通过C++20的标准库中的<bit>头文件引入的。
循环左移和循环右移 :std::rotl用于执行循环左移操作,std::rotr用于执行循环右移操作。注意普通左移2位会将位移出并丢弃,而循环左移2位会将位重新循环回来,保持位数不变,即将移除的最高位的位数添加回数的最低位。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include <iostream> #include <bit> int main () { unsigned int value = 0b1100 ; unsigned int result_left = std::rotl (value, 2 ); unsigned int result_right = std::rotr (value, 2 ); std::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的数量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 #include <iostream> #include <bit> int main () { unsigned int value = 0b11001100 ; int zeros = std::countr_zero (value); int ones = std::countr_one (value); std::cout << "Count of Zeros: " << zeros << std::endl; std::cout << "Count of Ones: " << ones << std::endl; return 0 ; }
以上就是C++20的主要新增特性了,当然还一些细节扩展,那些就待使用时再查阅相关文档了。总结下来,C++11主要是引入了类型自动推导的auto,智能指针。C++20主要是模块、协程、原子操作。总体发展线路可以看出,C++编码风格在向脚本语言靠拢了。