当前位置: 首页 > news >正文

C++20新特性

作者:billy
版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处

前言

C++20 是 C++ 标准中的一个重要版本,引入了许多新特性和改进,包括模块(Modules)、协程(Coroutines)、概念(Concepts)、三向比较运算符(<=>)、范围(Ranges)、日期时间库(Date and Time)、数字分隔符(Digit Separators)等等。

在这里插入图片描述

有的小伙伴可能会需要参考手册的链接,这里给一个网页版的:C++ 参考手册 - 网页版

一. 语言特性

1. 模块(Modules)

C++20 的模块通过引入 “导入(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++ 开发者的编译时间和代码组织问题。虽然在实际应用中可能会遇到一些挑战,但通过合理的规划和实践,开发者可以充分利用这一特性,提升开发效率和代码质量。随着编译器对 C++20 标准支持的不断成熟,模块化编程将成为现代 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::future<int> 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_handle<promise_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_to<T> From>std::suspend_always yield_value(From&& from) {value_ = std::forward<From>(from);return {};}void return_void() {}};handle_type h_;Generator(handle_type h) : h_(h) {}~Generator() { if (h_) h_.destroy(); }std::optional<T> operator()() {if (!h_ || h_.done()) return {};h_.resume();if (h_.promise().exception_)std::rethrow_exception(h_.promise().exception_);return h_.promise().value_;}
};// 生成斐波那契数列的协程
Generator<int> 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)

在传统的模板编程中,模板参数的类型约束通常依赖于隐式接口,即模板代码在实例化时才会检查类型是否满足要求。这可能导致在编译过程中出现复杂且难以理解的错误信息。C++20 引入的概念(Concepts)是一项强大的新特性,它为模板编程提供了更强大的类型约束机制,使得代码更加清晰、易读,并且能够在编译期更早地发现错误。概念通过显式地定义模板参数必须满足的条件,使得模板的使用更加安全和可维护。

#include <iostream>
#include <concepts>// 定义一个概念,检查类型是否为整数类型
template <typename T>
concept Integral = std::is_integral_v<T>;// 方式一:使用 Integral 概念约束模板参数
template <Integral T>
T add(T a, T b) 
{return a + b;
}// 方式二:使用 requires 子句
template <typename T>
requires Integral<T>
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_v<T>;// 定义一个概念,要求类型支持 ++ 运算符
template <typename T>
concept Incrementable = requires(T t) 
{{ ++t } -> std::same_as<T&>;
};// 定义一个复杂概念,要求类型既是整数类型,又支持 ++ 运算符
template <typename T>
concept IntegralAndIncrementable = Integral<T> && Incrementable<T>;// 使用复杂概念约束模板参数
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 <compare>struct 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 的语句初始化,现在 C++20 中也可以在范围循环中进行初始化

for ( std::vector v {1, 2, 3}; auto& e : v ) 
{ std::cout << e << std::endl; 
}

二. 标准库特性

1. Ranges 库

Ranges 库是对标准模板库(STL)的一个重要扩展,它提供了一种现代、简洁的方式来处理序列(如数组、容器等)上的操作。范围库的主要特点是允许通过组合函数和适配器链接操作,使代码更直观和易读

