2023-4-6-C++11、C++14、C++17、C++20版本新特性系统全面的学习!(四)

简介: 2023-4-6-C++11、C++14、C++17、C++20版本新特性系统全面的学习!

例如:

#include <optional>
#include <iostream>
int main()
{
    std::optional<int> opt;
    int i = opt.value_or(42); // 使用提供的默认值获取 optional 中的值
    std::cout << "The value of opt is: " << i << '\n';
    opt.reset(); // 将 optional 设置为空
    if (!opt) { // 使用 bool 转换函数判断 optional 是否为空
        std::cout << "opt does not have a value\n";
    }
    return 0;
}

上面的示例展示了通过 value_or() 函数获取默认值,以及使用 reset() 函数将 optional 对象设置为空的方法。

总之,std::optional 类型为 C++ 提供了一种安全、方便的方式来表示可能不存在值的情况,避免了在访问空指针时出现异常。由于 std::optional 实现了一些便捷的函数,因此使用起来非常方便。

std::any

是的,我可以为您介绍一下 C++17 新特性中新增的 std::any 类型及其使用方法。

std::any 是一个用于存储任意类型对象的类型,类似于 Python 中的 object 或者 Java 中的 Object。当我们需要存储不同类型的数据时,可以使用 std::any,在编译期间不需要知道所存储的具体类型,而在运行时再根据实际情况进行类型转换。

下面是一个简单的示例,演示如何定义、初始化和访问 std::any:

#include <any>
#include <iostream>
#include <string>
int main()
{
    std::any a;
    if (!a.has_value()) { // 判断 any 是否包含值
        std::cout << "a does not have a value\n";
    }
    a = 42; // 将一个整数值赋给 any
    if (a.has_value()) {
        int i = std::any_cast<int>(a); // 使用 any_cast 获取 any 中的值
        std::cout << "The value of a is: " << i << '\n';
    }
    a = std::string("Hello, world!"); // 将一个字符串赋给 any
    if (a.has_value()) {
        std::string s = std::any_cast<std::string>(a);
        std::cout << "The value of a is: " << s << '\n';
    }
    return 0;
}

在上面的示例中,我们首先定义了一个 std::any 对象,并检查它是否包含值。由于该对象没有被初始化,因此不包含任何值。接着,我们将一个整数值和一个字符串分别赋给该对象,并使用 any_cast() 函数获取 std::any 中的值。

需要注意的是,当 std::any 对象未被初始化时,它并不包含任何值,此时访问其内容会抛出 std::bad_any_cast 异常。因此,在对 std::any 进行访问之前,必须先确保它已经包含值。

除了使用 has_value() 和 any_cast() 函数外,std::any 还提供了一些便捷的函数,如:

  • reset():将 std::any 设置为空
  • type():获取 std::any 中存储的类型信息

例如:

#include <any>
#include <iostream>
#include <typeinfo>
int main()
{
    std::any a = 3.1415926;
    std::cout << "The type of a is: " << a.type().name() << '\n'; // 输出 a 中存储的类型信息
    a.reset(); // 将 a 设置为空
    if (!a.has_value()) {
        std::cout << "a does not have a value\n";
    }
    return 0;
}

上面的示例展示了通过 type() 函数获取 std::any 中所存储的类型信息,以及通过 reset() 函数将 std::any 设置为空的方法。

总之,std::any 类型为 C++ 提供了一种安全、方便的方式来存储任意类型的数据,避免了在存储不同类型的数据时需要使用多个变量或指针的麻烦。由于 std::any 实现了一些便捷的函数,因此使用起来非常方便。

std::apply

在 C++ 中,我们经常需要对一个 std::tuple 对象进行打包或解包操作。例如,当我们调用某个函数时,可能需要将一个 std::tuple 作为参数传递给该函数。而在函数内部,则需要对这个 std::tuple 进行解包,以获取其中的元素并进行处理。在 C++17 中,可以通过 std::apply 函数来方便地对 std::tuple 进行解包操作。

下面是一个简单的示例,演示如何使用 std::apply 来对 std::tuple 进行解包:

