C++入門|STLによるデータ管理

修行データが増えても、STLなら柔軟に受け止められる。vectorを使えば、戦闘力や技名をサイズに縛られずスマートに管理できます。

C++でプログラムを書いていると、複数のデータをまとめて扱いたい場面がたくさん出てきます。

たとえば、修行場に集まった戦士たちの戦闘力を順番に記録したい場合や、覚えた必殺技の名前を一覧で管理したい場合です。

これまでの学習では、複数の値をまとめる方法として配列を使ってきました。配列を使えば、同じ型のデータをまとめて保存できます。

ただし、普通の配列には大きな制限があります。
それは、基本的に最初に決めた要素数をあとから自由に変えにくいという点です。

たとえば、戦士が3人だけなら、int型の配列を3個分用意すればよいかもしれません。
しかし、修行場に何人来るか分からない場合はどうでしょうか。

最初は3人の予定でも、途中で5人、10人、20人と増えるかもしれません。
このような場面で、固定サイズの配列だけを使うと、あらかじめ大きな領域を用意したり、足りなくなったときの処理を自分で考えたりする必要があります。

そこで便利なのが、STLです。

STLは、Standard Template Libraryの略で、日本語では標準テンプレートライブラリと呼ばれます。
C++に標準で用意されている便利な部品の集まりで、データを効率よく管理するためのクラスや機能がそろっています。

ドラゴンボール風にたとえるなら、STLは修行場に標準装備されている高性能な管理システムです。
戦闘力の記録、技名の一覧、戦士名とランクの対応表など、目的に応じていろいろな管理装置を選べます。

今回の記事では、その中でも特によく使われるvectorを中心に学びます。

vectorは、サイズをあとから増やせる配列のようなクラスです。
固定サイズの配列より柔軟に使えるため、データ数があらかじめ分からない場面でとても役立ちます。

学習する内容内容
STLC++標準の便利なテンプレートライブラリ
vectorサイズを自由に増やせる動的配列
push_backvectorの末尾に要素を追加する関数
sizevectorに入っている要素数を取得する関数
[]配列のように要素へアクセスする書き方

STLとは何か

STLは、Standard Template Libraryの略です。
C++の標準機能として用意されているライブラリで、テンプレートを活用して作られています。

テンプレートを使うと、型をあとから指定できました。
たとえば、vectorと書けばint型のデータを管理でき、vectorと書けばstring型のデータを管理できます。

つまり、STLは特定の型だけに限定された部品ではありません。
int、double、string、自分で作ったクラスなど、さまざまな型に対応できるように作られています。

STLには、データを管理するための代表的なクラスがいくつもあります。

クラス役割イメージ
vector連続したデータを管理する動的配列戦闘力を順番に並べる記録ボード
list要素の追加や削除に向いたリスト戦士の隊列を入れ替えやすい名簿
mapキーと値の組み合わせを管理する戦士名とランクを対応させる管理表

今回の中心はvectorです。

vectorは、配列のように番号で要素へアクセスできます。
さらに、push_backを使えば、あとから要素を追加できます。

普通の配列は、最初に大きさを決めて使うことが多いです。
それに対してvectorは、データを追加しながら大きさを変えられます。

この柔軟さが、STLの便利さを感じやすいポイントです。

図:STLはデータ管理の標準装備

この図が示していること

この図では、STLがC++で使える標準的なデータ管理システムであることを表しています。

vector、list、mapは、それぞれ得意な管理方法が違います。
vectorは配列のように順番でデータを管理し、listは要素のつながりを管理し、mapはキーと値の対応を管理します。

今回扱うvectorは、複数のデータを順番に保存し、必要に応じて要素数を増やせる便利なクラスです。

配列とSTLの違い

普通の配列は、最初に要素数を決めて使います。

たとえば、次のように書くと、int型の値を3個保存できる配列になります。

int powerList[3];

この配列には、powerList[0]、powerList[1]、powerList[2]の3つの要素があります。
しかし、あとから4個目や5個目を自然に追加することはできません。

もちろん、工夫すれば新しい配列を作り直すような処理もできます。
しかし、その処理を自分で書くのは手間がかかります。

一方、vectorを使うと、最初に要素数を決めなくても使えます。

