c++20新特性

个人博客地址: https://cxx001.gitee.io

前言

自C++11这个大版本更新以来,后来陆续有两次小版本迭代C++14、C++17,它们主要是对C++11的补充扩展,并没有增加太多大的特性。

而这次的C++20,和当年C++11一样,又是一次重大更新,有人甚至说这是一门新语言。

1. 新增关键字

  • concept 用于约束模板参数的类型范围,从而限制模板的实例化范围。
#include <iostream>template <typename T>
concept Arithmetic = std::is_arithmetic_v<T>;  // 约束参数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_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 关键字,可以指定更多的条件,以进一步限制模板参数的属性。
template <typename T>
concept Incrementable = requires(T a) {{ ++a } -> std::same_as<T&>;  // 要求 a 可以递增,并返回 T&std::is_arithmetic_v<T>;      // 要求 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;
}

注意与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 字符串,而无需进行字符集转换或编码处理。更加方便了。

#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关键字在其他模块中导入所需的实体。

// 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 <iostream>import math;  // 导入模块int main()
{int result = add(5, 3);std::cout << result;return 0;
}

使用模块化编程可以带来许多优势,例如:

  • 更好的代码组织:模块将相关的实体封装在一起,使代码结构更清晰,易于维护和理解。
  • 更好的封装:模块可以选择性地导出实体,控制对外部的可见性,提供更好的封装性。
  • 更快的编译速度:由于模块只包含所需的实体,而不是整个头文件,编译速度可能会更快。
  • 避免头文件的预处理器宏:模块不需要使用预处理器宏来处理头文件的重复包含和条件编译。

3. 新增ranges标准库组件

C++20引入了一种新的标准库组件,称为Ranges(范围),用于简化和增强对序列(包括容器、数组、迭代器等)的操作和处理。

Ranges的设计目标是通过使用管道操作符(|)和函数式编程的风格,提供一种更现代、更可读、更易于组合的方式来操作序列。使用Ranges,你可以将多个操作链接在一起,形成一个连续的操作链,每个操作都是对序列进行转换、筛选、排序等操作。

#include <iostream>
#include <ranges>
#include <vector>int main()
{std::vector<int> 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::viewsstd::ranges来访问Ranges的操作。

下面列出Ranges的一些常用的操作:

  • 转换序列:使用std::views::transform将序列中的元素进行转换,例如将整数序列转换为字符串序列或进行数值计算。

  • 筛选元素:使用std::views::filter根据特定条件筛选序列中的元素,例如筛选出满足某个谓词的元素。

  • 排序序列:使用std::views::sort对序列进行排序操作,可以根据元素的某个属性进行排序。

  • 分组元素:使用std::views::group_by将序列中的元素按照某个条件进行分组,例如按照元素的奇偶性进行分组。

  • 聚合操作:使用std::views::reducestd::views::transform_reduce对序列中的元素进行聚合操作,例如计算序列的总和、平均值等。

  • 切片操作:使用std::views::takestd::views::dropstd::views::slice对序列进行切片操作,例如获取前几个元素或跳过前几个元素。

  • 迭代操作:使用std::ranges::for_each对序列中的每个元素进行迭代操作,例如打印序列中的所有元素。

  • 查找元素:使用std::ranges::findstd::ranges::find_if在序列中查找特定的元素或满足某个条件的元素。

  • 合并序列:使用std::views::concat将多个序列合并为一个序列,方便进行统一的操作。

  • 转换为容器:使用std::ranges::to将序列转换为特定类型的容器,例如将范围视图转换为std::vectorstd::list

注意std::views::xx和std::ranges::xx区别,std::views下的操作是懒操作,在用到时才执行,并且不会修改原序列,返回一个操作后的新视图。而std::ranges下的操作是立即执行,操作修改原序列,不返回任何值。

4. 协程

这特性又是一大干货哈,终于是引入标准了。也是很多脚本语言常用特性,感觉越往后发展,C++编码门槛会越来越低哈,会和写脚本一样丝滑~~

协程是一种轻量级的并发编程机制,本质是一个特殊函数,只是它允许函数在执行过程中暂停和恢复。协程通常用于处理异步操作,例如网络请求、文件读写等。

主要关键字:

  1. co_await
    • co_await 用于在协程内部暂停执行,通常用于等待异步操作完成。
    • 当协程遇到 co_await 表达式时,它将被暂停,并在异步操作完成后恢复执行。
  2. co_yield
    • co_yield 用于生成值并将其发送给调用者。它通常用于生成序列中的下一个值。
    • 当协程遇到 co_yield 表达式时,它会生成值并将其返回给调用者,然后暂停协程的执行,以便在需要时继续生成更多值。
  3. 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_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();}//! 通过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_ret<T> { 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++ 2022)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()
{// 进入协程看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 " : "isn't done ")<< "ret =" << c_r.get() << std::endl;done = c_r.move_next();// 此时是,co_await std::suspend_always{}std::cout << "Coroutine " << (done ? "is done " : "isn't done ")<< "ret =" << c_r.get() << std::endl;done = c_r.move_next();// 此时打印Stage 1std::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;
}
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原子智能指针可以保证对智能指针的操作是线程安全的,并且可以防止数据竞争。

