266

次の 2 行のコードを見てください。

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

この:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

2番目の方法が好ましいと言われています。これはなぜですか?

4

27 に答える 27

230

最初の形式は、 vector.size() が高速な操作である場合にのみ効率的です。これはベクトルには当てはまりますが、たとえばリストには当てはまりません。また、ループの本体内で何をする予定ですか? 次のように要素にアクセスする予定がある場合

T elem = some_vector[i];

operator[](std::size_t)次に、コンテナが定義したと仮定しています。繰り返しますが、これはベクターには当てはまりますが、他のコンテナーには当てはまりません。

イテレータを使用すると、コンテナの独立性に近づきます。ランダムアクセス機能や高速size()操作については仮定していませんが、コンテナーにイテレーター機能があることだけを想定しています。

標準アルゴリズムを使用して、コードをさらに拡張できます。達成しようとしていることに応じてstd::for_each()std::transform()などを使用することを選択できます。明示的なループではなく標準のアルゴリズムを使用することで、車輪の再発明を避けることができます。あなたのコードはより効率的で (適切なアルゴリズムが選択されている場合)、正しく、再利用可能です。

于 2008-09-25T03:10:27.047 に答える
56

これは、最新の C++ 教化プロセスの一部です。イテレーターは、ほとんどのコンテナーを反復処理する唯一の方法であるため、適切な考え方を身に付けるためだけにベクターでも使用します。真剣に、それが私がそうする唯一の理由です。ベクターを別の種類のコンテナに置き換えたことはないと思います。


うわー、これは3週間経ってもまだ反対票を投じています。少し冗談を言っても無駄だと思います。

配列インデックスの方が読みやすいと思います。これは、他の言語で使用される構文や、旧式の C 配列に使用される構文と一致します。また、冗長性も低くなります。コンパイラが優れている場合、効率は問題になるはずであり、とにかくそれが問題になるケースはほとんどありません。

それでも、ベクターでイテレータを頻繁に使用していることに今でも気付きます。イテレータは重要な概念であると信じているので、できる限り推進しています。

于 2008-09-25T03:35:42.867 に答える
53

コードを some_vector リストの特定の実装に結び付けていないためです。配列インデックスを使用する場合は、何らかの形式の配列でなければなりません。イテレータを使用すると、そのコードを任意のリスト実装で使用できます。

于 2008-09-25T03:02:54.537 に答える
35

some_vector がリンクリストで実装されていると想像してください。次に、i 番目の場所にある項目を要求するには、ノードのリストをトラバースするために i 回の操作を行う必要があります。ここで、イテレータを使用する場合、一般的に言えば、可能な限り効率的になるように最善を尽くします (リンクされたリストの場合、現在のノードへのポインタを維持し、各反復でそれを進めます。単一操作)。

したがって、次の 2 つのことが提供されます。

  • 使用の抽象化: いくつかの要素を反復したいだけで、それを行う方法は気にしません
  • パフォーマンス
于 2008-09-25T03:08:40.200 に答える
27

私はここで悪魔の擁護者になり、イテレータはお勧めしません。主な理由は、デスクトップアプリケーションの開発からゲームの開発まで、私が取り組んできたすべてのソースコードに、イテレータを使用する必要がないことです。常にそれらは必要とされておらず、第二に、イテレータで発生する隠れた仮定とコードの混乱、およびデバッグの悪夢は、速度を必要とするアプリケーションでそれを使用しないための代表的な例です。

メンテナンスの観点からさえ、彼らは混乱しています。それはそれらのせいではなく、舞台裏で起こるすべてのエイリアシングのせいです。標準とはまったく異なることを行う独自の仮想ベクトルまたは配列リストを実装していないことをどのように知ることができますか。実行時に現在どのタイプが使用されているか知っていますか?オペレーターをオーバーロードしましたか?すべてのソースコードをチェックする時間がありませんでした。地獄私はあなたが使用しているSTLのバージョンさえ知っていますか?

イテレータで発生する次の問題は、リークのある抽象化ですが、これについて詳細に説明しているWebサイトは多数あります。

