C++入門|6章を例題で確認する

入力される修行ポイントの数が分からなくても大丈夫。vectorで受け止め、イテレータでたどれば、戦士たちの修行成果をきれいに集計できます。

ここまでの6章では、テンプレートとSTLについて学んできました。

関数テンプレートでは、型が違っても同じ処理を1つの関数としてまとめる考え方を学びました。
クラステンプレートでは、クラス全体を型に合わせて使い分ける方法を確認しました。
そしてSTLでは、C++に標準で用意されている便利なデータ管理の仕組みとして、vectorやlistを扱いました。

今回の例題では、その中でも特によく使うvectorを使って、入力された複数の数値を管理します。

普通の配列では、最初に要素数を決めておく必要があります。
しかし、今回のように、何個の数値が入力されるか分からない場合は、vectorがとても便利です。

ドラゴンボール風にたとえるなら、修行場に集まった戦士たちが、順番に修行ポイントを報告していく場面です。
何人分の修行ポイントが入力されるかは、最初には分かりません。
そこで、固定サイズの記録用紙ではなく、必要に応じて記録枠を増やせるvectorを使います。

正の整数が入力されたら、その修行ポイントをvectorに追加します。
0以下の数値が入力されたら、入力を終了します。
最後に、入力された修行ポイントを一覧表示し、合計値を表示します。

この例題では、vector、push_back、while、break、イテレータ、begin、end、*itrをまとめて確認します。

確認する内容役割
vector入力された修行ポイントを順番に保存する
push_back入力された正の整数を末尾に追加する
while入力を繰り返す
break0以下の数値が入力されたらループを抜ける
iteratorvectorの要素を順番にたどる
begin先頭要素を指すイテレータを得る
end終端を表すイテレータを得る
*itrイテレータが指す要素の値を取り出す

例題6-1:修行ポイントを入力して合計を表示する

問題

キーボードから正の整数を複数入力し、0以下の数値が入力されたら入力を終了するプログラムを作りなさい。

入力された正の整数は、戦士たちの修行ポイントとしてvectorに保存します。
入力が終了したら、そこまでに入力された修行ポイントの一覧を表示し、さらに合計値を表示します。

実行結果の例は、次のようになります。

修行ポイントを入力:5
修行ポイントを入力:2
修行ポイントを入力:4
修行ポイントを入力:3
修行ポイントを入力:1
修行ポイントを入力:-1
5 2 4 3 1
合計:15

この例では、5、2、4、3、1が正の整数なのでvectorに保存されます。
最後に-1が入力されたため、入力を終了します。

そのあと、vectorに保存されている値を順番に表示し、合計15を表示します。

図:入力された修行ポイントをvectorに追加する

この図が示していること

この図では、入力された修行ポイントがvectorに順番に追加される流れを表しています。

正の整数が入力された場合は、push_backでvectorの末尾に追加します。
0以下の数値が入力された場合は、入力を終了します。

入力される数が最初から決まっていない場面では、固定サイズの配列よりもvectorが扱いやすいことが分かります。

プログラムを作る考え方

この例題では、最初に修行ポイントを保存するvectorを用意します。

vector<int> trainingPoints;

このvectorには、入力された正の整数だけを保存します。

入力は、whileを使って繰り返します。
何回入力されるかは最初には分からないため、whileで繰り返し続け、終了条件に達したらbreakで抜ける形にします。

正の整数が入力された場合は、push_backでvectorに追加します。

trainingPoints.push_back(point);

0以下の数値が入力された場合は、これ以上保存せず、breakでループを終了します。

break;

入力が終わったら、イテレータを使ってvectorの先頭から終端までたどります。

vector<int>::iterator itr;

イテレータをbeginから始めて、endに到達するまで進めます。

for (itr = trainingPoints.begin(); itr != trainingPoints.end(); itr++)

ループの中では、*itrで現在の要素を取り出し、表示しながら合計値に加算します。

sum += *itr;

このように、入力、保存、表示、合計という流れをvectorとイテレータで組み立てます。

解答例

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

#include <iostream>
#include <vector>

using namespace std;

