C++笔记:数组初始化列出了一个很好的数组初始化列表。我有一个
int array[100] = {-1};
我预期它应该完全由-1组成,但实际上只有第一个值是-1,其余都是0和随机值混合。
代码:
int array[100] = {0};
这段代码完全正常,并将每个元素设置为0。
我错过了什么吗?如果值不为零,不能初始化吗?
其次,这种默认的初始化方式(如上所述)比通常需要遍历整个数组并赋值的方法更快,还是它们执行的是相同的操作?
C++笔记:数组初始化列出了一个很好的数组初始化列表。我有一个
int array[100] = {-1};
我预期它应该完全由-1组成,但实际上只有第一个值是-1,其余都是0和随机值混合。
代码:
int array[100] = {0};
这段代码完全正常,并将每个元素设置为0。
我错过了什么吗?如果值不为零,不能初始化吗?
其次,这种默认的初始化方式(如上所述)比通常需要遍历整个数组并赋值的方法更快,还是它们执行的是相同的操作?
使用你所使用的语法,
int array[100] = {-1};
因为所有省略的元素都被设置为0
,所以“将第一个元素设置为-1
,其余元素设置为0
”。
在C++中,要将它们全部设置为-1
,您可以使用类似于std::fill_n
(来自<algorithm>
)的东西:
std::fill_n(array, 100, -1);
在可移植的 C 语言中,你需要自己编写循环。如果可以接受的话,可以使用编译器扩展或依赖于实现定义的行为作为快捷方式。
#include <algorithm>
才是正确的头文件,<vector>
可能会间接包含它,具体取决于你的实现。 - Evan Teranint
序列,并将其展开到数组的初始化器中。 - void-pointerfill_n
调用来填充整个2D数组。您需要在一个维度上循环,同时填写另一个维度。 - Evan Teranstd::fill_n
不是初始化。 - Ben Voigt有一个扩展可以在gcc编译器中使用这种语法:
int array[100] = { [0 ... 99] = -1 };
这将把所有元素都设置为-1。你链接的页面已经回答了第一个问题:
如果指定了显式数组大小,但指定了较短的初始化列表,则未指定的元素将设置为零。
没有内置的方法可以将整个数组初始化为某个非零值。
至于哪种方法更快,通常的规则适用:“给编译器最大自由度的方法可能更快”。
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,而不需要说明应该按照何种顺序进行,或者其他任何信息。
C++11有另一个(不完美的)选择:
std::array<int, 100> a;
a.fill(-1);
std::fill(begin(a), end(a), -1)
- justyystd::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
。不需要复制,我们只需构造它即可。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使用 {} 可以将元素分配为它们声明的那样;其余部分将被初始化为 0。
如果没有 = {}
进行初始化,则内容是未定义的。
你链接的页面说明:
如果指定了显式数组大小,但指定了较短的初始化列表,则未指定的元素将设置为零。
速度问题:对于这么小的数组,任何差异都可以忽略不计。如果您使用大型数组,并且速度比大小更重要,则可以拥有默认值的const数组(在编译时初始化),然后将它们使用memcpy
复制到可修改的数组中。
另一种将数组初始化为通用值的方法是实际上生成一系列定义元素的列表:
#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 ) };
最简单的方法是使用std::array
,编写一个函数模板,该模板将返回所需的std::array
,并将所有元素初始化为传递的参数,如下所示。
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,您可以将 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;
}
}
{0}
对于结构体和数组都不是特殊情况。规则是没有初始化器的元素会被初始化为如果它们有一个初始化器,则为0
。如果有嵌套聚合体(例如struct x array[100]
),则初始化器按“行主”顺序应用于非聚合体;在这样做时,可以选择省略大括号。struct x array[100] = { 0 }
在 C 中是有效的;在 C++ 中也是有效的,只要struct X
的第一个成员接受0
作为初始化器即可。 - M.M{ 0 }
并不是特殊的,但是要定义一个不能用它进行初始化的数据类型却更加困难,因为没有构造函数,也就无法阻止0
被隐式转换并赋值给某些东西。 - Alex Celeste