#include <iostream>
#include <ranges>
#include <vector>int main() 
{std::vector<int> 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. 日历和时区库

C++20 引入了强大的日历和时区库,提供了处理日期、时间、日历系统和时区的功能,使得在 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 <chrono>int main() 
{using namespace std::chrono;// 创建一个具体日期year_month_day date = 2025y / February / 7d;// 输出日期信息std::cout << "Year: " << static_cast<int>(date.year()) << std::endl;std::cout << "Month: " << static_cast<unsigned>(date.month()) << std::endl;std::cout << "Day: " << static_cast<unsigned>(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 <chrono>int 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

在 C++20 中引入的 std::span 是一个轻量级、非拥有的视图,它可以表示连续的对象序列。它提供了一种安全且高效的方式来处理数组、std::array、std::vector 等连续存储的数据结构,而无需复制数据

主要特点

  • 轻量级:std::span 仅包含指向数据的指针和长度信息,没有内存所有权,因此创建和复制 std::span 的开销非常小。
  • 通用性:可以与各种连续存储的数据结构(如 C 风格数组、std::array、std::vector 等)一起使用。
  • 安全性:std::span 会跟踪所引用数据的长度,避免越界访问。
#include <iostream>
#include <span>
#include <vector>
#include <array>int main() 
{// 从 std::vector 创建 std::spanstd::vector<int> vec = {1, 2, 3, 4, 5};std::span<int> vecSpan(vec);// 从 std::array 创建 std::spanstd::array<int, 5> arr = {1, 2, 3, 4, 5};std::span<int> arrSpan(arr);// 从 C 风格数组创建 std::spanint cArr[] = {1, 2, 3, 4, 5};std::span<int> cArrSpan(cArr, std::size(cArr));// 获取子视图std::span<int> subSpan = vecSpan.subspan(1, 3);// 输出子视图中的元素for (int element : subSpan) {std::cout << element << " ";}std::cout << std::endl;return 0;
}

4. std::jthread

std::jthread 是 C++20 标准库中引入的一个新线程类,std::jthread 与 std::thread 相比安全性更高,std::jthread 会自动处理线程的清理工作,避免了可能出现的资源泄漏问题。并且提供了内置的停止机制,使得线程的取消操作更加方便和安全

#include <iostream>
#include <thread>
#include <chrono>void 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

C++20 引入了 std::format 库,提供了一种功能强大且灵活的字符串格式化方式。与传统的 printf 和 std::stringstream 方法相比,std::format 更加简洁且安全,支持类型安全的格式化,并且可以更方便地与现代 C++ 标准库集成

#include <iostream>
#include <format>int 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("{:0>10}", 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 <string>struct Point {int x;int y;
};template<>
struct std::formatter<Point> 
{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 是 C++20 标准库引入的一个新特性,它允许在代码中获取当前代码的源文件位置信息,包括文件名、行号、列号以及函数名等。这对于调试、日志记录和错误报告等场景非常有用

// 日志记录
#include <iostream>
#include <source_location>void 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 <stdexcept>void 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 函数

在 C++20 中,std::map 和 std::set 引入了 contains 函数,这为判断容器中是否包含某个特定元素提供了更加简洁和直观的方式。在 C++20 之前,要判断 std::set 或 std::map 中是否包含某个元素或键,通常使用 find 函数。虽然 find 函数也能实现相同的功能,但 contains 函数的语义更加清晰,代码也更加简洁。

#include <iostream>
#include <set>
#include <map>
#include <string>int main() 
{std::set<int> 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::map<std::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)

C++20 中引入了 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::vector<std::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::vector<std::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::promise<int>& 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::vector<std::promise<int>> promises(numPromises);std::vector<std::future<int>> futures;std::vector<std::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 是 C++20 引入的一个函数,用于在编译时判断当前代码是否在常量求值上下文中执行。这可以让函数根据不同的上下文执行不同的逻辑

#include <iostream>
#include <type_traits>constexpr 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
在 C++20 之前,Lambda 表达式的参数类型必须是具体的类型。C++20 引入了模板 Lambda,允许在 Lambda 表达式中使用模板参数,使得 Lambda 可以处理不同类型的参数。

#include <iostream>int 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
C++20 引入了概念(Concepts),可以将其应用于 Lambda 表达式,对 Lambda 的参数类型进行约束。

#include <iostream>
#include <concepts>int main() 
{// 约束Lambda,要求参数是整数类型auto printInt = []<std::integral T>(T value) {std::cout << value << std::endl;};printInt(10);  // 可以正常调用// printInt(3.14);  // 编译错误,因为 3.14 不是整数类型return 0;
}

3)Lambda 默认构造函数
在 C++20 之前,lambda 表达式没有默认构造函数,这意味着你不能默认初始化一个 lambda 对象。而从 C++20 开始,无捕获的 lambda 表达式可以默认构造,这为代码的编写和使用带来了更多的灵活性。

#include <iostream>// 定义一个函数,接受一个无捕获的 lambda 类型的参数
template<typename Func>
void callFunction(Func func) {func();
}int main() 
{// 定义一个无捕获的 lambda 类型using MyLambda = void(*)();// C++20 中,无捕获的 lambda 可以默认构造MyLambda lambda{};// 为 lambda 赋值一个无捕获的 lambda 表达式lambda = []() {std::cout << "Hello, C++20 Lambda Default Constructor!" << std::endl;};// 调用 lambda 表达式lambda();// 也可以将 lambda 传递给模板函数callFunction(lambda);return 0;
}

4)使用模板形参
C++20 允许在 Lambda 表达式中显式使用模板形参,这使得 Lambda 更加灵活和强大,就像普通的模板函数一样。

#include <iostream>
#include <concepts>// 定义一个概念,要求类型为算术类型
template<typename T>
concept Arithmetic = std::is_arithmetic_v<T>;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 改进