vector<int> powerList;

この時点では、powerListは空です。
そこへpush_backを使って、必要な分だけ値を追加していきます。

powerList.push_back(1000);
powerList.push_back(2500);
powerList.push_back(5000);

これで、powerListには3つの要素が入ります。

比較項目普通の配列vector
大きさ最初に決めることが多いあとから増やせる
要素の追加自分で管理が必要push_backで追加できる
要素数の取得自分で管理することが多いsizeで取得できる
アクセス方法配列番号でアクセス[]で配列のようにアクセス
使いやすい場面要素数が決まっている場合要素数が変わる場合

ドラゴンボール風にたとえるなら、普通の配列は最初から座席数が決まっている修行ベンチです。
3人分しか席を作っていなければ、4人目が来たときに困ります。

vectorは、戦士が増えるたびに座席を追加できる修行管理ボードです。
何人来るか分からない修行イベントでは、vectorのほうが扱いやすくなります。

vectorクラスとは

vectorは、STLで用意されている動的配列です。

動的配列とは、プログラムの実行中に要素数を増やせる配列のようなものです。

普通の配列を静的配列と呼ぶことがあります。
静的配列は、最初に大きさを決めて使うため、要素数がはっきりしているときには分かりやすいです。

一方で、vectorのような動的配列は、データが増える可能性がある場面で便利です。

たとえば、修行場に登録される戦闘力データを考えてみましょう。
最初は1人分だけかもしれません。
あとから2人目、3人目が増えるかもしれません。

vectorなら、push_backを使って順番に追加できます。

vector<int> powerList;

powerList.push_back(1000);
powerList.push_back(2500);
powerList.push_back(5000);

このように、必要になったタイミングで要素を追加できるのがvectorの魅力です。

vectorを使う準備

vectorを使うには、vector用のヘッダーをインクルードします。

#include <vector>

また、vectorは標準ライブラリの機能なので、std名前空間に属しています。

サンプルでは、これまでの流れに合わせてusing namespace std;を使います。

using namespace std;

これにより、std::vectorではなくvectorと書けるようになります。

vectorを使うときの基本形は、次のようになります。

vector<型> 変数名;

たとえば、int型の値を管理するvectorなら、次のように書きます。

vector<int> powerList;

string型の値を管理するvectorなら、次のように書きます。

vector<string> techniqueList;

ここで、vectorやvectorのように書くのは、クラステンプレートの使い方と同じです。
vectorはテンプレートで作られているため、<>の中に管理したい型を書きます。

宣言意味
vector powerList;int型の値を管理するvector
vector techniqueList;string型の値を管理するvector

vectorで戦闘力と技名を管理する

それでは、実際にvectorを使ってみましょう。

今回のサンプルでは、int型のvectorで戦闘力を管理し、string型のvectorで技名を管理します。

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

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

using namespace std;

