先頭へ戻る

switch文 | Programming Place Plus 新C++編

Programming Place Plus トップページ -- 新C++編

先頭へ戻る

このページの概要

このページでは、switch文を取り上げます。switch文は、いくつか用意しておいた処理のいずれかを選択・実行させることができる機能です。この機能を理解すると、入力されたデータの内容によって、異なる処理を実行させるプログラムが作れます。

以下は目次です。要点だけをさっと確認したい方は、「まとめ」をご覧ください。



選択構造(分岐構造)

このページまでに登場したプログラムは、入力されるデータは変わるにしても、行う処理は同じ内容に固定されていました。そのため、電卓を模したプログラムを作りたくても、いつも加算(ほかの計算でもいいですが)に固定されてしまいます。「入力と変数」のページで、次のようなプログラムを書きました。

#include <iostream>

int main()
{
    int value1 {};
    std::cin >> value1;

    int value2 {};
    std::cin >> value2;

    std::cout << value1 + value2 << "\n";
}

実行結果:

3   <-- 入力した整数
5   <-- 入力した整数
8

問題なのは、value1 + value2 の部分です。ここで + を使っているので、減算や乗算や除算には切り替えられません。何とか、減算や乗算や除算もできる電卓プログラムにしたい、というのがこのページの目標です。

そのためには、実行中に、実行するコードを切り替えられるような仕組みが必要です。やりたいことを書き表すとこうなります。

#include <iostream>
#include <string>

int main()
{
    int value1 {};
    std::string op {};
    int value2 {};
    std::cin >> value1 >> op >> value2;

    /*
    op[0] が + なら、「std::cout << value1 + value2 << "\n";」を実行。
    op[0] が - なら、「std::cout << value1 - value2 << "\n";」を実行。
    op[0] が * なら、「std::cout << value1 * value2 << "\n";」を実行。
    op[0] が / なら、「std::cout << value1 / value2 << "\n";」を実行。
    */
}

「3 - 1」のように入力されたら「2」を出力し、「5 * 2」のように入力されたら「10」を出力したいというわけです。演算子は C++ での表現に合わせて「+」「-」「*」「/」を使うことにします。演算子の記号はどれも1文字なので、char型の変数で表現できます。

演算子を受け取る変数の名前を、演算子をあらわす「operator」から「op」としています。実は「operator」が C++ のキーワードの1つになっていて、変数名に使えません(「定数式と識別子」のページを参照)。不用意な省略は分かりづらさをうみますが、ここではそういう事情でこの名前を使っています。

問題はコメントにしているところです。入力された演算子に応じて実行したい文が4種類に分かれます。この4つの文はいずれもソースコード上に記述するものです。「4つの文の中から、実行中に適切なものを1つだけ選択して、その文だけを実行する」というのが、このページでやりたいことです。

このように、いくつかの文の中から実行するものを選択するようになっているプログラムの構造を、選択構造 (choice structure)、あるいは分岐構造 (branch structure)といいます。

switch文

C++ で選択構造のプログラムを実現するために使える方法は1つではありませんが、ここでは1つだけ説明します。それは、switch文 (switch statement) です。

switch文の文法は次のようになっています。

switch (条件式) {
case 定数式:
    // 実行する文
    break;  // 基本的には必要だが、省くことができる

(必要に応じて、上の3行分を繰り返す)

(以下は必要であれば書く)
default:
    // 実行する文
    break;  // 基本的には必要だが、省くことができる
}

少々複雑なので、先に実際のソースコードを見ておきましょう。次のプログラムは、入力された整数が 0、1、2 のいずれかなら、それぞれ別のメッセージを出力し、これら以外のときには ??? と出力します。

#include <iostream>

int main()
{
    int value {};
    std::cout << "Please enter the integer.\n";
    std::cin >> value;

    switch (value) {
    case 0:
        std::cout << "Good morning.\n";
        break;
    case 1:
        std::cout << "Hello.\n";
        break;
    case 2:
        std::cout << "Good evening.\n";
        break;
    default:
        std::cout << "???\n";
        break;
    }
}

実行結果:

Please enter the integer.
2   <-- 入力した整数
Good evening.

実行結果:

Please enter the integer.
8   <-- 入力した整数
???

では、switch文の構文を順番に確認していきます。

まず、switch というキーワード(条件式) が現れます。「条件式」は式なので、計算式でも代入式でも書けますが、評価した結果が整数にならなければなりません。文字の正体は整数ですから(「文字」のページを参照)、文字は使えます。

switch文の使い方として多いのは、変数名だけを書く使い方でしょう。たとえば、int型の変数 value があるとして、switch (value) のような記述ができます。少し脱線しますが、このように、変数名(識別子)が1つ現れるだけでも式ですし、リテラルが1つだけでも式です。こういった、識別子やリテラルが1つだけ現れる形の式を一次式 (primary expression) といいます。123


