C++ 标准库的典型内容
目录
- C++ 标准库的典型内容
- 1. std::declval
- 定义
- 使用方法
- 常见用途
- 注意事项
- 2. std::true_type 和 std::false_type
- 定义
- 使用方法
- 常见用途
- 注意事项
- 3. std::void_t
- 定义
- 使用方法
- 常见用途
- 注意事项
- 4. std::conditional
- 定义
- 使用方法
- 常见用途
- 注意事项
- 5. std::function
- 定义
- 使用方法
- 常见用途
- 注意事项
- 6. std::remove_all_extents
- 定义
- 使用方法
- 常见用途
- 注意事项
- 7. std::integer_sequence
- 定义
- 使用方法
- 常见用途
- 注意事项
- 8 std::is_union
- 定义
- 使用方法
- 常见用途
- 注意事项
- 9 std::is_class
- 定义
- 使用方法
- 常见用途
- 注意事项
- 10 std::integral_constant
- 定义
- 使用方法
- 常见用途
- 注意事项
C++ 标准库的典型内容
C++ 标准库提供了一系列强大且灵活的工具和组件,用于简化程序设计和提高开发效率。它包含多种功能模块,涵盖了内存管理、数据结构、算法、输入输出操作以及类型特征等多个方面。以下是一些典型内容及其主要用途:
- 类型特征:如
std::is_class、std::is_union和std::conditional,这些工具用于在编译时检查和选择类型,有助于实现模板元编程和类型安全。 - 函数和可调用对象:
std::function提供了一种通用的方式来存储和调用任意可调用对象,使得函数指针、lambda 表达式和绑定对象都能统一处理。 - 编译时常量:
std::integral_constant和std::void_t等工具使得开发者可以在编译时处理常量值,增强了类型推导和模板特化的能力。 - 数组和序列处理:
std::remove_all_extents和std::integer_sequence提供了对数组维度和整数序列的处理功能,在需要时可以方便地获取或生成所需的类型。 - SFINAE(替代失败不是错误):通过使用
std::declval和std::void_t,开发者能够实现更复杂的类型推导和模板特化逻辑,提升代码的灵活性和可重用性。
| 组件 | 描述 |
|---|---|
| std::declval | 一个模板函数,返回给定类型的右值引用,用于在不实际构造对象的情况下进行类型推导。常用于模板元编程。 |
| std::true_type 和 std::false_type | 这两个类型用于表示编译时布尔值。其中 std::true_type 代表 true,std::false_type 代表 false。 |
| std::void_t | 一个模板别名,将任意类型映射为 void,通常用于 SFINAE(替代失败不是错误)技术中。 |
| std::conditional | 一个条件类型选择器,根据一个布尔条件在编译时选择两种类型之一。即如果条件为真,则返回第一个类型,否则返回第二个类型。 |
| std::function | 一个通用的多态函数包装器,可以存储、复制和调用任何可调用目标(如函数、lambda 表达式等)。 |
| std::remove_all_extents | 类型特征,用于移除所有数组维度,返回基本类型。例如,对于 int[10][20],返回 int。 |
| std::integer_sequence | 表示一个编译时的整数序列,常用于模板元编程,特别是在处理参数包和索引序列时。 |
| std::is_union | 类型特征,用于检查一个类型是否是联合体(union)。如果是,则返回 true。 |
| std::is_class | 类型特征,用于检查一个类型是否是类(包括 struct)。如果是,则返回 true。 |
| std::integral_constant | 一个模板类,用于在编译时表示一个常量值,是许多类型特征的基础。 |
1. std::declval
std::declval是 C++11 标准中出现的一个函数模板。这个函数模板长得比较奇怪,因为它没有函数体(没有实现,只有声明,故意这样设计的),所以无法被调用,一般都是用于与decltype、sizeof等关键字配合使用进行类型推导、占用内存空间计算等。
std::declval 的主要目的是在编译时获取类型信息,而不实际创建对象。这对于模板编程和元编程非常有用。
定义
namespace std {template <class T>typename add_rvalue_reference<T>::type declval() noexcept;
}
解释:
std::declval是一个模板函数,它返回一个指定类型T的右值引用,而无需创建该类型的实际对象。typename add_rvalue_reference<T>::type:返回类型为T的右值引用。declval() noexcept:这是函数声明,表示declval函数不会抛出异常。
使用方法
示例1:推导成员函数的返回类型:
std::declval 通常与 decltype 一起使用,以便在编译时确定表达式的类型。这在处理没有默认构造函数或无法实例化的类时非常有用。
#include <utility> // std::declval
#include <type_traits> // std::is_sameclass MyClass {
public:MyClass(int x) {} // 没有默认构造函数double getValue() const { return 3.14; }
};// 使用 std::declval 和 decltype 推导 getValue 方法的返回类型
using ReturnType = decltype(std::declval<MyClass>().getValue());int main() {// 验证 ReturnType 是否为 double 类型static_assert(std::is_same<ReturnType, double>::value, "ReturnType should be double");return 0;
}
在这个示例中,std::declval<MyClass>() 返回一个 MyClass 类型的右值引用,而不实际创建 MyClass 对象。然后 decltype 用于推导 getValue() 方法的返回类型。
示例2:推导成员变量的类型:
std::declval 也可以用于推导类成员变量的类型。
#include <utility> // std::declval
#include <type_traits> // std::is_sameclass MyClass {
public:MyClass(int x) : value(x) {} // 没有默认构造函数int value;
};// 使用 std::declval 和 decltype 推导成员变量的类型
using ValueType = decltype(std::declval<MyClass>().value);int main() {// 验证 ValueType 是否为 int 类型static_assert(std::is_same<ValueType, int>::value, "ValueType should be int");return 0;
}
在这个示例中,std::declval<MyClass>() 返回一个 MyClass 类型的右值引用,然后 decltype 用于推导 value 成员变量的类型。
示例3:推导模板类成员函数的返回类型:
std::declval 在模板元编程中也非常有用,可以用来推导复杂表达式的类型。
#include <utility> // std::declval
#include <type_traits> // std::is_sametemplate <typename T>
class Container {
public:Container(T t) : value(t) {}T getValue() const { return value; }
private:T value;
};// 使用 std::declval 和 decltype 推导模板类成员函数的返回类型
template <typename T>
using ReturnType = decltype(std::declval<Container<T>>().getValue());int main() {// 验证 ReturnType<int> 是否为 int 类型static_assert(std::is_same<ReturnType<int>, int>::value, "ReturnType<int> should be int");// 验证 ReturnType<double> 是否为 double 类型static_assert(std::is_same<ReturnType<double>, double>::value, "ReturnType<double> should be double");return 0;
}
在这个示例中,定义了一个模板类 Container,并使用 std::declval 和 decltype 推导其成员函数 getValue 的返回类型。然后在 main 函数中,验证不同类型参数下的返回类型。
常见用途
- 类型推导:尤其是在模板编程中,推导复杂表达式的类型。
- 避免实例化对象:在需要类型但无法或不想实际创建对象时使用。
- 元编程:与其他元编程工具(如
decltype,std::is_same,std::add_rvalue_reference等)配合使用,实现高级类型操作。
注意事项
std::declval不能在运行时使用,因为它没有实现函数体,无法实际调用。- 通常只在编译时使用,用于类型推导和元编程。
总结来说,std::declval 是一个非常强大的工具,用于编译时的类型推导和元编程,使得 C++ 的模板编程更加灵活和强大。
2. std::true_type 和 std::false_type
std::true_type 和 std::false_type 是 C++ 标准库中定义的类型,用于元编程(metaprogramming)。它们是 std::integral_constant 的特化版本,分别表示布尔值 true 和 false。这些类型通常用于模板编程中,以便在编译时进行条件判断和类型选择。
定义
namespace std {template <typename T, T v>struct integral_constant {static constexpr T value = v;using value_type = T;using type = integral_constant<T, v>;constexpr operator value_type() const noexcept { return value; }};using true_type = integral_constant<bool, true>;using false_type = integral_constant<bool, false>;
}
解释:
std::integral_constant:这是一个模板类,用于封装一个常量值。它有两个模板参数,类型T和值v。std::true_type:这是std::integral_constant<bool, true>的别名,表示布尔值true。std::false_type:这是std::integral_constant<bool, false>的别名,表示布尔值false。
使用方法
示例1:基于类型的选择:
这个示例展示了如何使用 std::true_type 和 std::false_type 在模板编程中进行条件判断和类型选择。
#include <iostream>
#include <type_traits>// 主模板,假设我们有一个通用的处理函数
template <typename T>
void process(T value, std::true_type) {std::cout << "Processing an integral type: " << value << std::endl;
}// 重载模板,用于处理非整型类型
template <typename T>
void process(T value, std::false_type) {std::cout << "Processing a non-integral type: " << value << std::endl;
}// 包装函数,根据类型选择适当的实现
template <typename T>
void process(T value) {process(value, typename std::is_integral<T>::type());
}int main() {process(42); // 输出: Processing an integral type: 42process(3.14); // 输出: Processing a non-integral type: 3.14process("Hello"); // 输出: Processing a non-integral type: Helloreturn 0;
}
在这个示例中,std::is_integral<T>::type 将根据 T 是否为整型生成 std::true_type 或 std::false_type。然后,基于这个类型,我们调用相应的 process 函数重载。
示例2:启用/禁用函数模板:
这个示例展示了如何使用 std::true_type 和 std::false_type 来启用或禁用特定的函数模板实例。
#include <iostream>
#include <type_traits>// 主模板:仅在 T 是整型时有效
template <typename T>
void print(T value, std::true_type) {std::cout << "Integral type: " << value << std::endl;
}// 重载模板:仅在 T 不是整型时有效
template <typename T>
void print(T value, std::false_type) {std::cout << "Non-integral type: " << value << std::endl;
}// 包装函数:根据类型选择适当的实现
template <typename T>
void print(T value) {print(value, typename std::is_integral<T>::type());
}int main() {print(42); // 输出: Integral type: 42print(3.14); // 输出: Non-integral type: 3.14print("Hello"); // 输出: Non-integral type: Helloreturn 0;
}
在这个示例中,print 函数根据传入参数的类型选择调用不同的实现。std::is_integral<T>::type 将生成 std::true_type 或 std::false_type,从而选择正确的函数重载。
示例3:自定义类型特征:
这个示例展示了如何使用 std::true_type 和 std::false_type 定义自定义类型特征。
#include <iostream>
#include <type_traits>// 自定义类型特征,用于检查类型是否为指针类型
template <typename T>
struct is_pointer_type : std::false_type {};// 特化版本:针对指针类型
template <typename T>
struct is_pointer_type<T*> : std::true_type {};// 使用自定义类型特征的函数模板
template <typename T>
void checkPointerType(T) {if constexpr (is_pointer_type<T>::value) {std::cout << "Pointer type" << std::endl;} else {std::cout << "Non-pointer type" << std::endl;}
}int main() {int a = 10;int* p = &a;checkPointerType(a); // 输出: Non-pointer typecheckPointerType(p); // 输出: Pointer typereturn 0;
}
在这个示例中,自定义类型特征 is_pointer_type 用于检查某个类型是否为指针类型,并返回相应的 std::true_type 或 std::false_type。然后,通过 if constexpr 在编译时选择适当的实现。
常见用途
- 类型特化:根据不同类型选择不同的模板特化。
- 编译时条件判断:在编译时进行条件判断和选择实现,例如使用
std::enable_if和std::conditional。 - 模板元编程:作为元编程工具的一部分,用于实现复杂的编译时逻辑。
注意事项
std::true_type和std::false_type只能在编译时使用,不能在运行时使用。- 它们通常与其他元编程工具(如
std::enable_if,std::conditional,std::is_integral等)结合使用,以实现更复杂的编译时逻辑。
总结来说,std::true_type 和 std::false_type 是 C++ 元编程中的基本工具,用于在编译时进行条件判断和类型选择,使得模板编程更加灵活和强大。
3. std::void_t
std::void_t 是一个 C++17 引入的模板别名,用于简化类型萃取(type traits)和 SFINAE(Substitution Failure Is Not An Error)的编写。它将一组类型转换为 void,如果类型列表中的任何一个类型不合法(即不能被解析),则整个表达式失效,这样可以简化编译期条件的检测。
定义
std::void_t 是一个模板别名,它的定义如下:
namespace std {template<typename...>using void_t = void;
}
解释:
-
template<typename...>表示void_t可以接受任意数量的模板参数。这些参数可以是任何类型。 -
using void_t = void;是一个别名模板,它将所有传递给它的类型参数都映射为void。
使用方法
示例1:检查类型是否有特定成员函数:
假设需要检查某个类型是否有一个名为 foo 的成员函数,可以使用 std::void_t 来实现。
#include <iostream>
#include <type_traits>// 检查类型 T 是否有成员函数 foo()
template <typename, typename = std::void_t<>>
struct has_foo : std::false_type {};template <typename T>
struct has_foo<T, std::void_t<decltype(std::declval<T>().foo())>> : std::true_type {};// 测试类
class A {
public:void foo() {}
};class B {};int main() {std::cout << "A has foo: " << has_foo<A>::value << std::endl; // 输出: 1 (true)std::cout << "B has foo: " << has_foo<B>::value << std::endl; // 输出: 0 (false)return 0;
}
在这个示例中,has_foo 是一个模板结构体,用于检查类型 T 是否有一个名为 foo 的成员函数。std::void_t<decltype(std::declval<T>().foo())> 会尝试获取成员函数 foo 的类型。如果成功,则特化版本生效并继承自 std::true_type;如果失败,则主模板生效并继承自 std::false_type。
示例2:检查类型是否有特定成员类型:
同样,使用 std::void_t 可以检查某个类型是否有一个特定的嵌套类型。
#include <iostream>
#include <type_traits>// 检查类型 T 是否有嵌套类型 value_type
template <typename, typename = std::void_t<>>
struct has_value_type : std::false_type {};template <typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};// 测试类
class C {
public:using value_type = int;
};class D {};int main() {std::cout << "C has value_type: " << has_value_type<C>::value << std::endl; // 输出: 1 (true)std::cout << "D has value_type: " << has_value_type<D>::value << std::endl; // 输出: 0 (false)return 0;
}
在这个示例中,has_value_type 是一个模板结构体,用于检查类型 T 是否有一个名为 value_type 的嵌套类型。std::void_t<typename T::value_type> 会尝试获取嵌套类型 value_type 的类型。如果成功,则特化版本生效并继承自 std::true_type;如果失败,则主模板生效并继承自 std::false_type。
示例3:自定义类型萃取:
以下是一个更复杂的示例,展示了如何使用 std::void_t 自定义类型萃取:
#include <iostream>
#include <type_traits>// 检查类型 T 是否有嵌套类型 iterator
template <typename, typename = std::void_t<>>
struct has_iterator : std::false_type {};template <typename T>
struct has_iterator<T, std::void_t<typename T::iterator>> : std::true_type {};// 检查类型 T 是否有成员函数 begin 和 end
template <typename, typename = std::void_t<>>
struct has_begin_end : std::false_type {};template <typename T>
struct has_begin_end<T, std::void_t<decltype(std::declval<T>().begin()), decltype(std::declval<T>().end())>> : std::true_type {};// 测试类
class E {
public:using iterator = int*;iterator begin() { return nullptr; }iterator end() { return nullptr; }
};class F {};// 包装函数,根据是否有 begin 和 end 选择不同的实现
template <typename T>
void process(T value) {if constexpr (has_begin_end<T>::value) {std::cout << "Type has begin and end." << std::endl;} else {std::cout << "Type does not have begin and end." << std::endl;}
}int main() {std::cout << "E has iterator: " << has_iterator<E>::value << std::endl; // 输出: 1 (true)std::cout << "F has iterator: " << has_iterator<F>::value << std::endl; // 输出: 0 (false)process(E{}); // 输出: Type has begin and end.process(F{}); // 输出: Type does not have begin and end.return 0;
}
在这个示例中,定义了两个类型萃取结构体 has_iterator 和 has_begin_end,用于分别检查类型 T 是否有嵌套类型 iterator 以及成员函数 begin 和 end。然后,通过包装函数 process,可以根据类型 T 的特性选择不同的实现。
常见用途
- SFINAE:用于在模板元编程中进行编译期条件检查。
- 类型萃取:简化复杂类型条件的编写,使代码更易读。
注意事项
- 编译器支持:
std::void_t是 C++17 引入的特性,所以确保使用支持 C++17 的编译器。如果你使用的是更早版本的 C++,可以自行实现类似的功能。 - SFINAE 原则:
std::void_t的主要用途之一是利用 SFINAE 原则。SFINAE 是 C++ 模板编程中的一个重要概念,表示当模板实例化失败时,不会导致编译错误,而是继续尝试其他可行的模板特化。 - 代码可读性:使用
std::void_t可以使模板元编程的代码更加简洁和易读。但在某些复杂场景中,仍需小心使用,以避免过度复杂化代码。 - 与其他元编程工具结合使用:
std::void_t通常与其他元编程工具(如std::enable_if、std::conditional等)结合使用,以实现更强大的编译期逻辑。
总结来说,std::void_t 是一个简洁而强大的工具,特别适用于模板元编程中的类型检查和条件编译。它通过将一组类型转换为 void,使得我们能够更方便地进行编译期条件检测,从而简化了 SFINAE 的编写。这使得编译期错误更容易被捕获和处理,提高了代码的可读性和维护性。
4. std::conditional
std::conditional 是一个 C++ 标准库中的模板,用于在编译期根据一个条件选择一个类型。它的主要作用是在编译期实现条件判断,从而选择合适的类型。
定义
std::conditional 的定义如下:
namespace std {template<bool B, class T, class F>struct conditional {using type = T;};template<class T, class F>struct conditional<false, T, F> {using type = F;};
}
解释:
-
template<bool B, class T, class F>:这是一个模板,接受三个参数:- 一个布尔值
B,用于决定选择哪一个类型。 - 两个类型参数
T和F,分别表示当B为true和false时所选择的类型。
- 一个布尔值
-
using type = T;:当B为true时,conditional模板的成员类型type定义为T。 -
struct conditional<false, T, F> { using type = F; }:这是对B为false的特化,当B为false时,conditional模板的成员类型type定义为F。
使用方法
示例1:基础用法:
以下示例展示了如何使用 std::conditional 在编译期选择类型:
#include <iostream>
#include <type_traits>int main() {// 定义一个类型别名,根据布尔值选择 int 或 double 类型using Type = std::conditional<true, int, double>::type;// 输出 Type 的类型名std::cout << "Type is int: " << std::is_same<Type, int>::value << std::endl; // 输出: 1 (true)std::cout << "Type is double: " << std::is_same<Type, double>::value << std::endl; // 输出: 0 (false)return 0;
}
在这个示例中,std::conditional<true, int, double>::type 将根据条件 true 选择 int 类型,因此 Type 被定义为 int。
示例2:结合 SFINAE 使用:
std::conditional 常与 SFINAE 一起使用,以根据编译期条件选择合适的类型或函数重载:
#include <iostream>
#include <type_traits>// 定义两个不同的类
class A {
public:void foo() const { std::cout << "A::foo()" << std::endl; }
};class B {
public:void bar() const { std::cout << "B::bar()" << std::endl; }
};// 辅助模板,用于选择正确的成员函数指针类型
template<typename T>
struct SelectMemberFunction {using type = typename std::conditional<std::is_same<T, A>::value,void (A::*)() const,void (B::*)() const>::type;
};// 定义一个函数模板,根据条件选择调用不同的成员函数
template<typename T>
typename std::enable_if<std::is_same<T, A>::value, void>::type
call_member_function(const T& obj) {typename SelectMemberFunction<T>::type func = &T::foo;(obj.*func)();
}template<typename T>
typename std::enable_if<std::is_same<T, B>::value, void>::type
call_member_function(const T& obj) {typename SelectMemberFunction<T>::type func = &T::bar;(obj.*func)();
}int main() {A a;B b;call_member_function(a); // 输出: A::foo()call_member_function(b); // 输出: B::bar()return 0;
}
详细解释:
- 定义辅助模板
SelectMemberFunction:- 这个模板用于选择正确的成员函数指针类型。使用
std::conditional根据T的类型在编译期选择合适的成员函数指针类型。 - 如果
T是A类型,则选择void (A::*)() const。 - 如果
T是B类型,则选择void (B::*)() const。
- 这个模板用于选择正确的成员函数指针类型。使用
- 使用 SFINAE 和
std::enable_if:- 定义两个重载的
call_member_function函数模板。 - 第一个模板通过
std::enable_if在T为A类型时启用,并选择调用A::foo。 - 第二个模板通过
std::enable_if在T为B类型时启用,并选择调用B::bar。
- 定义两个重载的
- 在
main函数中测试:- 创建
A和B类型的对象,并调用call_member_function。 - 对于
A类型对象,输出A::foo()。 - 对于
B类型对象,输出B::bar()。
- 创建
通过这种方式,结合了 SFINAE 和 std::conditional,根据编译期条件选择合适的类型和函数重载。
常见用途
- 编译期条件类型选择:根据编译期条件选择不同的类型。
- 元编程:在模板元编程中实现条件判断,选择合适的类型或操作。
- SFINAE:与 SFINAE 结合使用,实现更复杂的编译期逻辑。
注意事项
- 编译器支持:
std::conditional是 C++11 引入的特性,确保使用支持 C++11 或更高版本的编译器。 - 代码可读性:合理使用
std::conditional可以使代码更加简洁和清晰,但滥用可能会导致代码难以理解。
总结来说,std::conditional 是一个强大的工具,特别适用于需要在编译期根据条件选择类型的场景。它通过模板参数中的布尔值进行条件判断,选择合适的类型,从而实现编译期的类型选择。这使得模板元编程中的条件判断变得更加简洁和易读,提高了代码的灵活性和可维护性。
5. std::function
std::function 是 C++ 标准库中的一个通用、多态的函数包装器。它能够存储、复制和调用任意可调用对象(如普通函数、Lambda 表达式、绑定表达式或其他函数对象),并通过统一的接口来使用这些可调用对象。
定义
std::function 的定义如下:
namespace std {template <class Signature>class function;template <class R, class... Args>class function<R(Args...)> {public:// 类型别名using result_type = R;// 构造函数function() noexcept;function(std::nullptr_t) noexcept;function(const function&);function(function&&) noexcept;template <class F>function(F);// 析构函数~function();// 操作符重载function& operator=(const function&);function& operator=(function&&) noexcept;function& operator=(std::nullptr_t) noexcept;template <class F>function& operator=(F);// 调用运算符R operator()(Args...) const;// 其他成员函数void swap(function&) noexcept;explicit operator bool() const noexcept;// 静态成员函数static constexpr size_t max_size = unspecified;};
}
解释:
template <class Signature>:这是一个模板,接受一个函数签名作为参数。class function<R(Args...)>:这是一个特化版本,用于表示返回类型为R且参数类型为Args...的函数。using result_type = R;:定义返回类型R的别名result_type。- 构造函数、析构函数和操作符重载用于管理
std::function对象的生命周期和赋值操作。 R operator()(Args...) const;:调用运算符,用于调用存储的可调用对象。
使用方法
示例1:基本用法:
以下示例展示了如何使用 std::function 存储和调用一个普通函数:
#include <iostream>
#include <functional>// 普通函数
int add(int a, int b) {return a + b;
}int main() {// 使用 std::function 存储普通函数std::function<int(int, int)> func = add;// 调用存储的函数std::cout << "Result: " << func(2, 3) << std::endl; // 输出: Result: 5return 0;
}
示例2:存储 Lambda 表达式:
std::function 还可以存储 Lambda 表达式:
#include <iostream>
#include <functional>int main() {// 使用 std::function 存储 Lambda 表达式std::function<int(int, int)> func = [](int a, int b) {return a + b;};// 调用存储的 Lambda 表达式std::cout << "Result: " << func(2, 3) << std::endl; // 输出: Result: 5return 0;
}
示例3:结合 std::bind 使用:
std::function 可以与 std::bind 结合使用,以创建部分应用的函数:
#include <iostream>
#include <functional>// 普通函数
int multiply(int a, int b) {return a * b;
}int main() {// 使用 std::bind 创建一个部分应用的函数auto multiply_by_two = std::bind(multiply, std::placeholders::_1, 2);// 使用 std::function 存储部分应用的函数std::function<int(int)> func = multiply_by_two;// 调用存储的函数std::cout << "Result: " << func(3) << std::endl; // 输出: Result: 6return 0;
}
常见用途
- 回调函数:在事件驱动编程中,
std::function常用于存储和调用回调函数。 - 高阶函数:在函数式编程中,
std::function可用于存储和传递高阶函数。 - 多态函数对象:通过
std::function可以实现函数对象的多态性,存储不同类型的可调用对象。
注意事项
- 性能开销:
std::function引入了一定的性能开销,特别是在频繁调用的场景下。因此,在性能要求较高的场合,应谨慎使用。 - 类型安全:使用
std::function时,需要确保存储的可调用对象与指定的函数签名匹配,否则会导致运行时错误。
总结来说,std::function 是一个功能强大的工具,用于存储和调用任意类型的可调用对象。它通过提供统一的接口,极大地提高了代码的灵活性和可维护性。在需要存储回调函数、高阶函数或实现函数对象多态性的场景中,std::function 提供了极大的便利。
6. std::remove_all_extents
std::remove_all_extents 是 C++ 标准库中的一个类型特性模板,用于移除多维数组的所有维度,使其变为元素类型。它在模板元编程中非常有用,特别是在处理多维数组时。
定义
std::remove_all_extents 的定义如下:
namespace std {template <class T> struct remove_all_extents { typedef T type; };template <class T> struct remove_all_extents<T[]> { typedef T type; };template <class T, size_t N> struct remove_all_extents<T[N]> { typedef T type; };
}
解释:
template <class T> struct remove_all_extents { typedef T type; };:这是默认情况下的模板定义,对于非数组类型,type定义为T本身。template <class T> struct remove_all_extents<T[]> { typedef T type; };:这是对未知大小数组类型的特化版本,移除数组的第一维度,将type定义为元素类型T。template <class T, size_t N> struct remove_all_extents<T[N]> { typedef T type; };:这是对已知大小数组类型的特化版本,移除数组的第一维度,将type定义为元素类型T。
使用方法
示例1:基本用法:
以下示例展示了如何使用 std::remove_all_extents 移除多维数组的所有维度:
#include <iostream>
#include <type_traits>int main() {// 定义一个多维数组类型using ArrayType = int[3][4][5];// 使用 std::remove_all_extents 移除数组的所有维度using ElementType = std::remove_all_extents<ArrayType>::type;// 输出 ElementType 的类型名std::cout << "Is ElementType int: " << std::is_same<ElementType, int>::value << std::endl; // 输出: 1 (true)return 0;
}
在这个示例中,std::remove_all_extents<ArrayType>::type 移除了 ArrayType 的所有维度,将其简化为元素类型 int。
示例2:处理不同维度的数组:
以下示例展示了如何处理不同维度的数组:
#include <iostream>
#include <type_traits>template<typename T>
void print_element_type() {using ElementType = typename std::remove_all_extents<T>::type;std::cout << "Element type is int: " << std::is_same<ElementType, int>::value << std::endl;
}int main() {int array1[10];int array2[10][20];int array3[10][20][30];print_element_type<decltype(array1)>(); // 输出: Element type is int: 1print_element_type<decltype(array2)>(); // 输出: Element type is int: 1print_element_type<decltype(array3)>(); // 输出: Element type is int: 1return 0;
}
在这个示例中,通过模板函数 print_element_type 处理不同维度的数组,std::remove_all_extents 移除了所有维度,将数组类型简化为元素类型 int。
常见用途
- 模板元编程:在模板元编程中,
std::remove_all_extents常用于处理和简化数组类型。 - 类型推导:当需要推导数组的元素类型时,
std::remove_all_extents提供了一种简单的方法来获取基础元素类型。 - 类型匹配:在需要进行类型匹配或类型转换时,
std::remove_all_extents可以帮助移除数组维度,使类型比较更加直接和简洁。
注意事项
- 多维数组处理:
std::remove_all_extents能够处理任意维度的数组,但对于非数组类型,其行为是将类型保持不变。 - 编译器支持:
std::remove_all_extents是 C++11 引入的特性,确保使用支持 C++11 或更高版本的编译器。
总结来说,std::remove_all_extents 是一个强大的工具,特别适用于需要在编译期处理和简化数组类型的场景。它通过模板元编程技术,提供了一种简单而有效的方法来移除数组的所有维度,从而获取基础的元素类型。这在类型推导、类型匹配和模板元编程中都具有重要的应用价值。
7. std::integer_sequence
std::integer_sequence 是 C++ 标准库中的一个模板类,用于表示一个整数序列。它在模板元编程中非常有用,特别是在处理与整数序列相关的编译期计算时。
定义
std::integer_sequence 的定义如下:
namespace std {template <class T, T... Ints>struct integer_sequence {using value_type = T;static constexpr size_t size() noexcept { return sizeof...(Ints); }};template <size_t... Ints>using index_sequence = integer_sequence<size_t, Ints...>;template <class T, T N>using make_integer_sequence = __make_integer_seq<integer_sequence, T, N>;template <size_t N>using make_index_sequence = make_integer_sequence<size_t, N>;template <class... T>using index_sequence_for = make_index_sequence<sizeof...(T)>;
}
解释:
template <class T, T... Ints> struct integer_sequence:这是一个模板类,接受一个类型T和一个可变参数包Ints,用于表示类型为T的整数序列。using value_type = T;:定义值类型T的别名value_type。static constexpr size_t size() noexcept { return sizeof...(Ints); }:静态成员函数,返回整数序列的大小。template <size_t... Ints> using index_sequence = integer_sequence<size_t, Ints...>;:定义一个别名模板index_sequence,用于表示size_t类型的整数序列。template <class T, T N> using make_integer_sequence = __make_integer_seq<integer_sequence, T, N>;:定义一个别名模板make_integer_sequence,用于生成从0到N-1的整数序列。template <size_t N> using make_index_sequence = make_integer_sequence<size_t, N>;:定义一个别名模板make_index_sequence,用于生成size_t类型的整数序列。template <class... T> using index_sequence_for = make_index_sequence<sizeof...(T)>;:定义一个别名模板index_sequence_for,用于生成与参数包T等长的整数序列。
使用方法
示例1:基本用法:
以下示例展示了如何使用 std::integer_sequence 生成和使用整数序列:
#include <iostream>
#include <utility>template<typename T, T... Ints>
void print_integer_sequence(std::integer_sequence<T, Ints...>) {((std::cout << Ints << ' '), ...);std::cout << std::endl;
}int main() {// 创建一个 integer_sequence 并打印其内容using MySequence = std::integer_sequence<int, 0, 1, 2, 3, 4>;print_integer_sequence(MySequence{});return 0;
}
在这个示例中,std::integer_sequence<int, 0, 1, 2, 3, 4> 生成一个整数序列,并通过 print_integer_sequence 函数打印其内容。
示例2:生成整数序列:
以下示例展示了如何使用 std::make_integer_sequence 和 std::make_index_sequence 生成整数序列:
#include <iostream>
#include <utility>// 基础模板函数,用于递归终止
template<typename T, T... Ints>
void print_integer_sequence(std::integer_sequence<T, Ints...>) {((std::cout << Ints << ' '), ...);std::cout << std::endl;
}// 辅助模板函数,用于生成整数序列并调用打印函数
template<typename T, T N>
void print_make_integer_sequence() {print_integer_sequence(std::make_integer_sequence<T, N>{});
}int main() {// 使用 make_integer_sequence 生成整数序列并打印其内容print_make_integer_sequence<int, 5>(); // 输出: 0 1 2 3 4return 0;
}
在这个示例中,std::make_integer_sequence<int, 5> 生成一个从 0 到 4 的整数序列,并通过 print_make_integer_sequence 函数打印其内容。
常见用途
- 编译期计算:
std::integer_sequence常用于模板元编程中的编译期计算。 - 参数包展开:在函数模板中使用参数包展开时,
std::integer_sequence可以帮助生成索引序列。 - 类型列表处理:在处理与类型列表相关的操作时,
std::integer_sequence提供了一种方便的方法来生成和操作整数序列。
注意事项
- 编译器支持:
std::integer_sequence是 C++14 引入的特性,确保使用支持 C++14 或更高版本的编译器。 - 递归深度:在生成非常长的整数序列时,可能会遇到编译器的递归深度限制,应注意避免过长的序列。
总结来说,std::integer_sequence 是一个功能强大的工具,特别适用于需要在编译期处理和生成整数序列的场景。它通过模板元编程技术,提供了一种简单而有效的方法来表示和操作整数序列,在编译期计算、参数包展开和类型列表处理等方面具有重要的应用价值。
8 std::is_union
std::is_union 是一个类型特性模板,用于判断一个类型是否为联合体(union)。
定义
namespace std {template <class T>struct is_union : public integral_constant<bool, __is_union(T)> {};
}
解释:
- 定义:
std::is_union是一个模板结构,它继承自std::integral_constant,用于在编译时判断类型是否为联合体。 - 成员:
value:该值为true或false,指示给定的类型是否为联合体。type:它是std::integral_constant<bool, value>的类型别名。value_type:它是bool类型的别名。
使用方法
以下示例展示了如何使用 std::is_union 判断一个类型是否为联合体:
#include <iostream>
#include <type_traits>union MyUnion {int i;float f;
};struct MyStruct {int i;float f;
};int main() {std::cout << std::boolalpha;std::cout << "Is MyUnion a union? " << std::is_union<MyUnion>::value << std::endl; // 输出: truestd::cout << "Is MyStruct a union? " << std::is_union<MyStruct>::value << std::endl; // 输出: falsereturn 0;
}
在这个示例中,std::is_union<MyUnion>::value 返回 true,而 std::is_union<MyStruct>::value 返回 false。
常见用途
- 类型检查:
std::is_union常用于模板元编程中的类型检查,以确保在编译时类型的正确性。 - 条件编译:结合
std::enable_if等技术,可以在编译时根据类型属性选择不同的代码路径。
注意事项
- 编译器支持:
std::is_union是 C++11 引入的特性,确保使用支持 C++11 或更高版本的编译器。 - 联合体定义:在使用
std::is_union时,确保所检查的类型确实是通过union定义的联合体。
总结来说,std::is_union 是一个有用的工具,特别适用于需要在编译时检查类型是否为联合体的场景。它通过模板元编程技术,提供了一种简单而有效的方法来确保类型的正确性和安全性。
9 std::is_class
std::is_class 是一个类型特性模板,用于判断一个类型是否为类类型。
定义
namespace std {template <class T>struct is_class : public integral_constant<bool, __is_class(T)> {};
}
解释:
- 定义:
std::is_class是一个模板结构,它继承自std::integral_constant,用于在编译时判断类型是否为类类型。 - 成员:
value:该值为true或false,指示给定的类型是否为类类型。type:它是std::integral_constant<bool, value>的类型别名。value_type:它是bool类型的别名。
使用方法
以下示例展示了如何使用 std::is_class 判断一个类型是否为类类型:
#include <iostream>
#include <type_traits>class MyClass {};struct MyStruct {int i;float f;
};union MyUnion {int i;float f;
};int main() {std::cout << std::boolalpha;std::cout << "Is MyClass a class? " << std::is_class<MyClass>::value << std::endl; // 输出: truestd::cout << "Is MyStruct a class? " << std::is_class<MyStruct>::value << std::endl; // 输出: truestd::cout << "Is MyUnion a class? " << std::is_class<MyUnion>::value << std::endl; // 输出: falsereturn 0;
}
在这个示例中,std::is_class<MyClass>::value 和 std::is_class<MyStruct>::value 返回 true,而 std::is_class<MyUnion>::value 返回 false。
常见用途
- 类型检查:
std::is_class常用于模板元编程中的类型检查,以确保在编译时类型的正确性。 - 条件编译:结合
std::enable_if等技术,可以在编译时根据类型属性选择不同的代码路径。 - 类类型操作:在需要对类类型进行特定操作时,如重载函数或模板特化,
std::is_class提供了一种简便的方法来识别类类型。
注意事项
- 编译器支持:
std::is_class是 C++11 引入的特性,确保使用支持 C++11 或更高版本的编译器。 - 类定义:在使用
std::is_class时,确保所检查的类型确实是通过class或struct定义的类类型。
总结来说,std::is_class 是一个有用的工具,特别适用于需要在编译时检查类型是否为类类型的场景。它通过模板元编程技术,提供了一种简单而有效的方法来确保类型的正确性和安全性。
10 std::integral_constant
std::integral_constant 是 C++ 标准库中的一个模板类,用于封装一个常量值,并在编译时提供该值的类型信息。它在模板元编程中非常有用,尤其是在定义和传递常量值时。
定义
namespace std {template <class T, T v>struct integral_constant {static constexpr T value = v;using value_type = T;using type = integral_constant;constexpr operator value_type() const noexcept { return value; }constexpr value_type operator()() const noexcept { return value; }};
}
解释:
- 定义:
std::integral_constant是一个模板结构,它接受两个模板参数:类型T和值v。它封装了一个常量值,并在编译时提供该值的类型信息。 - 成员:
value:静态成员,表示封装的常量值。value_type:类型别名,表示常量值的类型。type:类型别名,表示std::integral_constant本身的类型。operator value_type() const noexcept:类型转换操作符,允许将std::integral_constant转换为其封装的值类型。value_type operator()() const noexcept:函数调用操作符,返回封装的常量值。
使用方法
以下示例展示了如何使用 std::integral_constant:
#include <iostream>
#include <type_traits>int main() {// 创建一个 integral_constant<int, 42>using MyConstant = std::integral_constant<int, 42>;// 输出其值std::cout << "MyConstant value: " << MyConstant::value << std::endl; // 输出: 42// 使用 operator() 和类型转换操作符MyConstant my_constant;std::cout << "MyConstant as function: " << my_constant() << std::endl; // 输出: 42std::cout << "MyConstant as value: " << static_cast<int>(my_constant) << std::endl; // 输出: 42return 0;
}
在这个示例中,std::integral_constant<int, 42> 封装了一个常量值 42,并通过不同的方式访问该值。
常见用途
- 模板元编程:
std::integral_constant常用于模板元编程中的常量表达式和条件判断。 - 类型特性:许多类型特性模板(如
std::is_class、std::is_union等)都继承自std::integral_constant,并使用它来封装布尔值。 - 条件编译:结合
std::enable_if等技术,可以在编译时根据常量值选择不同的代码路径。
注意事项
- 编译器支持:
std::integral_constant是 C++11 引入的特性,确保使用支持 C++11 或更高版本的编译器。 - 编译期计算:使用
std::integral_constant可以进行编译期计算,但要注意避免复杂的递归或大量计算,以防编译时间过长。
总结来说,std::integral_constant 是一个功能强大的工具,特别适用于需要在编译时封装和传递常量值的场景。它通过模板元编程技术,提供了一种简单而有效的方法来处理常量表达式和类型信息,在模板元编程和类型特性处理中具有重要的应用价值。
相关文章:
C++ 标准库的典型内容
目录 C 标准库的典型内容1. std::declval定义使用方法常见用途注意事项 2. std::true_type 和 std::false_type定义使用方法常见用途注意事项 3. std::void_t定义使用方法常见用途注意事项 4. std::conditional定义使用方法常见用途注意事项 5. std::function定义使用方法常见用…...
【C++初阶】:C++入门,引用概念及其性质
文章目录 一、引用的概念二、引用的语法规则1、引用特性2、常引用 二、引用的使用场景1. 引用做参数2. 引用做返回值 三、引用和指针的区别 一、引用的概念 首先明确一下,引用不是定义一个新的变量,而是给已经存在的变量起一个别名,变量和他…...
Linux 中的 crontab 命令介绍以及使用
文章目录 Linux Crontab 完全指南什么是 Crontab?Crontab 文件的基本格式特殊符号解释: 如何使用 Crontab查看当前用户的 Crontab编辑 Crontab删除 Crontab Crontab 示例每天晚上 12 点备份数据库每个工作日的早上 9 点发送日报每隔 15 分钟清理临时文件…...
单片机组成原理
大纲 C语言指针如何与硬件对应 底层疑问的源头 我已造好轮子,等你来理解 外设电路大概是什么结构 解决底层开发中关于配置、寄存器) 外设电路的疑问 从此可以快速上手新的单片机、新的外设芯片 对外设芯片的内部结构有本质理解,看手册不再是问题 固件库…...
《机器学习》—— SVD奇异值分解方法对图像进行压缩
文章目录 一、SVD奇异值分解简单介绍二、代码实现—SVD奇异值分解方法对图像进行压缩 一、SVD奇异值分解简单介绍 SVD(奇异值分解)是一种在信号处理、统计学、线性代数、机器学习等多个领域广泛应用的矩阵分解方法。它将任何 mn 矩阵 A 分解为三个特定矩…...
英文文本预处理——文本清理
文本清理定义 文本清理是英文文本预处理的重要步骤,旨在提高数据质量和一致性。以下是文本清理的具体内容: 去除标点符号 (Removing Punctuation): 删除文本中的标点符号,如句号、逗号、问号等。这一步骤有助于减少文本噪音,使得文本分析更加专注于有意义的词汇内容。 去…...
Spring Boot 注解探秘:常用配置值读取注解的魔力
在 Spring Boot 应用开发中,我们会常常借助Apollo,Spring Cloud Config等配置中心来集中管理配置信息,在拥有配置信息之后,高效且准确地读取这些配置信息无疑是极为关键的一环。今天我们就来介绍几个常用的用于配置值读取的注解。…...
Ps初步使用教程
1.画面快捷键 Ctrl鼠标滚轮:画面左右移动 Shift鼠标滚轮:画面上下快速移动(不加Shift正常速度移动) Alt鼠标滚轮:画面大小缩放 2.工具快捷键 ShiftG:渐变、油漆桶、3D材质施放 切换 CtrlO:…...
远程连接Hiveserver2服务
目录 1.修改 core-site.xml 和 hive-site.xml 的配置文件 2.启动HiveServer2服务 3.启动Beeline工具连接Hiveserver2服务 4.利用IDEA工具连接Hiveserver2服务 完成Hive本地模式安装后,可以启动hiveserver2服务进行远程连接和操作Hive。 1.修改 core-site.xml …...
PDF样本图册转换为一个链接,随时打开无需印刷
想象一下,您手中有一本厚重的样本图册,里面包含了丰富多样的内容,如产品介绍、项目方案、学术论文等。在过去,您需要逐一翻阅、筛选,甚至为了便于查看,不得不将其印刷出来。如今,借助先进的数字…...
自己动手实现mybatis的底层框架(不用动态代理直接用执行器、用动态代理自己实现。图文分析!)
目录 一.原生mybits框架图分析 自己实现Mybatis框架的分析 两种框架操作数据库的方法: 二.搭建开发环境 1.先创建一个maven项目 2.加入依赖(mysql dom4j junit lombok) 三.mybatis框架的设计思路 具体实现过程 3.1实现任务阶段 1- 完成读取配置文件&#x…...
项目日志——日志落地模块的设计、实现、测试
文章目录 日志落地模块设计实现扩展实现测试 日志落地模块 设计 功能是,将格式化完成后的日志消息字符串,输出到指定的位置 支持将日志落地到不同的位置 标准输出指定文件滚动文件 滚动文件按照时间或者大小进行滚动切换,可以按照天数对…...
CTK框架(七):事件监听
目录 1.概述 2.监听接口 3.具体实现 1.概述 CTK(Common Toolkit)框架中的事件监听机制是一个重要的功能,它允许开发者在特定事件发生时接收到通知并执行相应的操作。CTK框架主要支持三种类型的事件监听:框架事件、插件事件和服…...
一区霜冰算法+双向深度学习模型+注意力机制!RIME-BiTCN-BiGRU-Attention
一区霜冰算法双向深度学习模型注意力机制!RIME-BiTCN-BiGRU-Attention 目录 一区霜冰算法双向深度学习模型注意力机制!RIME-BiTCN-BiGRU-Attention效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现RIME-BiTCN-BiGRU-Attention霜冰算法…...
C语言 | Leetcode C语言题解之第396题旋转函数
题目: 题解: #define MAX(a, b) ((a) > (b) ? (a) : (b))int maxRotateFunction(int* nums, int numsSize){int f 0, numSum 0;for (int i 0; i < numsSize; i) {f i * nums[i];numSum nums[i];}int res f;for (int i numsSize - 1; i &g…...
利士策分享,克服生活中的困难:走好勇攀高峰的每一步
利士策分享,克服生活中的困难:走好勇攀高峰的每一步 在这个纷繁复杂的世界里,每个人都是自己生命旅程中的行者,而生活,则是一条既铺满鲜花又布满荆棘的道路。 我们或许会在某个清晨醒来,发现自己正站在一座…...
PurchasereturnController
目录 1、 PurchasereturnController 1.1、 反审核退货单 1.1.1、 //配件ID 1.1.2、 //配件编码 1.1.3、 //修改后仓库 1.1.4、 //修改配件信息表库存量 PurchasereturnController using QXQPS.Models; using QXQPS.Vo; using System; using System.Collection…...
mysql 学习笔记 八
总结 自动提交 查看自动提交状态:SELECT AUTOCOMMIT ; 设置自动提交状态:SET AUTOCOMMIT 0 。 手动提交 AUTOCOMMIT 0 时,使用 COMMIT 命令提交事务。 事务回滚 AUTOCOMMIT 0 时,使用 ROLLBACK 命令回滚事务。 …...
反序列化漏洞练习2
拿到题目,发现目标是获得flag.php的内容,且sis中admin和passwd等于sis2407时会输出fag的内容 根据源码编写序列化代码 <?php error_reporting(0); class sis{public $admin;public $passwd;public function __construct(){$this->admin "sis2407"…...
基于SpringBoot的社区医院管理系统
作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于JavaSpringBootVueMySQL的社团管理系统【附源码文档】、…...
使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式
一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明:假设每台服务器已…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
linux 下常用变更-8
1、删除普通用户 查询用户初始UID和GIDls -l /home/ ###家目录中查看UID cat /etc/group ###此文件查看GID删除用户1.编辑文件 /etc/passwd 找到对应的行,YW343:x:0:0::/home/YW343:/bin/bash 2.将标红的位置修改为用户对应初始UID和GID: YW3…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
Web 架构之 CDN 加速原理与落地实践
文章目录 一、思维导图二、正文内容(一)CDN 基础概念1. 定义2. 组成部分 (二)CDN 加速原理1. 请求路由2. 内容缓存3. 内容更新 (三)CDN 落地实践1. 选择 CDN 服务商2. 配置 CDN3. 集成到 Web 架构 …...
OPENCV形态学基础之二腐蚀
一.腐蚀的原理 (图1) 数学表达式:dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一,腐蚀跟膨胀属于反向操作,膨胀是把图像图像变大,而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...
