閉じる

C言語のswitch文の使い方入門: caseとbreakの書き方と注意点

多くの条件に応じて分岐したい時に、if-elseが長く連なると読みづらくなります。

そんな時に活躍するのがswitch文です。

switchは値に応じて分岐を選び、コードの見通しを良くします。

本記事ではcaseとbreakの正しい使い方、defaultの役割、落とし穴とベストプラクティスを、初心者の方にも分かりやすい順序で解説します。

C言語のswitch文の基本

switch文の用途とメリット

多くの離散的な値に応じて処理を切り替える場合、switch文を使うと意図が明確になります。

if-elseでも同じことは可能ですが、switchは分岐対象が単一の式で、候補が列挙できる場面に特に適しています。

読み手はswitch(式)case 定数:の対応だけを追えば良いので、条件式の詳細を逐一読む必要がありません。

コンパイラ最適化によりジャンプテーブルが使われる場合もあり、実行効率が良くなることがあります。

基本構文

基本形は次の通りです。

caseの末尾にbreakを入れるのが標準的な書き方です。

C言語
#include <stdio.h>

int main(void) {
    int x = 2;

    // x の値に応じて分岐
    switch (x) {
        case 1:
            puts("x は 1 です");
            break;  // ここで switch を抜ける

        case 2:
            puts("x は 2 です");
            break;

        default:
            // どの case にも一致しない時
            puts("x は 1 でも 2 でもありません");
            break;
    }

    return 0;
}
実行結果
x は 2 です

breakを忘れると、次のcaseに処理が流れ落ちる(フォールスルーする)点がswitch最大の注意点です。

フォールスルーについてはこの記事の後半で解説します。

意図しないフォールスルーはバグの温床になります。

対応する型(int, char, enum)

switchの制御式とcaseラベルは、整数系の型で扱います。

浮動小数点や文字列は使えません。