次に { があります。これは末尾にある } が対応し、全体として1つの switch文であることを示しています。switch は文なので、最後に ; が付きそうに思えますが、最後に ; はありません。

最後に ; を書いてもエラーにはなりませんが、それは、switch文の後ろに空の文があるという意味になります。

続いて case 定数式: が現れます(末尾にあるのはコロン (:) です。セミコロン (;) ではありません)。これは caseラベル (case label) と呼ばれます。ラベル (label) は、文に対して付けられた名前(識別子)です。caseラベルは、case 定数式: に続く文に「case 定数式」という名前を与えていることになります。

「定数式」にはコンパイル時点で確定できる式しか書けませんし(それを定数式と呼ぶのでした)、「条件式」同様、整数にならなければなりません。また、同じ整数になる caseラベルが複数あると、コンパイルエラーになります。

プログラムの実行時、switch文のところに差し掛かると、「条件式」を評価してえられる値と、「定数式」を評価して得られる値を比較して、一致しているところの caseラベルが示す文へ移動します。移動した先に複数の文があるのなら、書いた順番どおりに実行されます。

ほとんどの switch文では、それぞれの caseラベルの処理の最後に、break文 (break statement)を記述します。break文は break; と記述された文のことで、switch文の処理を終えるという意味になります。終わりであることを示すものなので、基本的には必須ですが、書かないという選択を取る場合もあります(あとで取り上げます)。

switch (value) {  // 条件式を評価 --> 変数value の値が得られる
case 0:
    // 変数value の値が 0 だったときに実行する文
    break;
case 1:
    // 変数value の値が 1 だったときに実行する文
    break;
case 2:
    // 変数value の値が 2 だったときに実行する文
    break;
}

default:defaultラベル (default label) と呼ばれます。defaultラベルのところの文は、「条件式」がいずれの case の「定数式」とも一致しなかったときに実行されます。いわば「その他」の場合に実行される部分ということになります。その他の場合に実行する文が特にない場合には、defaultラベルの部分は省略できます。

「その他」というのは、「デフォルト (default: 規定)」という言葉とは逆な感じがしますが、多くの場合、「その他」の意味で使うことが多いです。

defaultラベルは最後に記述することが多いですが、switch文において、ラベルの記述順序は実行結果に影響しません。

switch (value) {  // 条件式を評価 --> 変数value の値が得られる
default:   // 最後以外の位置に書いても問題ない
    // いずれの case にも該当しないときに実行する文
    break;
case 0:
    // 変数value の値が 0 だったときに実行する文
    break;
case 1:
    // 変数value の値が 1 だったときに実行する文
    break;
case 2:
    // 変数value の値が 2 だったときに実行する文
    break;
}

文字列の場合

switch文の「条件式」は、整数にならなければいけないので、文字列を書くことはできません。

#include <iostream>
#include <string>

int main()
{
    std::cout << "Please enter the your profession.\n";
    std::string profession {};
    std::getline(std::cin, profession);

    switch (profession) {  // 条件式を文字列にできない
    case "programmer":  // caseラベルに文字列を記述することはできない
        std::cout << "Me too!\n";
        break;
    case "Software Enginner:
        std::cout << "Me too!\n";
        break;
    default:
        std::cout << "nice to meet you.\n";
        break;
    }
}

ほかのプログラミング言語では問題なくできる場合が多いことなので、これはとても不便な仕様です。C++ では、if、else-if を使って書きます。

電卓プログラムでの利用例

このページの冒頭の話題にもどって、電卓のプログラムを書いてみます。入力された演算子の種類に応じて、計算式を変えればいいので、次のようになります。

#include <iostream>
#include <string>

int main()
{
    constexpr auto ADDITION = '+';
    constexpr auto SUBTRACTION = '-';
    constexpr auto MULTIPLICATION = '*';
    constexpr auto DIVISION = '/';

    std::cout << "Please enter the formula.\n";
    int value1 {};
    std::string op {};
    int value2 {};
    std::cin >> value1 >> op >> value2;

    switch (op[0]) {
    case ADDITION:
        std::cout << value1 + value2 << "\n";
        break;
    case SUBTRACTION:
        std::cout << value1 - value2 << "\n";
        break;
    case MULTIPLICATION:
        std::cout << value1 * value2 << "\n";
        break;
    case DIVISION:
        std::cout << value1 / value2 << "\n";
        break;
    default:
        std::cout << "The formula is incorrect.\n";
        break;
    }
}

実行結果:

Please enter the formula.
10 + 5  <-- 入力した計算式
15

実行結果:

Please enter the formula.
10 - 5  <-- 入力した計算式
5

実行結果:

Please enter the formula.
10 * 5  <-- 入力した計算式
50

実行結果:

Please enter the formula.
10 / 5  <-- 入力した計算式
2

実行結果:

Please enter the formula.
10 @ 5  <-- 入力した計算式
The formula is incorrect.

加算、減算、乗算、除算がそれぞれ計算できるようになりました。defaultラベルを使って、いずれでもない演算子が入力されたときへの反応も記述しています。

