プレーン配列の範囲ベースの作業はどのように行われますか?


87

C ++ 11では、他の言語のforように機能する範囲ベースを使用できますforeach。プレーンなC配列でも機能します。

int numbers[] = { 1, 2, 3, 4, 5 };
for (int& n : numbers) {
    n *= 2;
}

いつ停止するかをどうやって知るのですか?forが使用されているのと同じスコープで宣言されている静的配列でのみ機能しますか?これforを動的配列でどのように使用しますか?


10
CまたはC ++自体には「動的」配列はありません。配列タイプがあり、配列または動的に割り当てられたメモリブロックを指す場合とそうでない場合があり、ほとんどが配列のように動作します。タイプT [n]の配列の場合、そのサイズはタイプにエンコードされており、によってアクセスできますfor。しかし、配列がポインタに減衰する瞬間、サイズ情報は失われます。
JohannesD

1
あなたの例でnumberssizeof(numbers)/sizeof(int)、の要素の数は、たとえばです。
JohannesD

回答:


57

これは、型が配列であるすべての式に対して機能します。例えば:

int (*arraypointer)[4] = new int[1][4]{{1, 2, 3, 4}};
for(int &n : *arraypointer)
  n *= 2;
delete [] arraypointer;

より詳細な説明のために、の右側に渡される:式の型が配列型である場合、ループはptrからptr + sizeptr配列の最初の要素を指し、配列sizeの要素数)から繰り返されます。

これは、調べても作業ユーザー定義型とは対照的であるbeginend非メンバ関数(そのように呼ばれる何のメンバーが存在しない場合)は、クラスオブジェクトまたはを渡す場合メンバーとして。これらの関数は、開始イテレータと終了イテレータを生成します(それぞれ最後の要素とシーケンスの開始の直後を指します)。

この質問は、なぜその違いが存在するのかを明らかにします。


8
私は質問だったと思います、それは、ない動作したときに、それが動作しない
sehe

1
@sehe質問には複数の「?」が含まれていました。1つは「それは...で動作しますか?」でした。私は両方の説明どのようにしてたときにそれが動作します。
Johannes Schaub-litb 2011年

8
@JohannesSchaub:ここでの「方法」の問題は、そもそも配列型のオブジェクトのサイズを正確に取得することだと思います(ポインターと配列の混乱のため、配列のサイズプログラマーが利用できます。)
JohannesD