項目許可される型/値補足
switch(式)整数型全般(int, short, long, unsigned系, char, enum など)ポインタや浮動小数点は不可
case ラベル整数定数式(リテラル、'A'enum定数、#define定数など)実行時に変わる変数は不可

charは整数型として扱われます。

'A'のような文字リテラルは実体は整数です。

一方、"A"は文字列リテラル(配列)であり、caseでは使えません。

caseとbreakの書き方

caseラベルの書式と定数式

caseラベルは整数定数式である必要があります。

以下のような書き方が可能です。

C言語
// 良い例
#define CMD_START 1
enum Level { LOW = 10, MID = 20, HIGH = 30 };

switch (cmd) {
    case 0:          /* 整数リテラル */
        /* ... */
        break;
    case CMD_START:  /* マクロ定数 */
        /* ... */
        break;
    case 'A':        /* 文字定数(実体は整数) */
        /* ... */
        break;
    case HIGH:       /* enum 定数 */
        /* ... */
        break;
}

逆に次のような書き方はできません。

変数や計算結果など、実行時に変化する値はcaseに置けないためです。

C言語
int a = 10;
switch (x) {
    // NG例: 変数や変数を使った式は不可
    /* case a: */
    /* case a + 1: */
    /* case x + 1: */
    default:
        break;
}

breakの役割と配置

breakは、その位置でswitch文から抜けるためのキーワードです。

breakがないと、次のcaseの文へと処理が流れます(フォールスルー)。

次の例では、case 2でbreakを忘れたため、case 3まで実行されます。

C言語
#include <stdio.h>

int main(void) {
    int x = 2;
    switch (x) {
        case 1:
            puts("1");
            break;
        case 2:
            puts("2");   // break がない!
        case 3:
            puts("3");   // ここも実行される
            break;
        default:
            puts("other");
            break;
    }
    return 0;
}
実行結果
2
3

意図せぬフォールスルーはほぼバグです。

基本は各caseの末尾にbreakを置く習慣をつけてください。

defaultの使い方

defaultは、どのcaseにも一致しなかった時に実行される最後の砦です。

defaultの位置はどこでも構いませんが、可読性のため最後に置くのが一般的です。

予期しない値に対しログ出力や防御的なエラー処理を行いましょう。

C言語
#include <stdio.h>

int main(void) {
    int cmd = 99; // 未知のコマンド
    switch (cmd) {
        case 1:
            puts("START");
            break;
        case 2:
            puts("STOP");
            break;
        default:
            // 予期しない値に備える
            fprintf(stderr, "不明なコマンド(%d)です\n", cmd);
            break;
    }
    return 0;
}
実行結果
不明なコマンド(99)です

複数caseをまとめる書き方

複数の値で全く同じ処理を行う場合、caseを連続して書くと簡潔になります。

C言語
#include <stdio.h>

int main(void) {
    char c = 'e';
    switch (c) {
        case 'a':
        case 'e':
        case 'i':
        case 'o':
        case 'u':
            puts("母音です");
            break;
        default:
            puts("子音かその他の文字です");
            break;
    }
    return 0;
}
実行結果
母音です

fallthroughの意味と注意

fallthrough(フォールスルー)は、明示的にbreakを書かず、次のcaseの文を続けて実行することです。

意図的に使う場合は、コメントやアトリビュートで明示して誤解や警告を避けます。

C言語
#include <stdio.h>

// 接尾辞による倍率: 'G' -> 1024*1024*1024, 'M' -> 1024*1024, 'K' -> 1024
int main(void) {
    unsigned long long base = 1;
    char suffix = 'M';

    switch (suffix) {
        case 'G':
            base *= 1024;
            /* FALLTHROUGH */  // 意図的に次へ
        case 'M':
            base *= 1024;
            /* FALLTHROUGH */  // 意図的に次へ
        case 'K':
            base *= 1024;
            break;
        default:
            // 倍率なし
            break;
    }

    printf("%llu\n", base);
    return 0;
}
実行結果
1048576

コンパイラによっては/* FALLTHROUGH // fall through */というコメントで意図を示すと警告を抑制できます。

新しめの規格やコンパイラでは[[fallthrough]];の属性が使える場合があります(対応状況はコンパイラのドキュメントを確認してください)。

よくあるエラーと注意点

break書き忘れによる誤動作

breakを忘れると、想定外のcaseまで実行されます。

例えばメニュー処理で「1:開始、2:停止」としていて、2の処理の後にbreakがないと、停止の直後に開始が実行される、といった事故が起こります。

各case末尾のbreakを習慣化し、意図的なフォールスルーには必ずコメントを添えましょう。

case値の重複エラー

同じ値のcaseラベルはひとつのswitch内に重複できません。

コンパイルエラーになります。

C言語
// コンパイルエラーの例
switch (42) {
    case 1:
        break;
    case 1:  // 重複
        break;
    default:
        break;
}
実行結果
コンパイルエラー(例):
error: duplicate case value '1'
note: previously used here

文字リテラル(‘A’)と数値の違い

文字リテラル'A'は整数(多くの処理系で65)で、caseに使えます。

一方"A"は文字列リテラルで、caseでは使えません。

さらにCでは文字リテラルの型はintである点も覚えておくと良いです。

C言語
#include <stdio.h>

int main(void) {
    printf("'A' の整数値: %d\n", 'A');
    printf("65 と 'A' は等しいか: %s\n", (65 == 'A') ? "true" : "false");

    switch ('A') {
        case 'A':  // OK
            puts("マッチします");
            break;
        /* case "A": */ // NG: 文字列リテラルは不可
        default:
            break;
    }
    return 0;
}
実行結果
'A' の整数値: 65
65 と 'A' は等しいか: true
マッチします

case直後の変数宣言はブロックで囲む

label(ここではcase)の直後には文(statement)が必要です。

変数宣言は文ではないため、複合文(ブロック)で囲んでください。

C言語
// NG例
/*
switch (n) {
    case 1:
        int a = 0;   // エラーになりやすい
        a++;
        break;
}
*/
C言語
// OK例: ブロックで囲む
switch (n) {
    case 1: {
        int a = 0;
        a++;
        break;
    }
    default:
        break;
}

範囲指定ができない時の対処

標準Cのswitchはcaseで範囲(1〜5など)を直接指定できません

次のような回避策が実用的です。

  • 値を前処理して类别化する。例: 点数scoreを10で割って十の位で分岐する。
  • 列挙できる範囲ならcaseを並べてまとめる。
  • 範囲など複雑な条件は素直にif-elseにする。
C言語
#include <stdio.h>

int main(void) {
    int score = 86;

    // 十の位で段階化してから switch
    switch (score / 10) {
        case 10: // 100点
        case 9:
            puts("評価: A");
            break;
        case 8:
            puts("評価: B");
            break;
        case 7:
            puts("評価: C");
            break;
        case 6:
            puts("評価: D");
            break;
        default:
            puts("評価: F");
            break;
    }
    return 0;
}
実行結果
評価: B

GCCなどにはcase 1 ... 5:のような拡張がありますが、標準Cではありません

可搬性を重視するコードでは避けましょう。

switch内のcontinueの挙動

continueはループを次の反復に進めるキーワードです。

switch単体の中で使っても意味はなく、そもそもループ外ではコンパイルエラーになります。

ループ内のswitchでcontinueを書くと、switchを抜けるのではなくループの次回反復へ進みます。

C言語
#include <stdio.h>

int main(void) {
    for (int i = 0; i < 5; i++) {
        switch (i) {
            case 2:
                puts("i=2: continue で次の反復へ");
                continue; // ループ( for )の次反復へ。下の puts は実行されない
            case 3:
                puts("i=3: break は switch を抜けるだけ");
                break;    // for には留まり、下の puts は実行される
            default:
                printf("i=%d: 通常処理\n", i);
                break;
        }
        puts("after switch"); // continue された反復では到達しない
    }
    return 0;
}
実行結果
i=0: 通常処理
after switch
i=1: 通常処理
after switch
i=2: continue で次の反復へ
i=3: break は switch を抜けるだけ
after switch
i=4: 通常処理
after switch

switchを抜けたいならbreakループを抜けたいならループ側のbreakやフラグ管理などを使い分けましょう。

実用例とベストプラクティス

メニュー選択のswitch-case

コンソールメニューはswitchの定番です。

defaultで不正入力を検出し、各case末尾にbreakを忘れないのが基本です。

C言語
#include <stdio.h>

int main(void) {
    int choice = 0;
    int a = 12, b = 3;

    puts("メニュー:");
    puts("  1) 加算");
    puts("  2) 減算");
    puts("  0) 終了");
    printf("選択を入力してください: ");

    if (scanf("%d", &choice) != 1) {
        fprintf(stderr, "入力エラーです\n");
        return 1;
    }

    switch (choice) {
        case 1:
            printf("%d + %d = %d\n", a, b, a + b);
            break;
        case 2:
            printf("%d - %d = %d\n", a, b, a - b);
            break;
        case 0:
            puts("終了します");
            break;
        default:
            fprintf(stderr, "不正な選択(%d)です\n", choice);
            break;
    }

    return 0;
}
実行結果
メニュー:
  1) 加算
  2) 減算
  0) 終了
