C++入門|listとイテレータ

listは戦士の隊列を自在に組み替える修行名簿、イテレータはその隊列をたどるためのスカウターです。

STLには、たくさんのデータを効率よく管理するための便利な仕組みがそろっています。
前回の vector は、戦闘力の記録を横一列の管理ボードに並べていくような感覚で使える、とても扱いやすいクラスでした。

今回学ぶ list は、同じく複数のデータをまとめて扱えるクラスですが、発想が少し違います。
list は、配列のように番号で管理するというより、戦士たちが順番に並ぶ修行隊列のようなイメージです。先頭に新しい戦士を加えたり、途中に別の戦士を差し込んだりといった操作が得意です。

ただし、list では vector のように 添字 を使って直接要素へアクセスすることはできません。
その代わりに登場するのが イテレータ です。イテレータは、今どの要素を見ているのかを示す目印で、ドラゴンボールの世界観でたとえるなら、隊列の中のどの戦士にスカウターの照準を合わせているかを表す仕組みだと考えるとわかりやすいです。

ここでは、list にデータを追加する方法、途中に要素を挿入する方法、そしてイテレータを使って全要素を順番に表示する方法を、修行隊列の管理というイメージでじっくり見ていきます。

listクラスとは何か

list は、STLに用意されているコンテナの1つです。
コンテナとは、複数のデータをまとめて管理するための入れ物のことです。

vector と同じように、list でも 型 を指定して使います。たとえば int 型のデータを並べたいなら list、文字列を並べたいなら list という形で宣言します。

今回のテーマである list は、前後への追加や途中への挿入がしやすいのが特徴です。
その反面、vector のように data[2] のような書き方で3番目の要素にアクセスすることはできません。

この違いを、修行システムにたとえて整理すると次のようになります。

クラスイメージ得意なことアクセス方法
vector番号付きの修行データボード何番目のデータかをすぐ取り出す添字でアクセス
list戦士がつながった修行隊列先頭・途中・末尾への追加や挿入イテレータでたどる

vector は整然と並んだ訓練パネル、list は戦士が前後につながる隊列、と考えると違いがつかみやすくなります。

図:vectorとlistの違い

この図が示していること

この図では、vector が番号付きで管理しやすい構造であるのに対し、list は要素同士がつながっている隊列型の構造であることを表しています。
そして list では、添字ではなくイテレータを使って要素を順番にたどる、という大切な違いも視覚的に確認できます。

listを使った基本サンプル

それでは、実際に list を使ってみましょう。
ここでは、修行場に集まった戦士たちの戦闘力を、隊列の順に管理するプログラムを作ります。

最初に末尾へ 1000 と 2000 を追加し、そのあと先頭へ 5000 を追加します。
さらに、イテレータを使って途中に 3000 を挿入してみます。

プロジェクト/ファイル名: Chap6_06/main.cpp

#include <iostream>
#include <list>

using namespace std;

int main(int argc, char** argv) {
    // 修行隊列の戦闘力を管理するlist
    list<int> powerList;

    // 後ろに戦闘力を追加
    powerList.push_back(1000);
    powerList.push_back(2000);

    // 先頭に戦闘力を追加
    powerList.push_front(5000);

    // イテレータの宣言
    list<int>::iterator itr;

    // 先頭を指す
    itr = powerList.begin();

    // 1つ次の要素へ進む
    itr++;

    // 指している位置の直前に要素を挿入
    powerList.insert(itr, 3000);

    // すべての要素を順番に表示
    for (itr = powerList.begin(); itr != powerList.end(); itr++) {
        cout << *itr << " ";
    }

    cout << endl;
    return 0;
}

実行結果

5000 3000 1000 2000

この結果を見ると、戦闘力の並びは 5000 → 3000 → 1000 → 2000 になっています。
ここには list の特徴がぎゅっと詰まっています。

最初の push_back によって 1000、2000 が後ろへ並びます。
そのあと push_front によって 5000 がいちばん前に入るので、並びは 5000、1000、2000 になります。
さらに、イテレータを1つ進めた位置に insert で 3000 を差し込むので、最終的に 5000、3000、1000、2000 という順番になります。

listへのデータの追加

list にデータを追加する方法として、まず覚えておきたいのが push_back と push_front です。

push_back は、末尾に要素を追加する関数です。
今回のプログラムでは、まず次の2行が実行されます。

powerList.push_back(1000);
powerList.push_back(2000);

この時点で powerList の中身は、次のようになります。

順番
11000
22000

ここへさらに push_front を使います。

powerList.push_front(5000);

すると、5000 が隊列の先頭に入ります。
そのため、並びは次のように変わります。

順番
15000
21000
32000

このように list は、後ろに追加するだけでなく、前にも自然に追加できるところが魅力です。
vector でも insert を使えば先頭への挿入はできますが、list はこうした操作をよりイメージしやすく扱えるコンテナです。

図:push_backとpush_frontによる追加

この図が示していること

この図では、push_back によって末尾へ要素が追加され、push_front によって先頭へ要素が追加される流れを確認できます。
list は、戦士の隊列を前後から組み替えるような感覚で扱えることがよくわかります。

イテレータとは何か

list を学ぶうえでいちばん大切なのが、イテレータという考え方です。

イテレータは、list のどの要素を見ているのかを示すための仕組みです。
ポインタに少し似た感覚で使えますが、役割としては、コンテナの中を順番にたどるための案内役だと考えるとよいです。

イテレータの宣言は、次のように書きます。

list<int>::iterator itr;

この itr が、powerList の要素を指し示すための変数になります。

ただし、宣言しただけでは、まだどこも指していません。
そこで begin を使います。

