在C ++中将数组的所有元素初始化为一个默认值?


248

C ++注意:数组初始化数组初始化方面有一个不错的清单。我有一个

int array[100] = {-1};

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

代码

int array[100] = {0};

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

我在这里想念的是什么。如果值不为零,无法初始化吗?

和2:默认初始化(如上所述)是否比遍历整个数组并分配值的常规循环更快?


1
C和C ++中的行为是不同的。在C中,{0}是结构初始化程序的一种特殊情况,但是AFAIK不适用于数组。int array [100] = {0}应该与array [100] = {[0] = 0}相同,其副作用是将所有其他元素清零。AC编译器的行为不应如上所述,而int array [100] = {-1}应该将第一个元素设置为-1,将其余元素设置为0(无噪声)。在C中,如果您具有struct x array [100],则将= {0}用作初始化程序无效。您可以使用{{0}}来初始化第一个元素,将所有其他元素归零,在大多数情况下将是同一件事。
Fredrik Widlund

1
@FredrikWidlund两种语言都相同。{0}对于结构或数组都不是特殊情况。规则是不使用初始化程序的元素将被初始化,就像初始化程序需要的一样0。如果存在嵌套的聚合(例如struct x array[100]),则将初始化程序以“行优先”顺序应用于非聚合;可以选择忽略括号。struct x array[100] = { 0 }在C中有效;并且在C ++中有效,只要的第一个成员struct X接受0为初始化即可。
MM

1
{ 0 }在C中不是特殊的,但是定义一个不能用它初始化的数据类型要困难得多,因为没有构造函数,因此也没有办法阻止0隐式转换并赋值给某物
Leushenko '16

3
投票重新开启,因为另一个问题是关于C.有许多C ++的方式来初始化数组是无效的C.
xskxzr

1
还投票支持重新开放-C和C ++是不同的语言
皮特

Answers:


350

使用您使用的语法,

int array[100] = {-1};

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

在C ++中,要将它们全部设置为-1,可以使用std::fill_n(from <algorithm>)之类的东西:

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

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


14
这也回答了有关如何“轻松”地使用默认值填充数组的间接问题。谢谢。
米兰

7
@chessofnerd:不完全#include <algorithm>是正确的标头,<vector>它是否可以间接包含或不包含,取决于您的实现。
埃文·特兰

2
您不必在运行时初始化数组。如果您确实需要初始化以静态方式进行,则可以使用可变参数模板和可变参数序列生成所需的ints 序列并将其扩展到数组的初始化程序中。
void-pointer

2
@ontherocks,没有正确的方法来使用单个调用fill_n来填充整个2D数组。您需要在一个维度上循环,而在另一个维度上进行填充。
埃文·特兰

7
这是对其他问题的解答。 std::fill_n不是初始化。
Ben Voigt 2014年

133

gcc编译器有一个扩展,它允许使用以下语法:

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

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

这称为“指定的初始化程序”,请参见此处以获取更多信息。

注意,这不是为gcc c ++编译器实现的。


2
太棒了 此语法似乎也可以在clang中使用(因此可以在iOS / Mac OS X上使用)。
JosephH 2014年

31

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

如果指定了显式数组大小,但指定了较短的初始化列表,则未指定的元素将设置为零。

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

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

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。您什么也没有说要按什么顺序完成。


5
好答案。注意,在C ++中(不是在C中),您可以执行int array [100] = {};。并赋予编译器最大的自由度:)
Johannes Schaub-litb

1
同意,很好的答案。但是对于固定大小的数组,它将使用std :: fill_n :-P。
Evan Teran 2009年

12

C ++ 11还有另一个(不完善的)选项:

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

std::fill(begin(a), end(a), -1)
doctorlai

9

使用{},您可以在声明元素时分配它们;其余的初始化为0。

如果没有= {}要初始化的内容,则内容是不确定的。


8

您链接的页面状态

如果指定了显式数组大小,但指定了较短的初始化列表,则未指定的元素将设置为零。

速度问题:对于这么小的阵列,任何差异都可以忽略不计。如果使用大型数组,并且速度比大小重要得多,则可以使用默认值的const数组(在编译时进行初始化),然后memcpy将其更改为可修改的数组。


2
memcpy并不是一个好主意,因为这相当于直接以速度为单位直接设置值。
Evan Teran

1
我看不到需要复制和const数组:为什么不首先使用预填充的值创建可修改的数组?
Johannes Schaub-litb

感谢您对速度的说明,以及在阵列大小较大时出现速度问题时该如何做(在我的情况下)
米兰

初始化列表在编译时完成,并在运行时加载。无需去复制任何东西。
马丁·约克

@ litb,@ Evan:例如,即使启用了优化,gcc也会生成动态初始化(很多movs)。对于大型阵列和严格的性能要求,您希望在编译时进行初始化。对于大型副本,memcpy可能比单独的许多简单动作更好地进行了优化。
laalto

4

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

#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的参数中启用宏替换



3

使用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

这将通过复制传入的值来构造第一个大小-1个参数。在这里,我们使用可变参数包索引来扩展内容。该数据包中有大小-1个条目(如我们在的构造中所指定make_index_sequence),并且它们的值分别为0、1、2、3,...,大小-2。但是,我们并不关心这些值(因此我们将其强制转换为void,以使所有编译器警告均无效)。参数包扩展将我们的代码扩展为如下所示(假设大小== 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.");
}

您的non_copyable类型实际上可以通过来复制operator=
赫兹

我想non_copy_constructible这将是该对象的更准确的名称。但是,此代码中的任何地方都没有分配,因此对于本示例而言,这无关紧要。
David Stone

1

1)使用初始化程序时,对于类似的结构或数组,未指定的值实质上是默认构造的。对于像int这样的基本类型,这意味着它们将被清零。请注意,这是递归应用的:您可以有一个包含数组的结构数组,并且如果仅指定第一个结构的第一个字段,则所有其余结构将使用零和默认构造函数初始化。

2)编译器可能会生成初始化代码,该代码至少与您手工完成的代码一样好。如果可能的话,我倾向于让编译器为我进行初始化。


1)POD的默认初始化在这里没有发生。使用该列表,编译器将在编译时生成值,并将其放在程序集的特殊部分中,该部分作为程序初始化的一部分(如代码)加载。因此,运行时成本为零。
马丁·约克

1
我看不出他哪里错了?int a [100] = {}当然被初始化为全0,而不管它出现在哪里,而struct {int a; } b [100] = {}; 也是。“基本上是默认构造的” =>“值构造的”,等等。但这对于int,PODS或带有用户声明的ctor的类型都没有关系。据我所知,这仅适用于没有用户声明的ctor的NON-Pods。但是我不会因此而投下反对票!无论如何,请+1为您再次使其变为0 :)
Johannes Schaub-litb

@Evan:我用“当您使用初始化程序时...”来限定我的语句,我不是在指未初始化的值。@马丁:这可能适用于恒定,静态或全局数据。但是我看不到它如何与类似的东西一起工作:int test(){int i [10] = {0}; int v = i [0]; i [0] = 5; 返回v; }每次调用test()时,编译器最好将i []初始化为零。
Boojum,2009年

它可以将数据放入静态数据段中,并用“ i”引用它:)
Johannes Schaub-litb

正确–从技术上讲,在这种情况下,它也可以完全省略“ i”并仅返回0。但是在多线程环境中,将静态数据段用于可变数据将很危险。我要回答Martin的要点仅仅是,您不能完全消除初始化的成本。当然,可以从静态数据段复制一个预制块,但是它仍然不是免费的。
Boojum,2009年


0

在C ++编程语言V4中,Stroustrup建议在内置数组上使用向量或valarray。使用valarrary,在创建它们时,可以将它们初始化为特定值,例如:

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

要初始化一个数组,其长度为7个成员,名称为“ 7777777”。

这是一种使用C ++数据结构而不是“普通C语言”数组来实现答案的C ++方法。

我改用valarray作为代码尝试使用C ++'isms v。C'isms...。


这是如何使用我见过的类型的第二个最糟糕的例子……
Steazy

-3

应该是标准功能,但是由于某种原因它没有包含在标准C或C ++中...

#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 i;

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

如果它是一个1,000,00字节的数组怎么办?我需要编写一个脚本来为我编写脚本,或者使用Assembly / etc进行破解。这是无稽之谈。

它完全可移植,没有理由不使用该语言。

只需像这样入侵即可:

#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);

您正在循环打印,为什么不能在循环中分配?
Abhinav Gauniyal

1
在循环内分配会导致运行时开销;而对缓冲区进行硬编码是免费的,因为该缓冲区已经嵌入到二进制文件中,因此它不会浪费时间在每次程序运行时从头开始构建数组。您说对了,尽管在循环中打印并不是一个好主意,但最好在循环中附加一个然后再打印一次,因为每个printf调用都需要系统调用,而使用应用程序堆/堆栈的字符串连接则不需要。由于此类程序的大小不是问题,因此最好在编译时而不是运行时构造此数组。
德米特里(Dmitry)

“在循环内分配会导致运行时开销”-您确实严重低估了优化器。
阿苏

根据数组的大小,gcc和clang将“硬编码”或欺骗其中的值,对于较大的数组memset,即使使用“硬编码”数组也是如此。
阿苏
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.