選択を入力してください: 1
12 + 3 = 15

enumを使った安全な分岐

enumは有効な値の集合を型として表現でき、switchと相性が良いです。

未知の値はdefaultで検知します。

C言語
#include <stdio.h>

typedef enum { ACT_EXIT = 0, ACT_ADD = 1, ACT_SUB = 2, ACT_MAX } Action;

void do_action(Action act, int x, int y) {
    switch (act) {
        case ACT_ADD:
            printf("ADD: %d\n", x + y);
            break;
        case ACT_SUB:
            printf("SUB: %d\n", x - y);
            break;
        case ACT_EXIT:
            puts("EXIT");
            break;
        default:
            // 範囲外の値を検知
            fprintf(stderr, "不正な Action(%d) です\n", act);
            break;
    }
}

int main(void) {
    do_action(ACT_ADD, 7, 5);
    do_action(ACT_SUB, 10, 3);
    // do_action(99, 1, 1); // 故意に範囲外(環境次第でコンパイルエラー)
    return 0;
}
実行結果
ADD: 12
SUB: 7

マクロ定数とswitchの組み合わせ

外部仕様やプロトコルの定数を#defineで表し、switchで分岐するのは一般的です。

ただし型安全性はenumの方が高いため、可能ならenumの採用を検討しましょう。

