热门标签 | HotTags
当前位置:  开发笔记 > 运维 > 正文

C++语言中std::array的用法小结(神器用法)

这篇文章主要介绍了C++语言中std::array的用法小结,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

摘要:在这篇文章里,将从各个角度介绍下std::array的用法,希望能带来一些启发。

td::array是在C++11标准中增加的STL容器,它的设计目的是提供与原生数组类似的功能与性能。也正因此,使得std::array有很多与其他容器不同的特殊之处,比如:std::array的元素是直接存放在实例内部,而不是在堆上分配空间;std::array的大小必须在编译期确定;std::array的构造函数、析构函数和赋值操作符都是编译器隐式声明的……这让很多用惯了std::vector这类容器的程序员不习惯,觉得std::array不好用。
但实际上,std::array的威力很可能被低估了。在这篇文章里,我会从各个角度介绍下std::array的用法,希望能带来一些启发。

本文的代码都在C++17环境下编译运行。当前主流的g++版本已经能支持C++17标准,但是很多版本(如gcc 7.3)的C++17特性不是默认打开的,需要手工添加编译选项-std=c++17。

自动推导数组大小

很多项目中都会有类似这样的全局数组作为配置参数:

uint32_t g_cfgPara[] = {1, 2, 5, 6, 7, 9, 3, 4};

当程序员想要使用std::array替换原生数组时,麻烦来了:

array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4}; // 注意模板参数“8”

程序员不得不手工写出数组的大小,因为它是std::array的模板参数之一。如果这个数组很长,或者经常增删成员,对数组大小的维护工作恐怕不是那么愉快的。有人要抱怨了:std::array的声明用起来还没有原生数组方便,选它干啥?
但是,这个抱怨只该限于C++17之前,C++17带来了类模板参数推导特性,你不再需要手工指定类模板的参数:

array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4}; // 数组大小与成员类型自动推导

看起来很美好,但很快就会有人发现不对头:数组元素的类型是什么?还是std::uint32_t吗?
有人开始尝试只提供元素类型参数,让编译器自动推导长度,遗憾的是,它不会奏效。

array g_cfgPara = {1, 2, 5, 6, 7, 9, 3, 4}; // 编译错误

好吧,暂时看起来std::array是不能像原生数组那样声明。下面我们来解决这个问题。

用函数返回std::array

问题的解决思路是用函数模板来替代类模板——因为C++允许函数模板的部分参数自动推导——我们可以联想到std::make_pair、std::make_tuple这类辅助函数。巧的是,C++标准真的在TS v2试验版本中推出过std::make_array,然而因为类模板参数推导的问世,这个工具函数后来被删掉了。
但显然,用户的需求还是存在的。于是在C++20中,又新增了一个辅助函数std::to_array。
别被C++20给吓到了,这个函数的代码其实很简单,我们可以把它拿过来定义在自己的C++17代码中[1]。

template
constexpr array to_array_impl(P (&a)[N], std::index_sequence) noexcept
{
 return { {a[I]...} };
}

template
constexpr auto to_array(T (&a)[N]) noexcept
{
 return to_array_impl, T, N>(a, std::make_index_sequence{});
}

template
constexpr array to_array_impl(P (&&a)[N], std::index_sequence) noexcept
{
 return { {move(a[I])...} };
}

template
constexpr auto to_array(T (&&a)[N]) noexcept
{
 return to_array_impl, T, N>(move(a), std::make_index_sequence{});
}

细心的朋友会注意到,上面这个定义与C++20的推荐实现有所差异,这是有目的的。稍后我会解释这么干的原因。

现在让我们尝试下用新方法解决老问题:

auto g_cfgPara = to_array({1, 2, 5, 6, 7, 9, 3, 4}); // 类型不是uint32_t?

不对啊,为什么元素类型不是原来的std::uint32_t?
这是因为模板参数推导对std::initializer_list的元素拒绝隐式转换,如果你把to_array的模板参数从int改为uint32_t,会得到如下编译错误:

D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: error: no matching function for call to 'to_array()'
 auto g_cfgPara = to_array({1, 2, 5, 6, 7, 9, 3, 4});
D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:34:16: note: candidate: 'template constexpr auto to_array(T (&)[N])'
 constexpr auto to_array(T (&a)[N]) noexcept
  ^~~~~~~~
D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:34:16: note: template argument deduction/substitution failed:
D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: note: mismatched types 'unsigned int' and 'int'
 auto g_cfgPara = to_array({1, 2, 5, 6, 7, 9, 3, 4});
D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:46:16: note: candidate: 'template constexpr auto to_array(T (&&)[N])'
 constexpr auto to_array(T (&&a)[N]) noexcept
  ^~~~~~~~
D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:46:16: note: template argument deduction/substitution failed:
D:\Work\Source_Codes\MyProgram\VSCode\main.cpp:51:61: note: mismatched types 'unsigned int' and 'int'

Hoho,有点惨是不,绕了一圈回到原点,还是不能强制指定类型。
这个时候,之前针对std::array做的修改派上用场了:我给to_array_impl增加了一个模板参数,让输入数组的元素和返回std::array的元素用不同的类型参数表示,这样就给类型转换带来了可能。为了实现转换到指定的类型,我们还需要添加两个工具函数:

template
constexpr auto to_typed_array(P (&a)[N]) noexcept
{
 return to_array_impl(a, std::make_index_sequence{});
}

template
constexpr auto to_typed_array(P (&&a)[N]) noexcept
{
 return to_array_impl(move(a), std::make_index_sequence{});
}

这两个函数和to_array的区别是:它带有3个模板参数:第一个是要返回的std::array的元素类型,后两个和to_array一样。这样我们就可以通过指定第一个参数来实现定制std::array元素类型了。

auto g_cfgPara = to_typed_array({1, 2, 5, 6, 7, 9, 3, 4}); // 自动把元素转换成uint32_t

这段代码可以编译通过和运行,但是却有类型转换的编译告警。当然,如果你胆子够大,可以在to_array_impl函数中放一个static_cast来消除告警。但是编译告警提示了我们一个不能忽视的问题:如果万一输入的数值溢出了怎么办?

auto g_a = to_typed_array({256, -1}); // 数字超出uint8_t范围

编译器还是一样的会让你编译通过和运行,g_a中的两个元素的值将分别为0和255。如果你不明白为什么这两个值和入参不一样,你该复习下整型溢出与回绕的知识了。
显然,这个方案还不完美。但我们可以继续改进。

编译期字面量数值合法性校验

首先能想到的做法是在to_array_impl函数中放入一个if判断之类的语句,对于超出目标数值范围的输入抛出异常或者做其他处理。这当然可行,但要注意的是这些工具函数是可以在运行期调用的,对于这种常用的基础函数来说,性能至关重要。一旦在里面加入了错误判断,意味着运行时的每一次调用性能都会下降。

理想的设计是:只有在编译期生成的数组才进行校验,并且报编译错误。但运行时调用函数时不要加入任何校验。

可惜的是,至少在C++20之前,没有办法指定函数只允许在编译期执行[2]。那有没有其他手段呢?

熟悉C++的人知道:C++的编译期处理大多可以用模板的trick来完成——因为模板参数一定是编译期常量。因此我们可以用模板参数来完成编译期处理——只要把数组元素全部作为模板的非类型参数就可以了。当然,这里有个问题:模板的非类型参数的类型怎么确定?正好C++17提供了auto模板参数的功能,可以派上用场:

template
constexpr void CheckIntRanges() noexcept {} // 用于终结递归

template
constexpr void CheckIntRanges() noexcept
{
 // 防止无符号与有符号比较
 static_assert(!((std::numeric_limits::min() >= 0) && (M <0)));

 // 范围校验
 static_assert((M >= std::numeric_limits::min()) && 
   (M <= std::numeric_limits::max()));
 
 CheckIntRanges();
}

template
constexpr auto DeclareArray() noexcept
{
 CheckIntRanges();
 array a{{static_cast(N)...}};
 return a;
};

注意这个函数中,所有的校验都通过static_assert完成。这就保证了校验一定只会发生在编译期,不会带来任何运行时开销。
DeclareArray的使用方法如下:

constexpr auto a1 = DeclareArray(); // 声明一个std::array,元素分别为1, 2, 3, 4, 255
static_assert(a1.size() == 5);
static_assert(a1[3] == 4);
auto a2 = DeclareArray(); // 编译错误,-1超出uint8_t范围
auto a3 = DeclareArray(); // 编译错误,65536超出uint16_t范围

这里有一个误区需要说明:有些人可能会把DeclareArray声明成这样:

template // 注意N的类型为T
constexpr auto DeclareArray() noexcept