#include <iostream>
#include <tuple>
void print(int x, float y, double z)
{
    std::cout << "x = " << x << ", y = " << y << ", z = " << z << '\n';
}
int main()
{
    std::tuple<int, float, double> tpl(42, 3.14f, 2.71828);
    std::apply(print, tpl); // 对 tpl 进行解包,并将解包后的参数传递给 print 函数
    return 0;
}

在上面的示例中,我们定义了一个 std::tuple 对象 tpl,并将其作为参数传递给了 std::apply 函数和 print 函数。在 std::apply 函数内部,它会将 std::tuple 中的元素进行解包,并将解包后的参数传递给指定的函数。由于 print 函数需要三个参数,因此 std::tuple 中的元素也必须是三个。

需要注意的是,在使用 std::apply 函数时,被调用的函数必须支持参数展开。例如,如果被调用的函数只接受一个 std::tuple 对象作为参数,则无法使用 std::apply 对其进行解包操作。

除了 std::tuple 对象之外,std::apply 还可以对任意类型的可调用对象进行参数展开。例如:

#include <iostream>
#include <functional>
int add(int x, int y)
{
    return x + y;
}
int main()
{
    std::function<int(int, int)> f = add; // 将函数转换为 std::function 对象
    std::tuple<int, int> tpl(1, 2);
    int sum = std::apply(f, tpl); // 对 tpl 进行解包,并将解包后的参数传递给 f 函数
    std::cout << "The sum is: " << sum << '\n';
    return 0;
}

在上面的示例中,我们将一个普通的函数 add 转换为 std::function 对象,并将其作为参数传递给了 std::apply 函数。std::apply 函数会对 std::tuple 进行解包,并将解包后的参数传递给 std::function 对象。

总之,std::apply 函数为 C++ 提供了一种便捷的方式来对 std::tuple 或者其他可调用对象进行参数展开操作。它使得我们能够更加灵活地处理函数的参数,同时避免了参数个数过多或过少的问题。

std::make_from_tuple

在 C++ 中,我们经常需要使用 std::tuple 来存储一组数据。有时候,我们需要将这些数据传递给一个函数或者构造函数,并以某种方式生成一个新的对象。在 C++17 中,可以通过 std::make_from_tuple 函数来方便地从 std::tuple 对象中生成任意类型的对象。

下面是一个简单的示例,演示如何使用 std::make_from_tuple 函数来生成一个 std::pair 对象:

#include <iostream>
#include <utility>
#include <tuple>
int main()
{
    std::tuple<int, double> tpl(42, 3.14);
    auto p = std::make_from_tuple<std::pair<int, double>>(tpl); // 使用 std::make_from_tuple 生成 std::pair 对象
    std::cout << "first = " << p.first << ", second = " << p.second << '\n';
    return 0;
}

需要注意的是,在使用 std::make_from_tuple 函数时,被创建的对象必须支持从 std::tuple 中提取出对应的元素进行构造。由于 std::make_from_tuple 的实现依赖于 std::apply 函数,因此该函数仅在 C++17 及以上版本的标准库中可用。

总之,std::make_from_tuple 函数为 C++ 提供了一种便捷的方式来从 std::tuple 对象中生成任意类型的对象。它使得我们能够更加灵活地处理函数的参数,并避免了手动提取 std::tuple 元素的麻烦。

std::string_view

请跳转我的另一篇文章学习2023-2-19-什么是string_view

file_system

文件系统库 file_system 为 C++ 提供了一组用于访问计算机文件系统的工具函数和类。它包含了许多常见的文件操作功能,例如获取文件大小、读取目录内容、创建目录等。在 C++17 中,可以通过 #include 头文件来包含该库。

下面是一个简单的示例,演示如何使用 file_system 库来列出指定目录下的所有文件和子目录:

#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main()
{
    fs::path current_path = fs::current_path(); // 获取当前路径
    for (auto& entry : fs::directory_iterator(current_path)) { // 遍历当前路径下的所有文件和子目录
        if (entry.is_directory()) {
            std::cout << "[dir] ";
        } else {
            std::cout << "[file]";
        }
        std::cout << " " << entry.path() << '\n';
    }
    return 0;
}

在上面的示例中,我们使用 fs::path 类型的 current_path 对象来表示当前路径,并通过 fs::directory_iterator 类型的对象遍历该目录下的所有文件和子目录。对于每个文件或子目录,我们检查其类型并打印出相应的信息。

