HOWTO · C++

C++ でリストを繰り返す

これは、リストデータ構造と、C++ でリストデータ構造を反復処理する方法についての簡単なチュートリアルです。

このページの内容

C++ の std::list を走査するには、begin() でイテレータを初期化し、end() と異なる間、各要素を処理したあとにイテレータをインクリメントします。イテレータをデリファレンスすると、現在の値を読み取ったり変更したりできます。ループは end() に達する前に終了します。end() は末尾の要素を指すイテレータではなく、末尾の次を示す位置です。

C++ の std::list とは?

std::list は、C++ 標準ライブラリの双方向連結リストコンテナです。要素は隣接するノード同士がリンクされた状態で格納されるため、std::vector や配列の要素のように、メモリ上の連続した領域を占めません。この構造では前後両方向への走査ができ、対象位置のイテレータがすでにある場合は、その位置への要素の挿入や削除を定数時間で行えます。

リストには items[4] のような直接の添字アクセスはありません。位置を指定して要素にたどり着くには、リスト内でイテレータを進める必要があり、所要時間は移動距離に比例します。また、各要素には値に加えてリンクを格納する領域も必要です。既知の位置での挿入や削除が役立つ場合は std::list が適しています。一方、メモリをコンパクトに使いたい場合や添字アクセスを頻繁に行う場合は、std::vector のほうが扱いやすいことがよくあります。

C++ には std::forward_list という別の単方向連結コンテナもあり、前方向にのみ走査できます。これは std::list とは異なるコンテナです。詳しくは双方向連結リストの記事を参照してください。

C++ の std::list を走査する方法

begin() が返すイテレータは最初の要素を指します。end() は最後の要素の次を指すため、ループの終了条件として使いますが、デリファレンスしてはいけません。イテレータをインクリメントすると、次の要素へ進みます。

次の例を実行します。

このセクションのコードと表示されるラベルや出力は英語のままです。

#include <iostream>
#include <list>

int main() {
    std::list<int> scores{12, 18, 25};

    for (std::list<int>::iterator it = scores.begin(); it != scores.end(); ++it) {
        std::cout << *it << '\n';
    }
}
12
18
25

ここでは、it は scores.begin() から始まります。ループを1回実行するたびに、*it で現在の値を読み取り、続いて ++it で次のノードへ進みます。イテレータが scores.end() に達すると条件が偽になり、末尾の次の位置を読み取ることなくループが終了します。

非 const のイテレータを使うと、*it に代入して要素を変更できます。たとえば、ループ本体で *it += 1; とすると、現在の値が1増えます。値を訪問するだけなら、範囲ベースの for ループを使えます: for (int score : scores)。この形式で値を変更するには、各要素を参照で受け取ります: for (int& score : scores)。現在位置の要素を削除する場合のように、イテレータ自体が必要なときは、明示的なイテレータループを使います。

走査中に要素を削除するときは、次の要素から続けられるように、erase が返すイテレータを代入します。削除した要素を指すイテレータは無効になりますが、ほかの std::list 要素を指すイテレータは有効なままです。