1)constexpr 动态内存分配
C++20 允许在 constexpr 函数中进行动态内存分配和释放,这意味着在编译时可以使用 new 和 delete 运算符。不过,这些动态分配的内存必须在编译时释放,否则会导致编译错误。

#include <iostream>constexpr 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;
}

2)constexpr std::vector 和其他标准库容器
C++20 允许在 constexpr 上下文中使用一些标准库容器,如 std::vector、std::string 等。这使得在编译时可以创建和操作容器。

#include <iostream>
#include <vector>constexpr std::vector<int> createVector() 
{std::vector<int> 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;
}

3)constexpr 虚函数调用的限制放宽
在 C++20 之前,constexpr 函数不能调用虚函数。C++20 放宽了这一限制,只要虚函数调用在编译时可以解析,就可以在 constexpr 函数中进行虚函数调用。

4)constexpr std::initializer_list
C++20 允许 std::initializer_list 在 constexpr 上下文中使用,这使得在编译时可以使用初始化列表来初始化对象。

#include <iostream>
#include <initializer_list>constexpr int sum(std::initializer_list<int> 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 扩展

在 C++11 及以后版本就支持使用 using 定义模板别名,C++20 进一步增强了模板别名在泛型编程中的灵活性,允许使用非类型模板参数

#include <iostream>
#include <vector>
#include <array>// c++11 定义一个模板别名
template<typename T>
using Vec = std::vector<T>;// c++20 定义一个模板别名,使用了非类型模板参数 std::size_t
template<typename T, std::size_t N>
using FixedArray = std::array<T, N>;int main() 
{Vec<int> vec = {1, 2, 3};for (int num : vec) {std::cout << num << " ";}std::cout << std::endl;FixedArray<int, 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 类型

在 C++20 之前,UTF - 8 编码的字符串通常使用 char 类型来表示。然而,char 类型既可以用于表示有符号整数,也可以用于表示无符号整数,这取决于编译器的实现,这就导致了在处理 UTF - 8 字符串时可能会出现一些未定义行为或移植性问题。为了解决这些问题,C++20 引入了 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 <string>int main() 
{// 使用 u8 前缀定义一个 char8_t 字符char8_t ch = u8'A';// 使用 u8 前缀定义一个 char8_t 字符串const char8_t* str = u8"Hello, 世界!";// 使用 u8 前缀定义一个 std::u8string 对象std::u8string u8str = u8"你好,C++20!";// 注意:std::cout 不能直接输出 char8_t 字符串,需要将其转换为 char 类型std::cout << "Character: " << static_cast<char>(ch) << std::endl;// 输出字符串std::cout << "String: ";for (const char8_t* p = str; *p; ++p) {std::cout << static_cast<char>(*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_cast<char>(ch);}std::cout << std::endl;return 0;
}

6. 智能指针优化

在 C++20 中,智能指针(如 std::unique_ptr、std::shared_ptr 和 std::weak_ptr)本身没有语法上的重大改变,但 C++20 引入的一些新特性可以帮助我们更好地使用和优化智能指针的使用。

1)使用consteval和constexpr进行编译时计算
constexpr 和 consteval 可以在编译时执行代码,对于智能指针相关的常量初始化和编译时计算非常有用。

#include <iostream>
#include <memory>// 编译时函数,返回一个 unique_ptr
consteval auto createUniquePtr() 
{return std::make_unique<int>(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 <span>void printSpan(std::span<int> sp) 
{for (int num : sp) {std::cout << num << " ";}std::cout << std::endl;
}int main() 
{auto arr = std::make_unique<int[]>(5);for (int i = 0; i < 5; ++i) {arr[i] = i;}// 使用 std::span 包装智能指针管理的数组std::span<int> sp(arr.get(), 5);printSpan(sp);return 0;
}

3)使用 std::jthread 和智能指针
使用智能指针管理 std::jthread 对象,确保线程资源的正确释放。

#include <iostream>
#include <memory>
#include <thread>void threadFunction() 
{std::cout << "Thread is running." << std::endl;
}int main() 
{auto threadPtr = std::make_unique<std::jthread>(threadFunction);// 当 threadPtr 离开作用域时,线程会自动加入return 0;
}

4)使用 std::atomic 和智能指针
C++20 对 std::atomic 进行了改进,可以使用 std::atomic<std::shared_ptr>实现线程安全的共享指针操作。

