C ++で配列のすべての要素を1つのデフォルト値に初期化しますか?


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}と同じにする必要があります。これは、副作用として他のすべての要素をゼロにします。Cコンパイラは上記のように動作しないはずです。代わりに、int array [100] = {-1}は最初の要素を-1に設定し、残りを0(ノイズなし)に設定する必要があります。Cでstruct x array [100]がある場合、初期化子として= {0}を使用することは無効です。最初の要素を初期化し、他のすべての要素をゼロにする{{0}}を使用できます。ほとんどの場合、同じものになります。
Fredrik Widlund、2015

1
@FredrikWidlund両方の言語で同じです。{0}構造体や配列の特別なケースではありません。ルールは、イニシャライザのない要素は、イニシャライザの場合と同様に初期化されること0です。ネストされた集約がある場合(例:) struct x array[100]、初期化子は「行優先」の順序で非集約に適用されます。中括弧はオプションでこれを行うことで省略できます。struct x array[100] = { 0 }Cで有効です。の最初のメンバーが初期化子としてstruct X受け入れ0られる限り、C ++でも有効です。
MM

1
{ 0 }Cでは特別ではありませんが、コンストラクターがないため、0暗黙的に変換されて何かに割り当てられるのを止める方法がないため、Cで初期化できないデータ型を定義することははるかに困難です
Leushenko

3
C.には有効でない配列を初期化するには多くのC ++の方法があり、他の質問はC.程度であるため、再度開くために投票
xskxzr

1
また、再オープンに投票-CとC ++は異なる言語
ピート

回答:


350

使用した構文を使用して、

int array[100] = {-1};

省略されたすべての要素がに設定されているため-10「最初の要素をに設定し、残りをに設定する」と述べてい0ます。

C ++では、これらをすべてに設定するには-1std::fill_n(from <algorithm>)のようなものを使用できます。

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

ポータブルCでは、独自のループをロールバックする必要があります。コンパイラー拡張機能があります。それが許容できる場合は、ショートカットとして実装定義の動作に依存できます。


14
また、配列をデフォルト値で「簡単に」埋める方法についての間接的な質問にも答えました。ありがとうございました。
ミラノ、

7
@chessofnerd:正確に#include <algorithm>は、正しいヘッダー<vector>です。間接的に含めるかどうかは、実装によって異なります。
エヴァンテラン2013年

2
実行時に配列を初期化する必要はありません。静的に初期化する必要がある場合は、可変個のテンプレートと可変個のシーケンスを使用して、必要なintsのシーケンスを生成し、それを配列の初期化子に展開することができます。
ボイドポインター

2
@ontherocks、単一の呼び出しを使用してfill_n2D配列全体を埋める正しい方法はありません。もう一方を埋めながら、一方の次元をループする必要があります。
Evan Teran 2013年

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 intをゼロに設定する」ように指示するだけで、コンパイラはこれを自由に最適化できます。

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を使用します。
エヴァンテラン

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はあまり良いアイデアではありません。これは、値を直接高速に設定するだけの場合に相当するためです。
エヴァン・テラン

1
コピーとconst配列の必要性がわかりません。値が事前に入力された変更可能な配列を最初に作成してみませんか?
Johannes Schaub-litb 09年

速度の説明に感謝し、速度が大きな配列サイズ(私の場合)の問題である場合の実行方法
Milan

初期化子リストはコンパイル時に実行され、実行時にロードされます。物事をコピーする必要はありません。
マーティンヨーク

@ litb、@ Evan:たとえば、gccは最適化を有効にしても動的初期化(大量のmovs)を生成します。以下のために大規模な配列と厳しい性能要件、あなたはコンパイル時に初期化を行いたいです。memcpyは、多くの単純なmovsだけよりも、大規模なコピー向けに最適化されている可能性があります。
laalto 2009年

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

注:DUPへのパラメーターでマクロ置換を有効にするために導入されたDUPx



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

3番目の関数は主に便宜上のものであるため、ユーザーは std::integral_constant<std::size_t, size>自分自身。実際の作業は、最初の2つの関数のいずれかによって行われます。

最初のオーバーロードは非常に簡単です。std::arrayサイズ0を作成します。コピーは必要ありません。作成するだけです。

2番目のオーバーロードは少しトリッキーです。ソースとして取得した値に沿って転送し、インスタンスを作成して、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)サイズ-1のエントリがあり、それらの値は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つの配列を作成する」と言った場合、5つのコピーではなく、4つのコピーと1つの移動が必要です。はこれを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] = {}; すぎます。「本質的にデフォルトの構成」=>「構成された値」、tho。しかし、これは、int、PODS、またはユーザーが宣言したctorを持つ型の場合には関係ありません。ユーザーが宣言したctorを持たない非ポッドについてのみ、私が知っていることは重要です。しかし、私はこれのために反対票を投じないでしょう!とにかく、+ 1してもう一度0にする:)
Johannes Schaub-litb 2009年

@エヴァン:「イニシャライザを使用するとき...」でステートメントを修飾しました。初期化されていない値については言及していません。@Martin:定数、静的、またはグローバルデータで機能する可能性があります。しかし、それが次のようにどのように機能するかはわかりません。inttest(){int i [10] = {0}; int v = i [0]; i [0] = 5; 戻り値v; コンパイラーは、test()を呼び出すたびにi []をゼロに初期化するほうがよいでしょう。
Boojum

静的データセグメントにデータを配置し、 "i"で参照できるようにすることができます:)
Johannes Schaub-litb 2009年

True-技術的には、この場合、 "i"を完全に省略して0を返すこともできます。ただし、可変データに静的データセグメントを使用すると、マルチスレッド環境では危険です。私がマーティンに答えて作ろうとしたのは、初期化のコストを完全に排除することはできないということです。静的データセグメントから作成済みのチャンクをコピーしてください。
Boojum


0

C ++プログラミング言語V4では、Stroustrupは組み込み配列ではなくベクトルまたはvalarrayを使用することをお勧めします。valarraryを使用すると、それらを作成するときに、次のような特定の値に初期化できます。

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

"7777777"を使用して7メンバー長のアレイを初期化します。

これは、「単純な古いC」配列の代わりにC ++データ構造を使用して回答を実装するC ++の方法です。

私はコードでC ++ 'isms対C'isms ...を使用する試みとして、valarrayを使用するように切り替えました。


これは、私が今まで見たタイプの使用方法の2番目に悪い例です...
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バイトの配列の場合はどうなりますか?私のためにそれを書くためのスクリプトを書くか、アセンブリなどを使ってハックする必要があります。これはナンセンスです。

それは完全に移植可能です、それが言語にない理由はありません。

次のようにハックするだけです:

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

それをハッキングする1つの方法は、前処理を使用することです...(以下のコードはエッジケースをカバーしていませんが、何ができるかをすばやく示すために書かれています。)

#!/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 2017年

1
ループ内に割り当てると、ランタイムオーバーヘッドが発生します。一方、バッファは既にバイナリに埋め込まれているため、バッファのハードコーディングは無料です。プログラムを実行するたびに、最初から配列を構築するのに時間を無駄にすることはありません。ループでの印刷は全体的に良い考えではありませんが、printfの呼び出しごとにシステムコールが必要ですが、アプリケーションのヒープ/スタックを使用した文字列の連結では必要ないため、ループ内に追加して1回印刷する方が適切です。この種のプログラムのサイズは問題ではないので、ランタイムではなくコンパイル時にこの配列を作成するのが最善です。
ドミトリー

「ループ内に割り当てると、ランタイムオーバーヘッドが発生します」-オプティマイザを大幅に過小評価します。
Asu

配列のサイズに応じて、gccとclangは値を「ハードコード」またはトリックします。より大きな配列でmemsetは、「ハードコードされた」配列であっても、それだけで直接です。
Asu
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.