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

英語の修行コードを入力すれば、日本語の修行内容がすぐに分かる。mapを使って、キーと値の対応を実践的に確認しよう。

6章では、テンプレートやSTLを使って、さまざまなデータを効率よく管理する方法を学んできました。

vectorでは、入力されたデータを順番に保存しました。listでは、イテレータを使って要素をたどり、途中へ要素を挿入しました。setでは、同じ値を重複させずに管理しました。

今回の例題で使うmapは、キーと値を組み合わせて管理するSTLコンテナです。

たとえば、英語で入力された修行タイプから、日本語の修行名を調べるプログラムを考えてみましょう。

powerというキーには戦闘力強化、speedというキーには高速移動訓練、healingというキーには回復修行という値を対応させます。

ドラゴンボール風にたとえるなら、mapは神殿の修行検索装置です。英語の修行コードを入力すると、そのコードに対応する日本語の修行内容が表示されます。

入力された英語がmapに登録されていれば、対応する日本語を表示します。登録されていない英語が入力された場合は、修行コードが見つからなかったことを知らせます。

この例題では、次のような流れを確認します。

処理使用する機能
英語と日本語を対応させるmap
文字列のキーと値を管理するmap<string, string>
キーボードから入力するcin
入力されたキーを探すfind
キーが見つかったか判定するend
mapの値を取り出すイテレータのsecond

例題6-2:英語の修行タイプを日本語へ変換する

問題

プログラムを実行すると、画面に「英語で修行タイプを入力:」と表示されるプログラムを作りなさい。

英語の修行タイプを入力すると、その英語に対応する日本語の修行名を表示します。英語から日本語への変換にはmapを使用します。

登録されていない英語が入力された場合は、「登録されていない修行タイプです」と表示します。

英語の修行タイプと日本語の修行名は、次のように対応させます。

英語のキー日本語の修行名
power戦闘力強化
speed高速移動訓練
guard防御鍛錬
healing回復修行
spirit気力集中

実行結果の例1

登録されている修行タイプとしてhealingを入力した場合は、次のように表示されます。

英語で修行タイプを入力:healing
healingは日本語では回復修行です。

実行結果の例2

登録されていないabcを入力した場合は、次のように表示されます。

英語で修行タイプを入力:abc
登録されていない修行タイプです。

図:mapによる修行タイプの変換

この図が示していること

この図では、mapが英語のキーと日本語の値を対応付けていることを表しています。

たとえば、healingというキーには回復修行という値が対応しています。そのため、healingを入力すると、mapから回復修行を取り出せます。

mapでは、0番目や1番目といった番号ではなく、意味のある文字列を使ってデータを探せることが分かります。

解答例

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

#include <iostream>
#include <string>
#include <map>

using namespace std;

int main(int argc, char** argv) {
    // 英語の修行タイプと日本語の修行名を管理するmap
    map<string, string> trainingTypes;

    // 英語をキー、日本語を値として登録する
    trainingTypes["power"] = "戦闘力強化";
    trainingTypes["speed"] = "高速移動訓練";
    trainingTypes["guard"] = "防御鍛錬";
    trainingTypes["healing"] = "回復修行";
    trainingTypes["spirit"] = "気力集中";

    // 入力された英語を保存する変数
    string englishName;

    // 英語の修行タイプを入力する
    cout << "英語で修行タイプを入力:";
    cin >> englishName;

    // 入力されたキーをmapから検索する
    map<string, string>::iterator itr;
    itr = trainingTypes.find(englishName);

    // キーが登録されているかを判定する
    if (itr != trainingTypes.end()) {
        // 見つかった場合は対応する日本語を表示する
        cout << englishName << "は日本語では"
             << itr->second << "です。" << endl;
    }
    else {
        // 見つからなかった場合はエラーを表示する
        cout << "登録されていない修行タイプです。" << endl;
    }

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

プログラム全体の流れ

このプログラムでは、最初に英語と日本語の対応をmapへ登録します。

次に、キーボードから英語の修行タイプを入力します。

入力された英語をfindで検索し、mapの中に対応するキーが存在するかを確認します。

キーが見つかった場合は、日本語の修行名を表示します。キーが見つからなかった場合は、登録されていないことを表示します。

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

順番処理
1mapを宣言する
2英語と日本語の対応を登録する
3英語の修行タイプを入力する
4findで入力されたキーを探す
5findの結果をendと比較する
6見つかった場合は日本語を表示する
7見つからなければエラーを表示する

mapの宣言

プログラムでは、次のようにmapを宣言しています。

map<string, string> trainingTypes;

mapの基本形は、次のようになります。

map<キーの型, 値の型> 変数名;

今回のtrainingTypesでは、キーと値の両方にstring型を指定しています。

キーにはpowerやhealingなどの英語を使用します。値には戦闘力強化や回復修行などの日本語を使用します。

宣言の部分意味
mapキーと値を管理するSTLコンテナ
1つ目のstringキーの型
2つ目のstring値の型
trainingTypesmapの変数名

この宣言によって、文字列から文字列を探せる修行検索表が作られます。

ドラゴンボール風に考えると、英語の修行コードを入力すると、日本語の修行内容が表示される神殿の検索装置です。

キーと値を登録する

英語と日本語の対応は、次の処理で登録しています。

trainingTypes["power"] = "戦闘力強化";
trainingTypes["speed"] = "高速移動訓練";
trainingTypes["guard"] = "防御鍛錬";
trainingTypes["healing"] = "回復修行";
trainingTypes["spirit"] = "気力集中";

角括弧の中に書かれているpowerやspeedがキーです。右辺の戦闘力強化や高速移動訓練が値です。

たとえば、次の処理では、powerというキーに戦闘力強化という値を対応させています。

trainingTypes["power"] = "戦闘力強化";

同じように、healingには回復修行が対応します。

trainingTypes["healing"] = "回復修行";

登録後のtrainingTypesは、次のような対応表になっています。

キー
power戦闘力強化
speed高速移動訓練
guard防御鍛錬
healing回復修行
spirit気力集中

mapでは、1つのキーに1つの値が対応します。

同じキーに別の値を代入した場合は、新しい値で上書きされます。

たとえば、powerに別の日本語を代入すると、以前の値は新しい値へ更新されます。

trainingTypes["power"] = "超重力修行";

この処理のあとでは、powerに対応する値は超重力修行になります。

図:mapへキーと値を登録する仕組み

この図が示していること

この図では、mapがキーと値を1組にして保存することを表しています。

powerは戦闘力強化に対応し、healingは回復修行に対応します。

mapを利用すると、順番や番号を覚えなくても、英語のキーを指定するだけで対応する日本語を取り出せます。

キーボードから英語を入力する

英語の修行タイプは、string型の変数englishNameに保存します。

string englishName;

画面に入力を促す文章を表示したあと、cinで入力を受け取ります。

cout << "英語で修行タイプを入力:";
cin >> englishName;

たとえば、キーボードからhealingと入力すると、englishNameにはhealingが保存されます。

そのあと、このenglishNameをfindへ渡して、trainingTypesの中を検索します。

なお、文字列の比較では、大文字と小文字が区別されます。

trainingTypesに登録しているキーは、すべて小文字です。

そのため、healingと入力すれば見つかりますが、HealingやHEALINGでは別の文字列として扱われるため、見つかりません。

入力検索結果
healing見つかる
Healing見つからない
HEALING見つからない

今回のプログラムでは、小文字で入力することを前提にしています。

findで入力されたキーを探す

入力されたキーの検索にはfindを使います。

まず、map用のイテレータを宣言します。

map<string, string>::iterator itr;

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

次に、findでenglishNameを検索します。

itr = trainingTypes.find(englishName);

englishNameがhealingなら、findはhealingに対応する要素を探します。

healingが見つかった場合、itrはhealingと回復修行の組み合わせを指します。

登録されていないabcを検索した場合、該当する要素がないため、findはtrainingTypes.end()を返します。

検索するキーfindの結果
powerpowerの要素を指すイテレータ
healinghealingの要素を指すイテレータ
abcendと同じイテレータ

findの結果を確認することで、入力された英語が登録済みかどうかを判定できます。

endと比較して検索結果を判定する

findで検索したあとは、戻り値をendと比較します。

if (itr != trainingTypes.end()) {

itrがtrainingTypes.end()と異なっていれば、入力されたキーが見つかったことを意味します。

反対に、itrがtrainingTypes.end()と同じなら、入力されたキーは見つからなかったことを意味します。

条件意味
itr != trainingTypes.end()キーが見つかった
itr == trainingTypes.end()キーが見つからなかった

endは最後の要素そのものではありません。最後の要素よりもさらに1つ先の、終端を表す特別な位置です。

findは、キーが見つからなかった場合に、このendを返します。

ドラゴンボール風にたとえるなら、神殿の検索装置が修行記録を最後まで探しても該当するデータが見つからず、記録の終端へ到達した状態です。

イテレータから値を取り出す

キーが見つかった場合は、イテレータが指しているmapの値を取り出します。

mapの要素は、キーと値の組み合わせになっています。

イテレータからキーを取り出す場合はfirstを使い、値を取り出す場合はsecondを使います。

itr->first
itr->second

今回のプログラムでは、日本語の修行名を表示したいため、secondを使います。

cout << englishName << "は日本語では"
     << itr->second << "です。" << endl;

healingが入力された場合、itr->secondは回復修行になります。

そのため、次のように表示されます。

healingは日本語では回復修行です。

mapのイテレータが持つfirstとsecondを整理すると、次のようになります。

書き方取り出すデータ
itr->firstキー
itr->second

healingが見つかった場合は、itr->firstがhealingで、itr->secondが回復修行です。

登録されていないキーが入力された場合

入力されたキーが見つからなかった場合は、else側の処理が実行されます。

else {
    cout << "登録されていない修行タイプです。" << endl;
}

たとえば、abcという文字列はtrainingTypesに登録されていません。

そのため、findの結果はtrainingTypes.end()になります。

ifの条件であるitr != trainingTypes.end()は成立しないので、elseへ進みます。

英語で修行タイプを入力:abc
登録されていない修行タイプです。

このように、findとendを組み合わせると、存在しないキーにも対応できる安全なプログラムを作れます。

図:findによる検索と分岐

この図が示していること

この図では、入力されたキーをfindで検索し、見つかった場合と見つからなかった場合に処理が分かれる流れを表しています。

healingはmapに登録されているため、対応する回復修行が表示されます。

abcはmapに登録されていないため、findの結果がendになり、登録されていないことが表示されます。

角括弧による検索との違い

mapの値は、角括弧を使って取り出すこともできます。

string japaneseName = trainingTypes[englishName];

登録済みのキーが入力された場合は、対応する値を取得できます。

ただし、角括弧を使って存在しないキーへアクセスすると、そのキーがmapへ新しく追加されます。string型の値であれば、空の文字列が初期値として登録されます。

たとえば、次の処理を考えます。

string japaneseName = trainingTypes["abc"];

abcが登録されていなかった場合でも、abcというキーがtrainingTypesへ追加されます。

今回のように、キーが存在するか調べるだけなら、findを使うほうが意図しないデータの追加を防げます。

検索方法未登録キーを検索した場合
trainingTypes[キー]新しいキーが追加される
trainingTypes.find(キー)キーは追加されず、endが返る

角括弧は値の登録や更新には便利です。findは存在確認を伴う検索に向いています。

今回の解答例では、入力された英語が登録されているかを調べる必要があるため、findを使用しています。

この例題で確認できるmapの役割

この例題では、英語の修行タイプと日本語の修行名を対応付けました。

powerというキーから戦闘力強化を取り出し、healingというキーから回復修行を取り出せます。

このようにmapは、名前、コード、IDなどを手がかりにして、対応するデータを取得したい場面で役立ちます。

ドラゴンボール風の修行管理では、次のようなデータにも応用できます。

キー
戦士名戦闘力
技名消費気力
流派名修行場所
アイテム名所持数
修行コード修行内容

たとえば、技名から消費気力を調べるmapは、次のように宣言できます。

map<string, int> techniqueEnergy;

戦士名から流派名を調べる場合は、次のように宣言できます。

map<string, string> warriorStyles;

mapでは、目的に合わせてキーと値の型を選べます。

例題で身につけたい処理の流れ

今回のプログラムで大切なのは、mapへデータを登録するだけではありません。

入力されたキーをfindで検索し、その結果をendと比較して、見つかった場合と見つからなかった場合を分けるところまでが重要です。

プログラムの中心部分は、次の流れになっています。

itr = trainingTypes.find(englishName);

if (itr != trainingTypes.end()) {
    cout << itr->second << endl;
}
else {
    cout << "登録されていない修行タイプです。" << endl;
}

この形は、mapでキーを安全に検索するときの基本的な書き方です。

findで探し、endと比較し、見つかったらsecondから値を取り出します。

ドラゴンボール風に考えるなら、神殿の修行検索装置へ英語のコードを入力し、記録が見つかったら修行内容を表示し、見つからなければ未登録だと知らせる流れです。

mapを使うと、番号ではなく意味のあるキーからデータを探せます。さらにfindを組み合わせることで、登録されていない入力にも安全に対応できるようになります。

この例題で、mapの宣言、キーと値の登録、キーボード入力、findによる検索、endによる判定、secondによる値の取得という一連の流れをしっかり確認しておきましょう。