关于c ++:使用一个默认值初始化普通数组

Initialization of a normal array with one default value

本问题已经有最佳答案,请猛点这里访问。

C++初始化:数组初始化在数组初始化时有一个很好的列表。我有一个

1
int array[100] = {-1};

期望它充满-1,但不是,只有第一个值是,其余的是0和随机值的混合。

代码

1
int array[100] = {0};

工作正常,并将每个元素设置为0。

我错过了什么……如果值不是零,就不能初始化它吗?

2:默认初始化(如上所述)是否比整个数组的常规循环更快并赋值,或者它做的是相同的事情?


使用您使用的语法,

1
int array[100] = {-1};

表示"将第一个元素设置为-1,将其余元素设置为0",因为所有省略的元素都设置为0

在C++中,将它们全部设置为EDCOX1(1),可以使用像EDCOX1,5(EDCOX1,6)那样的东西:

1
std::fill_n(array, 100, -1);

在便携式的C中,你必须滚动你自己的循环。有编译器扩展,或者如果可以接受的话,可以将实现定义的行为视为快捷方式。


GCC编译器有一个扩展,允许以下语法:

1
int array[100] = { [0 ... 99] = -1 };

这会将所有元素设置为-1。

这被称为"指定初始值设定项"。有关详细信息,请参阅此处。

注意这不是GCC C++编译器实现的。


您链接到的页面已经给出了第一部分的答案:

If an explicit array size is
specified, but an shorter
initiliazation list is specified, the
unspecified elements are set to zero.

没有内置方法将整个数组初始化为某个非零值。

至于哪个更快,通常的规则适用:"给编译器最大自由度的方法可能更快。"

1
int array[100] = {0};

只需告诉编译器"将这100个整数设置为零",编译器就可以自由地进行优化。

1
2
3
for (int i = 0; i < 100; ++i){
  array[i] = 0;
}

更具体一些。它告诉编译器创建一个迭代变量i,告诉它元素初始化的顺序,等等。当然,编译器可能会对此进行优化,但重点是,这里您对问题进行了过度指定,迫使编译器更加努力地工作以获得相同的结果。

最后,如果要将数组设置为非零值,则应该(在C++中至少使用EDCOX1)9:

1
std::fill(array, array+100, 42); // sets every value in the array to 42

同样,您也可以对数组执行同样的操作,但这更简洁,并且给编译器更多的自由。您只是想让整个数组都充满值42。你什么都不说应该按什么顺序去做,或者别的什么。


C++ 11有另一个(不完美)选项:

1
2
std::array<int, 100> a;
a.fill(-1);

使用可以按声明的方式分配元素;其余的元素初始化为0。

如果没有= {}初始化,则内容未定义。


您链接的页面显示

If an explicit array size is specified, but an shorter initiliazation list is specified, the unspecified elements are set to zero.

速度问题:对于这么小的阵列,任何差异都可以忽略不计。如果您使用大型数组,并且速度比大小重要得多,那么可以使用默认值的const数组(在编译时初始化),然后将它们添加到可修改的数组中。


将数组初始化为公共值的另一种方法是实际生成一系列定义中的元素列表:

1
2
3
4
5
6
7
8
9
10
11
#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 )

可以很容易地将数组初始化为公共值:

1
2
#define LIST_MAX 6
static unsigned char List[ LIST_MAX ]= { DUP( 123, LIST_MAX ) };

注意:引入dupx是为了在参数中启用对dup的宏替换。


使用EDCOX1(0),我们可以在C++ 14中用一种相当简单的方式来实现这一点。可以在C++ 11中进行,但稍微复杂一些。

我们的接口是编译时大小和默认值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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,因为这是一个相当复杂的结构。真正的工作是由前两个函数之一完成的。

第一个重载非常简单:它构造一个大小为0的std::array。没有必要复制,我们只是构造它。

第二次过载有点棘手。它沿着它得到的作为源的值前进,它还构造了一个make_index_sequence的实例,并调用了一些其他的实现函数。这个函数是什么样子的?

1
2
3
4
5
6
7
8
9
10
11
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

这将通过复制传入的值来构造第一个大小为-1的参数。这里,我们使用变量参数包索引作为扩展的内容。包中有大小为-1的条目(如我们在make_index_sequence的构造中指定的那样),它们的值为0、1、2、3、…、大小为-2。但是,我们不关心这些值(因此我们将其强制转换为void,以消除任何编译器警告)。参数包扩展将我们的代码扩展为类似的类型(假定大小=4):

1
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) };

我们使用这些括号来确保variadic pack expansion ...扩展我们想要的内容,并确保我们使用逗号运算符。如果没有括号,看起来像是在向数组初始化传递一系列参数,但实际上,我们正在评估索引,将其强制转换为void,忽略该void结果,然后返回复制到数组中的值。

最后一个论点,我们称之为std::forward,是一个小的优化。如果有人通过了一个临时的STD::字符串,并说"制作5个数组",我们希望有4个拷贝和1个移动,而不是5个拷贝。std::forward确保我们这样做。

完整的代码,包括头和一些单元测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include
#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.");
}


对于单字节元素数组,可以使用memset将所有元素设置为相同的值。

这里有一个例子。


在C++中,也可以使用元编程和可变模板。下面的帖子展示了如何做到这一点:在C++编译时以编程方式创建静态数组。


1)当使用初始值设定项时,对于结构或类似的数组,未指定的值基本上是默认构造的。对于像int这样的原始类型,这意味着它们将被归零。请注意,这是递归应用的:可以有一个包含数组的结构数组,如果只指定第一个结构的第一个字段,那么其余的所有字段都将用零和默认构造函数初始化。

2)编译器可能会生成初始值设定项代码,该代码至少与手工操作的效果相同。如果可能的话,我倾向于让编译器为我进行初始化。


在C++编程语言V4中,Stroustrup建议在构建数组上使用向量或Val阵列。使用valarrary,当您创建它们时,可以将它们初始化为特定的值,如:

1
valarray <int>seven7s=(7777777,7);

用"7777777"初始化数组7的长成员。

这是用C++数据结构实现答案的C++方式,而不是一个"普通的老C"数组。

我切换到使用ValStube作为我的代码中的一个尝试,尝试使用C++的ISS诉CISISS…


应该是一个标准的特性,但出于某种原因,它不包含在标准的C和C++中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#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.
"
, i, arr[i]);
    }
}

在Fortran中,您可以执行以下操作:

1
2
3
4
5
6
7
8
9
10
11
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++为什么不能实现它呢?真的这么难吗?为了达到同样的结果,必须手动编写这个命令是很愚蠢的…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#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 i;

    for(i = 0; i < 100; ++i) {
        printf("arr[%u] = %u.
"
, i, arr[i]);
    }
}

如果是1000,00字节的数组呢?我需要写一个脚本来为我写它,或者使用汇编等工具进行黑客攻击。这是胡说八道。

它是完全可移植的,没有理由不使用这种语言。

把它黑客化,就像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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.
"
, i, twos[i]);
    }

    return 0;
}

一种方法是通过预处理…(下面的代码不包括边缘案例,而是用来快速演示可以做什么。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/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);