#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::atomic<std::shared_ptr<Data>> sharedData;// 线程函数,从共享的std::shared_ptr中读取数据
void worker(int threadId)
{// 从原子std::shared_ptr中加载数据std::shared_ptr<Data> localPtr = std::atomic_load(&sharedData);// 使用本地副本进行操作std::cout << "Thread " << threadId << " read value: " << localPtr->value << std::endl;
}int main()
{// 初始化共享的std::shared_ptrsharedData.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;
}
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 可以用于提供线程安全的访问和修改共享变量的能力。

#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;
}
Final shared value: 200000

9. 自动合流和可中断的线程

  • 线程自动合流是指在线程执行完毕后,程序自动等待线程的结束并回收线程资源,而无需显式调用线程的 join() 方法。这使得线程的合流变得更加便捷和安全,避免了忘记合流或手动合流时出现的问题。
#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;// 不需要手动调用 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 <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;
}
Thread is interrupted.
Main thread is done.

10. 新的同步库

C++20引入了新的同步库,该库提供了多种同步工具,以帮助开发人员编写并发和多线程代码。

下面是一些C++20同步库的主要组件:

  1. std::latchstd::latch是一个计数器,它允许您等待某个事件发生。当计数器归零时,所有等待的线程都将被唤醒。使用std::latch::wait()等待计数器归零。
#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();}// 等待所有线程完成,即当计数器为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 可以用于控制一组线程的同步点,等待它们都完成后再进行下一步操作。这对于需要等待多个线程完成某项任务后再继续的情况非常有用。

  1. std::barrierstd::barrier是一个同步原语,它允许一组线程相互等待,直到所有线程都到达某个点。只有当所有线程都到达屏障点时,这些线程才能继续执行。

    std::latch 不同,std::barrier 允许线程多次参与同步,即在达到同步点后,线程可以再次加入到下一轮同步中。

#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;
}
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 允许线程多次参与同步,适用于需要多轮协作的情况,例如迭代式的并行计算或其他需要多次同步的场景。在每个同步点,所有线程都会等待,直到所有线程都到达同步点后才会继续执行。这可以用于更复杂的多线程协作任务。

  1. std::promisestd::futurestd::promisestd::future 提供了一种在不同线程之间传递数据的方式。std::promise 允许您在一个线程中设置一个值或异常,而 std::future 可以在另一个线程中获取该值或异常。您可以使用 std::promise::set_value() 设置值,使用 std::promise::set_exception() 设置异常,并使用 std::future::get() 获取值。
#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;
}
Result: 42

这在多线程之间通信和协作非常有用。

  1. std::semaphorestd::semaphore 是一种信号量,可以用于控制并发访问共享资源的线程,确保同时只有有限数量的线程可以访问共享资源,从而避免竞争条件和提高多线程程序的性能。
#include <iostream>
#include <semaphore>
#include <thread>
#include <vector>std::counting_semaphore<10> 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::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;
}
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 <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;
}
p1: x=10 y=20 z=3
p2: x=1 y=70 z=80

2. 航天飞机操作符<=>

也叫三路比较运算符,用于比较两个对象的关系,返回值是std::strong_ordering类型,该类型包括三个可能的值,std::strong_ordering::lessstd::strong_ordering::equalstd::strong_ordering::greater,分别表示左操作数小于、等于或大于右操作数。

#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<=>运算符一次,然后可以轻松地进行比较操作。

