跨越十年的C++演进系列,分为5篇,本文为第四篇,后续会持续更新C++23~

前3篇如下:

跨越十年的C++演进:C++11新特性全解析

跨越十年的C++演进:C++14新特性全解析

跨越十年的C++演进:C++17新特性全解析

C++20标准是C++语言的第四个正式标准,于2020年12月正式发布。

首先先上C++20特性思维导图:

接下来将从关键字、语法、宏、属性、弃用这5个类目来讲解~

1、关键字

1.1、concept

编译器版本:GCC 10

concept 是 C++20 引入的重要特性之一,尤其适用于模板库的设计与开发。其功能类似于 C# 中的泛型约束,但相比而言更为灵活和强大。

concept 允许我们为模板参数定义具有特定条件的类型约束。

示例:数值类型的约束

#include <type_traits>
// 定义一个名为 number 的 concept,用于约束模板参数 T 必须是算术类型
template<typename T>
concept number = std::is_arithmetic_v<T>;
// 使用该 concept 来限制函数模板的参数类型
template<number T>
void func(T t)
{ }
// 调用示例
func<int>(10);        // 合法,int 属于算术类型
func<double>(20.0);   // 合法,double 属于算术类型
struct A { };
func<A>(A());         // 非法,A 不是算术类型,编译失败

1.2、requires

编译器版本:GCC 10

仅使用 concept 还不足以完全发挥其潜力,真正使其强大的是 requires 表达式。通过结合 concept 和 requires,可以对模板参数进行更细粒度的限制,包括检查成员变量、成员函数及其返回值等。

示例:约束类型必须具备某些成员函数或行为

#include <type_traits>
template<typename T>
concept can_run = requires(T t)
{std::is_class_v<T>;                     // 类型 T 必须是一个类或结构体t();                                    // 必须支持无参调用(括号运算符重载)t.run();                                // 必须包含 run 成员函数std::is_same_v<decltype(t.run()), int>; // run 函数返回类型必须为 int
};
// 使用该 concept 的函数模板
template<can_run T>
int func(T t)
{t();return t.run(); // 可以直接返回 run() 的结果,因为其返回类型已被限定为 int
}
func<int>(10); // 错误,int 不是 class 或 struct
struct A {void run() { }
};
func<A>(A()); // 错误,缺少括号运算符重载
struct B {void operator()() { }
};
func<B>(B()); // 错误,缺少 run 函数
struct C {void operator()() { }void run() { }
};
func<C>(C()); // 错误,run 返回类型不是 int
struct D {int operator()() { }int run() { return 0; }
};
func<D>(D()); // 正确,满足所有条件

1.3、typename

编译器版本:GCC 9

typename 在模板中主要有两个用途:一是作为模板参数声明;二是明确告诉编译器某个嵌套名称是一个类型名。在早期版本中,为了避免歧义,需要频繁使用 typename 来辅助编译器解析。而新版本增强了类型推导能力,使得部分场景下可以省略 typename。

例如,在只能推导出类型的上下文中,可不加 typename。

示例:

// 函数返回类型位于全局作用域,只可能是类型,因此无需 typename
template<class T> T::R f(); // 合法
// 作为函数参数时则需要显式指定为类型
template<class T> void f(T::R); // 非法,无法推断为类型
template<typename T>
struct PtrTraits {using Ptr = T*;
};
template<class T>
struct S {using Ptr = PtrTraits<T>::Ptr; // 合法,在 defining-type-id 上下文中T::R f(T::P p) {return static_cast<T::R>(p); // 合法,在函数体内也可识别为类型}auto g() -> S<T*>::Ptr; // 合法,尾随返回类型
};
template<typename T>
void f() {void (*pf)(T::X);     // 合法,pf 是指向函数的指针void g(T::X);         // 非法,T::X 无法被解释为类型
}

1.4、explicit

编译器版本:GCC 9

新增于 C++11 版本,具体可参考 C++11 新特性相关内容。

C++20 中扩展了 explicit,允许传入一个布尔值来控制是否启用显式构造行为。

示例:

struct A {explicit(false) A(int) { } // 允许隐式转换
};
struct B {explicit(true) B(int) { } // 禁止隐式转换
};
A a = 10; // 合法,允许隐式构造
B b = 10; // 非法,禁止隐式构造

1.5、constexpr

编译器版本:GCC 9

该特性最初在 C++11 中引入,如需详细了解可参考 C++11 的新特性。

① 在 C++20 中,constexpr 的使用范围得到了进一步扩展,新增了对虚函数的支持。其用法与普通函数一致,无需额外说明。

② constexpr 函数中不再允许使用 try-catch 语句块。此限制也适用于构造函数和析构函数中的异常处理逻辑。

1.6、char8_t

编译器版本:GCC 9

char8_t 是为 UTF-8 编码专门设计的新类型。今后,UTF-8 字符字面量将由 char8_t 类型接收,而不再是 char 类型。

目前 GCC 编译器对该特性的支持尚未完全实现,相关内容仍在持续完善中。


1.7、consteval

编译器版本:GCC 11

consteval 关键字用于定义“立即函数”,这类函数必须在编译期执行完毕,并返回一个编译期常量结果。函数参数也必须是能够在编译期确定的值,且函数内部的所有运算都必须能在编译期完成。

相较于 constexpr,consteval 对函数的限制更加严格。constexpr 函数会根据传入参数是否为常量表达式自动决定是在编译期还是运行期执行;而 consteval 函数则强制要求所有调用都必须发生在编译期。

示例代码:

#include <iostream>
constexpr int f(int a)
{return a * a;
}
// 参数a必须是编译期常量
consteval int func(int a)
{return f(a); // 合法,因为f()可以在编译期计算
}
int main()
{int a;std::cin >> a;int r1 = f(a);             // 合法,a是运行期变量,f将在运行期执行int r2 = func(a);          // 错误,a不是编译期常量int r3 = func(1000);       // 合法,1000是常量int r4 = func(f(10));      // 合法,f(10)在编译期完成,符合consteval要求return 0;
}

1.8、co_await、co_yield、co_return

编译器版本:GCC 10
编译选项:-fcoroutines

协程三大关键字:co_await、co_yield 和 co_return。

1.8.1、语法示例

(先了解基本语法,后文将详细解释)