#include <iostream>
#include <memory>
#include <atomic>
#include <thread>std::atomic<std::shared_ptr<int>> atomicPtr;void writer() 
{auto newPtr = std::make_shared<int>(42);atomicPtr.store(newPtr, std::memory_order_release);
}void reader() 
{std::shared_ptr<int> 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)来约束智能指针的使用
C++20 引入的概念可以用于约束模板参数,确保智能指针操作的类型安全。

#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_unique<int>(42);printValue(ptr);return 0;
}

7. constinit 变量

constinit 是 C++20 引入的一个新的变量声明说明符,用于确保变量在程序启动时进行常量初始化。与 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)自定义哈希函数
对于自定义类型,需要提供自定义的哈希函数和相等比较函数。在 C++20 中,可以使用 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 hash<Person> {std::size_t operator()(const Person& p) const {// 使用 std::hash 组合多个成员的哈希值auto nameHash = std::hash<std::string>{}(p.name);auto ageHash = std::hash<int>{}(p.age);return nameHash ^ (ageHash << 1);}};
}  // namespace stdint main() 
{std::unordered_map<Person, 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;
}

2)std::erase_if
C++20 引入了 std::erase_if 函数,可用于删除满足特定条件的元素,避免了手动遍历和删除元素时的迭代器失效问题,同时也可能间接影响哈希表的性能。