#include <algorithm>
#include <compare>
#include <iostream>
#include <vector>struct Person {std::string name;int age;// 按年龄大小排序,std::sort内部自动按这个排序算法auto operator<=>(const Person& other) const{return age <=> other.age; // 如果要按从大到小反过来就行了, other.age <=> age}
};int main()
{std::vector<Person> 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 - 35

3.范围for循环支持初始化语句

在前面C++17中我们讲了if-switch支持初始化语句,现在for循环语句遍历容器时也支持初始化语句了。

#include <iostream>
#include <vector>int main()
{std::vector<int> numbers = { 1, 2, 3, 4, 5 };// 使用 C++20 范围 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: 5

4. 非类型模板形参支持字符串

这意味着您可以在模板中使用字符串作为模板参数,以实现更灵活的泛型编程。

#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]]标记

它们是用于标记代码分支的建议性属性,以帮助编译器优化执行路径。这些属性的主要目的是告诉编译器哪些分支更可能被执行,以便它可以更好地进行优化。

  1. [[likely]]属性:用于标记代码分支,表示这个分支更可能被执行。这有助于编译器在生成机器代码时对这个分支进行更好的优化。
  2. [[unlikely]]属性:用于标记代码分支,表示这个分支更不可能被执行。这有助于编译器避免在生成机器代码时对这个分支进行过多的优化,以提高性能。
if (condition) [[likely]] {// 告诉编译器这个分支很可能会执行// 在性能敏感的代码路径上使用
} else [[unlikely]] {// 告诉编译器这个分支不太可能会执行// 在不太可能执行的代码路径上使用
}

这些属性是可选的,编译器可以根据它们来进行代码优化,但不是必需的。它们主要用于提高性能,并且在性能敏感的代码路径上使用得最多,以确保编译器对这些路径进行更好的优化。

请注意,这些属性的效果取决于编译器的实现,不同的编译器可能会有不同的优化策略。因此,在使用这些属性时,最好进行性能测试,以确保它们对代码的性能产生了预期的影响。

6. 日历和时区功能

C++20引入了标准库中的<chrono>头文件的重大更新,其中包括了对日历和时区的新功能。这些功能使得在C++中处理日期、时间和时区更加容易和灵活。

以下是C++20中日历和时区功能的主要亮点:

  1. 日历支持:C++20引入了std::chrono::year_month_daystd::chrono::year_monthstd::chrono::weekday等类型,以更方便地表示日期和时间。这些类型可以帮助您执行日期算术操作,如添加或减去日期、计算两个日期之间的时间间隔等。
  2. 格式化和解析:C++20引入了std::format函数和std::chrono::from_stream函数,用于方便地格式化和解析日期和时间。这些函数可以将日期和时间以不同的格式进行字符串表示,并将字符串解析为日期和时间对象。
  3. 时区支持:C++20引入了std::chrono::zoned_time类型,用于表示带有时区信息的日期和时间。这使得在不同的时区之间进行转换和比较变得更容易。此外,C++20还引入了std::chrono::current_zone()函数,用于获取当前的本地时区。
  4. std::chrono::sys_time类型std::chrono::sys_time是一个系统级别的时钟,用于表示与特定时钟不相关的时间点。这对于在不同时钟之间进行时间计算和比较非常有用。
  5. 时钟和时钟间隔:C++20引入了std::chrono::leap时钟,用于处理闰秒,以及std::chrono::den时钟间隔,用于表示任意固定时钟周期。这些时钟和时钟间隔类型增加了时间计算的灵活性。
  6. 时区数据库:C++20的std::chrono库中包含了一个时区数据库,其中包含了大量的时区信息,允许您轻松地执行与时区相关的操作。

这些功能的引入使得C++在日期、时间和时区处理方面变得更加强大和标准化。使用这些功能,您可以更容易地处理不同时区的时间、执行日期算术操作、进行时间间隔计算等。

7. std::span

这是一个用于表示一段连续内存的非拥有式、轻量级的容器。std::span的目的是提供对现有内存的安全引用,而不进行内存分配或拷贝。通过它来引用数组传递使用就不会有越界风险,它内部自动实现了边界检查。它配合STL容器一起使用,更加灵活。

#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());// 使用 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 5

8. 新增特性测试宏

C++20引入了一组特性测试宏,用于在代码中检测编译器是否支持特定的C++20功能或库。

以下是一些常用的C++20特性测试宏:

  1. __cpp_concepts:用于测试是否支持C++20的概念(Concepts)特性。

    #ifdef __cpp_concepts
    // 在支持概念的编译器中编写的代码
    #endif
    
  2. __cpp_consteval:用于测试是否支持C++20的consteval特性,该特性允许在编译时执行函数。

    #ifdef __cpp_consteval
    // 在支持 consteval 的编译器中编写的代码
    #endif
    
  3. __cpp_modules:用于测试是否支持C++20的模块(Modules)特性。

    #ifdef __cpp_modules
    // 在支持模块的编译器中编写的代码
    #endif
    
  4. __cpp_ranges:用于测试是否支持C++20的范围(Ranges)库特性。

    #ifdef __cpp_ranges
    // 在支持范围库的编译器中编写的代码
    #endif
    
  5. __cpp_coroutines:用于测试是否支持C++20的协程(Coroutines)特性。

    #ifdef __cpp_coroutines
    // 在支持协程的编译器中编写的代码
    #endif
    
  6. __cpp_lib_concepts__cpp_lib_consteval__cpp_lib_modules__cpp_lib_ranges__cpp_lib_coroutines等:用于测试编译器是否支持C++20库中的相关功能。

    #ifdef __cpp_lib_ranges
    // 在支持范围库的编译器中编写的代码
    #endif
    

9. 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 <format>int 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. 增加数学常量

在C++20中,标准库引入了一组常见的数学常量,这些常量定义在<numbers>头文件中。这些数学常量是通过std::numbers命名空间提供的,可以用于进行数学计算,例如π、自然对数的底数e等。

以下是一些C++20中引入的数学常量示例:

  1. π(圆周率):可以使用std::numbers::pi访问。

    double pi = std::numbers::pi;
    
  2. e(自然对数的底数):可以使用std::numbers::e访问。

    double e = std::numbers::e;
    
  3. 黄金比例:可以使用std::numbers::phi访问。

    double phi = std::numbers::phi;
    
  4. 平方根2:可以使用std::numbers::sqrt2访问。

    double sqrt2 = std::numbers::sqrt2;
    
  5. 平方根3:可以使用std::numbers::sqrt3访问。

    double sqrt3 = std::numbers::sqrt3;
    
  6. 自然对数的2:可以使用std::numbers::ln2访问。

    double ln2 = std::numbers::ln2;
    
  7. 自然对数的10:可以使用std::numbers::ln10访问。

    double ln10 = std::numbers::ln10;
    

这些数学常量使得在C++中执行常见的数学计算更加方便和可读。它们以类型安全的方式提供了数学常数,避免了传统的宏定义或手动输入常数值的问题。

12. std::source_location

它允许在代码中获取当前文件的名称、当前行号、当前列号以及调用点的函数名称。这对于调试和日志记录非常有用,可以帮助定位代码中的问题。

std::source_location定义在<source_location>头文件中,并提供了以下常用成员函数:

  1. file_name():返回当前源文件的名称。
  2. line():返回当前源文件的行号。
  3. column():返回当前源文件的列号。
  4. function_name():返回调用点的函数名称。
#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;
}
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进一步扩展了这个特性,允许您提供一个可选的字符串参数,以解释为什么返回值应该被注意忽略。如果返回值没有被使用,编译则会给出这个警告信息。

#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>正在扫描源以查找模块依赖项...
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>头文件引入的。

  1. 循环左移和循环右移std::rotl用于执行循环左移操作,std::rotr用于执行循环右移操作。注意普通左移2位会将位移出并丢弃,而循环左移2位会将位重新循环回来,保持位数不变,即将移除的最高位的位数添加回数的最低位。
#include <iostream>
#include <bit>int 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;
}
  1. 计算位中0和1的数量std::countr_zero用于计算从右侧开始的连续0的数量,std::countr_one用于计算从右侧开始的连续1的数量。