itr = powerList.begin();

begin は、先頭要素を指すイテレータを返します。
今回の powerList では、先頭は 5000 なので、itr はまず 5000 を指します。

そのあと、次の1行で itr を1つ進めます。

itr++;

これで itr は、次の要素である 1000 を指すようになります。

このように、イテレータは要素の位置を順番に移動しながら使うのが基本です。
修行隊列に並ぶ戦士を先頭から1人ずつ確認していくイメージで考えると、とてもわかりやすいです。

insertで途中にデータを挿入する

イテレータの便利さがよくわかるのが insert の場面です。
今回のプログラムでは、itr を begin で先頭に合わせてから 1回だけ itr++ しています。
そのため、itr は 1000 を指しています。

その状態で次の処理を行っています。

powerList.insert(itr, 3000);

この書き方は、itr が指している位置の直前に 3000 を挿入する、という意味です。

ここはとても大事なポイントです。
insert は、イテレータが指している要素の前に値を入れます。

今回の流れを順に見ると、こうなります。

最初の状態では、

5000 → 1000 → 2000

です。

itr = powerList.begin() の直後は、itr は 5000 を指しています。
そこから itr++ を実行すると、itr は 1000 を指すようになります。

この状態で insert を実行すると、1000 の前に 3000 が入るため、最終的に次の並びになります。

5000 → 3000 → 1000 → 2000

このように、list はイテレータで位置を指定することで、途中への挿入がとても自然に行えます。

イテレータで全要素を順番に表示する

list のすべての要素を取り出すときにも、イテレータが活躍します。
今回のプログラムでは、次の for 文を使っています。

for (itr = powerList.begin(); itr != powerList.end(); itr++) {
    cout << *itr << " ";
}

この for 文は、かなり重要な定番の形です。

まず、itr = powerList.begin() によって、itr は先頭要素を指します。
次に、itr != powerList.end() という条件によって、終端に到達するまでループを続けます。
そして itr++ によって、1つずつ次の要素へ進みます。

ここで end の意味にも注意しておきましょう。
end は、最後の要素そのものを指しているわけではありません。最後の要素のさらに次の位置、つまり終端を表します。

そのため、ループの中では *itr を使って値を取り出します。

cout << *itr << " ";

*itr は、イテレータが現在指している要素の値です。

今回のループでは、*itr の値は順番に 5000、3000、1000、2000 となります。
2000 を表示したあとに itr++ が実行されると、itr は end と同じ位置になり、そこでループが終了します。

図:イテレータで隊列をたどる流れ

この図が示していること

この図では、イテレータが先頭から順に各要素を指し、最後に end へ到達してループが終了する流れを示しています。
特に、end は最後の要素ではなく、最後の次の位置である、という点が重要です。

beginとendの意味をしっかり理解する

begin と end は、STLを学ぶうえでとてもよく出てくる基本の関数です。

begin は先頭要素を表します。
一方で end は終端を表します。

この2つの違いは、イテレータを正しく使うために欠かせません。
もし end が最後の要素を指すと勘違いしてしまうと、ループ条件やアクセスの考え方が混乱してしまいます。

正しい理解は次のとおりです。

関数意味
begin先頭要素を指すイテレータを返す
end最後の要素の次を指すイテレータを返す

このため、for 文では begin から始めて、end に到達するまで繰り返す、という形になります。

また、値を取り出すのは *itr であり、end 自体は値を取り出す場所ではありません。
この感覚は list だけでなく、ほかのSTLコンテナを学ぶときにも役立ちます。

listとvectorの使い分け

ここまで学ぶと、vector と list はどちらも便利だけれど、向いている場面が違うことが見えてきます。

vector は、番号付きで素早く要素にアクセスしたいときに向いています。
たとえば 3番目の戦士の戦闘力をすぐ確認したい、といった場面では vector が使いやすいです。

それに対して list は、戦士の並び順を途中で入れ替えたり、先頭に新戦士を追加したり、途中に割り込ませたりする場面でイメージしやすいです。

違いを整理すると、次のようになります。

やりたいこと向いているもの
何番目の要素かをすぐ取り出したいvector
先頭にデータを加えたいlist
途中にデータを差し込みたいlist
順番に全部見ていきたいvector でも list でも可能
添字で扱いたいvector
イテレータで位置をたどりたいlist

どちらが優れている、というよりも、目的に合わせて選ぶことが大切です。
ドラゴンボール風にいえば、番号付きの修行記録帳として扱うなら vector、戦士の出撃順を柔軟に調整するなら list、と考えると使い分けやすくなります。

listで押さえておきたい基本機能

最後に、今回登場した list の基本機能を表で整理しておきます。
どれも、list を使うときに最初に覚えたいものばかりです。

機能役割
push_back(値)末尾に要素を追加する
push_front(値)先頭に要素を追加する
begin()先頭要素を指すイテレータを得る
end()終端を指すイテレータを得る
insert(位置, 値)指定位置の直前に要素を挿入する
iterator要素の位置を指し示す型
*itrイテレータが指す値を取り出す
itr++次の要素へ進む

list は、最初は少し独特に感じるかもしれません。
でも、戦士の隊列を管理するようなイメージで考えると、とても理解しやすくなります。

先頭に追加する、末尾に追加する、途中に差し込む、そして先頭から順番にたどっていく。
この一連の流れがわかれば、list とイテレータの基本はしっかり身についています。

STLは、このあとさらにいろいろなコンテナや仕組みが登場しますが、list とイテレータの考え方はその土台になります。
ここをしっかり理解しておくと、C++でのデータ管理がぐっと楽になります。