除了遍历目录之外,file_system 库还提供了许多其他实用的工具函数和类。例如,可以使用 fs::file_size 函数来获取文件的大小,使用 fs::create_directory 函数来创建新目录,使用 fs::exists 函数来检查文件或目录是否存在等等。

总之,文件系统库 file_system 提供了一组用于访问计算机文件系统的工具函数和类,可以帮助我们更加方便地进行文件操作。它是 C++17 的一个重要新增功能,在现代 C++ 开发中非常实用。

std::shared_mutex

std::shared_mutex 是一个读写锁(也称为共享-排他锁),允许多个线程同时读取某个共享资源,但只允许一个线程写入该资源。在 C++11 中,我们已经有了 std::mutex 和 std::lock_guard 等工具来实现互斥锁,但是它们都是排他锁,不能同时支持读取和写入操作。而在 C++17 中,我们可以使用 std::shared_mutex 类来实现读写锁。

下面是一个简单的示例,演示如何在多个线程中使用 std::shared_mutex 对共享变量进行读写操作:

#include <iostream>
#include <thread>
#include <mutex>
std::shared_mutex g_mutex;
int g_value = 0;
void read_value()
{
    std::shared_lock<std::shared_mutex> lock(g_mutex); // 获取读取锁
    std::cout << "The value is " << g_value << '\n';
}
void write_value()
{
    std::unique_lock<std::shared_mutex> lock(g_mutex); // 获取写入锁
    ++g_value;
}
int main()
{
    std::thread t1(read_value);
    std::thread t2(read_value);
    std::thread t3(write_value);
    t1.join();
    t2.join();
    t3.join();
    return 0;
}

在上面的示例中,我们定义了一个全局变量 g_value,并通过 std::shared_mutex 类型的 g_mutex 对其进行读写操作。在 read_value 函数中,我们通过 std::shared_lockstd::shared_mutex 获取读取锁,允许多个线程同时读取 g_value 变量的值。在 write_value 函数中,我们通过 std::unique_lockstd::shared_mutex 获取写入锁,保证只有一个线程可以对 g_value 变量进行写入操作。

需要注意的是,std::shared_mutex 类型的对象不能直接使用 std::lock_guard 进行加锁,而是需要使用 std::unique_lock 或 std::shared_lock 来获取锁。std::unique_lock 表示独占的锁,只允许一个线程获取,用于写入操作;std::shared_lock 表示共享的锁,允许多个线程同时获取,用于读取操作。

总之,std::shared_mutex 是 C++17 中新增的一个读写锁,用于实现多个线程对共享资源的读写操作。它是 C++ 并发编程中非常实用的工具,可以提高程序的并发性能和可靠性。


🎂五、C++20新特性

concept

C++20 引入了 Concept(概念)作为一种新的语言特性。Concept 主要用于约束模板参数,以帮助程序员定义和描述对类型的要求。接下来,我将为你提供一个简单的示例来说明 Concept 的用法。

我们假设有一个名为 Addable 的 Concept,该概念对可加性进行约束,即它只能应用于那些支持加法操作的类型。以下是一个示例代码:

#include <iostream>
#include <concepts>
template<typename T>
concept Addable = requires(T a, T b) {
    { a + b } -> std::same_as<T>;
};
template<Addable T>
T add(T a, T b) {
    return a + b;
}
int main() {
    std::cout << add(3, 4) << std::endl; // 输出:7
    // 编译错误!字符串类型不符合 Addable 概念的要求
    // std::cout << add("Hello", "World") << std::endl;
    return 0;
}

在上述示例中,我们首先定义了一个 Concept Addable,它通过使用 requires 关键字来定义对类型 T 的要求。这里要求 T 类型支持加法运算,并且返回的结果类型与 T 相同。

然后,我们编写了一个模板函数 add,其模板参数 T 受限于 Addable 概念。这意味着该函数只能接受满足 Addable 概念要求的类型作为参数。

最后,在 main 函数中,我们演示了如何使用 add 函数来进行加法运算。我们成功地将整数 3 和 4 相加,并输出结果 7。然而,如果我们试图使用字符串调用 add 函数,编译过程将会失败,因为字符串类型不符合 Addable 概念的要求。