#include <iostream>
#include <bit>int 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;
}

以上就是C++20的主要新增特性了,当然还一些细节扩展,那些就待使用时再查阅相关文档了。总结下来,C++11主要是引入了类型自动推导的auto,智能指针。C++20主要是模块、协程、原子操作。总体发展线路可以看出,C++编码风格在向脚本语言靠拢了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/68865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

多个pdf怎么合并在一起?跟着我的步骤一起合并

多个pdf怎么合并在一起&#xff1f;利用PDF文档合并功能可以帮助您更有效地管理文件&#xff0c;将多个相关文件整合成一个文件&#xff0c;避免分散在多个文件中。此外&#xff0c;合并后的文件更便于共享和传输&#xff0c;因为只需共享一个文件而不是多个文件。虽然合并文件…

IntelliJ IDEA(Windows 版)的所有快捷键

&#x1fa81;&#x1f341; 希望本文能够给您带来一定的帮助&#x1f338;文章粗浅&#xff0c;敬请批评指正&#xff01;&#x1f341;&#x1f425; 大家好 本文参考了 IntelliJ IDEA 的官网&#xff0c;列举了IntelliJ IDEA&#xff08;Windows 版&#xff09;的所有快捷…

数据脱敏sensitive(前端或数据库加密,解密)

