C++入門|vectorクラスとlistクラスの違い

vectorは番号で探しやすい修行データボード、listは隊列の組み替えに強い戦士名簿。STLは目的に合わせて使い分けることで力を発揮します。

STLには、複数のデータをまとめて管理するための便利なクラスが用意されています。
その中でも、vector と list はどちらも複数のデータを順番に保存できるため、最初はとても似ているように見えます。

vector は、配列を使いやすく発展させたようなクラスです。
データを順番に並べ、0番目、1番目、2番目というように番号でアクセスしやすいのが特徴です。

一方、list は、要素同士が前後につながった隊列のようなクラスです。
先頭や末尾への追加、途中の要素の挿入や削除を考えやすい構造になっています。

ドラゴンボール風にたとえるなら、vector は修行司令室にある番号付きの戦闘力ボードです。
1番目の戦士、2番目の戦士、3番目の戦士というように、番号でパッと確認するのに向いています。

list は、戦士たちが実際に並んでいる修行隊列です。
先頭の戦士を外したり、途中に別の戦士を入れたり、隊列そのものを組み替えるような場面に向いています。

今回の学習では、vector と list の共通点と違いを、イテレータを使った表示、list による要素削除を通して確認していきます。

比較項目vectorlist
基本イメージ番号付きの動的配列前後につながる隊列
添字アクセスできるできない
イテレータ使える使える
末尾への追加得意できる
途中への挿入や削除要素移動が発生しやすい得意
removeメンバ関数ないある
向いている場面番号で要素を取り出したい場面並びの変更や削除が多い場面

vectorとlistはどちらもSTLのコンテナ

vector と list は、どちらもSTLのコンテナです。
コンテナとは、複数のデータを入れて管理する入れ物のことです。

vector と書けば、文字列を複数管理する vector になります。
list と書けば、文字列を複数管理する list になります。

どちらもテンプレートを使ったクラスなので、string だけでなく int や double、自作クラスなども扱えます。

たとえば、技名を管理するなら string 型を使えます。
戦闘力を管理するなら int 型を使えます。

vector<string> techniqueVector;
list<string> techniqueList;

この2つは、どちらも文字列を複数保存できます。
ただし、内部の考え方や得意な操作は違います。

vector は配列の延長として考えるとわかりやすく、番号で管理しやすい構造です。
list は隊列として考えるとわかりやすく、要素の追加や削除によって並びが変化する場面に向いています。

図:vectorは番号付きボード、listは戦士の隊列

この図が示していること

この図では、vector と list の基本的な違いを表しています。

vector は、番号付きのデータボードのように、添字を使って要素を扱いやすい構造です。
list は、要素同士がつながった隊列のように、途中の挿入や削除を考えやすい構造です。

どちらも複数のデータを管理できますが、使う目的によって選び分けることが大切です。

vectorとlistを使ったサンプルプログラム

ここでは、vector と list の両方を使うサンプルを見ていきます。

vector には、表示用の技名を2つ保存します。
list には、隊列として管理する気の種類を3つ保存します。

そのあと、vector の内容はイテレータで表示します。
list では、先頭要素の値をもとに remove を使って削除し、残った要素を表示します。

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

#include <iostream>
#include <string>
#include <vector>
#include <list>

using namespace std;

int main(int argc, char** argv) {
    // 技名を番号付きで管理するvector
    vector<string> techniqueVector;

    // 気の隊列を管理するlist
    list<string> kiList;

    // vectorに技名を追加する
    techniqueVector.push_back("青白い気弾");
    techniqueVector.push_back("癒しの光");

    // listに気の種類を追加する
    kiList.push_back("亀仙流の気");
    kiList.push_back("神流の癒し");
    kiList.push_back("界王流の重力");

    // vector用のイテレータを宣言する
    vector<string>::iterator vectorItr;

    // list用のイテレータを宣言する
    list<string>::iterator listItr;

    // vectorの要素を表示する
    cout << "--- vectorの技名表示 ---" << endl;

    for (vectorItr = techniqueVector.begin(); vectorItr != techniqueVector.end(); vectorItr++) {
        cout << *vectorItr << endl;
    }

    // listの先頭を指す
    listItr = kiList.begin();

    // listの中から、listItrが指している値と同じ要素を削除する
    kiList.remove(*listItr);

    // listの要素を表示する
    cout << "--- listの隊列表示 ---" << endl;

    for (listItr = kiList.begin(); listItr != kiList.end(); listItr++) {
        cout << *listItr << endl;
    }

    // プログラムが正常に終わったことを示す
    return 0;
}

実行結果

--- vectorの技名表示 ---
青白い気弾
癒しの光
--- listの隊列表示 ---
神流の癒し
界王流の重力

このプログラムでは、vector と list の両方でイテレータを使っています。

vector::iterator vectorItr は、vector の要素を順番に指すためのイテレータです。
list::iterator listItr は、list の要素を順番に指すためのイテレータです。

