私はC++の初心者です。を繰り返し処理できないのはなぜですか、ここでint*
の使用は何&
ですか、このネストされた範囲ベースの for はどのように深く実行されるのでしょうか?
int arr[10][3];
for (auto &i : arr)
{
for (auto j : i)
{
//sth
}
}
まず、 の正確なデータ型を知る必要があり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]
への参照を宣言しています。int
arr
イテレータを使用してこの例を書くこともできます。
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-declarationとstatementは、基本的に、未解決の範囲ベースの for ループからコピー アンド ペーストされます。残り ( begin-expr、end-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];