可以对数据加密&#xff0c;解密&#xff0c;对数据库加密的数据进行解密显示&#xff0c;对数据库没有加密的数据进行加密处理展示前端等待 1&#xff1a;引入数据如下结构 1-1&#xff1a;SensitiveDecode脱敏解密注解 package com.example.poi.desensitization.annotation;…

将 Python 与 RStudio IDE 配合使用(R与Python系列第一篇)

目录 前言&#xff1a; 1-安装reticulate包 2-安装Python 3-选择Python的默认版本&#xff08;配置Python环境&#xff09; 4-使用Python 4.1 运行一个简单的Python脚本 4.2 在RStudio上安装Python模块 4.3 在 R 中调用 Python 模块 4.4 在RStudio上调用Python脚本写的…

ssh无法启动 报错:sshd:Missing privilege separation directory:/var/empty/sshd

ssh无法启动 报错&#xff1a;sshd:Missing privilege separation directory:/var/empty/sshd 根据提示检查/var/empty/sshd /var/empty&#xff1a;默认是sshd程序用到的这个目录&#xff0c;当建立ssh连接&#xff0c;ssh服务器必须使用该目录下的sshd子目录&#xff1b; …

Linux使用bonding实现双网冗余

1、简介 linux bonding 是一种将多个物理网卡绑定为一个逻辑网卡的技术&#xff0c;它可以实现网络的冗余、负载均衡和带宽扩展等功能。linux bonding 是 linux 内核中提供的一个模块&#xff0c;它支持七种工作模式&#xff0c;不同的模式有不同的特点和适用场景。linux bond…

vue前端解决跨域

1,首先 axios请求&#xff0c;看后端接口路径&#xff0c;http://122.226.146.110:25002/api/xx/ResxxList&#xff0c;所以baseURL地址改成 ‘/api’ let setAxios originAxios.create({baseURL: /api, //这里要改掉timeout: 20000 // request timeout}); export default s…

【C++ 二叉搜索树】

目录 1.什么是二叉搜索树2.构建二叉搜索树2.1首先搭建树的框架2.2搭建搜索树的框架 3.二叉搜索树的插入3.1非递归式插入3.2递归式插入 4.二叉搜索树的查找4.1非递归查找4.2递归查找 5.二叉搜索树的删除5.1非递归删除5.2递归删除 6.整个代码实现 1.什么是二叉搜索树 简单来讲就…

NSSCTF web 刷题记录1