vector でも list でも、begin から end までイテレータを進めることで、すべての要素を順番に表示できます。

for (vectorItr = techniqueVector.begin(); vectorItr != techniqueVector.end(); vectorItr++) {
    cout << *vectorItr << endl;
}

for (listItr = kiList.begin(); listItr != kiList.end(); listItr++) {
    cout << *listItr << endl;
}

このように、イテレータを使って要素をたどれる点は、vector と list の共通点です。

vectorでもイテレータは使える

前回 list を学んだとき、イテレータは list の要素をたどるために使いました。
ただし、イテレータは list だけのものではありません。

vector でもイテレータを使えます。

vector<string>::iterator vectorItr;

このように宣言した vectorItr は、vector の要素を指すためのイテレータです。

vector は添字でもアクセスできます。

techniqueVector[0]
techniqueVector[1]

しかし、イテレータを使って次のように順番に表示することもできます。

for (vectorItr = techniqueVector.begin(); vectorItr != techniqueVector.end(); vectorItr++) {
    cout << *vectorItr << endl;
}

begin は先頭要素を指すイテレータを返します。
end は最後の要素の次を表すイテレータを返します。

そのため、vectorItr が end に到達するまで処理を繰り返せば、vector の全要素を表示できます。

ドラゴンボール風に言えば、vector は番号付きの修行データボードですが、イテレータというスカウター照準を使って、左から右へ順番に確認することもできるわけです。

listではremoveを使って要素を削除できる

サンプルのlist側では、removeを使って要素を削除しています。

listItr = kiList.begin();
kiList.remove(*listItr);

まず、listItr = kiList.begin(); によって、listItr は list の先頭要素を指します。

この時点で、kiList の中身は次のようになっています。

亀仙流の気
神流の癒し
界王流の重力

listItr は先頭の 亀仙流の気 を指しています。

そのあと、次の処理を実行します。

kiList.remove(*listItr);

ここで、*listItr は listItr が指している要素の値を表します。
つまり、この場合は 亀仙流の気 です。

kiList.remove(*listItr); は、kiList の中から 亀仙流の気 と同じ値の要素を削除します。

そのため、削除後のlistは次のようになります。

神流の癒し
界王流の重力

ここで注意したいのは、remove はイテレータの位置そのものを指定して削除する関数ではない、という点です。
remove は、指定した値と同じ要素を削除します。

今回のプログラムでは、先頭の値が 亀仙流の気 であり、その値は1つだけなので、先頭要素が削除されたように見えます。

もし同じ値が複数入っていれば、remove はその値と一致する要素をまとめて削除します。

listの状態処理結果
亀仙流の気、神流の癒し、界王流の重力remove(亀仙流の気)神流の癒し、界王流の重力
亀仙流の気、神流の癒し、亀仙流の気remove(亀仙流の気)神流の癒し

vector には、list の remove のようなメンバ関数はありません。
vector で要素を削除する場合は、別の方法を使います。

ここでは、list は値を指定して要素を削除できる remove メンバ関数を持っている、という点を押さえておきましょう。

図:listのremoveで値を削除する

この図が示していること

この図では、list の先頭要素をイテレータで確認し、その値を remove に渡して削除する流れを表しています。

listItr が先頭の 亀仙流の気 を指しているため、*listItr は 亀仙流の気 になります。
kiList.remove(*listItr) を実行すると、kiList の中から 亀仙流の気 と同じ値の要素が削除されます。

remove は、イテレータの位置そのものではなく、指定した値と一致する要素を削除する関数である点が大切です。

vectorとlistの共通点

vector と list は構造が違いますが、共通している部分もあります。

まず、どちらもSTLのコンテナです。
そのため、複数のデータをまとめて管理できます。

また、どちらもテンプレートで作られているので、扱う型を指定できます。

vector<string> techniqueVector;
list<string> kiList;

このように、string を指定すれば文字列を管理できます。
int を指定すれば整数を管理できます。

さらに、どちらも push_back で末尾に要素を追加できます。

techniqueVector.push_back("青白い気弾");
kiList.push_back("亀仙流の気");

そして、どちらもイテレータを使って先頭から終端までたどることができます。

begin()
end()

共通点を整理すると、次のようになります。

共通点内容
STLのコンテナである複数のデータを管理できる
テンプレートで型を指定するvector や list のように書ける
push_backが使える末尾に要素を追加できる
beginとendが使えるイテレータで範囲をたどれる
イテレータで値を取り出せる*iterator で要素の値を参照できる

vector と list は、使い方がまったく別物というわけではありません。
STLコンテナとして共通した操作も多くあります。

vectorとlistの違い

vector と list の大きな違いは、データの管理方法です。

vector は、配列の延長として考えるとわかりやすいクラスです。
要素が順番に並び、添字を使ってアクセスできます。

techniqueVector[0]
techniqueVector[1]