#include <iostream>
#include <unordered_map>int main() 
{std::unordered_map<int, 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_map>int main() 
{std::unordered_map<int, 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;
}

相关文章:

C++20新特性

作者&#xff1a;billy 版权声明&#xff1a;著作权归作者所有&#xff0c;商业转载请联系作者获得授权&#xff0c;非商业转载请注明出处 前言 C20 是 C 标准中的一个重要版本&#xff0c;引入了许多新特性和改进&#xff0c;包括模块&#xff08;Modules&#xff09;、协程…...

电机实验曲线数据提取

处理Python 代码供参考: 1、曲线数据还原 import cv2 import numpy as np import matplotlib.pyplot as plt# 读取图像 image_path 1.png image cv2.imread(image_path) image_copy image.copy() # 创建图像副本&#xff0c;用于叠加显示# 转换为灰度图像 gray cv2.cvtCo…...

windows蓝牙驱动开发-调试及支持的HCI和事件

调试蓝牙配置文件驱动程序 开发蓝牙配置文件驱动程序时&#xff0c;可以使用驱动程序验证程序来协助其调试。 若要启用验证检查&#xff0c;必须为 Bthusb.sys 启用驱动程序验证程序。 如果不执行此操作&#xff0c;将禁用验证检查。 若要完全利用验证检查&#xff0c;请确保…...

Excel大数据量导入导出

github源码 地址&#xff08;更详细&#xff09; : https://github.com/alibaba/easyexcel 文档&#xff1a;读Excel&#xff08;文档已经迁移&#xff09; B 站视频 : https://www.bilibili.com/video/BV1Ff4y1U7Qc 一、JAVA解析EXCEL工具EasyExcel Java解析、生成Excel比较…...

Linux系统命令无法使用(glib库相关问题)

1.背景描述 Yum强制安装了一些软件&#xff0c;安装软件成功无报错&#xff0c;完成后不久突然发现系统出问题了&#xff0c;所有的命令无法使用了&#xff0c;如ls、mv、cat等基本命令报错。 relocation error&#xff1a; /lib64/libpthread.so.0: symbol_libc_dl_error_tsd …...

Qt修仙之路2-1 仿QQ登入 法宝初成

widget.cpp #include "widget.h" #include<QDebug> //实现槽函数 void Widget::login1() {QString userusername_input->text();QString passpassword_input->text();//如果不勾选无法登入if(!check->isChecked()){qDebug()<<"xxx"&…...

DeepSeek-V3 论文解读:大语言模型领域的创新先锋与性能强者

论文链接&#xff1a;DeepSeek-V3 Technical Report 目录 一、引言二、模型架构&#xff1a;创新驱动性能提升&#xff08;一&#xff09;基本架构&#xff08;Basic Architecture&#xff09;&#xff08;二&#xff09;多令牌预测&#xff08;Multi-Token Prediction&#xf…...

配置#include “nlohmann/json.hpp“,用于处理json文件

#include “nlohmann/json.hpp” // 需要安装 nlohmann/json.hpp 头文件 using json = nlohmann::json; 下载链接:https://github.com/nlohmann/json/tree/develop 1.下载并解压:首先,需要从nlohmann/json的GitHub仓库下载源代码,并解压得到的文件。 地址: nlohmann/json…...

索引失效的14种常见场景

在 MySQL 中&#xff0c;索引有时可能会失效&#xff0c;导致查询性能下降。以下是常见的 14 种场景&#xff0c;在这些场景下&#xff0c;索引可能会失效 1. 使用 OR 连接多个条件 场景: 当查询中包含 OR 时&#xff0c;如果 OR 连接的多个条件中有一个没有使用索引&#xff0…...

解决com.kingbase8.util.KSQLException: This _connection has been closed.

问题描述 一个消息管理系统,系统采用kingbase8数据库,数据库采用单体模式,后台应用也采用springboot单体模式。系统正式上线后,出现几个JDBC响应的异常信息: com.kingbase8.util.KSQLException: An I/O error occurred while sending to the backend.java.net.SocketTime…...

openAI官方prompt技巧(二)

1. 赋予 ChatGPT 角色 为 ChatGPT 指定一个角色&#xff0c;让其从特定的身份或视角回答问题。这有助于生成针对特定受众或场景的定制化回答。 例如&#xff1a; 你是一名数据分析师&#xff0c;负责我们的市场营销团队。请总结上个季度的营销活动表现&#xff0c;并强调与未…...

【非 root 用户下全局使用静态编译的 FFmpeg】

在非 root 用户下全局使用静态编译的 FFmpeg&#xff0c;可以按照以下方法操作&#xff1a; 1. 下载静态编译的 FFmpeg 如果你还没有下载静态编译的 FFmpeg&#xff0c;可以从官方网站获取&#xff1a; wget https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd6…...

【嵌入式 Linux 音视频+ AI 实战项目】瑞芯微 Rockchip 系列 RK3588-基于深度学习的人脸门禁+ IPC 智能安防监控系统

前言 本文主要介绍我最近开发的一个个人实战项目&#xff0c;“基于深度学习的人脸门禁 IPC 智能安防监控系统”&#xff0c;全程满帧流畅运行。这个项目我目前全网搜了一圈&#xff0c;还没发现有相关类型的开源项目。这个项目只要稍微改进下&#xff0c;就可以变成市面上目前…...

前端布局与交互实现技巧

前端布局与交互实现技巧 1. 保持盒子在中间位置 在网页设计中&#xff0c;经常需要将某个元素居中显示。以下是一种常见的实现方式&#xff1a; HTML 结构 <!doctype html> <html lang"en"> <head><meta charset"UTF-8"><m…...

idea 找不到或者无法加载主类

idea项目&#xff0c;之前一直是正常运行的&#xff0c;放假了之后再回来就遇到启动不了的问题。 WebApplication这个类右键运行的时候&#xff0c;也提示找不到主类。 对于这种之前运行没有问题&#xff0c;突然出问题的项目。 我的点是没有改动代码和数据的情况下项目就跑不起…...

Flink 调用海豚调度器 SQL 脚本实现1份SQL流批一体化的方案和可运行的代码实例

目录 一、流批一体化概述 二、Flink 与海豚调度器结合实现流批一体化的好处 2.1 代码复用性增强 2.2 开发和维护成本降低 2.3 数据一致性保证 2.4 提高系统的灵活性和可扩展性 三、实现思路步骤 3.1 环境准备 3.2 编写 SQL 脚本并上传到海豚调度器 3.3 实现资源下载功…...

ES6 Map 数据结构是用总结

1. Map 基本概念 Map 是 ES6 提供的新的数据结构&#xff0c;它类似于对象&#xff0c;但是"键"的范围不限于字符串&#xff0c;各种类型的值&#xff08;包括对象&#xff09;都可以当作键。Map 也可以跟踪键值对的原始插入顺序。 1.1 基本用法 // 创建一个空Map…...

go结构体详解

结构体简介 Golang 中没有“类”的概念&#xff0c;Golang 中的结构体和其他语言中的类有点相似。和其他面向对象语言中的类相比&#xff0c;Golang 中的结构体具有更高的扩展性和灵活性。 Golang 中的基础数据类型可以表示一些事物的基本属性&#xff0c;但是当我们想表达一…...

机器学习-关于线性回归的表示方式和矩阵的基本运算规则

最近在学习机器学习的过程中&#xff0c;发现关于线性回归的表示和矩阵的运算容易费解&#xff0c;而且随着学习的深入容易搞混&#xff0c;因此特意做了一些研究&#xff0c;并且记录下来和大家分享。 一、线性模型有哪些表示方式&#xff1f; 器学习中&#xff0c;线性模型…...

kafka 3.5.0 raft协议安装

前言 最近做项目&#xff0c;需要使用kafka进行通信&#xff0c;且只能使用kafka&#xff0c;笔者没有测试集群&#xff0c;就自己搭建了kafka集群&#xff0c;实际上笔者在很早之前就搭建了&#xff0c;因为当时还是zookeeper&#xff08;简称ZK&#xff09;注册元数据&#…...

后台管理系统网页开发

CSS样式代码 /* 后台管理系统样式文件 */ #container{ width:100%; height:100%; /* background-color:antiquewhite;*/ display:flex;} /* 左侧导航区域:宽度300px*/ .left{ width:300px; height: 100%; background-color:#203453; display:flex; flex-direction:column; jus…...

使用一个大语言模型对另一个大语言模型进行“调教”

使用一个大语言模型对另一个大语言模型进行“调教”&#xff08;通常称为微调或适配&#xff09;&#xff0c;是一种常见的技术手段&#xff0c;用于让目标模型更好地适应特定的任务、领域或风格。以下是基于搜索结果整理的详细步骤和方法&#xff1a; 1.准备工作 安装必要的…...

golang使用sqlite3,开启wal模式,并发读写

因为sqlite是基于文件的&#xff0c;所以默认情况下&#xff0c;sqlite是不支持并发读写的&#xff0c;即写操作会阻塞其他操作&#xff0c;同时sqlite也很容易就产生死锁。 但是作为一个使用广泛的离线数据库&#xff0c;从sqlite3.7.0版本开始&#xff08;SQLite Release 3.…...

如何利用maven更优雅的打包

最近在客户现场部署项目&#xff0c;有两套环境&#xff0c;无法连接互联网&#xff0c;两套环境之间也是完全隔离&#xff0c;于是问题就来了&#xff0c;每次都要远程到公司电脑改完代码&#xff0c;打包&#xff0c;通过网盘&#xff08;如果没有会员&#xff0c;上传下载慢…...

音频进阶学习十二——Z变换一(Z变换、收敛域、性质与定理)

文章目录 前言一、Z变换1.Z变换的作用2.Z变换公式3.Z的状态表示1&#xff09; r 1 r1 r12&#xff09; 0 < r < 1 0<r<1 0<r<13&#xff09; r > 1 r>1 r>1 4.关于Z的解释 二、收敛域1.收敛域的定义2.收敛域的表示方式3.ROC的分析1&#xff09;当 …...

cursor指令工具

Cursor 工具使用指南与实例 工具概览 Cursor 提供了一系列强大的工具来帮助开发者提高工作效率。本指南将通过具体实例来展示这些工具的使用方法。 1. 目录文件操作 1.1 查看目录内容 (list_dir) 使用 list_dir 命令可以查看指定目录下的文件结构: 示例: list_dir log…...

MySQL 主从读写分离实现方案(一)—MariaDB MaxScale实现mysql8读写分离

一&#xff1a;MaxScale 是干什么的&#xff1f;? MaxScale是maridb开发的一个mysql数据中间件&#xff0c;其配置简单&#xff0c;能够实现读写分离&#xff0c;并且可以根据主从状态实现写库的自动切换&#xff0c;对多个从服务器能实现负载均衡。 二&#xff1a;MaxScale …...

阿里云 | DeepSeek人工智能大模型安装部署

ModelScope是阿里云人工智能大模型开源社区 ModelScope网络链接地址 https://www.modelscope.cn DeepSeek模型库网络链接地址 https://www.modelscope.cn/organization/deepseek-ai 如上所示&#xff0c;在阿里云人工智能大模型开源社区ModelScope中&#xff0c;使用阿里云…...

LLAMA-Factory安装教程(解决报错cannot allocate memory in static TLS block的问题)

步骤一&#xff1a; 下载基础镜像 # 配置docker DNS vi /etc/docker/daemon.json # daemon.json文件中 { "insecure-registries": ["https://swr.cn-east-317.qdrgznjszx.com"], "registry-mirrors": ["https://docker.mirrors.ustc.edu.c…...

STM32 CUBE Can调试

STM32 CUBE Can调试 1、CAN配置2、时钟配置3、手动添加4、回调函数5、启动函数和发送函数6、使用方法(采用消息队列来做缓存)7、数据不多在发送函数中获取空邮箱发送&#xff0c;否则循环等待空邮箱 1、CAN配置 2、时钟配置 3、手动添加 需要注意的是STM32CUBE配置的代码需要再…...