申し訳ありませんが、イテレータのポイントはまだ確認されていません。彼らがあなたから離れてリストまたはベクトルを抽象化する場合、実際にはあなたがどのベクトルまたはあなたが扱っているリストをすでに知っているべきであるかを知らなければ、あなたは将来いくつかの素晴らしいデバッグセッションのためにあなた自身をセットアップするつもりです。

于 2008-09-25T04:53:04.583 に答える
24

ベクトルを繰り返し処理しているときに項目をベクトルに追加/削除する場合は、反復子を使用することをお勧めします。

some_iterator = some_vector.begin(); 
while (some_iterator != some_vector.end())
{
    if (/* some condition */)
    {
        some_iterator = some_vector.erase(some_iterator);
        // some_iterator now positioned at the element after the deleted element
    }
    else
    {
        if (/* some other condition */)
        {
            some_iterator = some_vector.insert(some_iterator, some_new_value);
            // some_iterator now positioned at new element
        }
        ++some_iterator;
    }
}

インデックスを使用している場合は、挿入と削除を処理するために、配列内でアイテムを上下にシャッフルする必要があります。

于 2008-09-25T03:23:12.833 に答える
16

関心事の分離

反復コードをループの「コア」の問題から分離することは非常に良いことです。それはほとんど設計上の決定です。

実際、インデックスによる反復は、コンテナーの実装に結び付けられます。コンテナに開始イテレータと終了イテレータを要求すると、ループ コードを他のコンテナ タイプで使用できるようになります。

また、std::for_each途中で、内部について何かを尋ねるのではなく、何をすべきかをコレクションに伝えます。

0x 標準はクロージャを導入する予定で、これによりこのアプローチがより使いやすくなります - たとえば Ruby の表現力を見てください[1..6].each { |i| print i; }...

パフォーマンス

しかし、おそらく見過ごされがちな問題は、このfor_eachアプローチを使用すると、反復を並列化する機会が得られることです。インテルのスレッド化ブロックは、システム内のプロセッサの数にコード ブロックを分散できます!

注:algorithmsライブラリ、特にを発見した後foreach、仲間の開発者を夢中にさせる途方もなく小さな「ヘルパー」演算子構造体を 2 ~ 3 か月書きました。この後、私は実用的なアプローチに戻りました-小さなループ本体はもう価値がありませforeachん:)

イテレータに関する必読の参考書は、「Extended STL」という本です。

GoF には Iterator パターンの最後に小さな段落があり、この一連の反復について説明しています。これは「内部イテレータ」と呼ばれます。こちらもご覧ください。

于 2008-09-27T20:05:00.090 に答える
15

よりオブジェクト指向だからです。想定しているインデックスを使用して反復している場合:

a) それらのオブジェクトが順序付けられている
こと b) それらのオブジェクトがインデックスによって取得できること
c) インデックスのインクリメントがすべてのアイテムにヒットすること
d) そのインデックスがゼロから始まること

イテレータを使用すると、基礎となる実装が何であるかを知らなくても、「それで作業できるようにすべてを渡してください」と言っています。(Java では、インデックスを介してアクセスできないコレクションがあります)

また、反復子を使用すると、配列の範囲外になることを心配する必要はありません。

于 2008-09-25T03:04:54.920 に答える
15

他のすべての優れた回答は別として...intベクターには十分な大きさではない可能性があります。代わりに、インデックス作成を使用する場合はsize_type、コンテナーに を使用します。

for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
    Foo& this_foo = myvector[i];
    // Do stuff with this_foo
}
于 2008-09-25T03:19:21.487 に答える
15

イテレータのもう 1 つの優れた点は、const 設定をより適切に表現 (および強制) できることです。この例では、ループの途中でベクトルを変更しないようにします。


for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
    // Foo & foo = *pos; // this won't compile
    const Foo & foo = *pos; // this will compile
}
于 2008-09-25T03:24:32.613 に答える
12

電話をかけることもできます。

std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);

于 2008-09-26T11:44:35.730 に答える
8