这么做的话,会发现对数值的校验总是能通过——因为模板参数在进入校验之前就已经被转换为T类型了。如果你的编译器不支持C++17的auto模板参数,那么可以通过使用std::uint64_t、std::int64_t这些“最大”的类型来间接达到目的。

另一点要说明的是,C++对于非类型模板参数的允许类型存在限制,DeclareArray的方法只能用于数组元素为基本类型的场景(至少在C++20以前如此)。但是这也足够了。如果数组的元素是自定义类型,就可以通过自定义的构造函数等方法来控制类型转换。

如果你看到这里觉得有点意思了,那就对了,后面还有更过瘾的。

编译期生成数组

C++11中新增的constexpr修饰符可以在编译期完成很多计算工作。但是一般constexpr函数只能返回单个值,一旦你想用它返回一串对象的集合,就会遇到麻烦:STL容器都有动态内存申请功能,不能作为编译期常量(至少在C++20之前如此);而原生数组作为返回值会退化为指针,导致返回悬空的指针。即使是返回数组的引用也是不行的,会产生悬空的引用。

constexpr int* Func() noexcept
{
 int a[] = {1, 2, 3, 4};
 return a; // 严重错误!返回局部对象的地址
}

直到std::array的出现,这个问题才得到较好解决。std::array既可以作为编译期常量,又可以作为函数返回值。于是,它成为了编译期返回集合数据的首选。

在上面to_array等工具函数的实现中,我们已经见过了编译期返回数组是怎么做的。这里我们再大胆一点,写一个编译期冒泡排序:

template
constexpr std::array Sort(const std::array& numbers) noexcept
{
 std::array sorted(numbers);
 for (int i = 0; i  i; --j) {
  if (sorted[j]  before{4, 2, 3, 1};
 constexpr std::array after = Sort(before);
 static_assert(after[0] == 1);
 static_assert(after[1] == 2);
 static_assert(after[2] == 3);
 static_assert(after[3] == 4);
 return 0;
}

因为整个排序算法都是在编译期完成,所以我们没有必要太关注冒泡排序的效率问题。当然,只要你愿意,完全可以写出一个编译期快速排序——毕竟constexpr函数也可以在运行期使用,不好说会不会有哪个憨憨在运行时调用它。

在编写constexpr函数时,有两点需要注意:

1.constexpr函数中不能调用非constexpr函数。因此在交换元素时不能用std::swap,排序也不能直接调用std::sort。

2. 传入的数组是constexpr的,因此参数类型必须加上const,也不能对数据进行就地排序,必须返回一个新的数组。

虽然限制很多,但编译期算法的好处也是巨大的:如果运算中有数组越界等未定义行为,编译将会失败。相比起运行时的测试,编译期测试constexpr函数能有效的提前拦截问题。而且只要编译通过就意味着测试通过,比起额外跑白盒测试用例方便多了。

上面的一大串static_assert语句让人看了不舒服。这么写的原因是std::array的operator==函数并非constexpr(至少在C++20前如此)。但是我们也可以自己定义一个模板函数用于判断两个数组是否相等:

template
constexpr bool EqualsImpl(const T& lhs, const U& rhs)
{
 static_assert(M == N);
 for (size_t i = 0; i 
constexpr bool Equals(const T& lhs, const U& rhs)
{
 return EqualsImpl(lhs, rhs);
}

template
constexpr bool Equals(const T& lhs, const U (&rhs)[N])
{
 return EqualsImpl(lhs, rhs);
}

int main()
{
 constexpr std::array before{4, 2, 3, 1};
 constexpr std::array after = Sort(before);
 static_assert(Equals(after, {1, 2, 3, 4})); // 比较std::array和原生数组
 static_assert(!Equals(before, after)); // 比较两个std::array
 return 0;
}

我们定义的Equals比std::array的比较运算符更强大,甚至可以在std::array和原生数组之间进行比较。

对于Equals有两点需要说明:

1.std::size是C++17提供的工具函数,对各种容器和数组都能返回其大小。当然,这里的Equals只会允许编译期确定大小的容器传入,否则触发编译失败。

2.Equals定义了两个版本,这是被C++的一个限制所逼的迫不得已:C++禁止{...}这种std::initializer_list字面量被推导为模板参数类型,因此我们必须提供一个版本声明参数类型为数组,以便{1, 2, 3, 4}这种表达式能作为参数传进去。

编译期排序是一个启发性的尝试,我们可以用类似的方法生成其他的编译期集合常量,比如指定长度的自然数序列:

template
constexpr auto NaturalNumbers() noexcept
{
 array arr{0}; // 显式初始化不能省
 for (size_t i = 0; i ();
 static_assert(Equals(arr, {1, 2, 3, 4, 5}));
 return 0;
}

这段代码的编译运行都没有问题,但它并不是推荐的做法。原因是在NaturalNumbers函数中,先定义了一个内容全0的局部数组,然后再挨个修改它的值,这样没有直接返回指定值的数组效率高。有人会想能不能把arr的初始化给去掉,但这样会导致编译错误——constexpr函数中不允许定义没有初始化的局部变量。

可能有人觉得这些计算都是编译期完成的,对运行效率没影响——但是不要忘了constexpr函数也可以在运行时调用。更好的做法可以参见前面to_array函数的实现,让数组的初始化一气呵成,省掉挨个赋值的步骤。

我们用这个新思路,写一个通用的数组生成器,它可以接受一个函数对象作为参数,通过调用这个函数对象来生成数组每个元素的值。下面的代码还演示了下如何用这个生成器在编译期生成奇数序列和斐波那契数列。

template
constexpr T OddNumber(size_t i) noexcept
{
 return i * 2 + 1;
}

template
constexpr T Fibonacci(size_t i) noexcept
{
 if (i <= 1) {
 return 1;
 }
 return Fibonacci(i - 1) + Fibonacci(i - 2);
}

template
constexpr array, N> GenerateArrayImpl(F f, std::index_sequence) noexcept
{
 return { {f(I)...} };
}

template>
constexpr array GenerateArray(F f) noexcept
{
 return GenerateArrayImpl(f, std::make_index_sequence{});
}

int main()
{
 constexpr auto oddNumbers = GenerateArray<5>(OddNumber);
 static_assert(Equals(oddNumbers, {1, 3, 5, 7, 9}));
 constexpr auto fibOnumbers= GenerateArray<5>(Fibonacci);
 static_assert(Equals(fiboNumbers, {1, 1, 2, 3, 5}));

 // 甚至可以传入lambda来定制要生成的数字序列(限定C++17)
 constexpr auto specified = GenerateArray<3>([](size_t i) { return i + 10; });
 static_assert(Equals(specified, {10, 11, 12}));
 return 0;
}

最后那个传入lambda来定制数组的做法存在一个疑问:lambda是constexpr函数吗?答案为:可以是,但需要C++17支持。

GenerateArray这个数组生成器将会在后面发挥重大作用,继续往下看。

截取子数组

std::array并未提供输入一个指定区间来建立新容器的构造函数,但是借助上面的数组生成器,我们可以写个辅助函数来实现子数组生成操作(这里再次用上了lambda函数作为生成算法)。

template
constexpr auto SubArray(T&& t, size_t base) noexcept
{
 return GenerateArray([base, t = forward(t)](size_t i) { return t[base + i]; });
}

template
constexpr auto SubArray(const T (&t)[M], size_t base) noexcept
{
 return GenerateArray([base, &t](size_t i) { return t[base + i]; });
}

int main()
{
 // 以std::initializer_list字面量为原始数据
 constexpr auto x = SubArray<3>({1, 2, 3, 4, 5, 6}, 2); // 下标为2开始,取3个元素
 static_assert(Equals(x, {3, 4, 5}));

 // 以std::array为原始数据
 constexpr auto x1 = SubArray<2>(x, 1); // 下标为1开始,取2个元素
 static_assert(Equals(x1, {4, 5}));

 // 以原生数组为原始数据
 constexpr uint8_t a[] = {9, 8, 7, 6, 5};
 constexpr auto y = SubArray<2>(a, 3);
 static_assert(Equals(y, {6, 5})); // 下标为3开始,取2个元素

 // 以字符串为原始数据,注意生成的数组不会自动加上'\0'
 constexpr const char* str = "Hello world!";
 constexpr auto z = SubArray<5>(str, 6);
 static_assert(Equals(z, {'w', 'o', 'r', 'l', 'd'})); // 下标为6开始,取5个元素
 
 // 以std::vector为原始数据,非编译期计算
 vector v{10, 11, 12, 13, 14};
 size_t n = 2;
 auto d = SubArray<3>(v, n); // 运行时生成数组
 assert(Equals(d, {12, 13, 14})); // 注意不能用static_assert,不是编译期常量
 return 0;
}

使用SubArray时,模板参数N是要截取的子数组大小,入参t是任意能支持下标操作的类型,入参base是截取元素的起始位置。由于std::array的大小在编译期是确定的,因此N必须是编译期常量,但参数base可以是运行时变量。

当所有入参都是编译期常量时,生成的子数组也是编译期常量。

SubArray提供了两个版本,目的也是为了让std::initializer_list字面量可以作为参数传入。

拼接多个数组

采用类似的方式可以做多个数组的拼接,这里同样用了lambda作为生成函数。

template
constexpr auto TotalLength(const T& arr) noexcept
{
 return size(arr);
}

template
constexpr auto TotalLength(const P& p, const T&... arr) noexcept
{
 return size(p) + TotalLength(arr...);
}

template
constexpr auto PickElement(size_t i, const T& arr) noexcept
{
 return arr[i];
}

template
constexpr auto PickElement(size_t i, const P& p, const T&... arr) noexcept
{
 if (i 
constexpr auto ConcatArrays(const T&... arr) noexcept
{
 return GenerateArray([&arr...](size_t i) { return PickElement(i, arr...); });
}

int main()
{
 constexpr int32_t a[] = {1, 2, 3}; // 原生数组
 constexpr auto b = to_typed_array({4, 5, 6}); // std::array
 constexpr auto c = DeclareArray(); // std::array
 constexpr auto x = ConcatArrays(a, b, c); // 把3个数组拼接在一起
 static_assert(Equals(x, {1, 2, 3, 4, 5, 6, 7, 8}));
 return 0;
}

和之前一样,ConcatArrays使用了模板参数来同时兼容原生数组和std::array,它甚至可以接受任何编译期确定长度的自定义类型参与拼接。

ConcatArrays函数因为可变参数的语法限制,没有再对std::initializer_list字面量进行适配,这导致std::initializer_list字面量不能再直接作为参数:

constexpr auto x = ConcatArrays(a, {4, 5, 6}); // 编译错误

但是我们有办法规避这个问题:利用前面介绍过的工具把std::initializer_list先转成std::array就可以了:

constexpr auto x = ConcatArrays(a, to_array({4, 5, 6})); // OK

编译期拼接字符串

std::array适合用来表示字符串么?回答这个问题前,我们先看看原生数组是否适合表示字符串:

char str[] = "abc"; // str数组大小为4,包括结尾的'\0'

上面是很常见的写法。由于数组名可退化为指针,str可用于各种需要字符串的场合,如传给cout打印输出。

std::array作为对原生数组的替代,自然也适合用来表示字符串。有人可能会觉得std::array没法直接作为字符串类型使用,不太方便。但实际上只要调用data方法,std::array就会返回能作为字符串使用的指针:

constexpr auto str = to_array("abc"); // to_array可以将字符串转换为std::array
static_assert(str.size() == 4);
static_assert(Equals(str, "abc")); // Equals也可以接受字符串字面量
cout <

由于字符串字面量是char[]类型,因此前面所编写的工具函数,都可以将字符串作为输入参数。上面的Equals只是其中一个例子。

那之前写的数组拼接函数ConcatArrays能用于拼接字符串么?能,但结果和我们想的有差异:

constexpr auto str = ConcatArrays("abc", "def");
static_assert(str.size() == 8); // 长度不是7?
static_assert(Equals(str, {'a', 'b', 'c', '\0', 'd', 'e', 'f', '\0'}));

因为每个字符串结尾都有'\0'结束符,用数组拼接方法把它们拼到一起时,中间的'\0'没有被去掉,导致结果字符串被切割为了多个C字符串。

这个问题解决起来也很容易,只要在拼接数组时把所有数组的最后一个元素('\0')去掉,并且在返回数组的末尾加上'\0'就可以了。下面的代码实现了字符串拼接功能,非类型参数E是字符串的结束符,通常为'\0',但是也允许定制。我们甚至可以利用它来拼接结束符为其他值的对象,比如消息、报文等。

// 最后一个字符,放入结束符
template
constexpr auto PickChar(size_t i)
{
 return E;
}

template
constexpr auto PickChar(size_t i, const P& p, const T&... arr)
{
 if (i <(size(p) - 1)) {
 if (p[i] == E) { // 结束符不允许出现在字符串中间
  throw "terminator in the middle";
 }
 return p[i];
 }
 if (p[size(p) - 1] != E) { // 结束符必须是最后一个字符
 throw "terminator not at end";
 }
 return PickChar(i - (size(p) - 1), arr...);
}

template
constexpr auto ConcatStrings(const T&... str)
{
 return GenerateArray([&str...](size_t i) { 
  return PickChar(i, str...);
  });
}

int main()
{
 constexpr char a[] = "I "; // 原生数组形式的字符串
 constexpr auto b = to_array("love "); // std::array形式的字符串
 constexpr auto str = ConcatStrings(a, b, "C++"); // 拼接 数组 + std::array + 字符串字面量
 static_assert(Equals(str, "I love C++"));
 return 0;
}

这段代码中用了两个throw,这是为了校验输入的参数是否都为合法的字符串,即:字符串长度=容器长度-1。如果不符合该条件,会导致拼接结果的长度计算错误。

当编译期的计算抛出异常时,只会出现编译错误,因此只要不在运行时调用ConcatStrings,这两个throw语句不会有更多影响。但因为这个校验的存在,强烈不建议在运行期调用ConcatStrings做拼接,何况运行期也没必要用这种方法——std::string的加法操作它不香么?

有人会想:能否在编译期计算字符串的实际长度,而不是用容器的长度呢?这个方法看似可行,定义一个编译期计算字符串长度的函数确实很容易:

template
constexpr size_t StrLen(const T& str) noexcept
{
 size_t i = 0;
 while (str[i] != E) {
 ++i;
 }
 return i;
}

constexpr const char* g_str = "abc";

int main()
{
 // 利用StrLen把一个字符串按实际长度转成std::array
 constexpr auto str = SubArray(g_str, 0);
 static_assert(Equals(str, "abc"));
 return 0;
}

但是,一旦你试图把StrLen放到ConcatStrings的内部去声明数组长度,就会产生问题:C++的constexpr机制要求只有在能看到输入参数的constexpr属性的地方,才允许StrLen的返回结果确定为constexpr。而在函数内部时,看到的参数类型并不是constexpr。

当然我们可以变通一下,做出一些有趣的工具,比如使用万恶的宏:

// 把一个字符串按实际长度转成std::array
#define StrToArray(x) SubArray(x, 0)

constexpr const char* g_str = "abc";

int main()
{
 // 使用宏,可以让constexpr指针类型也参与编译期字符串的拼接
 constexpr auto str = ConcatStrings(StrToArray(g_str), "def");
 static_assert(Equals(str, "abcdef"));
 return 0;
}

使用宏以后,ConcatStrings连编译期不确定大小的指针类型都可以间接作为输入了[3]。如果你狠得下心使用变参宏,甚至可以定义出按实际字符串长度计算结果数组长度的更通用拼接函数。但我严重怀疑这种需求的必要性——毕竟我们只是做编译期的拼接,而编译期的字符串不应该会有结束符位置不在末尾的场景。

看到这里的人,或多或少该佩服一下std::array的强大了。上面这些编译期操作,用原生数组很难完成吧?

展望C++20——打破更多的枷锁

我在文章中说了多少次“至少在C++20之前如此”?不记得了,但是能确定的是:C++20会带来很多美好的东西:std::array会有constexpr版本的比较运算符;函数可以用consteval限定只在编译期调用;模板非类型参数允许更多的类型;STL容器对象可以作为constexpr常量……所有这一切,都只是C++20的minor更新而已,在绝大多数的特性介绍中,它们连提都不会被提到!

可想而知,用上C++20以后,编程会发生多大的变化。那时我们再来找找更多有趣的用法

尾注

[1]to_array定义了两个版本,分别以左值引用和右值引用作为参数类型。按照C++11的最优实践,这样的函数本应该只定义一个版本并且使用完美转发。但是to_array的场景如果用万能引用会带来一个问题:C++禁止std::initializer_list字面量{...}被推导为模板类型参数,完美转发方案会导致std::initializer_list字面量不能作为to_array的入参。在后面内容中我们会看到多次这个限制所带来的影响。

[2]C++20加入了consteval修饰符,可以指定函数只允许在编译期调用。

[3] 需要注意的是:constexpr用于修饰指针时,表示的是指针本身为常量(而不是其指向的对象)。和const不同,constexpr并不允许放在类型声明表达式的中间。因此如果要在编译期计算一个constexpr指针指向的字符串长度,这个字符串必须位于静态数据区里,不能位于栈或者堆上(否则其地址无法在编译期确定)。

到此这篇关于C++语言中std::array的用法小结(神器用法)的文章就介绍到这了,更多相关C++中std::array的用法内容请搜索以前的文章或继续浏览下面的相关文章希望大家以后多多支持!


推荐阅读
  • 深入解析Android自定义View面试题
    本文探讨了Android Launcher开发中自定义View的重要性,并通过一道经典的面试题,帮助开发者更好地理解自定义View的实现细节。文章不仅涵盖了基础知识,还提供了实际操作建议。 ... [详细]
  • 深入理解C++中的KMP算法:高效字符串匹配的利器
    本文详细介绍C++中实现KMP算法的方法,探讨其在字符串匹配问题上的优势。通过对比暴力匹配(BF)算法,展示KMP算法如何利用前缀表优化匹配过程,显著提升效率。 ... [详细]
  • 自学编程与计算机专业背景者的差异分析
    本文探讨了自学编程者和计算机专业毕业生在技能、知识结构及职业发展上的不同之处,结合实际案例分析两者的优势与劣势。 ... [详细]
  • 2023年京东Android面试真题解析与经验分享
    本文由一位拥有6年Android开发经验的工程师撰写,详细解析了京东面试中常见的技术问题。涵盖引用传递、Handler机制、ListView优化、多线程控制及ANR处理等核心知识点。 ... [详细]
  • 深入解析 Apache Shiro 安全框架架构
    本文详细介绍了 Apache Shiro,一个强大且灵活的开源安全框架。Shiro 专注于简化身份验证、授权、会话管理和加密等复杂的安全操作,使开发者能够更轻松地保护应用程序。其核心目标是提供易于使用和理解的API,同时确保高度的安全性和灵活性。 ... [详细]
  • 非公版RTX 3080显卡的革新与亮点
    本文深入探讨了图形显卡的进化历程,重点介绍了非公版RTX 3080显卡的技术特点和创新设计。 ... [详细]
  • 深入理解OAuth认证机制
    本文介绍了OAuth认证协议的核心概念及其工作原理。OAuth是一种开放标准,旨在为第三方应用提供安全的用户资源访问授权,同时确保用户的账户信息(如用户名和密码)不会暴露给第三方。 ... [详细]
  • 本文详细探讨了KMP算法中next数组的构建及其应用,重点分析了未改良和改良后的next数组在字符串匹配中的作用。通过具体实例和代码实现,帮助读者更好地理解KMP算法的核心原理。 ... [详细]
  • 优化ListView性能
    本文深入探讨了如何通过多种技术手段优化ListView的性能,包括视图复用、ViewHolder模式、分批加载数据、图片优化及内存管理等。这些方法能够显著提升应用的响应速度和用户体验。 ... [详细]
  • Søren Kierkegaard famously stated that life can only be understood in retrospect but must be lived moving forward. This perspective delves into the intricate relationship between our lived experiences and our reflections on them. ... [详细]
  • 计算机网络复习:第五章 网络层控制平面
    本文探讨了网络层的控制平面,包括转发和路由选择的基本原理。转发在数据平面上实现,通过配置路由器中的转发表完成;而路由选择则在控制平面上进行,涉及路由器中路由表的配置与更新。此外,文章还介绍了ICMP协议、两种控制平面的实现方法、路由选择算法及其分类等内容。 ... [详细]
  • 本文将介绍如何使用 Go 语言编写和运行一个简单的“Hello, World!”程序。内容涵盖开发环境配置、代码结构解析及执行步骤。 ... [详细]
  • 本文探讨了Hive中内部表和外部表的区别及其在HDFS上的路径映射,详细解释了两者的创建、加载及删除操作,并提供了查看表详细信息的方法。通过对比这两种表类型,帮助读者理解如何更好地管理和保护数据。 ... [详细]
  • 深入解析:手把手教你构建决策树算法
    本文详细介绍了机器学习中广泛应用的决策树算法,通过天气数据集的实例演示了ID3和CART算法的手动推导过程。文章长度约2000字,建议阅读时间5分钟。 ... [详细]
  • 机器学习中的相似度度量与模型优化
    本文探讨了机器学习中常见的相似度度量方法,包括余弦相似度、欧氏距离和马氏距离,并详细介绍了如何通过选择合适的模型复杂度和正则化来提高模型的泛化能力。此外,文章还涵盖了模型评估的各种方法和指标,以及不同分类器的工作原理和应用场景。 ... [详细]
author-avatar
茶不离
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有