using namespace std::chrono;
struct TimeAwaiter 
{std::chrono::system_clock::duration duration;bool await_ready() const { return duration.count() <= 0; }void await_resume() {}void await_suspend(std::coroutine_handle<> h) {}
};
template<typename _Res>
struct FuncAwaiter 
{_Res value;bool await_ready() const { return false; }_Res await_resume() { return value; }void await_suspend(std::coroutine_handle<> h) { std::cout << __func__ << std::endl; }
};
TimeAwaiter operator co_await(std::chrono::system_clock::duration d) 
{return TimeAwaiter{d};
}
static FuncAwaiter<std::string> test_await_print_func()
{std::this_thread::sleep_for(1000ms);std::cout << __func__ << std::endl;return FuncAwaiter<std::string>{std::string("emmmmmmm ") + __func__};
}
static generator_with_arg f1() 
{ std::cout << "11111" << std::endl;co_yield 1; std::cout << "22222" << std::endl;co_yield 2; std::cout << "33333" << std::endl;co_return 3;
}
static generator_without_arg f2() 
{ std::cout << "44444" << std::endl;std::cout << "55555" << std::endl;std::cout << "66666" << std::endl;co_return;
}
static generator_without_arg test_co_await() 
{std::cout << "just about go to sleep...\n";co_await 5000ms;std::cout << "resumed 1111\n";std::string ret = co_await test_await_print_func();
}

总结:

  • co_return [result]
    表示协程最终的返回结果。若未指定值,则默认为 void。
  • co_yield value
    表示协程挂起时返回的值。不可省略,且必须与 co_return 返回类型一致。当 co_return 返回类型为 void 时,不能使用 co_yield。
  • co_await value
    • 可以被重载,重载函数应返回一个 awaiter。
    • 若 value 是某个函数调用,则该函数必须返回一个 awaiter。

1.8.2、awaiter 说明

一个合法的 awaiter 类型必须实现以下三个接口函数:

  • await_ready()
    首先被调用,返回一个布尔值。若为 true,表示操作已完成,继续执行后续代码;若为 false,则协程将被挂起,并进入 await_suspend()。
  • await_suspend(h)
    接收当前协程的句柄 h。返回类型可以是 void 或 bool。若返回 false,协程不会挂起;若返回 void,等效于返回 true,即协程挂起。
  •  std::coroutine_handle<> 是标准库提供的类型,用于引用协程对象,控制其生命周期和唤醒行为。
  • await_resume()
    当协程恢复执行时调用。其返回值就是 co_await 表达式的返回值。

1.8.3、协程函数

协程函数的返回类型必须包含一个名为 promise_type 的嵌套类型。这个 promise_type 负责管理协程的状态和返回值。

编译器会自动调用 promise_type::get_return_object() 来获取协程函数的返回值(通常是 generator 类型),用户无需手动编写 return 语句。

通常情况下,generator 类型需要保存协程的句柄,以便外部程序控制协程的执行流程。

1.9、constinit

编译器版本:GCC 10

constinit 用于确保变量在编译期完成初始化,禁止动态初始化。

适用条件:
变量必须具有静态存储周期或线程局部存储周期(thread_local)。thread_local 变量可以选择不初始化。

示例代码:

const char * get_str1()
{return "111111";
}
constexpr const char * get_str2()
{return "222222";
}
const char *hahah = " hhahahaa ";
constinit const char *str1 = get_str2(); // 合法,使用 constexpr 函数初始化
constinit const char *str2 = get_str1(); // 非法,使用非 constexpr 函数初始化
constinit const char *str3 = hahah;      // 非法,使用非常量表达式初始化
int main()
{static constinit const char *str4 = get_str2(); // 合法,静态变量constinit const char *str5 = get_str2();        // 非法,非静态/非 thread_localconstinit thread_local const char *str6;        // 合法,thread_local 可不初始化return 0;
}

2、语法

2.1、位域变量的默认成员初始化

编译器版本:GCC 8

C++20 允许在定义位域变量时为其指定默认初始值。这一特性提升了代码的可读性和安全性。

声明语法格式如下:

类型 变量名 : 位数 = 初始值;
类型 变量名 : 常量表达式 {初始值};

示例:

int a;
const int b = 1;
struct S
{int x1 : 8 = 42;            // 合法,x1 是一个 8 位整型,并被初始化为 42int x2 : 6 {42};            // 合法,同上,使用花括号初始化int x3 : true ? 10 : a = 20; // 合法,三目运算结果是 10,未进行赋值操作(优先级问题)int x4 : true ? 10 : b = 20; // 非法,b 是 const,不能对其赋值int x5 : (true ? 10 : b) = 20; // 合法,强制优先级后,位宽为 10,并初始化为 20int x6 : false ? 10 : a = 20; // 非法,a = 20 不是常量表达式
};

2.2、修改 const 限定的成员指针

编译器版本:GCC 8

在 C++20 中,允许对 .* 表达式中的第二个操作数进行更灵活的处理。特别是当该操作数是一个指向带有 & 限定符的成员函数指针时,只有在其具有 const 限定的情况下才是合法的。

示例:

struct S {void foo() const& { }
};
void f()
{S{}.foo();           // 合法,调用 const& 成员函数(S{}.*&S::foo)();    // C++20 起支持这种语法
}

2.3、允许 lambda 表达式按值捕获 this

编译器版本:GCC 8

lambda 表达式现在可以显式地按值捕获 this 指针,这使得 lambda 在捕获对象状态时更加清晰明确。

示例:

struct S
{int value;void print(){auto f = [=, this]() {this->value++;};}
};

上述代码中,[=, this] 表示以值方式捕获所有外部变量,并且显式捕获 this 指针。


2.4、指定初始化

编译器版本:GCC 8

C++20 引入了类似 C99 的“指定初始化”语法,允许在构造聚合类型时通过字段名称来初始化特定成员。但必须按照成员在类或结构体中定义的顺序进行初始化。

示例:

struct A { int x, y; };
struct B { int y, x; };
void f(A a, int);  // #1
void f(B b, ...);  // #2
void g(A a);       // #3
void g(B b);       // #4
void h()
{f({.x = 1, .y = 2}, 0);         // 合法,调用 #1f({.y = 1, .x = 2}, 0);         // 非法,成员顺序与定义不一致,无法匹配 #1f({.y = 1, .x = 2}, 1, 2, 3);   // 合法,调用 #2g({.x = 1, .y = 2});            // 非法,无法确定调用 #3 还是 #4
}