STLイテレータはほとんどが存在するため、sortなどのSTLアルゴリズムはコンテナに依存しません。

ベクトル内のすべてのエントリをループするだけの場合は、インデックスループスタイルを使用します。

ほとんどの人間にとって、タイピングが少なく、解析が簡単です。テンプレートの魔法でやり過ぎずに、C++に単純なforeachループがあればいいのにと思います。

for( size_t i = 0; i < some_vector.size(); ++i )
{
   T& rT = some_vector[i];
   // now do something with rT
}
'
于 2008-09-25T13:36:12.267 に答える
5

ベクトルと大差ないと思います。私は自分でインデックスを使用することを好みます。インデックスの方が読みやすく、必要に応じて 6 アイテムを前方にジャンプしたり、後方にジャンプしたりするなどのランダム アクセスを実行できるからです。

また、次のようにループ内のアイテムへの参照を作成することも好きなので、場所の周りに多くの角括弧がありません:

for(size_t i = 0; i < myvector.size(); i++)
{
    MyClass &item = myvector[i];

    // Do stuff to "item".
}

将来のある時点でベクトルをリストに置き換える必要があると思われる場合は、イテレータを使用するのが良い場合があります。また、STL フリークにはよりスタイリッシュに見えますが、他の理由は考えられません。

于 2008-09-25T03:03:05.410 に答える
3

インデックス作成には追加のmul操作が必要です。たとえば、 の場合vector<int> v、コンパイラは に変換v[i]&v + sizeof(int) * iます。

于 2009-05-08T04:47:42.890 に答える
3

この回答の主題についてもう少し学んだ後、私はそれが少し単純化しすぎていることに気付きました. このループの違い:

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
    some_iterator++)
{
    //do stuff
}

そして、このループ:

for (int i = 0; i < some_vector.size(); i++)
{
    //do stuff
}

かなり最小限です。実際、この方法でループを実行する構文は、私の中で成長しているようです。

while (it != end){
    //do stuff
    ++it;
}

イテレータはかなり強力な宣言型機能のロックを解除し、STL アルゴリズム ライブラリと組み合わせると、配列インデックス管理の範囲外でかなりクールなことを行うことができます。

于 2008-12-16T22:46:17.620 に答える
3

2 番目の形式は、何をしているのかをより正確に表しています。あなたの例では、 i の値は気にしません。必要なのは、イテレータの次の要素だけです。

于 2008-09-25T03:20:39.283 に答える
2

反復中に、処理するアイテムの数を知る必要はありません。アイテムが必要なだけで、イテレータはそのようなことを非常にうまく行います。

于 2008-09-25T04:25:11.937 に答える
2

C++11機能にアクセスできる場合は、次のように範囲ベースのforループを使用してベクター (またはその他のコンテナー) を反復処理することもできます。

for (auto &item : some_vector)
{
     //do stuff
}

このループの利点は、item変数を介してベクトルの要素に直接アクセスできることです。インデックスを台無しにしたり、反復子を逆参照するときに間違いを犯したりするリスクはありません。さらに、プレースホルダーautoを使用すると、コンテナー要素の型を繰り返す必要がなくなり、コンテナーに依存しないソリューションにさらに近づきます。

ノート:

  • ループ内に要素インデックスが必要であり、operator[]コンテナーに存在する (そして十分に高速である) 場合は、最初の方法を使用することをお勧めします。
  • 範囲ベースのforループを使用して、コンテナーへの要素の追加/コンテナーからの要素の削除を行うことはできません。それを行いたい場合は、Brian Matthewsが提供する解決策に固執することをお勧めします。
  • コンテナー内の要素を変更したくない場合は、キーワードconstを次のように使用する必要がありますfor (auto const &item : some_vector) { ... }
于 2020-02-21T12:29:15.493 に答える
1