这个示例展示了 Concept 的基本用法,它可以帮助程序员对模板参数进行约束,提供更好的类型安全性和代码可读性。

requires

C++20 引入了 requires 关键字作为一种新的语言特性。requires 关键字主要用于在概念(Concept)中对类型或表达式进行要求约束。接下来,我将为您提供一个示例代码来说明 requires 的用法。

假设我们有一个 Printable 概念,它要求类型 T 必须具有一个名为 print 的成员函数,该函数将对象输出到标准输出。以下是一个示例代码:

#include <iostream>
#include <concepts>
template<typename T>
concept Printable = requires(T a) {
    { a.print() } -> std::same_as<void>;
};
struct MyType {
    void print() {
        std::cout << "Printing MyType" << std::endl;
    }
};
struct AnotherType {};
template<Printable T>
void printObject(T obj) {
    obj.print();
}
int main() {
    MyType myObj;
    printObject(myObj); // 输出:Printing MyType
    AnotherType anotherObj;
    // 编译错误!AnotherType 类型不符合 Printable 概念的要求
    // printObject(anotherObj);
    return 0;
}

在上述示例中,我们首先定义了一个 Concept Printable,它使用 requires 关键字来定义对类型 T 的要求。这里要求类型 T 必须有一个名为 print 的成员函数,并且返回类型是 void

然后,我们创建了一个名为 MyType 的结构体,它满足 Printable 概念的要求。该结构体拥有一个名为 print 的成员函数,它将字符串输出到标准输出。

接下来,我们定义了一个模板函数 printObject,其模板参数 T 受限于 Printable 概念。这意味着该函数只能接受满足 Printable 概念要求的类型作为参数,并调用其 print 成员函数进行输出操作。

最后,在 main 函数中,我们演示了如何使用 printObject 函数来输出对象。我们成功地将 MyType 类型的对象传递给 printObject 函数,并在控制台上输出了相应的信息。然而,如果我们尝试使用 AnotherType 类型的对象调用 printObject 函数,编译过程将会失败,因为 AnotherType 类型不符合 Printable 概念的要求。

这个示例展示了 requires 关键字的基本用法,它可以帮助程序员对概念进行约束和检查,以提高代码的可读性和可靠性。

constinit

C++20 引入了 constinit 关键字作为一种新的语言特性。constinit 主要用于声明具有静态存储期并且仅在编译时初始化的变量。接下来,我将为您提供一个示例代码来说明 constinit 的用法。

假设我们有一个名为 Vector 的结构体,它表示一个二维向量,并且我们想要确保该结构体的默认构造函数在编译时进行常量初始化。以下是一个示例代码:

#include <iostream>
struct Vector {
    double x;
    double y;
    constinit Vector() : x(0.0), y(0.0) {}
};
int main() {
    constinit Vector v; // 使用 constinit 声明需要编译时常量初始化的变量
    std::cout << "x: " << v.x << ", y: " << v.y << std::endl; // 输出:x: 0, y: 0
    return 0;
}

在上述示例中,我们定义了一个名为 Vector 的结构体,它具有两个成员变量 xy,分别表示二维向量的坐标。

在默认构造函数中,我们使用 constinit 关键字对 Vector 类型的对象进行了修饰。这意味着该对象必须在编译时进行常量初始化,并且不能在运行时修改。

main 函数中,我们创建了一个名为 vVector 类型的对象,并在控制台上输出了它的坐标值。由于我们使用了 constinit 关键字,编译器会在编译时对 v 进行常量初始化,并保证其在运行时不会被修改。

这个示例展示了 constinit 关键字的基本用法,它可以帮助程序员声明需要在编译时进行常量初始化的变量,并提供更好的性能和代码安全性。请注意,constinit 关键字在 C++20 中仍属于实验性特性,因此在使用时需谨慎考虑兼容性和标准遵循性。

consteval

C++20 引入了 consteval 关键字作为一种新的语言特性。consteval 主要用于声明一个只能在编译时求值的函数,它必须产生一个常量表达式结果。接下来,我将为您提供一个示例代码来说明 consteval 的用法。

假设我们想要实现一个用于计算斐波那契数列的函数,并且希望该函数在编译时就能够被求值。以下是一个示例代码:

#include <iostream>
consteval int fibonacci(int n) {
    if (n <= 1)
        return n;
    else
        return fibonacci(n - 1) + fibonacci(n - 2);
}
int main() {
    constexpr int result = fibonacci(5); // 使用 consteval 声明需要在编译时求值的函数
    std::cout << "Fibonacci(5): " << result << std::endl; // 输出:Fibonacci(5): 5
    return 0;
}

在上述示例中,我们定义了一个名为 fibonacci 的函数,它使用 consteval 关键字进行修饰。这意味着该函数只能在编译时求值,并且返回的结果必须是一个常量表达式。

在函数体内部,我们使用递归的方式计算斐波那契数列的第 n 个数,并返回结果。

main 函数中,我们使用 constexpr 关键字声明了一个常量 result,并调用 fibonacci 函数来计算斐波那契数列的第 5 个数。由于我们使用了 consteval 关键字,编译器会在编译时对函数进行求值,并将结果作为常量存储在 result 中。

最后,在控制台上输出了计算得到的斐波那契数列的结果。

这个示例展示了 consteval 关键字的基本用法,它可以帮助程序员声明只能在编译时求值的函数,并在需要进行编译时计算的场景中提供更好的性能和代码安全性。请注意,在使用 consteval 关键字时需要确保函数的实现是可以被编译器求值的常量表达式。

co_await

C++20 引入了 co_await 关键字作为一种新的语言特性。co_await 主要用于协程(coroutine)的实现,用于暂停当前协程并在稍后继续执行。接下来,我将为您提供一个示例代码来说明 co_await 的用法。

假设我们有一个异步任务,需要在某个时间点后返回结果。以下是一个简单的示例代码:

#include <iostream>
#include <chrono>
#include <experimental/coroutine>
struct AsyncTask {
    struct promise_type {
        int result;
        auto get_return_object() {
            return AsyncTask{std::experimental::coroutine_handle<promise_type>::from_promise(*this)};
        }
        auto initial_suspend() {
            return std::experimental::suspend_always{};
        }
        auto final_suspend() {
            return std::experimental::suspend_always{};
        }
        void return_value(int value) {
            result = value;
        }
        void unhandled_exception() {
            std::terminate();
        }
    };
    std::experimental::coroutine_handle<promise_type> handle;
    AsyncTask(std::experimental::coroutine_handle<promise_type> h) : handle(h) {}
    ~AsyncTask() {
        if (handle)
            handle.destroy();
    }
    bool await_ready() const {
        return false;
    }
    void await_suspend(std::experimental::coroutine_handle<>) const {}
    int await_resume() const {
        return handle.promise().result;
    }
};
AsyncTask performAsyncTask() {
    co_await std::chrono::seconds(3); // 模拟一个长时间运行的异步操作
    co_return 42; // 返回异步任务的结果
}
int main() {
    auto task = performAsyncTask();
    std::cout << "Performing async task..." << std::endl;
    int result = task.await_resume(); // 获取异步任务的结果
    std::cout << "Async task result: " << result << std::endl;
    return 0;
}

在上述示例中,我们首先定义了一个 AsyncTask 结构体,它代表一个异步任务。在该结构体中,我们实现了一个嵌套的 promise_type,用于管理异步任务的状态和结果。

get_return_object() 函数负责创建一个 AsyncTask 对象,并将其与协程句柄关联起来。

initial_suspend()final_suspend() 函数分别指定异步任务的初始暂停点和最终暂停点。

return_value() 函数用于设置异步任务的结果。

unhandled_exception() 函数处理异常情况。

接下来,我们实现了 performAsyncTask() 函数,该函数使用 co_await 关键字来暂停当前协程,并模拟一个耗时的异步操作。在等待时间过后,我们使用 co_return 关键字返回异步任务的结果。

main 函数中,我们创建了一个 task 对象,并调用 await_resume() 函数来获取异步任务的结果。

最后,在控制台上输出了异步任务的结果。

这个示例展示了 co_await 关键字的基本用法,它可以帮助程序员实现协程,并支持异步操作的简洁编写和灵活控制。请注意,协程相关的特性仍然处于实验性阶段,并且需要使用适当的编译器和标准库支持。