int main(int argc, char** argv) {
    // 修行ポイントを保存するvector
    vector<int> trainingPoints;

    // 入力を繰り返す
    while (true) {
        int point;

        // 修行ポイントを入力する
        cout << "修行ポイントを入力:";
        cin >> point;

        // 正の整数ならvectorに追加する
        if (point > 0) {
            trainingPoints.push_back(point);
        }
        else {
            // 0以下なら入力を終了する
            break;
        }
    }

    // 合計値を保存する変数
    int sum = 0;

    // vectorの要素を順番にたどるイテレータ
    vector<int>::iterator itr;

    // 入力された修行ポイントを表示しながら合計を求める
    for (itr = trainingPoints.begin(); itr != trainingPoints.end(); itr++) {
        cout << *itr << " ";
        sum += *itr;
    }

    // 改行する
    cout << endl;

    // 合計値を表示する
    cout << "合計:" << sum << endl;

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

実行結果の例

修行ポイントを入力:5
修行ポイントを入力:2
修行ポイントを入力:4
修行ポイントを入力:3
修行ポイントを入力:1
修行ポイントを入力:-1
5 2 4 3 1
合計:15

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

このプログラムでは、まず修行ポイントを保存するためにvectorを用意しています。

vector<int> trainingPoints;

vectorは、int型の値を複数保存できるSTLコンテナです。

普通の配列と違い、最初に要素数を決めておく必要がありません。
push_backを使えば、入力された値を必要な分だけ追加できます。

今回の例題では、何個の修行ポイントが入力されるか分かりません。
そのため、固定サイズの配列よりもvectorが向いています。

ドラゴンボール風に言えば、trainingPointsは修行場の記録ボードです。
戦士が修行ポイントを報告するたびに、新しい記録枠が追加されていきます。

whileで入力を繰り返す

入力部分では、whileを使っています。

while (true) {

while(true)は、条件が常に真になるため、そのままではずっと繰り返します。

このような形は、入力終了のタイミングをループの中で判断したいときに使えます。

今回のプログラムでは、0以下の値が入力されたときにbreakでループを抜けます。

else {
    break;
}

つまり、正の整数が入力されている間は修行ポイントを保存し続け、0以下が入力された瞬間に入力を終了する仕組みです。

入力値処理
5vectorに追加する
2vectorに追加する
4vectorに追加する
3vectorに追加する
1vectorに追加する
-1ループを終了する

この表のように、正の整数だけがvectorに保存されます。
終了用に入力された-1は、修行ポイントとしては保存されません。

push_backで値を追加する

正の整数が入力された場合、次の処理が実行されます。

trainingPoints.push_back(point);

push_backは、vectorの末尾に要素を追加するメンバ関数です。

たとえば、5、2、4、3、1の順に入力された場合、vectorの中身は次のように増えていきます。

入力trainingPointsの状態
55
25 2
45 2 4
35 2 4 3
15 2 4 3 1

vectorは、このように入力された順番を保ったままデータを保存します。

修行場のイメージで考えると、戦士が報告した順に修行ポイントが記録ボードへ並んでいく状態です。

図:push_backで修行ポイントが増えていく

この図が示していること

この図では、push_backによってvectorに値が順番に追加されていく流れを表しています。

入力された5、2、4、3、1は、vectorの末尾へ順番に保存されます。
0以下の数値が入力されたらbreakで入力ループを終了します。

このように、vectorは入力数が決まっていないデータを順番に保存したいときに便利です。

イテレータで一覧表示と合計を行う

入力が終わったら、vectorに保存された修行ポイントを順番に表示します。

ここではイテレータを使っています。

vector<int>::iterator itr;

itrは、vectorの要素を指すためのイテレータです。

for文では、itrをtrainingPoints.begin()で先頭に合わせます。

itr = trainingPoints.begin()

そして、itrがtrainingPoints.end()に到達するまで繰り返します。

itr != trainingPoints.end()

1回のループが終わるたびにitr++で次の要素へ進みます。

itr++

ループ全体は次の形になります。

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

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

たとえば、vectorの中身が5、2、4、3、1なら、*itrは順番に5、2、4、3、1になります。

その値を表示しながら、sumに加算しています。

ループ回数*itrの値sumの変化
1回目50 → 5
2回目25 → 7
3回目47 → 11
4回目311 → 14
5回目114 → 15

最終的にsumは15になります。

beginとendの役割

イテレータを使うときは、beginとendの意味をしっかり理解しておくことが大切です。

beginは、vectorの先頭要素を指すイテレータを返します。

trainingPoints.begin()

endは、最後の要素そのものではなく、最後の次の位置を表すイテレータを返します。

trainingPoints.end()

そのため、for文ではitr != trainingPoints.end()という条件を使います。

for (itr = trainingPoints.begin(); itr != trainingPoints.end(); itr++)

この形にすると、先頭から最後の要素まで順番に処理し、最後の次の位置に到達したところでループが終わります。

ドラゴンボール風に言えば、beginは修行記録ボードの最初の記録を指すスカウター照準です。
endは最後の記録ではなく、記録の終わりを示す印です。

そのため、endの位置にある値を読むのではなく、endに到達するまで読む、という感覚が大切です。

図:イテレータでvectorをたどりながら合計する

この図が示していること

この図では、イテレータを使ってvectorの要素を先頭から順にたどり、値を表示しながら合計していく流れを表しています。

*itrは、現在の要素の値を表します。
itr++によって次の要素へ進みます。
endに到達したらループを終了します。

sumは、各要素の値を加算することで、0から15へ変化していきます。

添字を使って書くこともできる

今回の解答例では、イテレータを使ってvectorの要素を表示しました。

ただし、vectorは配列のように添字でアクセスすることもできます。

そのため、次のように書くこともできます。

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

この書き方では、sizeを使ってvectorの要素数を取得し、0番目から順番にアクセスしています。

vectorは、STLコンテナの中でも配列に近い感覚で使えます。
そのため、イテレータでも添字でも要素をたどることができます。

書き方特徴
イテレータを使うSTLらしい走査方法で、listなどにも応用しやすい
添字を使う配列に近い感覚で読みやすい

今回の例題では、6章で学んだSTLの確認として、イテレータを使った書き方を採用しています。

listでも同じような処理ができる

この例題ではvectorを使いましたが、listを使っても同じような処理を作れます。

listも、push_backで末尾に値を追加できます。
また、イテレータでbeginからendまでたどることもできます。

ただし、listは添字でアクセスできません。
そのため、listを使う場合はイテレータで要素をたどる考え方がより大切になります。

vectorは番号付きの修行記録ボードのように扱えます。
listは戦士がつながる修行隊列のように扱えます。

今回のように、入力された順番に値を保存し、最後に一覧表示と合計をするだけなら、vectorでもlistでも実現できます。
しかし、添字でも扱いたい場合はvectorが使いやすく、途中への挿入や削除が多い場合はlistが向いています。

例題で確認したいポイント

この例題では、6章で学んだSTLの基本を実際のプログラムで確認しました。

入力されるデータ数が事前に分からないため、固定サイズの配列ではなくvectorを使いました。
正の整数だけをpush_backで追加し、0以下の数値が入力されたらbreakで入力を終了しました。

そのあと、イテレータを使ってvectorの先頭から終端まで順番にたどり、値を表示しながら合計値を求めました。

この流れを整理すると、次のようになります。

処理使った機能
入力値を保存するvector
正の整数を追加するpush_back
入力を続けるwhile
入力を終了するbreak
先頭から順にたどるbegin、end、iterator
値を取り出す*itr
合計するsum += *itr

ドラゴンボール風に言えば、戦士たちが報告した修行ポイントをvectorという記録ボードに順番に保存し、最後にスカウターの照準であるイテレータを使って全記録を確認する流れです。

保存されたポイントを1つずつ読み取り、合計していくことで、修行場全体の成果が分かります。

6章で学んだvector、push_back、イテレータ、begin、endは、C++で複数のデータを扱うときの基本になります。
この例題を通して、入力数が決まっていないデータを安全に保存し、あとから順番に処理する感覚をしっかり身につけておきましょう。