int main(int argc, char** argv) {
    // 戦闘力を管理するvector
    vector<int> powerList;

    // 技名を管理するvector
    vector<string> techniqueList;

    // 戦闘力を追加する
    powerList.push_back(1000);
    powerList.push_back(2500);
    powerList.push_back(5000);

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

    // 戦闘力一覧を表示する
    for (int i = 0; i < powerList.size(); i++) {
        cout << "powerList[" << i << "]=" << powerList[i] << endl;
    }

    // 技名一覧を表示する
    for (int i = 0; i < techniqueList.size(); i++) {
        cout << "techniqueList[" << i << "]=" << techniqueList[i] << endl;
    }

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

実行結果

powerList[0]=1000
powerList[1]=2500
powerList[2]=5000
techniqueList[0]=青白い気弾
techniqueList[1]=癒しの光

このプログラムでは、powerListとtechniqueListという2つのvectorを使っています。

powerListはvectorなので、int型の値を保存します。
techniqueListはvectorなので、string型の値を保存します。

同じvectorでも、<>の中に書く型を変えることで、管理できるデータの種類が変わります。

push_backで要素を追加する

vectorに値を追加するときは、push_backを使います。

powerList.push_back(1000);

これは、powerListの末尾に1000を追加するという意味です。

続けてpush_backを呼び出すと、要素が順番に増えていきます。

powerList.push_back(1000);
powerList.push_back(2500);
powerList.push_back(5000);

この3行を実行すると、powerListの中身は次のような状態になります。

添字
01000
12500
25000

vectorの添字は、普通の配列と同じく0から始まります。

最初に追加した1000はpowerList[0]に入ります。
次に追加した2500はpowerList[1]に入ります。
最後に追加した5000はpowerList[2]に入ります。

string型のvectorでも同じです。

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

この場合、techniqueList[0]に青白い気弾、techniqueList[1]に癒しの光が入ります。

図:push_backでvectorに要素が増える

この図が示していること

この図では、push_backを呼び出すたびに、vectorの末尾へ要素が追加されていく様子を表しています。

powerList.push_back(1000)を実行すると、powerList[0]に1000が入ります。
次に2500を追加するとpowerList[1]に入り、さらに5000を追加するとpowerList[2]に入ります。

vectorは、普通の配列のように添字でアクセスできますが、push_backによって要素数をあとから増やせるところが大きな特徴です。

sizeで要素数を取得する

vectorにいくつ要素が入っているかを知りたいときは、sizeを使います。

powerList.size()

powerListに1000、2500、5000の3つが入っていれば、sizeは3を返します。

for文でvectorの中身をすべて表示するときは、sizeを使うと便利です。

for (int i = 0; i < powerList.size(); i++) {
    cout << "powerList[" << i << "]=" << powerList[i] << endl;
}

このfor文では、iが0から始まり、powerList.size()より小さい間だけ繰り返されます。

powerList.size()が3なら、iは0、1、2の3回分だけ動きます。
そして、powerList[0]、powerList[1]、powerList[2]を順番に表示します。

普通の配列では、要素数を自分で覚えておく必要がある場面もあります。
しかし、vectorならsizeで現在の要素数を確認できます。

これは、データ数が変わる場面でとても便利です。

[]で要素にアクセスする

vectorの要素には、普通の配列と同じように[]を使ってアクセスできます。

powerList[0]
powerList[1]
powerList[2]

値を取り出すだけでなく、値を書き換えることもできます。

次のサンプルでは、vectorに3つの戦闘力を追加したあと、3番目の値を書き換えます。

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

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char** argv) {
    // 戦闘力を管理するvector
    vector<int> powerList;

    // 戦闘力を追加する
    powerList.push_back(1000);
    powerList.push_back(2500);
    powerList.push_back(5000);

    // 3番目の戦闘力を変更する
    powerList[2] = 9000;

    // 戦闘力一覧を表示する
    for (int i = 0; i < powerList.size(); i++) {
        cout << "powerList[" << i << "]=" << powerList[i] << endl;
    }

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

実行結果

powerList[0]=1000
powerList[1]=2500
powerList[2]=9000

最初にpush_backを3回呼び出しているため、powerListには1000、2500、5000が入ります。

その後、次の行でpowerList[2]の値を変更しています。

powerList[2] = 9000;

添字は0から始まるため、powerList[2]は3番目の要素です。
そのため、5000だった値が9000に変わります。

添字変更前変更後
010001000
125002500
250009000

このように、vectorは配列と同じような感覚で要素を読み書きできます。

vectorを使うときの注意

vectorは便利ですが、[]で存在しない要素にアクセスしないように注意が必要です。

たとえば、要素が3つしかないvectorであれば、使える添字は0、1、2です。

powerList[0]
powerList[1]
powerList[2]

この状態でpowerList[3]にアクセスすると、範囲外の要素を見に行くことになります。

powerList[3]

このようなアクセスは危険です。

vectorをfor文で扱うときは、sizeを使って、現在の要素数に合わせて繰り返すのが基本です。

for (int i = 0; i < powerList.size(); i++) {
    cout << powerList[i] << endl;
}

sizeを使えば、vectorに入っている要素数に合わせて安全に繰り返しやすくなります。

ドラゴンボール風に言えば、修行管理ボードに3人分しか登録されていないのに、4人目の記録を読もうとすると、まだ存在しない枠を見に行ってしまうようなものです。

vectorの主なメンバ関数

vectorには、データを管理するための便利なメンバ関数が用意されています。

関数役割
push_back(値)末尾に要素を追加する
clear()すべての要素を削除する
size()現在の要素数を取得する
capacity()現在確保されている容量を取得する
empty()要素が空かどうかを調べる

push_backは、今回のサンプルで使ったように、vectorの末尾へ値を追加します。

clearは、vectorの中身をすべて消したいときに使います。

sizeは、現在入っている要素数を取得します。

capacityは、vectorが内部的にどれくらいの容量を確保しているかを確認する関数です。sizeが現在の人数だとすれば、capacityは修行場に用意されている席数のようなものです。

emptyは、vectorが空かどうかを調べます。要素が1つも入っていなければtrueになります。

vectorには、このほかにもたくさんの機能があります。
まずは、push_back、size、[]の3つをしっかり使えるようになると、基本的なデータ管理がかなり楽になります。

図:vectorの基本操作

この図が示していること

この図では、vectorでよく使う基本操作をまとめています。

push_backで要素を追加し、sizeで要素数を確認し、[]で要素にアクセスします。
さらに、clearで要素をすべて消したり、emptyで空かどうかを確認したりできます。

vectorは配列に近い感覚で使えますが、要素をあとから増やせるため、より柔軟なデータ管理ができます。

STLとテンプレートの関係

vectorやvectorという書き方を見ると、クラステンプレートと同じ考え方であることが分かります。

vectorは、管理するデータの型を<>の中で指定します。

vector<int> powerList;
vector<string> techniqueList;

vectorでは、int型のデータを管理します。
vectorでは、string型のデータを管理します。

これは、前に学んだクラステンプレートと同じように、型をあとから指定して使う仕組みです。

つまり、STLはテンプレートの考え方を実用的な形にしたものです。

自分でクラステンプレートを書くときは、型に応じて使えるクラスを作りました。
STLでは、vectorのような便利なクラステンプレートが、C++標準としてすでに用意されています。

ドラゴンボール風にたとえるなら、前回までに学んだテンプレートは、自分で万能修行装置の設計図を作る方法でした。
今回のSTLは、すでに完成済みの高性能修行装置を使うようなものです。

配列ではなくvectorを使いたい場面

vectorは、データ数が変わる可能性がある場面に向いています。

たとえば、ファイルから戦闘力データを読み込む場合、何件のデータが入っているか事前に分からないことがあります。

また、ユーザーが入力した技名を順番に保存する場合も、何個入力されるか最初は分かりません。

このような場面で普通の配列を使うと、あらかじめ大きなサイズを用意する必要があります。
大きすぎる配列を用意すると無駄が出ますし、小さすぎると足りなくなります。

vectorなら、必要に応じてpush_backで追加していけます。

場面vectorが便利な理由
データ数が事前に分からない必要に応じて追加できる
要素を順番に保存したい配列のように添字で扱える
要素数を確認したいsizeで取得できる
途中で値を書き換えたい[]でアクセスして代入できる

もちろん、要素数が完全に決まっていて、サイズを変える必要がない場面では普通の配列でも十分です。

ただ、現実のプログラムではデータ数が変わることが多いため、vectorを使えるようになると、データ管理の幅が大きく広がります。

STLによるデータ管理で身につけたい感覚

STLは、C++で大量のデータを扱うための標準的な道具です。

今回学んだvectorは、その中でも特によく使われるクラスです。
普通の配列と同じように添字でアクセスでき、push_backで要素を追加でき、sizeで要素数を取得できます。

C++でデータを管理するとき、普通の配列だけで考えると、要素数やメモリの扱いを自分で意識しなければならない場面が増えます。

vectorを使うと、要素の追加やサイズ管理がしやすくなり、より柔軟なプログラムを書けます。

ドラゴンボール風に言えば、普通の配列は座席数が固定された修行台です。
vectorは、戦士が増えるたびに枠を増やせる修行管理ボードです。

最初に何人来るか分からない修行場でも、vectorならpush_backで順番に登録できます。
登録された人数はsizeで確認でき、各戦士の戦闘力や技名は[]で取り出せます。

STLは、C++のテンプレートを活用した強力な標準機能です。
まずはvectorをしっかり使えるようになることで、C++でのデータ管理がぐっと実践的になります。