3

私はC++の初心者です。を繰り返し処理できないのはなぜですか、ここでint*の使用は何&ですか、このネストされた範囲ベースの for はどのように深く実行されるのでしょうか?

int arr[10][3];

for (auto &i : arr)
{
    for (auto j : i)
    {
        //sth
    }
}
4

1 に答える 1

6

まず、 の正確なデータ型を知る必要がありint arr[10][3];ます。3 の 10 配列の配列intです。

ループは通常、多次元コンテナーの 1 つの次元を反復します。

for(int i = 0; i < 10; ++i)
{
    for(int j = 0; j < 3; ++j)
    {
        arr[i][j] = 0;
    }
}

最初のループは 10 の配列をX反復し、次に 2 番目のループは を反復します。Xここでは3 の配列ですint

次のステップは、これXをコードで明示的に使用することです。

for(int i = 0; i < 10; ++i)
{
    int (&x)[3] = arr[i];  // you won't see this syntax often

    for(int j = 0; j < 3; ++j)
    {
        int &elem = x[j];
        elem = 0;
    }
}

この行は、多次元配列の最初のレベルにアクセスした結果である3 の配列int (&x)[3]への参照を宣言しています。intarr

イテレータを使用してこの例を書くこともできます。

for(int (*px)[3] = arr; px != arr+10; ++px)
{
    // `px` is a _pointer to an array of 3 `int`_
    // `*px` then is an _array of 3 `int`_

    for(int *pelem = *px; pelem != (*px)+3; ++pelem)
    {
        *pelem = 0;
    }
}

ここでは、配列をその最初の要素へのポインターに変換する機能を使用していることに注意してください。これは減衰と呼ばれます: 配列は (その配列の最初の要素への) ポインターに減衰されます/できます。

int my_arr[3];
int *p = my_arr;  // `p` now points to the first element of `my_arr`
     p = &my_arr[0]; // equivalent

多次元配列の場合、これは次のようになります。

int arr[10][3];
int (*p)[3];    // a pointer to an _array of 3 `int`_
p = arr;        // `p` now points to the first element of `arr`, i.e.
                // the first _array of 3 `int`_

最後になりましたが、多次元配列の場合、次のように書くこともできます。

for(int *pelem = arr[0]; pelem != arr[0]+10*3; ++pelem)
{
    *pelem = 0;
}

ただし、多次元配列はメモリ内で連続して配置され、多次元配列のメモリ レイアウトが指定されているため、これは多次元配列でのみ可能です。

のようなコンテナではこれは不可能ですがvector<vector<int>>

vector<int> v = {1,2,3,4,5};
for(int* i = &v[0]; i != &v[0] + 5; ++i)
{
    *i = 0;
}

整形式であり、未定義の動作はありません。


同じロジックが範囲ベースの for ループに適用されるようになりました。

for(int (&x)[3] : arr)
{
    for(int &elem : x)
    {
        elem = 0;
    }
}

範囲ベースの for ループを持つことの要点は、明示的な反復子を取り除くことです。はそのような反復子であるため、 IMOint*を反復処理する範囲ベースの for ループを使用しても意味がありません。int*


このネストされた範囲ベースの for は、どのように深く実行されますか?

C++ 言語の標準では、[stmt.ranged] の範囲ベースの for ステートメントを次のように定義しています (少し単純化したことに注意してください)。

for ( for範囲 宣言: 式文 )

は次のように解決されます:

{
    for ( auto __begin = /*begin-expr*/,
               __end = /*end-expr*/;
          __begin != __end;
          ++__begin )
    {
        /*for-range-declaration*/ = *__begin;
        /*statement*/
    }
}

where for-range-declarationstatementは、基本的に、未解決の範囲ベースの for ループからコピー アンド ペーストされます。残り ( begin-exprend-expr ) には複雑な部分があります。簡略化したバージョンを次に示します。

{
    using std::begin;
    using std::end;

    for ( auto __begin = begin(/*expression*/),
               __end = end(/*expression*/);
          __begin != __end;
          ++__begin )
    {
        /*for-range-declaration*/ = *__begin;
        /*statement*/
    }
}

範囲ベースの for ループの私の例は、から解決されます

for(int (&x)[3] : arr)
{
    /*statements*/
}

{
    using std::begin;
    using std::end;

    for ( auto __begin = begin(arr),
               __end = end(arr);
          __begin != __end;
          ++__begin )
    {
        int (&x)[3] = *__begin;
        /*statements*/
    }
}

または、begin/end呼び出しを解決することによって:

{
    for ( int (*__begin)[3] = arr,
               __end = arr + 10;
          __begin != __end;
          ++__begin )
    {
        int (&x)[3] = *__begin;           // (A)
        /*statements*/
    }
}

でマークされた行は、例のが必要な(A)理由も示しています。&for (int x[3] : arr)

int arr[10][3];
int (&x)[3] = arr[0];   // well-formed
int   x [3] = arr[0];   // ill-formed for arrays

次のような例からわかるように、raw/C スタイルの配列を直接割り当てることはできません。

int my_arr[10];
int my_sec_arr[10] = my_arr;  // not legal, ill-formed

これが、参照を使用する必要がある理由です。

標準ライブラリの のような他のコンテナstd::arrayでは、参照を避けることができます:

std::array<int, 10> my_arr;
std::array<int, 10> my_sec_arr = my_arr;  // well-formed

ただし、割り当てはコピーを意味するため、配列全体をコピーする必要がありました。ここでの参照はコピーを必要としません。


Yakkがコメントで指摘したように、これはあなたの例で&が必要な理由ではありません。しかし、ご覧のとおり、配列をポインターに減衰させるため、2 回目の反復は失敗します。for (auto &i : arr)auto &i = arr[0];int (*i)[3] = arr[0];auto

for(auto i : arr)
{
    // type of `i` now is _pointer to an array of 3 `int`_
    for(auto j : i) // can't iterate over a pointer: what are the boundaries?
    {
        /* ... */
    }
}

もう少し正確に言うと、コンパイラは配列内にいくつの要素があるかを知っているので、配列を反復処理できます。これは型の一部であり、たとえばarray of 3intであり、型はコンパイラに認識されています。

ポインターの場合、コンパイラーは、ポインターが単一の要素を参照しているのか、要素の配列を参照しているのかを知りません。後者の場合、その配列の大きさはわかりません。いずれにせよ、型はちょうど、例えばへのポインタintです:

int my_arr[10];
int my_int;

int *p;
p = my_arr;
p = &my_int;
p = new int[25];
于 2013-07-28T12:27:48.357 に答える