C言語
#include <stdio.h>

#define MSG_HELLO  100
#define MSG_GOODBYE 200

void handle_message(int msg) {
    switch (msg) {
        case MSG_HELLO:
            puts("Hello!");
            break;
        case MSG_GOODBYE:
            puts("Goodbye!");
            break;
        default:
            fprintf(stderr, "未知のメッセージ(%d)\n", msg);
            break;
    }
}

int main(void) {
    handle_message(MSG_HELLO);
    handle_message(123); // 未知
    return 0;
}
実行結果
Hello!
未知のメッセージ(123)

defaultでエラー処理を徹底

defaultは単なる「その他」ではなく、不整合検知の最後の砦です。

ログ出力、例外終了(組み込み環境でなければabort()等)、安全なデフォルト動作を入れるなど、プロジェクトの方針に従って徹底します。

C言語
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    int state = 3; // 想定: 0,1,2 のみ
    switch (state) {
        case 0: puts("Idle");  break;
        case 1: puts("Run");   break;
        case 2: puts("Sleep"); break;
        default:
            fprintf(stderr, "致命的: 未知の状態(%d)\n", state);
            /* abort(); */ // 必要なら強制終了
            break;
    }
    return 0;
}
実行結果
致命的: 未知の状態(3)

コメントで意図的fallthroughを明示

フォールスルーが設計意図であることをコメントで伝えると、レビューや将来の修正での事故を防げます

コンパイラ警告抑制にも有効です。

C言語
#include <stdio.h>

void log_with_level(int level, const char *msg) {
    // level: 0=ERROR, 1=WARN, 2=INFO
    switch (level) {
        case 0:
            puts("[ERROR]");
            /* FALLTHROUGH */  // ERROR は WARN/INFO 相当も含む
        case 1:
            puts("[WARN]");
            /* FALLTHROUGH */
        case 2:
            puts("[INFO]");
            puts(msg);
            break;
        default:
            fprintf(stderr, "未知のレベル(%d)\n", level);
            break;
    }
}

int main(void) {
    log_with_level(0, "重大な問題");
    return 0;
}
実行結果
[ERROR]
[WARN]
[INFO]
重大な問題

対応しているコンパイラ/規格では[[fallthrough]];の属性を使用することも可能です。

いずれにせよ、意図を明文化することが重要です。

まとめ

switch文は単一の式の離散値で分岐する際に、if-elseよりも読みやすく安全に書ける強力な構文です。

caseラベルには整数定数式を用い、各case末尾のbreakを徹底し、defaultで予期しない値を確実に扱いましょう。

フォールスルーは意図がある場合のみに限定し、コメントや属性で明示します。

範囲条件が必要な時は前処理(例: 除算で類別)やif-elseに切り替えると、正しさと可読性を両立できます。

enumの活用やログ/エラー処理の徹底と合わせて、安全で意図が伝わる分岐コードを書いていきましょう。

この記事を書いた人
エーテリア編集部
エーテリア編集部

プログラミングの基礎をしっかり学びたい方向けに、C言語の基本文法から解説しています。ポインタやメモリ管理も少しずつ理解できるよう工夫しています。

クラウドSSLサイトシールは安心の証です。

URLをコピーしました!