すでにいくつかの良い点。追加のコメントがいくつかあります。

  1. C++ 標準ライブラリについて話していると仮定すると、「ベクトル」は、C 配列の保証 (ランダム アクセス、連続したメモリ レイアウトなど) を持つランダム アクセス コンテナーを意味します。「some_container」と言った場合、上記の回答の多くはより正確になります (コンテナーの独立性など)。

  2. コンパイラの最適化への依存を排除​​するには、次のように、インデックス付きコードのループから some_vector.size() を移動できます。

    const size_t numElems = some_vector.size();
    for (size_t i = 0; i
  3. 常にイテレータをプリインクリメントし、ポストインクリメントを例外的なケースとして扱います。

for (some_iterator = some_vector.begin(); some_iterator != some_vector.end(); ++some_iterator){ //処理を行う }

したがって、コンテナのようにインデックス可能であると仮定するとstd::vector<>、コンテナを順番に通過して、一方を優先する正当な理由はありません。古いまたは新しい要素インデックスを頻繁に参照する必要がある場合は、インデックス付きバージョンの方が適切です。

一般に、アルゴリズムは反復子を使用し、反復子の型を変更することで動作を制御 (および暗黙的に文書化) できるため、反復子の使用が推奨されます。反復子の代わりに配列の場所を使用できますが、構文上の違いが目立ちます。

于 2008-09-25T08:30:47.667 に答える
1

foreach ステートメントが嫌いなのと同じ理由で、イテレータは使用しません。複数の内部ループがある場合、すべてのローカル値と反復子名も覚えておかなくても、グローバル/メンバー変数を追跡するのは非常に困難です。私が便利だと思うのは、さまざまな場合に 2 つのインデックス セットを使用することです。

for(int i=0;i<anims.size();i++)
  for(int j=0;j<bones.size();j++)
  {
     int animIndex = i;
     int boneIndex = j;


     // in relatively short code I use indices i and j
     ... animation_matrices[i][j] ...

     // in long and complicated code I use indices animIndex and boneIndex
     ... animation_matrices[animIndex][boneIndex] ...


  }

たとえば、「animation_matrixs[i]」のようなものを、ランダムな「anim_matrix」という名前のイテレータに省略したくありません。これは、この値がどの配列から生成されたのかを明確に確認できないためです。

于 2009-07-13T07:27:58.570 に答える
1
  • 金属に近づきたい/実装の詳細を信頼しない場合は、イテレータを使用しないでください。
  • 開発中にあるコレクション タイプを別のコレクション タイプに定期的に切り替える場合は、反復子を使用します。
  • さまざまな種類のコレクションを反復する方法を覚えるのが難しい場合 (おそらく、いくつかの異なる外部ソースからのいくつかの型を使用している場合)、反復子を使用して、要素をたどる手段を統一します。これは、リンクされたリストを配列リストで切り替えるなどに当てはまります。

本当に、それだけです。どちらの方法でも平均してより簡潔になるというわけではありません。簡潔にすることが本当に目標である場合は、いつでもマクロに頼ることができます。

于 2015-11-15T12:58:07.240 に答える
0

コンテナの独立性のために

于 2008-09-25T11:14:58.830 に答える
0

私のアプリケーションの多くは「サムネイル画像の表示」のようなものを必要とするため、私は常に配列インデックスを使用します。だから私はこのようなものを書いた:

some_vector[0].left=0;
some_vector[0].top =0;<br>

for (int i = 1; i < some_vector.size(); i++)
{

    some_vector[i].left = some_vector[i-1].width +  some_vector[i-1].left;
    if(i % 6 ==0)
    {
        some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
        some_vector[i].left = 0;
    }

}
于 2008-09-25T06:30:54.053 に答える
0

どちらの実装も正しいですが、私は「for」ループの方が好きです。他のコンテナーではなくベクターを使用することにしたので、インデックスを使用するのが最善の選択肢です。ベクトルでイテレータを使用すると、アクセスを容易にする連続したメモリ ブロックにオブジェクトを配置する利点が失われます。

于 2012-07-02T11:04:57.043 に答える
0

「CPUに何をすべきかを伝える」(必須)よりもさらに優れているのは、「必要なものをライブラリに伝える」(機能的)ことです。

したがって、ループを使用する代わりに、stl に存在するアルゴリズムを学ぶ必要があります。

于 2008-09-25T04:58:46.410 に答える