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

341

C++笔记:数组初始化列出了一个很好的数组初始化列表。我有一个

int array[100] = {-1};

我预期它应该完全由-1组成,但实际上只有第一个值是-1,其余都是0和随机值混合。

代码:

int array[100] = {0};

这段代码完全正常,并将每个元素设置为0。

我错过了什么吗?如果值不为零,不能初始化吗?

其次,这种默认的初始化方式(如上所述)比通常需要遍历整个数组并赋值的方法更快,还是它们执行的是相同的操作?


1
C和C++的行为是不同的。在C中,{0}是结构体初始化器的特殊情况,但据我所知,对于数组则不是。int array[100]={0}应该与array[100]={[0]=0}相同,这将副作用地将所有其他元素都设置为零。C编译器不应该像上面描述的那样行为,而是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 作为初始化器即可。 - M.M
1
在C语言中,{ 0 }并不是特殊的,但是要定义一个不能用它进行初始化的数据类型却更加困难,因为没有构造函数,也就无法阻止0被隐式转换并赋值给某些东西 - Alex Celeste
3
投票重新开放,因为另一个问题是关于C语言的。有许多用于初始化数组的C++方法,在C语言中是无效的。 - xskxzr
1
也投票支持重新开放 - C和C ++是不同的语言。 - Pete
我删除了C标签,因为这个问题显然是关注于C++的,并相应地编辑了问题的标题。现在问题可以重新打开,因为它不再是一个重复的C问题。对于那些将来遇到这个问题并可能想查看同样的C问题的人,这里是链接:如何将数组的所有成员初始化为相同的值? - RobertS supports Monica Cellio
12个回答

453

使用你所使用的语法,

int array[100] = {-1};

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

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

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

在可移植的 C 语言中,你需要自己编写循环。如果可以接受的话,可以使用编译器扩展或依赖于实现定义的行为作为快捷方式。


24
谢谢。这也回答了一个间接的问题,即如何“轻松”地填充数组默认值。 - Milan
9
并不完全正确,#include <algorithm> 才是正确的头文件,<vector> 可能会间接包含它,具体取决于你的实现。 - Evan Teran
2
你不必在运行时初始化数组。如果你真的需要静态地进行初始化,可以使用可变模板和可变序列来生成所需的 int 序列,并将其展开到数组的初始化器中。 - void-pointer
2
@ontherocks,没有一种正确的方法可以使用单个fill_n调用来填充整个2D数组。您需要在一个维度上循环,同时填写另一个维度。 - Evan Teran
8
这是对另一个问题的回答。std::fill_n 不是初始化。 - Ben Voigt
显示剩余6条评论

156

有一个扩展可以在gcc编译器中使用这种语法:

int array[100] = { [0 ... 99] = -1 };
这将把所有元素都设置为-1。
这被称为“指定初始化项”,进一步的信息请参见这里
请注意,这在gcc c++编译器中未被实现。

4
太棒了。这种语法在clang中似乎也可以使用(因此可以在iOS / Mac OS X上使用)。 - JosephH

44

你链接的页面已经回答了第一个问题:

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

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

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

int array[100] = {0};

这只是让编译器“将这100个整数设置为零”,而编译器可以自由地进行优化。

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

for(int i=0; i<n; ++i)比较具体,它告诉编译器创建一个迭代变量i,告诉它元素初始化的顺序等等。 当然,编译器可能会优化掉这些细节,但重点在于这里你过度指定了问题,迫使编译器更加努力地获得相同的结果。

最后,如果您想将数组设置为非零值,在C++中,您应该使用std::fill:

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

你也可以使用数组来完成相同的操作,但这种方式更加简洁,并且给编译器更多自由度。你只需要告诉它你想要整个数组都填满值42,而不需要说明应该按照何种顺序进行,或者其他任何信息。


8
好的回答。请注意,在C++中(而非C语言),您可以执行int array[100] = {}; 并给编译器最大的自由度 :) - Johannes Schaub - litb
1
同意,答案很棒。但对于固定大小的数组,它会使用std::fill_n :-P。 - Evan Teran

22

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

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

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

12
使用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>,因为这是一个相当冗长的构造方式。真正的工作由前两个函数中的一个完成。
第一个重载很简单:它构造了一个大小为0的std::array。不需要复制,我们只需构造它即可。
第二个重载有点棘手。它将其获得的值作为源进行转发,并且还构造了一个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.");
}

你的 non_copyable 类型实际上可以通过 operator= 进行复制。 - Hertz
@Hertz:显式默认移动构造函数会导致复制和移动赋值运算符被隐式删除。不过,在这个测试用例中并不重要,因为没有赋值操作。 - David Stone

10

使用 {} 可以将元素分配为它们声明的那样;其余部分将被初始化为 0。

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


8

你链接的页面说明:

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

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


2
使用memcpy并不是一个很好的选择,因为在速度上与直接设置值相当。 - Evan Teran
1
我认为不需要复制和const数组:为什么不一开始就创建带有预填值的可修改数组呢? - Johannes Schaub - litb
感谢您对速度的解释,以及如果速度是一个问题,如何处理大数组大小(这在我的情况下是) - Milan
初始化列表在编译时完成并在运行时加载。无需复制任何内容。 - Martin York
@laalto,如果是这样的话,那么就是一个bug:根据C++标准,编译器必须对这些完全使用常量表达式(-1,...)初始化的POD(int [N])应用静态初始化。我想你指的是int data[] = { 1, 2, 3 };。当然,我在这里谈论的是具有命名空间范围的对象。 - Johannes Schaub - litb
显示剩余6条评论

6

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

#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进行操作。

5

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

这里有一个例子(链接)


3

最简单的方法是使用std::array,编写一个函数模板,该模板将返回所需的std::array,并将所有元素初始化为传递的参数,如下所示。

C++11版本

template<std::size_t N> std::array<int, N> make_array(int val)
{
    std::array<int, N> tempArray{};
    for(int &elem:tempArray)
    {
        elem = val;
    }
    return tempArray;
}
int main()
{
    //---------------------V-------->number of elements  
    auto arr  = make_array<8>(5);
    //------------------------^---->value of element to be initialized with

    
    //lets confirm if all objects have the expected value 
    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl; //prints all 5 
    }
    
}

工作演示


C++17 版本

使用 C++17,您可以将 constexpr 添加到函数模板中,以便它可以在 constexpr 上下文中使用:

//-----------------------------------------vvvvvvvvv--->added constexpr
template<std::size_t N> std::array<int, N> constexpr make_array(int val)
{
    std::array<int, N> tempArray{};
    for(int &elem:tempArray)
    {
        elem = val;
    }
    return tempArray;
}
int main()
{
//--vvvvvvvvv------------------------------>constexpr added
    constexpr auto arr  = make_array<8>(5);

    for(const auto &elem: arr)
    {
        std::cout << elem << std::endl;
    }
    
}

Working demo


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接