co_return

C++20 引入了 co_return 关键字作为一种新的语言特性。co_return 主要用于在协程中返回结果并终止协程的执行。接下来,我将为您提供一个示例代码来说明 co_return 的用法。

假设我们有一个异步任务,需要在某个时间点后返回结果。以下是一个简单的示例代码:

#include <iostream>
#include <chrono>
#include <experimental/coroutine>
struct AsyncTask {
    struct promise_type {
        int result;
        auto get_return_object() {
            return AsyncTask{std::experimental::coroutine_handle<promise_type>::from_promise(*this)};
        }
        auto initial_suspend() {
            return std::experimental::suspend_always{};
        }
        auto final_suspend() {
            return std::experimental::suspend_always{};
        }
        void return_value(int value) {
            result = value;
        }
        void unhandled_exception() {
            std::terminate();
        }
    };
    std::experimental::coroutine_handle<promise_type> handle;
    AsyncTask(std::experimental::coroutine_handle<promise_type> h) : handle(h) {}
    ~AsyncTask() {
        if (handle)
            handle.destroy();
    }
    bool await_ready() const {
        return false;
    }
    void await_suspend(std::experimental::coroutine_handle<>) const {}
    int await_resume() const {
        return handle.promise().result;
    }
};
AsyncTask performAsyncTask() {
    co_await std::chrono::seconds(3); // 模拟一个长时间运行的异步操作
    co_return 42; // 返回异步任务的结果并终止协程
}
int main() {
    auto task = performAsyncTask();
    std::cout << "Performing async task..." << std::endl;
    int result = task.await_resume(); // 获取异步任务的结果
    std::cout << "Async task result: " << result << std::endl;
    return 0;
}

在上述示例中,我们定义了一个 AsyncTask 结构体,它代表一个异步任务。在该结构体中,我们实现了一个嵌套的 promise_type,用于管理异步任务的状态和结果。

get_return_object() 函数负责创建一个 AsyncTask 对象,并将其与协程句柄关联起来。

initial_suspend()final_suspend() 函数分别指定异步任务的初始暂停点和最终暂停点。

return_value() 函数用于设置异步任务的结果。

unhandled_exception() 函数处理异常情况。

接下来,我们实现了 performAsyncTask() 函数,该函数使用 co_await 关键字来暂停当前协程,并模拟一个耗时的异步操作。在等待时间过后,我们使用 co_return 关键字返回异步任务的结果并终止协程的执行。

main 函数中,我们创建了一个 task 对象,并调用 await_resume() 函数来获取异步任务的结果。

最后,在控制台上输出了异步任务的结果。

这个示例展示了 co_return 关键字的基本用法,它可以帮助程序员实现协程,并支持异步操作的简洁编写和灵活控制。请注意,协程相关的特性仍然处于实验性阶段,并且需要使用适当的编译器和标准库支持。

co_yield

C++20 引入了 co_yield 关键字作为一种新的语言特性。co_yield 主要用于在协程中暂停执行并将值产生给调用方,然后再次恢复协程的执行。接下来,我将为您提供一个示例代码来说明 co_yield 的用法。

假设我们有一个异步任务,需要以递增顺序生成一系列数字,并将每个数字传递给调用方。以下是一个简单的示例代码:

#include <iostream>
#include <experimental/coroutine>
struct AsyncGenerator {
    struct promise_type {
        int current;
        auto get_return_object() {
            return AsyncGenerator{std::experimental::coroutine_handle<promise_type>::from_promise(*this)};
        }
        auto initial_suspend() {
            return std::experimental::suspend_always{};
        }
        auto final_suspend() {
            return std::experimental::suspend_always{};
        }
        void return_void() {}
        void unhandled_exception() {
            std::terminate();
        }
        auto yield_value(int value) {
            current = value;
            return std::experimental::suspend_always{};
        }
    };
    std::experimental::coroutine_handle<promise_type> handle;
    AsyncGenerator(std::experimental::coroutine_handle<promise_type> h) : handle(h) {}
    ~AsyncGenerator() {
        if (handle)
            handle.destroy();
    }
    bool move_next() {
        if (!handle.done()) {
            handle.resume();
            return true;
        }
        return false;
    }
    int current_value() const {
        return handle.promise().current;
    }
};
AsyncGenerator generateNumbers(int start, int end) {
    for (int i = start; i <= end; ++i) {
        co_yield i; // 暂停协程并将当前值产生给调用方
    }
}
int main() {
    auto generator = generateNumbers(1, 5);
    while (generator.move_next()) {
        std::cout << "Current value: " << generator.current_value() << std::endl;
    }
    return 0;
}

在上述示例中,我们首先定义了一个 AsyncGenerator 结构体,它代表一个异步生成器。在该结构体中,我们实现了一个嵌套的 promise_type,用于管理异步生成器的状态和值。

get_return_object() 函数负责创建一个 AsyncGenerator 对象,并将其与协程句柄关联起来。

initial_suspend()final_suspend() 函数分别指定异步生成器的初始暂停点和最终暂停点。

return_void() 函数用于处理 void 类型的返回值。

unhandled_exception() 函数处理异常情况。

yield_value() 函数接收一个值,并将该值设为当前值,然后暂停协程并将当前值产生给调用方。

接下来,我们实现了 generateNumbers() 函数,该函数使用 co_yield 关键字在循环中暂停协程,并以递增顺序产生数字。

main 函数中,我们创建了一个 generator 对象,并使用 move_next() 函数在每次迭代中继续协程的执行。通过 current_value() 函数获取当前生成器产生的值,并在控制台上输出。

最后,我们按顺序输出了生成器产生的数字。

这个示例展示了 co_yield 关键字的基本用法,它可以帮助程序员实现协程,并支持生成器模式的简洁编写和灵活控制。请注意,协程相关的特性仍然处于实验性阶段,并且需要使用适当的编译器和标准库支持。

char8_t

C++20 引入了 char8_t 类型作为一种新的字符类型,用于表示 UTF-8 编码的字符。以下是一个简单的示例代码来说明 char8_t 的用法:

#include <iostream>
int main() {
    const char8_t* utf8Str = u8"Hello, 世界!"; // 使用 char8_t 类型的字符串
    std::cout << "UTF-8 String: " << reinterpret_cast<const char*>(utf8Str) << std::endl;
    return 0;
}

在上述示例中,我们声明一个变量 utf8Str,它是一个指向 const char8_t 类型的字符串的指针。我们使用 u8 前缀将字符串字面量标记为 char8_t 类型。

通过 reinterpret_cast,我们将 utf8Str 转换为 const char* 类型,并通过 std::cout 输出到控制台。

请注意,在 C++20 中,char8_t 类型主要用于 UTF-8 字符串的处理和操作。与传统的 charwchar_t 不同,char8_t 类型只能用于存储和处理 UTF-8 编码的字符。

这个示例展示了如何使用 char8_t 类型来处理 UTF-8 编码的字符串。通过 char8_t 类型,我们可以更加准确地表示和操作 UTF-8 字符,以适应全球化和国际化的需求。

import

在C++20中,import是一项新的语言特性,它允许我们直接从模块导入定义,而不需要使用传统的头文件包含方式。这可以提供更清晰、更可靠的代码组织和模块化。

下面是一个简单的示例,说明如何使用import来导入一个模块并使用其中的定义:

// math_module.cppm
export module math_module;
export int add(int a, int b) {
    return a + b;
}

上面的代码定义了一个名为math_module的模块,并导出了一个add函数。

现在,我们可以创建另一个源文件来导入该模块并使用其中的定义:

// main.cpp
import math_module;
int main() {
    int result = add(5, 3);
    return 0;
}

在上面的示例中,我们使用import语句将math_module模块导入到main.cpp中。然后,我们可以直接使用add函数来执行加法运算。

需要注意的是,为了支持模块导入,编译器需要以模块文件(通常使用.cppm扩展名)的形式编译模块定义。因此,在上述示例中,math_module.cppm文件应该被编译为模块。

这只是import特性的基本用法示例,还有其他高级用法,例如命名空间导入和重命名导入等。然而,由于C++20的标准实现在目前尚未完全普及,这些高级用法可能在不同的编译器之间存在差异。请查阅你所使用的编译器的文档以获取更多详细信息。

module

在C++20中,module是一项新的语言特性,它用于定义和导出模块,以实现更好的代码组织和模块化。下面是一个简单的示例,说明如何使用module来定义和使用模块:

// math_module.cppm
export module math_module;
export int add(int a, int b) {
    return a + b;
}

上述代码创建了一个名为math_module的模块,并导出了一个add函数。

然后,我们可以创建另一个源文件来导入该模块并使用其中的定义:

// main.cpp
import math_module;
int main() {
    int result = add(5, 3);
    return 0;
}

在上面的示例中,我们使用import语句将math_module模块导入到main.cpp中。然后,我们可以直接使用add函数来执行加法运算。

需要注意的是,为了支持模块化,编译器需要将模块文件(通常使用.cppm扩展名)作为模块进行编译。因此,在上述示例中,math_module.cppm文件应该被编译为模块。

通过使用模块,我们可以避免传统头文件的包含方式,提供更清晰、更可靠的代码组织,并提升编译速度和可维护性。同时,模块也支持命名空间、重命名导入等高级用法,以满足更复杂的需求。

需要注意的是,由于C++20的标准实现在目前尚未完全普及,编译器对模块的支持可能存在差异。要使用模块特性,请确保你所使用的编译器已经支持C++20标准中的模块功能,并查阅相关文档以获取更多详细信息。

Lambda 表达式的更新

常量表达式(constexpr) 的更新

原子(Atomic)智能指针

常量表达式(constexpr) 的更新

自动合流(Joining), 可中断(Cancellable) 的线程

C++20 同步(Synchronization)库

std::atomic_ref

指定初始化(Designated Initializers)

航天飞机操作符 <=>

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

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

[[likely]], [[unlikely]]

日历(Calendar)和时区(Timezone)功能

std::span

特性测试宏

consteval 函数

constinit

用 using 引用 enum 类型

格式化库(std::format)

增加数学常量

std::source_location

[[nodiscard(reason)]]

位运算


🍳参考文献

  1. https://zhuanlan.zhihu.com/p/139515439
  2. https://blog.csdn.net/qq_45254369/article/details/125491031
  3. https://zhuanlan.zhihu.com/p/165389083
目录
相关文章
|
2月前
|
Linux 编译器 测试技术
【C++】CentOS环境搭建-快速升级G++版本
通过上述任一方法,您都可以在CentOS环境中高效地升级G++至所需的最新版本,进而利用C++的新特性,提升开发效率和代码质量。
171 64
|
2月前
|
编译器 程序员 定位技术
C++ 20新特性之Concepts
在C++ 20之前,我们在编写泛型代码时,模板参数的约束往往通过复杂的SFINAE(Substitution Failure Is Not An Error)策略或繁琐的Traits类来实现。这不仅难以阅读,也非常容易出错,导致很多程序员在提及泛型编程时,总是心有余悸、脊背发凉。 在没有引入Concepts之前,我们只能依靠经验和技巧来解读编译器给出的错误信息,很容易陷入“类型迷路”。这就好比在没有GPS导航的年代,我们依靠复杂的地图和模糊的方向指示去一个陌生的地点,很容易迷路。而Concepts的引入,就像是给C++的模板系统安装了一个GPS导航仪
123 59
|
2月前
|
Linux 编译器 测试技术
【C++】CentOS环境搭建-快速升级G++版本
通过上述任一方法,您都可以在CentOS环境中高效地升级G++至所需的最新版本,进而利用C++的新特性,提升开发效率和代码质量。
213 63
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(三)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
存储 编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(二)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
编译器 C++
【C++】面向对象编程的三大特性:深入解析多态机制(一)
【C++】面向对象编程的三大特性:深入解析多态机制
|
2月前
|
C++
C++ 20新特性之结构化绑定
在C++ 20出现之前,当我们需要访问一个结构体或类的多个成员时,通常使用.或->操作符。对于复杂的数据结构,这种访问方式往往会显得冗长,也难以理解。C++ 20中引入的结构化绑定允许我们直接从一个聚合类型(比如:tuple、struct、class等)中提取出多个成员,并为它们分别命名。这一特性大大简化了对复杂数据结构的访问方式,使代码更加清晰、易读。
41 0
|
13天前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
25 2
|
19天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
53 5
|
25天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
56 4