C++ 注释:数组初始化有一个很好的数组初始化列表。我有一个
int array[100] = {-1};
期望它充满-1但它不是,只有第一个值是,其余的是0与随机值混合。
编码
int array[100] = {0};
工作得很好,并将每个元素设置为 0。
我在这里缺少什么.. 如果值不为零,就不能初始化它吗?
和 2:默认初始化(如上)是否比通过整个数组并分配值的通常循环更快,还是它做同样的事情?
C++ 注释:数组初始化有一个很好的数组初始化列表。我有一个
int array[100] = {-1};
期望它充满-1但它不是,只有第一个值是,其余的是0与随机值混合。
编码
int array[100] = {0};
工作得很好,并将每个元素设置为 0。
我在这里缺少什么.. 如果值不为零,就不能初始化它吗?
和 2:默认初始化(如上)是否比通过整个数组并分配值的通常循环更快,还是它做同样的事情?
使用您使用的语法,
int array[100] = {-1};
表示“将第一个元素设置为-1
,其余元素设置为0
”,因为所有省略的元素都设置为0
.
在 C++ 中,要将它们全部设置为-1
,您可以使用类似std::fill_n
(from <algorithm>
) 的内容:
std::fill_n(array, 100, -1);
在便携式 C 中,您必须滚动自己的循环。有编译器扩展,或者如果可以接受,您可以依赖实现定义的行为作为快捷方式。
gcc 编译器有一个扩展,它允许使用以下语法:
int array[100] = { [0 ... 99] = -1 };
这会将所有元素设置为-1。
这被称为“指定初始化程序”,请参阅此处了解更多信息。
请注意,这不是为 gcc c++ 编译器实现的。
您链接到的页面已经给出了第一部分的答案:
如果指定了显式数组大小,但指定了较短的初始化列表,则未指定的元素设置为零。
没有内置方法可以将整个数组初始化为某个非零值。
至于哪个更快,通常的规则适用:“给予编译器最大自由的方法可能更快”。
int array[100] = {0};
只是告诉编译器“将这 100 个整数设置为零”,编译器可以自由优化。
for (int i = 0; i < 100; ++i){
array[i] = 0;
}
更具体。它告诉编译器创建一个迭代变量i
,告诉它元素应该被初始化的顺序,等等。当然,编译器可能会对其进行优化,但关键是您在这里过度指定了问题,迫使编译器更加努力地获得相同的结果。
最后,如果要将数组设置为非零值,则应该(至少在 C++ 中)使用std::fill
:
std::fill(array, array+100, 42); // sets every value in the array to 42
同样,您可以对数组执行相同的操作,但这更简洁,并为编译器提供了更多自由。你只是说你希望整个数组都用值 42 填充。你没有说它应该以什么顺序完成,或者其他任何事情。
C++11 有另一个(不完美的)选项:
std::array<int, 100> a;
a.fill(-1);
使用 {},您可以在声明元素时分配它们;其余的用 0 初始化。
如果没有= {}
初始化,则内容未定义。
您链接的页面状态
如果指定了显式数组大小,但指定了较短的初始化列表,则未指定的元素设置为零。
速度问题:对于这么小的数组,任何差异都可以忽略不计。如果您使用大型数组并且速度比大小更重要,您可以拥有一个包含默认值的 const 数组(在编译时初始化),然后将memcpy
它们添加到可修改的数组中。
使用std::array
,我们可以在 C++14 中以相当简单的方式做到这一点。只能在 C++11 中执行,但稍微复杂一些。
我们的接口是一个编译时大小和一个默认值。
template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
return std::array<std::decay_t<T>, 0>{};
}
template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}
template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}
第三个函数主要是为了方便,所以用户不必std::integral_constant<std::size_t, size>
自己构造,因为那是一个相当罗嗦的构造。真正的工作是由前两个函数之一完成的。
第一个重载非常简单:它构造了std::array
大小为 0 的 a。不需要复制,我们只需构造它。
第二个重载有点棘手。它沿着它作为源获得的值转发,它还构造一个实例make_index_sequence
并调用其他一些实现函数。该功能是什么样的?
namespace detail {
template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
// Use the comma operator to expand the variadic pack
// Move the last element in if possible. Order of evaluation is well-defined
// for aggregate initialization, so there is no risk of copy-after-move
return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}
} // namespace detail
这通过复制我们传入的值来构造第一个 size - 1 参数。在这里,我们使用可变参数包索引作为扩展的东西。该包中有 size - 1 个条目(正如我们在构造 中指定的那样make_index_sequence
),它们的值是 0、1、2、3、...、size - 2。但是,我们不关心值 (因此我们将其强制转换为 void,以消除任何编译器警告)。参数包扩展将我们的代码扩展为如下所示(假设 size == 4):
return std::array<std::decay_t<T>, 4>{ (static_cast<void>(0), value), (static_cast<void>(1), value), (static_cast<void>(2), value), std::forward<T>(value) };
我们使用这些括号来确保可变参数包扩展...
扩展我们想要的内容,并确保我们使用逗号运算符。如果没有括号,看起来我们正在向数组初始化传递一堆参数,但实际上,我们正在评估索引,将其强制转换为 void,忽略该 void 结果,然后返回值,该值被复制到数组中.
最后一个参数,我们调用的那个std::forward
,是一个小的优化。如果有人传入一个临时的 std::string 并说“制作一个由 5 个组成的数组”,我们希望有 4 个副本和 1 个移动,而不是 5 个副本。std::forward
确保我们这样做。
完整的代码,包括标题和一些单元测试:
#include <array>
#include <type_traits>
#include <utility>
namespace detail {
template<std::size_t size, typename T, std::size_t... indexes>
constexpr auto make_array_n_impl(T && value, std::index_sequence<indexes...>) {
// Use the comma operator to expand the variadic pack
// Move the last element in if possible. Order of evaluation is well-defined
// for aggregate initialization, so there is no risk of copy-after-move
return std::array<std::decay_t<T>, size>{ (static_cast<void>(indexes), value)..., std::forward<T>(value) };
}
} // namespace detail
template<typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, 0>, T &&) {
return std::array<std::decay_t<T>, 0>{};
}
template<std::size_t size, typename T>
constexpr auto make_array_n(std::integral_constant<std::size_t, size>, T && value) {
return detail::make_array_n_impl<size>(std::forward<T>(value), std::make_index_sequence<size - 1>{});
}
template<std::size_t size, typename T>
constexpr auto make_array_n(T && value) {
return make_array_n(std::integral_constant<std::size_t, size>{}, std::forward<T>(value));
}
struct non_copyable {
constexpr non_copyable() = default;
constexpr non_copyable(non_copyable const &) = delete;
constexpr non_copyable(non_copyable &&) = default;
};
int main() {
constexpr auto array_n = make_array_n<6>(5);
static_assert(std::is_same<std::decay_t<decltype(array_n)>::value_type, int>::value, "Incorrect type from make_array_n.");
static_assert(array_n.size() == 6, "Incorrect size from make_array_n.");
static_assert(array_n[3] == 5, "Incorrect values from make_array_n.");
constexpr auto array_non_copyable = make_array_n<1>(non_copyable{});
static_assert(array_non_copyable.size() == 1, "Incorrect array size of 1 for move-only types.");
constexpr auto array_empty = make_array_n<0>(2);
static_assert(array_empty.empty(), "Incorrect array size for empty array.");
constexpr auto array_non_copyable_empty = make_array_n<0>(non_copyable{});
static_assert(array_non_copyable_empty.empty(), "Incorrect array size for empty array of move-only.");
}
将数组初始化为公共值的另一种方法是实际生成一系列定义中的元素列表:
#define DUP1( X ) ( X )
#define DUP2( X ) DUP1( X ), ( X )
#define DUP3( X ) DUP2( X ), ( X )
#define DUP4( X ) DUP3( X ), ( X )
#define DUP5( X ) DUP4( X ), ( X )
.
.
#define DUP100( X ) DUP99( X ), ( X )
#define DUPx( X, N ) DUP##N( X )
#define DUP( X, N ) DUPx( X, N )
可以很容易地将数组初始化为一个公共值:
#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };
注意:引入 DUPx 以启用 DUP 参数中的宏替换
对于单字节元素数组的情况,您可以使用 memset 将所有元素设置为相同的值。
这里有一个例子。
1)当您使用初始化程序时,对于这样的结构或数组,未指定的值本质上是默认构造的。对于像 int 这样的原始类型,这意味着它们将被清零。请注意,这适用于递归:您可以拥有一个包含数组的结构数组,如果您仅指定第一个结构的第一个字段,那么所有其余部分都将使用零和默认构造函数进行初始化。
2) 编译器可能会生成至少与您可以手动执行的一样好的初始化程序代码。如果可能,我倾向于让编译器为我做初始化。
在 C++ 中,也可以使用元编程和可变参数模板。以下帖子展示了如何做到这一点:在 C++ 编译时以编程方式创建静态数组。
在 C++ 编程语言 V4 中,Stroustrup 建议在内置数组上使用向量或 valarray。使用 valarrary,当您创建它们时,您可以将它们初始化为特定值,例如:
valarray <int>seven7s=(7777777,7);
用“7777777”初始化一个长 7 个成员的数组。
这是使用 C++ 数据结构而不是“普通的旧 C”数组来实现答案的 C++ 方式。
我转而使用 valarray 作为我的代码中尝试使用 C++'isms v. C'isms 的尝试。
我希望这是一个标准功能:
#include <stdio.h>
__asm__
(
" .global _arr; "
" .section .data; "
"_arr: .fill 100, 1, 2; "
);
extern char arr[];
int main()
{
int i;
for(i = 0; i < 100; ++i) {
printf("arr[%u] = %u.\n", i, arr[i]);
}
}
在 Fortran 中,您可以这样做:
program main
implicit none
byte a(100)
data a /100*2/
integer i
do i = 0, 100
print *, a(i)
end do
end
不过,据我所知,它没有无符号数字。
我希望现代 C/C++ 将它作为以下代码中执行操作的简写:
#include <stdio.h>
#include <stdint.h>
/* did I count it correctly? I'm not quite sure. */
uint8_t arr[] = {
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
};
int main(int argc, char **argv)
{
int i;
for(i = 0; i < 100; ++i) {
printf("arr[%u] = %u.\n", i, arr[i]);
}
return 0;
}
如果它是一个 1,000,000 字节的数组,那就更令人沮丧了,可能会让人们选择在运行时使用函数进行填充。
它非常便携,没有理由不使用该语言。
只需像这样破解它:
#include <stdio.h>
#include <stdint.h>
/* a byte array of 100 twos declared at compile time. */
uint8_t twos[] = {100:2};
int main()
{
uint_fast32_t i;
for (i = 0; i < 100; ++i) {
printf("twos[%u] = %u.\n", i, twos[i]);
}
return 0;
}
破解它的一种方法是通过预处理......(下面的代码不涵盖边缘情况,但编写是为了快速演示可以做什么。)
#!/usr/bin/perl
use warnings;
use strict;
open my $inf, "<main.c";
open my $ouf, ">out.c";
my @lines = <$inf>;
foreach my $line (@lines) {
if ($line =~ m/({(\d+):(\d+)})/) {
printf ("$1, $2, $3");
my $lnew = "{" . "$3, "x($2 - 1) . $3 . "}";
$line =~ s/{(\d+:\d+)}/$lnew/;
printf $ouf $line;
} else {
printf $ouf $line;
}
}
close($ouf);
close($inf);
note: this is a naive(clumsy knee jerk) answer from 2016, at the time the issues of compatibility didn't hit me, and I really wanted to have this feature and tried to naively "automate" it myself. I no longer want languages to get "cool new features just because I want them" because dealing with backward compatibility from that is a nightmare, and maintaining custom tools to automate conveniences is also not particularly fun.