非メンバーの「endstd :: begin use the member関数」のみを検索し、より適切な一致が利用できない場合に使用されると思います。begin. It just happens that `std::end
Dennis Zickefoose 2011年

3
@Dennis no in Madridは、それを変更し、開始メンバーと終了メンバーを優先することが決定されました。開始メンバーと終了メンバーを支持しないことは、回避するのが難しいあいまいさを引き起こしました。
Johannes Schaub-litb 2011年

44

この質問の最も重要な部分は、C ++が配列のサイズをどのように知っているかということだと思います(少なくとも、この質問を見つけたときに知りたかったのです)。

C ++は配列のサイズを認識しています。これは、配列の定義の一部であり、変数の型であるためです。コンパイラは型を知っている必要があります。

C ++ 11std::extentを使用して配列のサイズを取得できるため、次のようになります。

int size1{ std::extent< char[5] >::value };
std::cout << "Array size: " << size1 << std::endl;

もちろん、これはあまり意味がありません。最初の行でサイズを明示的に指定する必要があり、それを2番目の行で取得するからです。しかし、使用することもできdecltype、それからそれはより面白くなります:

char v[] { 'A', 'B', 'C', 'D' };
int size2{ std::extent< decltype(v) >::value };
std::cout << "Array size: " << size2 << std::endl;

6
これは確かに私が最初に尋ねていたものです。:)
ポールマンタ

19

最新のC ++ワーキングドラフト(n3376)によると、rangedforステートメントは次のようになります。

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

したがって、forイテレータを使用した通常のループと同じ方法で停止する方法を知っています。

ポインタとサイズのみで構成される配列(動的配列)で上記の構文を使用する方法を提供するために、次のようなものを探していると思います。

template <typename T>
class Range
{
public:
    Range(T* collection, size_t size) :
        mCollection(collection), mSize(size)
    {
    }

    T* begin() { return &mCollection[0]; }
    T* end () { return &mCollection[mSize]; }

private:
    T* mCollection;
    size_t mSize;
};

次に、このクラステンプレートを使用して範囲を作成し、その範囲を新しいrangedfor構文を使用して繰り返すことができます。これを使用して、配列へのポインターとサイズを個別の値として返すだけのライブラリを使用してインポートされたシーン内のすべてのアニメーションオブジェクトを実行しています。

for ( auto pAnimation : Range<aiAnimation*>(pScene->mAnimations, pScene->mNumAnimations) )
{
    // Do something with each pAnimation instance here
}

私の意見では、この構文は、使用する構文std::for_eachや単純なforループよりもはるかに明確です。


3

静的配列の境界を知っているので、いつ停止するかを知っています。

「動的配列」とはどういう意味かわかりません。いずれにせよ、静的配列を反復処理しない場合、コンパイラーは非公式に、反復処理するオブジェクトのクラスの名前beginendスコープを検索します。以下のためのアップbegin(range)end(range)引数依存の検索やイテレータなどの用途にそれらを使用します。

詳細については、C ++ 11標準(またはその公開ドラフト)の「6.5.4範囲ベースのforステートメント」、145ページを参照してください。


4
「動的配列」は、で作成されたものになりnew[]ます。その場合、サイズが示されていないポインタしかないため、範囲ベースでポインタを操作する方法はありませんfor
マイクシーモア

私の答えには、コンパイル時にサイズ(4)がわかっている動的配列が含まれていますが、「動的配列」の解釈が質問者の意図したものであるかどうかはわかりません。
Johannes Schaub-litb 2011年

3

プレーン配列の範囲ベースの作業はどのように行われますか?

それは、と読むことです「を教えてくださいどのような範囲であった-用(配列で)いますか?

私はそれを仮定して答えます-ネストされた配列を使用して次の例を見てください:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};

for (auto &pl : ia)

テキストバージョン:

iaは配列の配列(「ネストされた配列」)であり、[3]配列を含み、それぞれに[4]値が含まれています。上記の例は、そのiaプライマリ '範囲'([3])によってループスルーするため、[3]時間をループします。各ループは、最初から最後までia[3]プライマリ値の1つを生成します-[4]値を含む配列。

  • 最初のループ:pl等しい{1,2,3,4}-配列
  • 2番目のループ:pl等しい{5,6,7,8}-配列
  • 3番目のループ:pl等しい{9,10,11,12}-配列

プロセスを説明する前に、配列に関するいくつかのわかりやすい注意事項を次に示します。

  • 配列は最初の値へのポインタとして解釈されます-反復なしで配列を使用すると、最初の値のアドレスが返されます
  • pl 配列をコピーできないため、参照である必要があります
  • 配列の場合、配列オブジェクト自体に数値を追加すると、その数だけ前方に進み、「ポイント」が同等のエントリを指します-nが問題の数値である場合は、(エントリであるアドレスを逆参照しています)ia[n]と同じ*(ia+n)です。nforward)でありia+n&ia[n](配列内のそのエントリのアドレスを取得しています)と同じです。

何が起こっているのか:

  • 各ループに、plのように設定されている基準ia[n]して、n0だからから起動電流ループ回数に等しい、plであるia[0]ことがだ第二に、最初のラウンドでia[1]、など。反復によって値を取得します。
  • ia+n未満である限り、ル​​ープは続きend(ia)ます。

...そしてそれはそれについてです。

これは本当にこれを書くための単純化された方法です:

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int n = 0; n != 3; ++n)
  auto &pl = ia[n];

配列ネストされていない場合、反復値は配列ではなく「通常の」値であるため、このプロセスは参照が不要であるという点で少し単純になります。

 int ib[3] = {1,2,3};

 // short
 for (auto pl : ib)
   cout << pl;

 // long
 for (int n = 0; n != 3; ++n)
   cout << ib[n];

いくつかの追加情報

auto作成時にキーワードを使用したくない場合はどうなりplますか?それはどのように見えるでしょうか?

次の例でplは、を参照しますarray of four integers。各ループplで値が与えられますia[n]

int ia[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
for (int (&pl)[4] : ia)

そして...それはそれがどのように機能するかであり、混乱を取り除くための追加情報があります。これはfor、自動的にカウントされる単なる「短縮」ループですが、手動で実行せずに現在のループを取得する方法がありません。


@Andyタイトルの10回のうち9回は、Google /どの検索でも一致するものです-タイトルは、これらどのように機能するかを尋ねます。いつ停止するかがわかりませんか?。それでも、暗示されている根本的な質問、この回答である程度カバーされており、他の回答を探している他の人のために回答し続けます。このような構文の質問には、検索者が質問を見つけるために必要なすべての情報であるため、それだけを使用して回答を記述できるように、タイトルを表現する必要があります。あなたは確かに間違っていません-質問は本来あるべきタイトルが付けられていません。
スーパーキャット

0

スタック上の配列とヒープ上の配列の違いを示すサンプルコード


/**
 * Question: Can we use range based for built-in arrays
 * Answer: Maybe
 * 1) Yes, when array is on the Stack
 * 2) No, when array is the Heap
 * 3) Yes, When the array is on the Stack,
 *    but the array elements are on the HEAP
 */
void testStackHeapArrays() {
  int Size = 5;
  Square StackSquares[Size];  // 5 Square's on Stack
  int StackInts[Size];        // 5 int's on Stack
  // auto is Square, passed as constant reference
  for (const auto &Sq : StackSquares)
    cout << "StackSquare has length " << Sq.getLength() << endl;
  // auto is int, passed as constant reference
  // the int values are whatever is in memory!!!
  for (const auto &I : StackInts)
    cout << "StackInts value is " << I << endl;

  // Better version would be: auto HeapSquares = new Square[Size];
  Square *HeapSquares = new Square[Size];   // 5 Square's on Heap
  int *HeapInts = new int[Size];            // 5 int's on Heap

  // does not compile,
  // *HeapSquares is a pointer to the start of a memory location,
  // compiler cannot know how many Square's it has
  // for (auto &Sq : HeapSquares)
  //    cout << "HeapSquare has length " << Sq.getLength() << endl;

  // does not compile, same reason as above
  // for (const auto &I : HeapInts)
  //  cout << "HeapInts value is " << I << endl;

  // Create 3 Square objects on the Heap
  // Create an array of size-3 on the Stack with Square pointers
  // size of array is known to compiler
  Square *HeapSquares2[]{new Square(23), new Square(57), new Square(99)};
  // auto is Square*, passed as constant reference
  for (const auto &Sq : HeapSquares2)
    cout << "HeapSquare2 has length " << Sq->getLength() << endl;

  // Create 3 int objects on the Heap
  // Create an array of size-3 on the Stack with int pointers
  // size of array is known to compiler
  int *HeapInts2[]{new int(23), new int(57), new int(99)};
  // auto is int*, passed as constant reference
  for (const auto &I : HeapInts2)
    cout << "HeapInts2 has value " << *I << endl;

  delete[] HeapSquares;
  delete[] HeapInts;
  for (const auto &Sq : HeapSquares2) delete Sq;
  for (const auto &I : HeapInts2) delete I;
  // cannot delete HeapSquares2 or HeapInts2 since those arrays are on Stack
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.