1回の計算ごとにプログラムが終わってしまう点が不便です。次以降のページでは、この問題の解決に向けて進めていきます。

フォールスルー

ほとんどの switch文は、break文を使って、case と case(あるいは default)のコードを区切るような書き方をします。これは、1つの選択肢ごとに1つの処理(その処理のために必要な文は複数かもしれない)を実行することが基本だからです。1つの処理の完結を示すために break; を入れています。

もし、break文がないとどうなるかというと、そのまま次の case や default のコードへ侵入して実行されます。これは、フォールスルー (fallthrough) と呼ばれる動作です。

また、break文が現れないまま、switch文の終わりの } に達した場合は、単に switch文の実行が終わりになります。こちらはフォールスルーではありません。

実際に試してみます。

#include <iostream>

int main()
{
    std::cout << "Please enter the integer (1~3).\n";
    int value {};
    std::cin >> value;

    switch (value) {
    case 3:
        std::cout << "*";
    case 2:
        std::cout << "*";
    case 1:
        std::cout << "*";
    default:
        std::cout << "\n";
    }
}

実行結果:

3  <-- 入力した整数
***

このプログラムは、1~3 の整数を入力すると、その数に応じた個数の * を出力します。

3 が入力されたとしましょう。すると、switch文のところに来たとき、変数value の値は 3 ですから、case 3: のコードが実行されます。そこには、std::cout << "*"; があるので、* を1つだけ出力します。この直後、break文がなく case 2: が現れます。フォールスルーが起きて、そのまま case 2: のコードへ侵入していきます。

そこには std::cout << "*"; があるので、* を1つ出力します。次も同じで、break文がなく case 1: が現れます。フォールスルーにより、case 1: のコードへ侵入します。そして、std::cout << "*"; が実行され、* が1つ出力され、今度は default: のコードへ侵入します。そこには、std::cout << "\n"; があって、改行されます。

最後のところにも break文はなく、case文の終わりの } が現れます。これで、switch文の処理が終了となります。結果的に、* が3個と改行が出力されたことになります。


基本的にフォールスルーは使わない方向でいた方がいいです。switch文は「どれか1つの処理が選ばれる」という印象が強く、実はいくつかの処理が実行されているというのは、単純に分かりづらくなります。また、break文の記述がないので、フォールスルーを意図したものなのか、書き忘れたのかがはっきりしない問題もあります(なので、フォールスルーを意図しているのなら、コメントを書き残しましょう)。

【上級】C++17 で追加された [[fallthrough]]属性を使うと、フォールスルーしていることが明確になります。4

フォールスルーをせずに、分かりやすくきれいに書く方法がある場合が多いので、ほかの方法を検討した方がいいです。さきほどのサンプルプログラムなら、次のように書いたほうが素直です。

switch (value) {
case 3:
    std::cout << "***\n";
    break;
case 2:
    std::cout << "**\n";
    break;
case 1:
    std::cout << "*\n";
    break;
default:
    std::cout << "\n";
    break;
}

まとめ


新C++編の【本編】の各ページには、末尾に練習問題があります。ページ内で学んだ知識を確認する簡単な問題から、これまでに学んだ知識を組み合わせなければならない問題、あるいは更なる自力での調査や模索が必要になるような高難易度な問題をいくつか掲載しています。


参考リンク


練習問題

問題の難易度について。

★は、すべての方が取り組める入門レベルの問題です。
★★は、自力でプログラミングができるようなるために、入門者の方であっても取り組んでほしい問題です。
★★★は、本格的にプログラマーを目指す人のための問題です。

問題1 (確認★)

電卓プログラムを改造して、剰余を計算する演算子「%」も受け付けられるようにしてください。

解答・解説

問題2 (確認★)

次のプログラムがコンパイルエラーになる理由を説明してください。

#include <iostream>

int main()
{
    int value {3};

    switch (value + 1) {
    case 0:
        break;
    case 1:
        std::cout << "*\n";
        break;
    case 1 + 1:
        std::cout << "**\n";
        break;
    case 0:
        std::cout << "\n";
        break;
    }
}

解答・解説

問題3 (基本★★)

1~13 のいずれかの整数の入力を受け取ります。この整数をトランプの数字に見立てて、1 は A、11 は J、12 は Q、13 は K、それ以外の数はそのまま出力するプログラムを作成してください。

解答・解説

問題4 (応用★★★)

あまり奇麗なプログラムにはならないものの、ここまでの知識だけでも、電卓プログラムを改造して、「3 + 2 * 4」のように、整数3つ演算子2つの計算式に対応させることができます(逆に「3 + 2」で止められなくなりますが)。そのようなプログラムを書いてみてください。

解答・解説


解答・解説ページの先頭



更新履歴




はてなブックマーク に保存 Pocket に保存 Facebook でシェア
Twitter でツイート Twitter をフォロー LINE で送る
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー