コンテンツにスキップ

C言語/case

出典: フリー教科書『ウィキブックス(Wikibooks)』

はじめに

[編集]

C言語におけるswitch文は複数の選択肢から一つを選ぶ強力な制御構造ですが、その効果を最大限に発揮するためにはcaseラベルの適切な理解と使用が不可欠です。本章ではcaseラベルの詳細な仕様、動作原理、および効果的な使用パターンについて解説します。

caseラベルの基本

[編集]

caseラベルはswitch文内で使用され、特定の値に対応する処理を定義します。基本構文は以下の通りです:

switch () {
    case 定数式1:
        /* 定数式1に一致した場合の処理 */
        break;
    case 定数式2:
        /* 定数式2に一致した場合の処理 */
        break;
}

caseの後には定数式が続き、コロン(:)で終わります。その後に、対応する処理のコードブロックが続きます。

caseラベルの特性

[編集]

定数式の要件

[編集]

caseラベルには定数式のみが使用できます。以下に有効な定数式と無効な式の例を示します:

#define MAX_VALUE 100
const int MIN_VALUE = 0;
int variable = 50;

switch (value) {
    case 10:                /* 有効: 整数リテラル */
    case 'A':               /* 有効: 文字リテラル */
    case MAX_VALUE:         /* 有効: マクロ定数 */
    case MIN_VALUE:         /* 有効: const変数 */
    case MAX_VALUE - 10:    /* 有効: 定数式 */
    case sizeof(int):       /* 有効: sizeofはコンパイル時に評価 */
    
    /* 以下は無効な例 */
    /*
    case variable:          // 無効: 変数は使用不可
    case getValue():        // 無効: 関数呼び出しは使用不可
    case 1.5:               // 無効: 浮動小数点は使用不可
    */
}
有効な定数式 無効な式
整数リテラル 変数
文字リテラル 関数呼び出し
列挙型定数 浮動小数点リテラル
マクロ定数 文字列リテラル
const修飾された変数 ポインタ
コンパイル時評価可能な式 実行時に値が決まる式

ラベルの一意性

[編集]

同一のswitch文内で同じ値を持つcaseラベルを複数定義することはできません。

switch (value) {
    case 1:
        /* 処理 */
        break;
    case 1:    /* エラー: 重複したcaseラベル */
        /* 処理 */
        break;
}

ただし、異なるswitch文であれば同じ値のcaseラベルを使用できます。

caseラベルと制御フロー

[編集]

フォールスルー

[編集]

caseラベルの最も重要な特性の一つは「フォールスルー」と呼ばれる動作です。break文がないと、一致したcase以降のすべての文が実行されます。

#include <stdio.h>
int main() {
    int value = 1;
    
    switch (value) {
        case 1:
            printf("値は1です\n");
            /* breakがないので次のcaseも実行される */
        case 2:
            printf("値は1または2です\n");
            break;
        case 3:
            printf("値は3です\n");
            break;
    }
    
    return 0;
}

この例では、valueが1のため、「値は1です」と「値は1または2です」の両方が出力されます。

意図的なフォールスルーの活用

[編集]

フォールスルーは時に有用な機能であり、以下のような場合に活用できます:

switch (month) {
    case 1: case 3: case 5: case 7: case 8: case 10: case 12:
        days = 31;
        break;
    case 4: case 6: case 9: case 11:
        days = 30;
        break;
    case 2:
        days = is_leap_year(year) ? 29 : 28;
        break;
    default:
        days = 0; /* 無効な月 */
}

この例では、同じ日数を持つ月を効率的にグループ化しています。

複合文とcaseラベル

[編集]

caseラベル後の処理が複数の文からなる場合、ブロック(中括弧)で囲む必要はありませんが、変数宣言を含む場合は注意が必要です。

switch (value) {
    case 1: {
        int temp = value * 2;
        printf("結果: %d\n", temp);
        break;
    }
    case 2:
        /* 処理 */
        break;
}

変数宣言を含む場合、ブロックで囲むことでスコープを明確にし、異なるcase間での変数名の競合を避けることができます。

caseラベルの配置と最適化

[編集]

コンパイラの最適化

[編集]

多くのコンパイラはswitch文を最適化し、単純なif-elseチェーンよりも効率的なジャンプテーブルを生成します。

/* switchを使用 */
switch (value) {
    case 0: return "ゼロ";
    case 1: return "一";
    case 2: return "二";
    // ...多数のケース
    case 99: return "九十九";
    default: return "範囲外";
}

/* if-elseを使用 */
if (value == 0) return "ゼロ";
else if (value == 1) return "一";
else if (value == 2) return "二";
// ...多数の条件分岐
else if (value == 99) return "九十九";
else return "範囲外";

caseラベルが多数ある場合、switch文は通常より効率的に実行されます。

caseラベルの順序

[編集]

C言語仕様ではcaseラベルの順序に関する要件はありませんが、読みやすさと保守性のために論理的な順序で配置することが推奨されます。

/* 論理的な順序 */
switch (grade) {
    case 'A':
        printf("優秀\n");
        break;
    case 'B':
        printf("良好\n");
        break;
    case 'C':
        printf("普通\n");
        break;
    case 'D':
        printf("要注意\n");
        break;
    case 'F':
        printf("不合格\n");
        break;
    default:
        printf("無効な成績\n");
}

caseラベルの応用例

[編集]

列挙型との組み合わせ

[編集]

列挙型はswitch文と相性が良く、コードの可読性を高めます:

enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

void print_schedule(enum Day day) {
    switch (day) {
        case MONDAY:
            printf("会議 9:00-10:30\n");
            break;
        case TUESDAY:
            printf("プロジェクト作業 10:00-12:00\n");
            break;
        case WEDNESDAY:
            printf("チームランチ 12:30-14:00\n");
            break;
        case THURSDAY:
            printf("トレーニング 15:00-17:00\n");
            break;
        case FRIDAY:
            printf("週次報告 16:00-16:30\n");
            break;
        case SATURDAY:
        case SUNDAY:
            printf("休日\n");
            break;
    }
}

範囲処理のシミュレーション

[編集]

C言語のcaseラベルは単一の値のみをサポートしますが、フォールスルーを活用して範囲処理をシミュレートできます:

int grade = 85;

switch (grade / 10) {
    case 10:
    case 9:
        printf("成績: A\n");
        break;
    case 8:
        printf("成績: B\n");
        break;
    case 7:
        printf("成績: C\n");
        break;
    case 6:
        printf("成績: D\n");
        break;
    default:
        printf("成績: F\n");
}

この例では、90〜100点はA、80〜89点はB、というように点数の範囲に応じた成績を表示します。

状態遷移表の実装

[編集]

caseラベルは状態遷移表の実装に適しています:

enum State { IDLE, ACTIVE, SUSPENDED, TERMINATED };
enum Event { START, SUSPEND, RESUME, STOP };

enum State state_transition(enum State current, enum Event event) {
    switch (current) {
        case IDLE:
            switch (event) {
                case START:   return ACTIVE;
                case SUSPEND: return IDLE;
                case RESUME:  return IDLE;
                case STOP:    return TERMINATED;
                default:      return current;
            }
        case ACTIVE:
            switch (event) {
                case START:   return ACTIVE;
                case SUSPEND: return SUSPENDED;
                case RESUME:  return ACTIVE;
                case STOP:    return TERMINATED;
                default:      return current;
            }
        case SUSPENDED:
            switch (event) {
                case START:   return SUSPENDED;
                case SUSPEND: return SUSPENDED;
                case RESUME:  return ACTIVE;
                case STOP:    return TERMINATED;
                default:      return current;
            }
        case TERMINATED:
            return TERMINATED; /* 終了状態からは遷移しない */
        default:
            return IDLE;     /* 未知の状態の場合 */
    }
}

この例では、switch文をネストして状態遷移テーブルを実装しています。

コンパイラ警告とcaseラベル

[編集]

未使用のcaseラベル

[編集]

多くのコンパイラは未使用のcaseラベルに対して警告を発します。これは潜在的な論理エラーを検出するのに役立ちます。

enum Color { RED, GREEN, BLUE };

void process_color(enum Color color) {
    switch (color) {
        case RED:
            printf("赤を処理します\n");
            break;
        case GREEN:
            printf("緑を処理します\n");
            break;
        /* BLUEのケースが漏れている */
    }
}

この例では、BLUEに対応するcaseラベルが欠けています。コンパイラはこのような不完全な列挙型のハンドリングに対して警告を発することがあります。 ここでdefaultが使われていないことに注意してください。もし、defaultが使われていたのならばBLUEdefaultによって処理され、コンパイラは不完全な列挙型のハンドリングとは判断できません。

暗黙的なフォールスルーの警告

[編集]

多くの現代的なコンパイラは、意図しないフォールスルーを防ぐため、break文のないcaseに対して警告を発します。

コンパイラ 警告オプション 備考
GCC -Wimplicit-fallthrough GCC 7以降ではデフォルトで有効
Clang -Wimplicit-fallthrough 類似のオプションを提供
MSVC /W4 警告レベル4以上で有効

caseラベルのベストプラクティス

[編集]

1. 明確な構造化

[編集]

caseブロックを明確に構造化し、処理の開始と終了を分かりやすくします:

switch (option) {
    case 1: {
        /* オプション1の処理 */
        break;
    }
    case 2: {
        /* オプション2の処理 */
        break;
    }
    default: {
        /* デフォルト処理 */
        break;
    }
}

中括弧を使用することで、各caseのスコープが明確になり、特に変数を宣言する場合に有用です。

2. 意図的なフォールスルーの明示

[編集]

意図的にフォールスルーを使用する場合は、コメントでその意図を明確にします:

switch (command) {
    case CMD_COPY:
        copy_data();
        /* 意図的なフォールスルー - コピー後にも表示を更新 */
    case CMD_REFRESH:
        update_display();
        break;
}

3. defaultラベルの適切な使用

[編集]

defaultケースを常に含めることで、予期しない値に対する処理を明確にします:

switch (status_code) {
    case 200:
        handle_success();
        break;
    case 404:
        handle_not_found();
        break;
    case 500:
        handle_server_error();
        break;
    default:
        handle_unknown_error();
        break;
}

実践的な例:コマンドインタプリタ

[編集]

以下は、caseラベルを活用したシンプルなコマンドインタプリタの実装例です:

#include <stdio.h>
#include <string.h>
#define MAX_COMMAND 64
typedef enum {
    CMD_HELP,
    CMD_LIST,
    CMD_COPY,
    CMD_MOVE,
    CMD_DELETE,
    CMD_EXIT,
    CMD_UNKNOWN
} Command;

Command parse_command(const char *cmd_str) {
    if (strcmp(cmd_str, "help") == 0) return CMD_HELP;
    if (strcmp(cmd_str, "list") == 0) return CMD_LIST;
    if (strcmp(cmd_str, "copy") == 0) return CMD_COPY;
    if (strcmp(cmd_str, "move") == 0) return CMD_MOVE;
    if (strcmp(cmd_str, "delete") == 0) return CMD_DELETE;
    if (strcmp(cmd_str, "exit") == 0) return CMD_EXIT;
    return CMD_UNKNOWN;
}

void execute_command(Command cmd) {
    switch (cmd) {
        case CMD_HELP:
            printf("利用可能なコマンド:\n");
            printf("  help   - このヘルプを表示\n");
            printf("  list   - ファイル一覧を表示\n");
            printf("  copy   - ファイルをコピー\n");
            printf("  move   - ファイルを移動\n");
            printf("  delete - ファイルを削除\n");
            printf("  exit   - プログラムを終了\n");
            break;
        case CMD_LIST:
            printf("ファイル一覧を表示します...\n");
            /* 実際のリスト処理 */
            break;
        case CMD_COPY:
            printf("ファイルコピーモード\n");
            /* コピー処理 */
            break;
        case CMD_MOVE:
            printf("ファイル移動モード\n");
            /* 移動処理 */
            break;
        case CMD_DELETE:
            printf("ファイル削除モード\n");
            /* 削除処理 */
            break;
        case CMD_EXIT:
            printf("プログラムを終了します\n");
            /* 終了フラグをセット */
            break;
        case CMD_UNKNOWN:
        default:
            printf("不明なコマンドです。'help'と入力してヘルプを表示してください\n");
            break;
    }
}

int main() {
    char command[MAX_COMMAND];
    Command cmd;
    int running = 1;
    
    printf("シンプルコマンドインタプリタ\n");
    printf("'help'と入力してヘルプを表示\n");
    
    while (running) {
        printf("> ");
        if (scanf("%63s", command) != 1) continue;
        
        cmd = parse_command(command);
        
        if (cmd == CMD_EXIT) {
            running = 0;
        }
        
        execute_command(cmd);
    }
    
    return 0;
}

この例では、文字列コマンドを列挙型に変換し、switch文で適切な処理を実行しています。

まとめ

[編集]

caseラベルはC言語のswitch文における基本的な構成要素であり、複数の選択肢から一つを効率的に選択するための手段を提供します。定数式のみを受け入れるという制限がありますが、適切に使用することでコードの可読性と実行効率を向上させることができます。

caseラベルのフォールスルー特性を理解し、意図的に活用することで、重複コードを減らし、より簡潔で表現力豊かなコードを書くことができます。また、最新のコンパイラ機能を活用して、意図しないフォールスルーによるバグを防止することも重要です。

switch文とcaseラベルは、特に列挙型や状態マシンのプログラミングにおいて強力なツールとなります。C言語プログラマとして、これらの構造を効果的に使いこなすことで、より堅牢で保守性の高いコードを作成することができるでしょう。