2.5、lambda 表达式支持模板

编译器版本:GCC 8

C++20 支持在 lambda 表达式中使用模板参数,从而实现泛型 lambda。这一特性极大增强了 lambda 的灵活性和通用性。

示例 1:

int a;
auto f = [&a]<typename T>(const T &m) {a += m;
};
f(10); // 正确,T 推导为 int

示例 2:

template<typename T>
int func(int t) 
{return t * t;
}
int f()
{return func<decltype([] {})>(20); // 使用 lambda 类型作为模板参数
}

示例 3:

using A = decltype([] {});
void func(A *) { }
func(nullptr);
template<typename T>
using B = decltype([] {});
void f1(B<int> *) { }
template<typename T>
void f2(B<T> *) { }
f1(nullptr);          // 合法
f2<int>(nullptr);     // 合法

2.6、从构造函数推导出模板参数类型

编译器版本:GCC 8

C++20 允许在变量声明时省略模板参数,编译器将根据构造函数参数自动推导出实际类型。

示例:

vector v{vector{1, 2}}; // 合法,v 被推导为 vector<vector<int>>
tuple t{tuple{1, 2}};   // 合法,t 被推导为 tuple<int, int>

2.7、简化 lambda 的隐式捕获

编译器版本:GCC 8

本节内容较为复杂,涉及 lambda 表达式捕获机制的改进。由于篇幅限制,此处不再详细展开。

如需深入了解,请参考提案文档:

https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0588r1.html

2.8、ADL与不可见的模板函数

编译器版本:GCC 9

ADL(Argument-Dependent Lookup)是 C++ 中的一种机制,用于自动解析调用函数的位置,简化代码编写。C++20 扩展了这一机制,使得它也可以应用于模板函数的推导。

示例:

int h;
void g();
namespace N {struct A {};template<typename T> int f(T);template<typename T> int g(T);template<typename T> int h(T); // 注意这里的 h 是一个模板函数
}
int x = f<N::A>(N::A()); // 正确,调用了 N::f
int y = g<N::A>(N::A()); // 正确,调用了 N::g
int z = h<N::A>(N::A()); // 错误,因为全局命名空间中的 h 被认为是一个变量而非模板函数

2.9、operator<=>

由于篇幅限制,关于 operator<=> 的详细讨论请参考官方文档:

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0515r3.pdf

2.10、基于范围的 for 循环初始化

编译器版本:GCC 9

C++20 引入了一种新的基于范围的 for 循环语法,允许在循环开始前执行初始化语句。

新语法格式如下:

for([init-statement;] for-range-declaration : for-range-initializer) ...

示例:

int a[] = {1, 2, 3, 4};
for(int b = 0; int i : a) {// 使用 i 和 b 进行操作
}

2.11、默认可构造可分配的无状态 lambdas

编译器版本:GCC 9

C++20 允许获取 lambda 表达式的类型,并创建该类型的对象,即使这个 lambda 没有捕获任何外部变量。

示例:

#include <iostream>
#include <map>
auto greater = [](auto x, auto y) { return x > y; };
std::map<std::string, int, decltype(greater)> map;
static void f() {}
int main() {decltype(f) ff;ff(); // 调用静态函数decltype(greater) d;d(10, 20); // 调用比较操作return 0;
}

2.12、专门的访问检查

此特性已在 GCC 和 MSVC 编译器中实现,但在其他编译器中可能未完全支持。它允许在模板类内部忽略访问权限来访问另一个类的嵌套类。

示例:

class A {struct impl1 { int value; };template<typename T> class impl2 { T value; };class impl3 { int value; };
};
struct B {A::impl1 t; // 错误:'struct A::impl1' 在此上下文中为私有
};
template<typename T>
struct trait {A::impl1 t;     // 正确A::impl2<T> t2; // 正确void func() {A::impl1 tmp;  // 正确tmp.value = 10;// 正确t2.value = 20; // 正确A::impl3 t3;   // 正确t3.value = 30; // 正确}
};
int main() {trait<int> a;a.t.value = 10; // 正确a.t2.value = 20; // 错误:'int A::impl2<int>::value' 在此上下文中为私有return 0;
}

2.13、constexpr 函数的实例化

编译器版本:GCC 9

当仅需获取 constexpr 函数的返回值类型时,无需实例化整个函数,只需推导其返回类型即可。

示例:

template<typename T> 
constexpr int f() { return T::value; }
// 此处仅推导 f<T>() 的返回值类型
template<bool B, typename T> 
void g(decltype(B ? f<T>() : 0)) { }
template<bool B, typename T> void g(...) { }
// 因为需要实际获取 int 类型的数据,所以必须实例化 f<T>()
template<bool B, typename T> void h(decltype(int{B ? f<T>() : 0})) { }
template<bool B, typename T> void h(...) { }
void x() {g<false, int>(0); // OK,因为不需要实例化 f<int>()h<false, int>(0); // 错误,即使 B 为 false,也需要实例化 f<int>()
}

2.14、允许 lambda 在初始化捕获时进行包扩展

编译器版本:GCC 9

C++20 扩展了包扩展的应用场景,现在可以在 lambda 初始化捕获时使用包扩展。

示例:

#include <functional>
template<class F, class... Args>
auto invoke1(F f, Args... args) {return [f, args...]() -> decltype(auto) {return std::invoke(f, args...);};
}
template<class F, class... Args>
auto invoke2(F f, Args... args) {return [f=std::move(f), ...args=std::move(args)]() -> decltype(auto) {return std::invoke(f, args...);};
}
template<class F, class... Args>
auto invoke3(F f, Args... args) {return [f=std::move(f), tup=std::make_tuple(std::move(args)...)]() -> decltype(auto) {return std::apply(f, tup);};
}

2.15、放宽结构化绑定,新增自定义查找规则

编译器版本:GCC 8

C++20 放宽了结构化绑定的限制,并允许自定义查找规则以适应更复杂的绑定需求。

自定义条件包括:

  1. 实现 get<int>(Type) 函数或 Type::get<int>() 成员函数。
  2. 特化 tuple_size 和 tuple_element 结构体。
  3. 确保 get<int> 返回路径数量与 tuple_size 指定的数值一致。
  4. get<int N>
  5.  函数返回类型应与 tuple_element 对应索引指定的类型相同。