このように、何番目の要素かがわかっている場合に扱いやすいです。

一方、list は添字でアクセスできません。
要素同士がつながるように管理されているため、イテレータで順番にたどる使い方が基本になります。

for (listItr = kiList.begin(); listItr != kiList.end(); listItr++) {
    cout << *listItr << endl;
}

また、list は任意の位置での挿入や削除を考えやすいコンテナです。
remove、push_front、pop_front、insert など、隊列を組み替えるような操作に向いたメンバ関数を持っています。

違いvectorlist
管理の考え方配列の延長前後につながる隊列
添字アクセスできるできない
要素の削除removeメンバ関数はないremoveメンバ関数がある
先頭への追加得意とは限らないpush_frontが使える
途中への挿入要素移動が発生しやすいinsertとイテレータで扱いやすい
主な使い方番号で要素を扱う並びの変更を扱う

ドラゴンボール風に言えば、vector は修行記録を番号で管理する台帳です。
list は戦士の実際の隊列です。

番号で何番目かを素早く見たいなら vector が合っています。
先頭を外したり、途中を差し替えたり、隊列の並びを変えたいなら list が合っています。

listの主なメンバ関数

list には、要素の追加や削除を行うための便利なメンバ関数があります。

メンバ関数意味
push_front(値)先頭に要素を追加する
push_back(値)末尾に要素を追加する
pop_front()先頭の要素を削除する
pop_back()末尾の要素を削除する
insert(イテレータ, 値)指定位置の直前に要素を挿入する
remove(値)指定した値と同じ要素を削除する
clear()すべての要素を削除する

push_front は、先頭に要素を追加します。
push_back は、末尾に要素を追加します。

pop_front は、先頭の要素を削除します。
pop_back は、末尾の要素を削除します。

insert は、イテレータで指定した位置の直前に要素を挿入します。

remove は、指定した値と同じ要素を削除します。
clear は、すべての要素を削除します。

list は、このように並びを変化させる操作が豊富です。
そのため、要素を追加したり削除したりする場面で使いやすいクラスです。

図:vectorとlistの使い分け

この図が示していること

この図では、vector と list をどのように使い分けるかを表しています。

vector は、番号で要素にアクセスしたい場面に向いています。
list は、先頭への追加、途中への挿入、要素の削除など、隊列を組み替えるような操作に向いています。

STLでは、どのコンテナを使うかによって、プログラムの書きやすさや処理の考え方が変わります。

サンプルプログラムの流れを確認する

今回のサンプルでは、まず vector に2つの技名を追加しています。

techniqueVector.push_back("青白い気弾");
techniqueVector.push_back("癒しの光");

そのあと、vector のイテレータで内容を表示しています。

for (vectorItr = techniqueVector.begin(); vectorItr != techniqueVector.end(); vectorItr++) {
    cout << *vectorItr << endl;
}

実行結果では、次のように表示されます。

青白い気弾
癒しの光

次に、list には3つの気の種類を追加しています。

kiList.push_back("亀仙流の気");
kiList.push_back("神流の癒し");
kiList.push_back("界王流の重力");

この状態で、listItr を先頭に合わせます。

listItr = kiList.begin();

先頭の値は 亀仙流の気 です。

その値を使って、removeで削除します。

kiList.remove(*listItr);

その結果、亀仙流の気 が削除され、list には 神流の癒し と 界王流の重力 が残ります。

最後に、list のイテレータで内容を表示します。

for (listItr = kiList.begin(); listItr != kiList.end(); listItr++) {
    cout << *listItr << endl;
}

実行結果では、次のように表示されます。

神流の癒し
界王流の重力

この流れから、vector でも list でもイテレータを使えること、そして list では remove による要素削除ができることを確認できます。

vectorとlistで身につけたい使い分けの感覚

vector と list は、どちらも複数のデータを扱える便利なSTLコンテナです。
しかし、何をしたいかによって、選ぶべきコンテナは変わります。

要素を番号で扱いたいなら vector が向いています。
たとえば、techniqueVector[0] のように何番目の技かを指定して取り出したい場合です。

一方で、先頭や途中の要素を追加したり削除したりする操作が多いなら list が向いています。
list は、隊列の中から特定の値を削除したり、イテレータで位置を指定して挿入したりする考え方に合っています。

ドラゴンボール風に言えば、vector は修行データを番号で管理する記録ボードです。
list は、戦士たちの並びを柔軟に変えられる出撃隊列です。

どちらも便利ですが、得意分野は違います。

STLを使うときは、ただデータを入れられればよいと考えるのではなく、そのデータをあとでどのように使うのかを考えることが大切です。

番号でよく取り出すのか。
途中でよく削除するのか。
先頭に追加することが多いのか。
全体を順番にたどるだけなのか。

こうした使い方を考えて、vector と list を選び分けると、C++のデータ管理がよりわかりやすく、扱いやすくなります。