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 中支持还不够,还是用的原始的 #include

int 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 秒后返回 42
    auto 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::span
    std::vector<int> vec = {1, 2, 3, 4, 5};
    std::span<int> vecSpan(vec);

    // 从 std::array 创建 std::span
    std::array<int, 5> arr = {1, 2, 3, 4, 5};
    std::span<int> arrSpan(arr);

    // 从 C 风格数组创建 std::span
    int 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;              // 右对齐,宽度为10
    std::cout << std::format("{:<10}", age) << std::endl;              // 左对齐,宽度为10
    std::cout << std::format("{:0>10}", age) << std::endl;             // 用0填充,宽度为10
    std::cout << std::endl;
    std::cout << std::format("Decimal: {}", age) << std::endl;
    std::cout << std::format("Hexadecimal: {:#x}", age) << std::endl;  // 带有前缀0x
    std::cout << std::format("Octal: {:#o}", age) << std::endl;        // 带有前缀0
    std::cout << std::format("Binary: {:#b}", age) << std::endl;       // 带有前缀0b

    return 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;
    
    // 完成工作后,减少闩锁的计数,将计数值减 1
    latch.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::promise
    for (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() 
{
    // 带有模板形参和概念约束的 Lambda
    auto 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_ptr
    constexpr 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 std

int 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;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

新增md、html压缩文档上传,开放接口访问密钥改为多个,zyplayer-doc 2.4.7 发布啦!

zyplayer-doc是一款适合企业和个人使用的WIKI知识库管理工具&#xff0c;支持在线编辑富文本、Markdown、表格、Office文档、API接口、思维导图、Drawio以及任意的文本文件&#xff0c;专为私有化部署而设计&#xff0c;最大程度上保证企业或个人的数据安全&#xff0c;支持以内…

ES管理器焕新升级:紫色银狼主题来袭!

ES管理器&#xff08;安卓版&#xff09;迎来了一次令人眼前一亮的改头换面&#xff01;此次更新最直观的变化集中在UI界面设计上。开发团队大胆突破&#xff0c;摒弃了以往稍显平庸的风格&#xff0c;引入了极具个性的全新主题——以热门游戏《崩坏&#xff1a;星穹铁道》中的…

SwiftUI 学习 Toggle 遇到的问题

最近学习SwifyUI&#xff0c;心中存疑&#xff0c;于是记录这些问题 List {Toggle(isOn: $showFavoriteOnly) {Text("Favorite only")}ForEach(filterLandmarks) { landmark in// 在 NavigationLink 中&#xff0c;label 是用于指定导航链接显示内容的部分Navigati…

HarmonyOS:电话号码格式化

一、使用场景 不同国家和地区的电话号码在号码位数、组合方式、呈现方式等都存在差异。同时&#xff0c;在不同环境和条件下&#xff0c;电话号码可能存在不同的拨打方式和号码格式。例如&#xff0c;在中国境内跨地区打电话&#xff0c;通常需要先输入“0”&#xff0c;再拨打…

网络分析工具—WireShark的安装及使用

Wireshark 是一个广泛使用的网络协议分析工具&#xff0c;常被网络管理员、开发人员和安全专家用来捕获和分析网络数据包。它支持多种网络协议&#xff0c;能够帮助用户深入理解网络流量、诊断网络问题以及进行安全分析。 Wireshark 的主要功能 数据包捕获与分析&#xff1a; …

优惠券平台(十七):实现用户查询/取消优惠券预约提醒功能

业务背景 当用户预约了一个或多个优惠券抢购提醒后&#xff0c;如果不再需要提醒&#xff0c;可以取消预约通知。不过&#xff0c;虽然用户可以取消提醒&#xff0c;但已经发送到 MQ 的消息不会被撤回&#xff0c;消费者在时间点到达时依然会收到消息。此时&#xff0c;我们不…

10vue3实战-----实现登录的基本功能

10vue3实战-----实现登录的基本功能 1.基本页面的搭建2.账号登录的验证规则配置3.点击登录按钮4.表单的校验5.账号的登录逻辑和登录状态保存6.定义IAccount对象类型 1.基本页面的搭建 大概需要搭建成这样子的页面: 具体的搭建界面就不多讲。各个项目都有自己的登录界面&#…

盘姬工具箱:完全免费的电脑工具箱

今天给大家介绍一个非常好用的系统工具箱&#xff0c;里面内含100多个工具&#xff0c;完全免费使用&#xff0c;而且没有广告&#xff0c;非常的棒。 盘姬工具箱&#xff1a;完全免费的电脑工具箱 盘姬工具箱是一款完全免费的电脑工具箱&#xff0c;功能丰富且实用。软件下载并…

国产编辑器EverEdit - 编辑辅助功能介绍

1 编辑辅助功能 1.1 各编辑辅助选项说明 1.1.1 行号 打开该选项时&#xff0c;在编辑器主窗口左侧显示行号&#xff0c;如下图所示&#xff1a; 1.1.2 文档地图 打开该选项时&#xff0c;在编辑器主窗口右侧靠近垂直滚动条的地方显示代码的缩略图&#xff0c;如下图所示&…

【Golang学习之旅】Go + MySQL 数据库操作详解

文章目录 前言1. GORM简介2. 安装GORM并连接MySQL2.1 安装GORM和MySQL驱动2.2 连接MySQL 3. GORM数据模型&#xff08;Model&#xff09;3.1 定义User结构体3.2 自动迁移&#xff08;AutoMigrate&#xff09; 4. GORM CRUD 操作4.1 插入数据&#xff08;Create&#xff09;4.2 …

DeepSeek Janus Pro 论文解析

目录 介绍 统一的多模态理解与生成 图像理解任务 图像生成任务 统一模型的好处 Janus 和 Janus Pro 架构 Janus Pro主要设计原理 Janus Pro 图像编码器 LLM 处理和输出 Rectified Flow Janus Pro 训练流程 第一阶段——适应 第二阶段——统一预训练 第三阶段——监…

《Java核心技术 卷II》本地化的数字格式

数字格式 数字和货币的格式高度依赖locale。 格式化对象的集合&#xff0c;可以对java.text包中的数字进行格式化和解析。 格式化数字值 对特定locale的数字进行格式化的步骤&#xff1a; 得到Locale对象使用工厂方法得到一个格式器对象。使用这个格式器对象来完成格式化解析工…

四模型消融实验!DCS-CNN-BiLSTM-Attention系列四模型多变量时序预测

四模型消融实验&#xff01;DCS-CNN-BiLSTM-Attention系列四模型多变量时序预测 目录 四模型消融实验&#xff01;DCS-CNN-BiLSTM-Attention系列四模型多变量时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 基于DCS-CNN-BiLSTM-Attention、CNN-BiLSTM-Attention…

51单片机之引脚图(详解)

8051单片机引脚分类与功能笔记 1. 电源引脚 VCC&#xff08;第40脚&#xff09;&#xff1a;接入5V电源&#xff0c;为单片机提供工作电压。GND&#xff08;第20脚&#xff09;&#xff1a;接地端&#xff0c;确保电路的电位参考点。 2.时钟引脚 XTAL1&#xff08;第19脚&a…

基于Flask的历史空难数据可视化分析系统的设计与实现

【FLask】基于Flask的历史空难数据可视化分析系统的设计与实现&#xff08;完整系统源码开发笔记详细部署教程&#xff09;✅ 目录 一、项目简介二、项目界面展示三、项目视频展示 一、项目简介 该系统采用Python语言及Flask框架开发&#xff0c;结合Echarts进行数据可视化&am…

neo4j-解决导入数据后出现:Database ‘xxxx‘ is unavailable. Run :sysinfo for more info.

目录 问题描述 解决方法 重新导入 问题描述 最近在linux上部署了neo4j&#xff0c;参照之前写的博客:neo4j-数据的导出和导入_neo4j数据导入导出-CSDN博客 进行了数据导出、导入操作。但是在进行导入后&#xff0c;重新登录网页版neo4j&#xff0c;发现对应的数据库状态变…

DeepSeek-R1 论文解析

目录 介绍 LLM训练流程 介绍 DeepSeek-R1-Zero 模型 基于规则的强化学习 DeepSeek-R1-Zero 性能洞察 DeepSeek-R1-Zero 的自我进化过程 “顿悟时刻”现象 DeepSeek-R1 模型的训练过程 为什么需要DeepSeek-R1? DeepSeek-R1 的训练流程 DeepSeek-R1 的出色成果 介绍 …

【计组】实验五 J型指令设计实验

目录 一、实验目的 二、实验环境 三、实验原理 四、实验任务 代码 一、实验目的 1. 理解MIPS处理器指令格式及功能。 2. 掌握lw, sw, beq, bne, lui, j, jal指令格式与功能。 3. 掌握ModelSim和ISE\Vivado工具软件。 4. 掌握基本的测试代码编写和FPGA开发板使用方法。 …

【AI】在Ubuntu中使用docker对DeepSeek的部署与使用

这篇文章前言是我基于部署好的deepseek-r1:8b模型跑出来的 关于部署DeepSeek的前言与介绍 在当今快速发展的技术环境中&#xff0c;有效地利用机器学习工具来解决问题变得越来越重要。今天&#xff0c;我将引入一个名为DeepSeek 的工具&#xff0c;它作为一种强大的搜索引擎&a…

Web自动化测试:如何生成高质量的测试报告

运行了所有测试用例&#xff0c;控制台输入的结果&#xff0c;如果很多测试用例那也不能够清晰快速的知道多少用例通过率以及错误情况。 web自动化测试实战之批量执行测试用例场景: 运行 AllTest.py 文件后得到的测试结果不够专业&#xff0c;无法直观的分析测试结果,我们能否…