switch文 | Programming Place Plus C言語編 第11章

トップページC言語編

このページの概要

以下は目次です。


選択構造(分岐構造)

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

#include <stdio.h>

// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value;
    sscanf(s, "%d", &value);
    return value;
}

int main(void)
{
    int value1 = get_input_integer();
    int value2 = get_input_integer();
    printf("%d\n", value1 + value2);
}

実行結果:

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

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

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

int main(void)
{
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value1;
    int value2;
    char op;
    sscanf(s, "%d%c%d", &value1, &op, &value2);

    /* 3 - 1 とか 5 * 2 のような入力を受け取り、
    op が + なら、「printf("%d\n", value1 + value2);」を実行。
    op が - なら、「printf("%d\n", value1 - value2);」を実行。
    op が * なら、「printf("%d\n", value1 * value2);」を実行。
    op が / なら、「printf("%d\n", value1 / value2);」を実行。
    */
}

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

演算子を受け取る変数の名前を、演算子をあらわす「operator」から「op」としています。「operator」は C++ ではキーワードの1つになっているためか、Visual Studio 2015 でこの名前を使うと、C言語としてコンパイルしていても、妙なコンパイルエラーが出ます。不用意な名前の省略は分かりづらさをうみますが、ここではそういう事情でこの名前を使っています。

コメントで書いているように、入力された演算子に応じて実行したい文が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 <stdio.h>

// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value;
    sscanf(s, "%d", &value);
    return value;
}

int main(void)
{
    puts("Please enter the integer.");
    int value = get_input_integer();

    switch (value) {
    case 0:
        puts("Good morning.");
        break;
    case 1:
        puts("Hello.");
        break;
    case 2:
        puts("Good evening.");
        break;
    default:
        puts("???");
        break;
    }
}

実行結果:

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

実行結果:

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

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

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

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


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

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

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

「整数定数」なので、整数の定数にならなければなりませんが、定数式(第10章)を置くことはできます。

同じ整数になる 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 <stdio.h>

int main(void)
{
    puts("Please enter the your profession.");
    char profession[80];
    fgets(profession, sizeof(profession), stdin);

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

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

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

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

#include <stdio.h>

int main(void)
{
    #define ADDITION        '+'
    #define SUBTRACTION     '-'
    #define MULTIPLICATION  '*'
    #define DIVISION        '/'

    puts("Please enter the formula.");
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value1;
    int value2;
    char op;
    sscanf(s, "%d %c %d", &value1, &op, &value2);

    switch (op) {
    case ADDITION:
        printf("%d\n", value1 + value2);
        break;
    case SUBTRACTION:
        printf("%d\n", value1 - value2);
        break;
    case MULTIPLICATION:
        printf("%d\n", value1 * value2);
        break;
    case DIVISION:
        printf("%d\n", value1 / value2);
        break;
    default:
        puts("The formula is incorrect.");
        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ラベルを使って、いずれでもない演算子が入力されたときへの反応も記述しています。

フォールスルー

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

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

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

実際に試してみます。

#include <stdio.h>

// 整数の入力を受け取る
// 戻り値: 標準入力から整数の入力を受け取り、その値を返す。
int get_input_integer(void)
{
    char s[40];
    fgets(s, sizeof(s), stdin);
    int value;
    sscanf(s, "%d", &value);
    return value;
}

int main(void)
{
    puts("Please enter the integer (1~3).");
    int value = get_input_integer();

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

実行結果:

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

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

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

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

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


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

【C++プログラマー】[[fallthrough]]属性は(そもそも属性という仕組みも)C言語の標準にはありません。

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

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


練習問題

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

#include <stdio.h>

int main(void)
{
    int value = 3;

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

問題② 標準入力から、0~6 の整数を受け取り、それぞれに該当する曜日を出力するプログラムを switch文を使って、作成してください。「0」を日曜日、「1」を月曜日…といった具合に考えるものとします。

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


解答ページはこちら

参考リンク


更新履歴



前の章へ (第10章 定数)

次の章へ (第12章 for文)

C言語編のトップページへ

Programming Place Plus のトップページへ



はてなブックマーク に保存 Pocket に保存 Facebook でシェア
X で ポストフォロー LINE で送る noteで書く
rss1.0 取得ボタン RSS 管理者情報 プライバシーポリシー
先頭へ戻る