示例 1:

#include <string>
#include <tuple>
struct A {int a;int b;
};
struct X : private A {std::string value1;std::string value2;
};
template<int N> 
auto& get(X &x) {if constexpr (N == 0)return x.value2;
}
namespace std {template<> class tuple_size<X> : public std::integral_constant<int, 1> {};template<> class tuple_element<0, X> {public: using type = std::string;};
}
int main() {X x;auto& [y] = x; // y 的类型为 stringauto& [y1, y2] = x; // 错误:提供了 2 个名称进行结构化绑定,而 'X' 解构为 1 个元素return 0;
}

示例 2:

#include <string>
#include <tuple>
struct A {int a;int b;
};
struct X : protected A {std::string value1;std::string value2;template<int N> auto& get() {if constexpr (N == 0)return value1;else if constexpr (N == 1)return a;}
};
namespace std {template<> class tuple_size<X> : public std::integral_constant<int, 2> {};template<> class tuple_element<0, X> {public: using type = std::string;};template<> class tuple_element<1, X> {public: using type = int;};
}
int main() {X x;auto& [y1, y2] = x; // y1 为 string 类型,y2 为 int 类型return 0;
}

2.16、放宽基于范围的 for 循环,新增自定义范围方法

编译器版本:GCC 8

在 C++20 中,允许不通过类内部的 begin() 和 end() 成员函数来实现基于范围的 for 循环。现在可以将这两个函数实现在类外部,依然可以被正确识别。

示例:

#include <iostream>
struct X {int a = 1;int b = 2;int c = 3;int d = 4;int e = 5;
};
int* begin(X& x) {return reinterpret_cast<int*>(&x);
}
int* end(X& x) {return reinterpret_cast<int*>(&x) + sizeof(x) / sizeof(int);
}
int main() {X x;for (int i : x) {std::cout << i << std::endl;}std::cout << "finish" << std::endl;return 0;
}

2.17/类类型的非类型模板参数

编译器版本:GCC 9

C++20 允许使用类类型作为非类型模板参数,前提是该类的所有操作可以在编译期完成,并且满足特定条件(如支持常量比较等)。

基本用法示例:

#include <iostream>
struct A {int value;// operator== 必须是 constexprconstexpr bool operator==(const A& v) const {return value == v.value;}
};
template<A a, A b>
struct Equal {static constexpr bool value = a == b; // 编译期比较
};
int main() {static constexpr A a{10}, b{20}, c{10};std::cout << std::boolalpha;std::cout << Equal<a, b>::value << std::endl; // 输出 falsestd::cout << Equal<a, a>::value << std::endl; // 输出 truereturn 0;
}

① operator== 的缺口与地址一致性

当使用类类型作为非类型模板参数时,如果两个对象逻辑上相等(即 <=> 比较结果为 0),那么它们实例化的模板也应共享相同的地址。

#include <iostream>
template<auto v>
int Value;
struct A {int value;
};
int main() {static constexpr A a{10}, b{20}, c{10};std::cout << std::boolalpha;std::cout << (&Value<a> == &Value<b>) << std::endl; // falsestd::cout << (&Value<a> == &Value<c>) << std::endl; // truereturn 0;
}

② 成员函数调用要求 constexpr

由于模板参数必须在编译期求值,因此类中的成员函数用于模板参数计算时,必须标记为 constexpr。

③ 类模板参数的相互推导

C++20 支持从字符串字面量等自动推导模板参数。

#include <string>
template<typename _Tp, std::size_t N>
struct MyArray {constexpr MyArray(const _Tp (&foo)[N + 1]) {std::copy_n(foo, N + 1, m_data);}auto operator<=>(const MyArray&, const MyArray&) = default;_Tp m_data[N];
};
template<typename _Tp, std::size_t N>
MyArray(const _Tp (&str)[N]) -> MyArray<_Tp, N - 1>;
template<std::size_t N>
using CharArray = MyArray<char, N>;
// 旧写法需要显式指定大小
template<std::size_t N, CharArray<N> Str>
struct A {};
using hello_A = A<5, "hello">;
// 新写法可自动推导
template<CharArray Str>
struct B {};
using hello_B = B<"hello">;

④ 用户自定义字面量支持

结合上述特性,可以实现基于字符串字面量的模板参数化处理:

template<CharArray Str>
auto operator""_udl();
"hello"_udl; // 等价于 operator""_udl<"hello">()

类类型非类型模板参数的条件(满足任意一个即可):

  • 是字面量类型;
  • 是左值引用;
  • 包含占位符类型;
  • 是派生类类型的占位符;
  • 拥有强结构可比较性(即支持默认的 operator<=>,没有 mutable 或 volatile 成员);

强结构可比较性的定义:对于任意类型 T,若 const T 的 glvalue 对象 x,使得 x <=> x 返回 std::strong_ordering 或 std::strong_equality,并且不调用用户定义的三向比较运算符或结构化比较函数,则该类型具有强结构可比较性。

2.18、禁止使用用户声明的构造函数进行聚合初始化

编译器版本:GCC 9

在 C++20 中,禁止使用用户显式声明的构造函数(即使为 default 或 delete)来进行聚合初始化,从而修复了一些边缘情况下的语义不一致问题。

旧版存在的几个问题:

① delete 构造函数仍可聚合初始化

struct X {X() = delete;
};
int main() {X x1;       // 错误:X() 被删除X x2{};     // 旧版本可能通过编译(错误)
}

② 双重聚合初始化

struct X {int i{4};X() = default;
};
int main() {X x1(3);   // 错误:没有匹配的构造函数X x2{3};   // 旧版本可能通过编译(错误)
}

③ 类外 default 构造函数导致聚合失败

struct X {int i;X() = default;
};
struct Y {int i;Y();
};
Y::Y() = default;
int main() {X x{4};   // 旧版本可能通过编译(错误)Y y{4};   // 旧版本可能报错(不一致)
}

C++20 的修正方案:

如果类中显式声明了除拷贝/移动构造函数以外的其他构造函数,则该类不能使用聚合初始化。

修正后的行为如下:

struct X {X() = delete;
};
int main() {X x1;      // 错误:构造函数被删除X x2{};    // 错误:构造函数被删除
}
struct X {int i{4};X() = default;
};
int main() {X x1(3);   // 错误:无匹配构造函数X x2{3};   // 错误:不允许聚合初始化
}
#include <initializer_list>
struct X {int i;X() = default;
};
struct Y {int i;Y();
};
Y::Y() = default;
struct A {int i;A(int);
};
struct B {int i;B(int);
};
B::B(int) {}
struct C {int i;C() = default;C(std::initializer_list<int> list);
};
int main() {X x{4};    // 错误:无匹配构造函数Y y{4};    // 错误:无匹配构造函数A a{5};    // 正确B b{5};    // 正确C c{6};    // 正确:支持 initializer_list 构造函数return 0;
}

2.19、嵌套内联命名空间

编译器版本:GCC 9

C++20 引入了嵌套内联命名空间的新语法,使得在定义多个层级的命名空间时更加简洁,并且可以更灵活地控制符号的可见性。

旧写法(使用 inline关键字):

#include <iostream>
namespace A {inline namespace B {void func() {std::cout << "B::func()" << std::endl;}} // namespace B
} // namespace A
int main() {A::func(); // 输出 B::func()return 0;
}

新特性写法(支持 A::inline C写法):

#include <iostream>
namespace A {namespace B {void func() {std::cout << "B::func()" << std::endl;}} // namespace B
} // namespace A
namespace A::inline C {void func() {std::cout << "C::func()" << std::endl;}
}
int main() {A::func(); // 输出 C::func()return 0;
}
  • A::inline C 是 namespace A { inline namespace C { ... } } 的简写形式。
  • 内联命名空间中的函数/变量默认在父命名空间中可见,用于实现向后兼容或接口简化。

2.20、约束声明的另一种办法

编译器版本:GCC 10

C++20 支持将 auto 和 concept 结合使用,从而简化模板约束的写法,使代码更具表现力和可读性。

示例:

#include <iostream>
struct Compare {// 使用 auto 替代模板类型,自动推导参数bool operator()(const auto& t1, const auto& t2) const {return t1 < t2;}
};
template<typename T>
concept CanCompare = requires(T t) {t * t;  // 需要支持乘法运算符Compare().operator()(T(), T()); // 需要支持 < 运算符
};
// 使用 concept + auto 的函数返回值和参数
CanCompare auto pow2(CanCompare auto x) {CanCompare auto y = x * x;return y;
}
struct A {int value = 0;bool operator<(const A& a) const {return value < a.value;}A operator*(const A& a) const {return {.value = a.value * this->value};}
};
int main() {A a;a.value = 100;A aa = pow2(a); // 推导 x 为 A 类型,满足 CanCompare 约束std::cout << aa.value << std::endl;return 0;
}

2.21、允许在常量表达式中使用 dynamic_cast、多态 typeid

编译器版本:GCC 10

C++20 允许在 constexpr 上下文中使用 dynamic_cast 和 typeid,前提是它们的操作对象是已知的静态类型或具有多态性的对象。

示例:

#include <iostream>
#include <typeinfo>
struct Base {virtual ~Base() = default;
};
struct Derived : Base {};
constexpr bool test_dynamic_cast() {Derived d;Base* b = &d;return dynamic_cast<Derived*>(b) != nullptr;
}
static_assert(test_dynamic_cast(), "dynamic_cast in constexpr failed");
constexpr bool test_typeid() {Derived d;Base* b = &d;return typeid(*b) == typeid(Derived);
}
static_assert(test_typeid(), "typeid in constexpr failed");

2.22、允许用圆括号的值进行聚合初始化

编译器版本:GCC 10

C++20 扩展了聚合初始化语法,允许使用圆括号 ( ) 来代替花括号 { },只要目标类型是聚合类型。

示例:

#include <iostream>
struct A {int v;
};
struct B {int a;double b;A&& c;long long&& d;
};
A get() {return A();
}
int main() {int i = 100;B b1{1, 20.0, A(), 200};         // 正常聚合初始化B b2(1, 20.0, A(), 300);         // C++20 新增支持B b3{1, 20.0, get(), 300};       // 正常B b4(2, 30.0, std::move(get()), std::move(i)); // 正常return 0;
}

2.23、new 表达式的数组元素个数推导

编译器版本:GCC 11

C++20 支持在使用 new 创建数组时省略大小,由编译器根据初始化列表自动推导。

示例:

#include <iostream>
#include <cstring>
int main() {double a[]{1, 2, 3};              // 普通数组推导double* p = new double[]{1, 2, 3}; // 自动推导大小为 3p = new double[0]{};              // 显式指定大小为 0p = new double[]{};               // 自动推导大小为 0char* d = new char[]{"Hello"};   // 推导为包含 '\0' 的字符串数组int size = std::strlen(d);std::cout << size << std::endl;   // 输出 5return 0;
}

2.24、Unicode 字符串字面量

编译器版本:GCC 10

C++20 支持 UTF-16 和 UTF-32 编码的字符串字面量,分别使用前缀 u 和 U。

示例:

#include <string>
int main() {std::u16string str1 = u"aaaaaa"; // UTF-16 字符串std::u32string str2 = U"bbbbbb"; // UTF-32 字符串return 0;
}

2.25、允许转换成未知边界的数组

编译器版本:GCC 10

C++20 允许将数组作为实参传递给接受未知边界数组的形参。这种特性对于泛型编程非常有用。

示例:

template<typename T>
static void func(T (&arr)[]) {// 接收任意大小的数组
}
template<typename T>
static void func(T (&&arr)[]) {// 接收临时数组
}
int main() {int a[3];int b[6];func<int>(a);                // OKfunc<int>(b);                // OKfunc<int>({1, 2, 3, 4});     // OKfunc<double>({1.0, 2, 3, 4, 8.0}); // OKreturn 0;
}

2.26、聚合初始化推导类模板参数

编译器版本:GCC 8

C++20 支持通过聚合初始化的方式推导类模板参数类型,提升了模板编程的灵活性。

示例:

template <typename T>
struct S {T x;T y;
};
template <typename T>
struct C {S<T> s;T t;
};
template <typename T>
struct D {S<int> s;T t;
};
C c1 = {1, 2};             // error: deduction failed
C c2 = {1, 2, 3};          // error: deduction failed
C c3 = {{1u, 2u}, 3};      // OK: 推导为 C<int>
D d1 = {1, 2};             // error: deduction failed
D d2 = {1, 2, 3};          // OK: 推导为 D<int>
template <typename T>
struct I {using type = T;
};
template <typename T>
struct E {typename I<T>::type i;T t;
};
E e1 = {1, 2};             // OK: 推导为 E<int>

2.27、隐式地将返回的本地变量转换为右值引用

编译器版本:GCC 11

C++20 引入了一项优化:在某些情况下,函数中返回局部变量时会自动使用移动语义(move)而不是复制(copy),即使没有显式使用 std::move。

触发条件:

以下两种情况会触发隐式 move:

  1. return 或 co_return 表达式中的 id-expression 是函数最内层块或 lambda 主体中声明的“隐式可移动实体”;
  2. throw 表达式中引用的是一个“隐式可移动实体”,并且该实体的作用域不超过其所在的 try 块或构造函数初始化列表。

✅ 隐式可移动实体定义:

是局部变量;

没有被 const 修饰;

不是数组;

不是通过花括号 {} 初始化的聚合类型;

没有绑定到引用;

没有被取地址(&)操作符使用过。

示例:

#include <iostream>
struct base {base() {}base(const base&) {std::cout << "base(const base &)" << std::endl;}
private:base(base&&) noexcept {std::cout << "base(base &&)" << std::endl;}
};
struct derived : base {};
base f() {base b;throw b; // 自动调用移动构造函数(如果可用)derived d;return d; // 自动调用移动构造函数(从 derived -> base)
}
int main() {try {f();} catch (base) {// 输出两次 "base(base &&)"}return 0;
}

2.28、允许 default修饰按值比较的运算符

编译器版本:GCC 10

C++20 支持对按值传递参数的比较运算符使用 = default,用于自动生成默认实现。

示例:

struct C {friend bool operator==(C, C) = default; // 合法!C++20 起支持
};

2.29、非类型模板参数等效的条件

编译器版本:GCC 10

当两个非类型模板参数的值被认为是“等效”的时候,它们可以被视为相同的模板实参。这在模板特化、别名推导等场景中非常重要。

等效条件(满足任意一条即可):

类型

判断条件

整型

值相同

浮点型

值相同

std::nullptr_t

都为 nullptr

枚举类型

枚举值相同

指针类型

指向同一对象或函数

成员指针类型

指向同一个类的同一成员,或者都为空

引用类型

引用同一个对象或函数

数组类型

所有元素都满足等效条件

共用体类型

没有活动成员,或具有相同的活动成员且其值等效

类类型

所有直接子对象和引用成员满足等效条件

2.30、Destroying Operator Delete

编译器版本:GCC 9

C++20 引入了新的 operator delete 形式:destroying operator delete,它允许在析构对象的同时控制内存释放行为。

语法格式:

void operator delete(T* ptr, std::destroying_delete_t);

特点:

  1. 优先级高于普通 operator delete
  2. 不会自动释放内存
  3. ,只负责销毁对象;
  4. 支持虚析构函数的行为一致性
  5. (即遵循多态规则);
  6. 仅适用于非数组类型的 delete 操作。

示例 1:基本用法

#include <iostream>
#include <new> // 包含 std::destroying_delete_t
struct A {void operator delete(void* ptr) {std::cout << "111" << std::endl;}void operator delete(A* ptr, std::destroying_delete_t) {std::cout << "222" << std::endl;}
};
struct B {int value = 10;void operator delete(B* ptr, std::destroying_delete_t) {std::cout << "333" << std::endl;}
};
struct C {void operator delete(void* ptr) {std::cout << "444" << std::endl;}
};
int main() {A* a = new A;delete a; // 输出 222B* b = new B;b->value = 100;delete b; // 输出 333,b->value 仍可访问(未释放内存)std::cout << b->value << std::endl; // 输出 100C* c = new C;delete c; // 输出 444return 0;
}

示例 2:继承与虚析构函数

#include <iostream>
#include <new>
struct A {virtual ~A() {}void operator delete(A* ptr, std::destroying_delete_t) {std::cout << "111" << std::endl;}
};
struct B {virtual ~B() {}
};
struct C : A {void operator delete(C* ptr, std::destroying_delete_t) {std::cout << "222" << std::endl;}
};
struct D : B {void operator delete(D* ptr, std::destroying_delete_t) {std::cout << "333" << std::endl;}
};
int main() {A* a = new A;delete a; // 输出 111C* c = new C;delete c; // 输出 222B* b = new D;delete b; // 输出 333(静态类型为 B,但实际调用 D 的 destroying delete)return 0;
}

3、宏

3.1、__VA_OPT__宏

编译器版本:GCC 12

在 C++20 中,__VA_OPT__ 是一个用于支持 可变参数宏(variadic macros)中空变参处理 的新特性。它允许你在宏定义中根据是否存在可变参数来选择性地插入内容。

示例 :根据不同参数执行不同逻辑

可以结合 __VA_OPT__ 来根据是否传入参数做不同的事情。

#define CALL(func, ...) func(__VA_OPT__(__VA_ARGS__))

调用示例:

CALL(foo);              // 展开为 foo()
CALL(bar, 1, 2, 3);     // 展开为 bar(1, 2, 3)

4、属性

4.1、likely与 unlikely

编译器版本:GCC 9

这两个属性用于向编译器提示某个分支的执行概率,帮助其进行更高效的指令调度和分支预测优化。

适用于 if、switch 等控制流语句中的分支判断,尤其在性能敏感的逻辑路径中非常有用。

示例:

int f(int i) {switch(i) {case 1: [[fallthrough]]; // 显式说明允许 fall-through[[likely]] case 2: return 1; // 高概率进入该分支[[unlikely]] case 3: return 2; // 极低概率进入该分支}return 4;
}

4.2、no_unique_address

编译器版本:GCC 9

此属性用于指示某个非静态成员变量可以不占用唯一地址空间,通常用于优化空类型(empty type)成员对象的内存布局。

示例 1:基本用法

#include <iostream>
struct A {}; // 空类型
struct C {};
struct B {long long v;[[no_unique_address]] C a, b;
};
int main() {B b;std::cout << &b.v << std::endl; // 输出 v 的地址std::cout << &b.a << std::endl; // 地址为 &v + 1std::cout << &b.b << std::endl; // 地址为 &v + 2std::cout << sizeof(B) << std::endl; // 输出 8return 0;
}

示例 2:多个空对象共享地址空间

#include <iostream>
struct A {}; // 空对象
struct B {int v;[[no_unique_address]] A a, b, c, d, e, f, g;
};
int main() {B b;std::cout << &b.v << std::endl;std::cout << &b.a << std::endl;std::cout << &b.b << std::endl;std::cout << &b.c << std::endl;std::cout << &b.d << std::endl;std::cout << &b.e << std::endl;std::cout << &b.f << std::endl;std::cout << &b.g << std::endl;std::cout << sizeof(B) << std::endl; // 输出 8return 0;
}

4.3、nodiscard(带消息支持)

编译器版本:GCC 10

C++20 扩展了 [[nodiscard]] 属性,允许为其附加一条自定义警告信息,提醒调用者不要忽略返回值。

示例:

[[nodiscard("返回值不可忽略,请检查错误码")]]
const char* get() {return "";
}
int main() {get(); // 警告:ignoring return value of ‘const char* get()’, declared with attribute nodiscard: "返回值不可忽略,请检查错误码"return 0;
}

5、弃用

C++20 对一些长期存在但容易引发误解或错误使用的语言特性进行了弃用(deprecation)处理。这些特性的使用仍然合法,但鼓励开发者采用更安全、更明确的新方式替代它们。

5.1、Lambda 表达式中 [=]隐式捕获 this

弃用说明:

在 lambda 表达式中使用 [=] 捕获列表时,会隐式地将 this 指针按值捕获,从而允许访问类成员变量。但由于这种行为不直观,容易导致悬空引用或难以察觉的生命周期问题,因此 C++20 中将其标记为弃用

5.2、比较运算符的改进(弃用部分隐式转换)

① 枚举类型的隐式算术转换被弃用

在 C++20 之前,枚举类型可以隐式转换为整型进行比较或算术运算。但在 C++20 中,这类操作已被标记为弃用。

旧写法(弃用):

enum E1 { e };
enum E2 { f };
int main() {bool b = e <= 3.7;     // 弃用:e 被隐式转换为 intint k = f - e;         // 弃用:f 和 e 被隐式转换为 intauto cmp = e <=> f;    // 错误:无法使用 spaceship 运算符return 0;
}

推荐做法:

  • 如需进行数值比较,应显式转换为整型:
bool b = static_cast<int>(e) <= 3.7;
int k = static_cast<int>(f) - static_cast<int>(e);
  • 若需要支持 <=> 比较,应为枚举定义合适的运算符重载。

② 数组之间的比较被弃用

数组之间的直接比较(如 ==、!=)在 C++20 中也被标记为弃用,因为其实际比较的是数组首地址,而非数组内容,这容易引起误解。

旧写法(弃用):

int arr1[5];
int arr2[5];
bool same = arr1 == arr2;   // 弃用:比较的是 &arr1[0] == &arr2[0]
auto cmp = arr1 <=> arr2;   // 错误:不支持 spaceship 运算符

推荐做法:

使用标准库函数逐个比较数组内容:

#include <algorithm>
bool same = std::equal(std::begin(arr1), std::end(arr1), std::begin(arr2));

5.3、下标表达式中的逗号操作符被弃用

弃用说明:

在下标表达式中使用逗号操作符(,)来分隔多个表达式的行为,在 C++20 中被标记为弃用。虽然逗号操作符本身并未被弃用,但在数组索引上下文中使用它容易引起混淆。

旧写法(弃用):

int main() {int a[3] = {0, 1, 3};int tmp1 = a[4, 1];       // tmp1 = a[1] = 1 (只取最后一个表达式)int tmp2 = a[10, 1, 2];   // tmp2 = a[2] = 3 (同样只取最后一个)return 0;
}

推荐做法:

将逗号操作符从下标表达式中移除,改为单独计算索引值:

int index = (10, 1, 2); // 明确写出逗号表达式的结果
int tmp2 = a[index];    // 明确表示索引

或者直接避免使用逗号表达式:

int tmp1 = a[1];
int tmp2 = a[2];

C++20 在保持语言强大性能的同时,引入了许多实用的新特性和语法改进,提升了代码的可读性、安全性和开发效率。从属性增强到宏优化,从 lambda 捕获规范到弃用易错用法,这些变化体现了 C++ 标准不断演进的方向。

掌握这些新特性,有助于我们编写更现代、更可靠的 C++ 程序,并为未来学习更高版本打下坚实基础。

点击下方关注【Linux教程】,获取编程学习路线、原创项目教程、简历模板、面试题库、AI 知识库、编程交流圈子。

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

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

相关文章

LeetCode--40.组合总和II

前言&#xff1a;如果你做出来了39题&#xff0c;但是遇到40题就不会做了&#xff0c;那我建议你去再好好缕清39题的思路&#xff0c;再来看这道题&#xff0c;会有种豁然开朗的感觉解题思路&#xff1a;这道题其实与39题基本一致&#xff0c;所以本次题解是借着39题为基础来讲…

Docker Desktop 安装到D盘(包括镜像下载等)+ 汉化

目录 一、 开启电脑虚拟化 1. 搜索并打开控制面板 2. 点击程序 3. 点击启用或关闭 Windows 功能 4. 打开相关功能 5. 没有Hyper-V的情况&#xff1a; 二、配置环境 1. 更新 WSL 到最新版 2. 设置 WSL 2为默认版本 3. 安装 Ubuntu 三. WSL 迁移到D盘 1. 停止运行wsl…

基于 OpenCV 的图像 ROI 切割实现

一、引言 在计算机视觉领域&#xff0c;我们经常需要处理各种各样的图像数据。有时候&#xff0c;我们只对图像中的某一部分区域感兴趣&#xff0c;例如在一张人物照片中&#xff0c;我们可能只关注人物的脸部。在这种情况下&#xff0c;将我们感兴趣的区域从整个图像中切割出…

Linux操作系统01

一、操作系统简史 二、Linux诞生与分支 三、Linux内核与发行版 内核版本号&#xff1a;cat /proc/version 、 u name -a 操作系统内核漏洞 【超详细】CentOS编译安装升级新内核_centos源码编译安装新版本内核 ntfs-CSDN博客 四、虚拟机 五、Docker容器技术 典型靶场集成环境…

Chrome 下载文件时总是提示“已阻止不安全的下载”的解决方案

解决 Chrome 谷歌浏览器下载文件时提示“已阻止不安全的下载”的问题。 ‍ 前言 最近更新 Chrome 后&#xff0c;下载文件时总是提示“已拦截未经验证的下载内容”、“已阻止不安全的下载”&#xff1a; ‍ 身为一个互联网冲浪高手&#xff0c;这些提醒非常没有必要&#x…

RocketMQ延迟消息是如何实现的?

RocketMQ的延迟消息实现机制非常巧妙&#xff0c;其核心是通过多级时间轮 定时任务 消息重投递来实现的。以下是详细实现原理&#xff1a; ⏰ 一、延迟消息的核心设计 预设延迟级别&#xff08;非任意时间&#xff09; RocketMQ不支持任意时间延迟&#xff0c;而是预设了18个…

D3 面试题100道之(21-40)

这里是D3的面试题,我们从第 21~40题 开始逐条解答。一共100道,陆续发布中。 🟩 面试题(第 21~40 题) 21. D3 中的数据绑定机制是怎样的? D3 的数据绑定机制通过 selection.data() 方法实现。它将数据数组与 DOM 元素进行一一对应,形成三种状态: Update Selection:已…

PyTorch nn.Parameter理解及初始化方法总结

一、理解 nn.Parameter 本质是什么&#xff1f; nn.Parameter 是 torch.Tensor 的一个子类。这意味着它继承了 Tensor 的所有属性和方法&#xff08;如 .data, .grad, .requires_grad, .shape, .dtype, .device, .backward() 等&#xff09;。它本身不是一个函数或模块&#xf…

【Linux】环境基础和开发工具

Linux 软件包管理器 yum 什么是软件包 在Linux下安装软件, 一个通常的办法是下载程序的源代码, 并进行编译, 得到可执行程序. 但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通过包管理器可以很方便…

多模态进化论:GPT-5V图文推理能力在工业质检中的颠覆性应用

前言 前些天发现了一个巨牛的人工智能免费学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 &#x1f680;《多模态进化论&#xff1a;GPT-5V图文推理能力在工业质检中的颠覆性应用》 副标题&#xff1a;2025年实测报告显…

Linux实现一主二从模式

主从复制&#xff1a; 复制概念中分为两类数据库&#xff0c;一类是主数据库&#xff08;master&#xff09;&#xff0c;一类是从数据&#xff08;slave&#xff09;&#xff0c;主 数据库可以进行读写操作&#xff0c;并把写的操作同步给从数据库&#xff0c;一般从数据库是只…

大势智慧亮相第十八届中国智慧城市大会

6月26日-28日&#xff0c;第十八届中国智慧城市大会在武汉盛大举行。本次大会以“数智赋能城市创新协同共治发展蓝图”为主题&#xff0c;汇聚了李德仁、刘经南等八位院士及全国智慧城市领域的专家学者、行业精英&#xff0c;共同探讨行业发展新方向。作为实景三维技术领域领军…

Xbox One 控制器转换为 macOS HID 设备的工作原理分析

Xbox One 控制器转换为 macOS HID 设备的工作原理分析 源代码在 https://github.com/guilhermearaujo/xboxonecontrollerenabler.git 这个工程的核心功能是将 Xbox One 控制器&#xff08;macOS 原生不支持的设备&#xff09;转换为 macOS 可识别的 HID 设备。这里通过分析代…

Notepad++ 复制宏、编辑宏的方法

Notepad具有宏的功能&#xff0c;能够记录当下所有操作&#xff0c;后续只需要一键就可以重复执行&#xff0c;大大减少工作量。 比如我需要把很多文件里面的字符完成替换&#xff0c;那我只需要把替换的过程录制成宏&#xff0c;后续打开文件就可以一键替换了。 但是Notepad的…

Oracle:报错jdbc:oracle:thin:@IP地址:端口:实例名, errorCode 28001, state 99999

报错原因是oracle密码过期&#xff0c;根本解决办法是让密码不再过期&#xff0c;永久有效。具体操作记录一下。 cmd命令行输入&#xff1a; sqlplus / as sysdba修改Oracle密码期限为无限&#xff1a; SQL> ALTER PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME UNLIMITED;SQL&…

Apipost 签约中原消费金融:共建企业级 API 全链路协作平台,推动接口管理与测试智能化升级

随着企业数字化转型的不断深化&#xff0c;API 正在从技术细节演变为业务协作的核心枢纽。特别是在金融行业&#xff0c;微服务架构、系统联动、合规要求等多重因素交织下&#xff0c;接口数量激增、管理复杂度提升、质量保障难度加大。近日&#xff0c;Apipost 与中原消费金融…

AntV L7 之LarkMap 地图

一、安装$ npm install -S antv/l7 antv/larkmap # or $ yarn add antv/l7 antv/larkmap二、引入包import type { LarkMapProps, LineLayerProps } from antv/larkmap; import { LarkMap, LineLayer, Marker } from antv/larkmap;三、config配置const layerOptions:Omit<Lin…

客户案例 | 某新能源车企依托Atlassian工具链+龙智定制开发服务,打造符合ASPICE标准的研发管理体系

客户案例 ASPICE标准已成为衡量整车厂及供应商研发能力的重要标尺。某知名车企在其重点项目研发过程中&#xff0c;面临着ASPICE 4.0评估认证的挑战——项目团队缺乏体系经验、流程规范和数字化支撑工具。 为帮助该客户团队顺利通过ASPICE认证并提升研发合规性&#xff0c;At…

stm32的USART使用DMA配置成循环模式时发送和接收有着本质区别

stm32的USART使用DMA配置成循环模式时发送和接收有着本质区别&#xff0c;不要被网上误导了。发送数据时会不停的发送数据&#xff0c;而接收只有有数据时才会接收&#xff0c;没有数据时就会挂起等待。 一、触发机制的差异‌ ‌发送方向&#xff08;TX&#xff09;——状态驱…

银河麒麟系统上利用WPS的SDK进行WORD的二次开发

目录 1.下载安装包 2.安装WPS 3.获取示例代码 4.编译示例代码 5.完整示例代码 相关链接 1.下载安装包 去wps的官网 https://www.wps.cn/ 下载linux版本。 下载的安装包名称为&#xff1a;wps-office_12.8.2.21176.AK.preload.sw_amd64.deb, 官网有介绍适用于Ubuntu、麒麟…