文章目录 前言题目[GXYCTF 2019]禁止套娃方法一方法二 [NCTF 2019]Fake XML cookbook[NSSRound#7 Team]ec_RCE[NCTF 2018]Flask PLUS 前言 今天是2023.9.3&#xff0c;大二开学前的最后一天。老实说ctf的功力还是不太够做的题目太少&#xff0c;新学期新气象。不可急于求成&am…

java中ThreadPoolExecutor线程池如何设置核心线程数和最大线程数,跟cpu核数有关系吗?

在 ThreadPoolExecutor 中&#xff0c;可以通过设置核心线程数和最大线程数来控制线程池的行为。这两个参数可以根据实际需求进行调整&#xff0c;并且它们与 CPU 核数之间存在一定的关系。 通常情况下&#xff0c;可以根据 CPU 核数来设置线程池的核心线程数和最大线程数。以…

设置 Hue Server 与 Hue Web 界面之间的会话超时时间

设置 Hue Server 与 Hue Web 界面之间的会话超时时间 在 CDH 的 Hue 中&#xff0c;Auto Logout Timeout 参数表示用户在不活动一段时间后将自动注销&#xff08;登出&#xff09;的超时时间。当用户在 Hue 中处于不活动状态超过该设定时间时&#xff0c;系统将自动注销用户&am…

redis问题:三种集群——主从、哨兵、cluster集群;16384槽等

目录 redis三种集群模式 1、主从 2、哨兵&#xff08;Sentinel&#xff09; 3、集群&#xff08;Cluster&#xff09; Redis Cluster为什么有16384个槽&#xff1f; 1、8KB的心跳包太大 2、集群的数量不会超过1000。 主从配置和集群配置区别 1、主从 2、集群 redis三种…

如何处理ChatGPT与用户之间的互动和反馈?

处理ChatGPT与用户之间的互动和反馈是关于改进和优化用户体验的关键方面。这涉及到在聊天、对话和交互中建立积极的用户关系&#xff0c;同时利用用户的反馈来不断改进ChatGPT的性能和功能。本文将探讨如何有效地处理ChatGPT与用户之间的互动和反馈&#xff0c;以提供更好的用户…

FreeRTOS中断与任务之间同步(Error:..\..\FreeRTOS\portable\RVDS\ARM_CM4F\port.c,422 )

前言&#xff1a; FreeRTOS中&#xff0c;中断需要注意几点&#xff1a; 何时使用中断&#xff1b;中断服务函数&#xff08;ISR&#xff09;要处理的数据量有多大&#xff0c;通常我们希望中断的切换越快越好&#xff0c;也就是说&#xff0c;ISR尽量采用耗时较少的处理方式…

撮合前端平台在低代码平台的落地实践 | 京东云技术团队

在京东技术的发展当下&#xff0c;不同的业务线&#xff0c;不同的区域&#xff0c;甚至于很多触达消费者的端&#xff0c;正在被中台架构能力所支撑。大家都很清楚&#xff0c;中台建设能够带来技术的规模化效应&#xff0c;具有提高业务协同、加速创新和交付速度、提高系统稳…

Java问题诊断和排查工具

文章目录 一、前言二、Java问题诊断和排查工具1、JDK自带工具2、常用命令3、JAVA Dump&#xff1a;3.1、jps3.2、jstack3.3、jmap3.3.1、jmap -heap pid:查看堆使用情况3.3.2、jmap -histo pid&#xff1a;查看堆中对象数量和大小3.3.3、jmap -dump:formatb,fileheapdump pid&a…

FFmpeg rtp rtp_mpegts的区别

rtp 在FFmpeg中&#xff0c;rtpenc是一个用于将音视频数据封装成RTP&#xff08;Real-time Transport Protocol&#xff09;数据包并发送到网络上的编码器。RTP是一种用于实时传输音视频数据的协议&#xff0c;常用于视频会议、流媒体等场景。 rtpenc可以将音视频数据封装成R…

【数据库】如何利用Python中的petl将PostgreSQL中所有表的外键删除,迁移数据,再重建外键

一、简介 在数据库管理中&#xff0c;外键是一种重要的约束&#xff0c;用于确保数据的一致性和完整性。然而&#xff0c;在某些情况下&#xff0c;我们可能需要删除或修改外键。本文将介绍如何使用Python中的petl库将PostgreSQL中所有表的外键删除&#xff0c;迁移数据&#…

K8S的介绍和架构

仅供入门 K8S的介绍和架构 一. 什么是kubernetes二、Kubernetes架构和组件 2.1 核心组件 2.1.1 Kubernetes Master控制组件&#xff0c;调度管理整个系统&#xff08;集群&#xff09;&#xff0c;包含如下组件: a、Kubernetes API Serverb、Kubernetes Schedulerc、Kubernet…

Linux 禁止用户或 IP通过 SSH 登录

Linux 禁止用户或 IP通过 SSH 登录 限制用户 SSH 登录 1.只允许指定用户进行登录(白名单): 在 /etc/ssh/sshd_config 配置文件中设置 AllowUsers 选项,(配置完成需要重启 SSHD 服务)格式如下: AllowUsers aliyun test@192.168.1.1 # 允许 aliyun 和从 19…