C言語 switch文の基本と使い方【初心者向け解説】


C言語 switch文の基本と使い方【初心者向け解説】

C言語を学ぶ上で、プログラムの流れを制御する「制御構造」は非常に重要です。これまでに if 文を使った条件分岐を学んだ方にとって、次に理解しておきたいのが switch 文です。switch 文も条件分岐の一つですが、if 文とは異なる特徴を持ち、特定の状況下で非常に効果的な記述方法となります。

この記事では、C言語の switch 文について、その基本的な概念から、具体的な使い方、if-else if 文との比較、よくある間違い、そしてより発展的な使い方まで、初心者の方でもしっかりと理解できるように詳細かつ丁寧に解説していきます。約5000語のボリュームで、switch 文のあらゆる側面を網羅することを目指します。

さあ、C言語の switch 文の世界に入り込みましょう!

1. なぜ条件分岐が必要なのか? switch 文とは何か?

プログラムは通常、書かれたコードを上から順番に実行していきます。しかし、実際のアプリケーションでは、様々な状況に応じて処理を変える必要が頻繁にあります。例えば:

  • ユーザーが入力した値によって、表示するメッセージを変えたい。
  • ある変数の状態に応じて、異なる計算を行いたい。
  • エラーの種類ごとに、異なるエラー処理を実行したい。

このような「〜ならばAの処理、そうでなければBの処理」といった判断を行い、プログラムの実行経路を分ける仕組みを「条件分岐」と呼びます。

C言語には、条件分岐を実現するための主要な方法がいくつかあります。

  1. if 文 / if-else 文 / if-else if 文: 条件式が真(True)か偽(False)かに基づいて処理を分けます。特に、特定の範囲や複雑な条件(かつ〜、あるいは〜など)を判定するのに適しています。
  2. switch 文: ある一つの変数や式の値が、いくつかの離散的な値のどれに一致するかを判定し、それぞれに対応する処理を実行します。

この記事で解説する switch 文は、特に「ある値が、複数の具体的な値(10だったら、’A’だったら、’exit’という文字列だったら…ではなく、C言語のswitchでは主に整数値)のどれかと一致するか」という場面で非常に役立ちます。多くの選択肢の中から、一致するものを選び取るイメージです。

例えば、「ユーザーが入力した数字(1〜7)に対応する曜日を表示する」というような状況を考えてみましょう。

  • 1が入力されたら「月曜日」
  • 2が入力されたら「火曜日」
  • 7が入力されたら「日曜日」
  • それ以外の数字が入力されたら「無効な入力です」

このようなケースを if-else if で書くことも可能ですが、switch 文を使うと、より簡潔で読みやすいコードになることがあります。

2. switch 文の基本的な構文

まず、switch 文の基本的な書き方を見てみましょう。

“`c
switch (式) {
case 定数式1:
// 式の値が定数式1と一致した場合に実行される処理
// …
break; // ここでswitch文から抜ける

case 定数式2:
    // 式の値が定数式2と一致した場合に実行される処理
    // ...
    break; // ここでswitch文から抜ける

// ... 他の case ラベル ...

default:
    // 式の値がいずれの定数式とも一致しなかった場合に実行される処理
    // ...
    break; // defaultの最後にbreakは必須ではないが、安全のために付けることが多い

}
“`

この構文は、いくつかのキーワードとブロックで構成されています。それぞれの要素について詳しく見ていきましょう。

2.1. switch (式)

  • switch キーワードの後に続く丸括弧 () の中に、評価したい式を書きます。この式は「制御式」と呼ばれます。
  • switch 文は、まずこの制御式の値を計算します。
  • 重要: C言語の switch 文の制御式は、整数型(int, char, short, long, long long など)または列挙型(enum)の値である必要があります。浮動小数点型 (float, double) や文字列型 (char *) を直接 switch の制御式として使用することはできません。(ただし、文字リテラルは char 型として扱えるため使用可能です。また、文字列は後述する方法で間接的に扱うことはあります。)

2.2. case 定数式:

  • case キーワードの後に続くのは、制御式の値と比較するための値です。これは「ケースラベル」と呼ばれます。
  • ケースラベルは、コンパイル時に値が確定する定数式でなければなりません。変数を使うことはできません。例えば、10, 'A', 1 + 2 のような値が使えます。
  • 制御式の値が、いずれかの case の後の定数式と一致した場合、その case に対応する処理ブロック(: の後のコード)の先頭から実行が開始されます。
  • それぞれの case ラベルは、switch 文の中でユニーク(一意)である必要があります。同じ値を持つ case ラベルを複数書くことはできません。
  • case ラベルの最後にはコロン : を付けます。

2.3. break;

  • break キーワードは、その名の通り、処理の流れを中断(break)させるために使用します。
  • switch 文の case ブロック内で break; が実行されると、switch 文のブロック全体を抜け出しswitch 文の直後にある次の文からプログラムの実行が再開されます。
  • ほとんどの場合、一つの case に対応する処理が終わったら、他の case の処理を続けて実行してほしくありません。そのため、各 case ブロックの処理の最後に break; を書くのが一般的です。
  • break;書き忘れると、非常に重要な挙動(フォールスルー (fall-through))を引き起こします。これについては後ほど詳しく解説します。

2.4. default:

  • default キーワードは、switch 文の制御式の値が、いずれの case ラベルとも一致しなかった場合に実行される処理を指定します。
  • これは if-else if 文における最後の else ブロックのようなものです。
  • default ブロックは省略可能です。省略した場合、いずれの case とも一致しなかった場合は、switch 文の中のコードは何も実行されずに switch 文全体を抜け出します。
  • default ブロックは、switch 文内のどこにでも書くことができますが、一般的には最後に書くのが慣習です。
  • default ブロックの最後に break; を書くことは、厳密には必須ではありません。なぜなら、default が最後にあり、かつ switch 文の最後に到達すれば、自動的に switch 文全体を抜け出すからです。しかし、コードの保守性を高めるため(後で default の後に別の case を追加する可能性などを考慮して)、break; を付けておくことが推奨される場合もあります。

3. switch 文の実行の流れ

switch 文がどのように実行されるかをステップごとに見ていきましょう。

  1. まず、switch (式) の中の制御式が評価されます。その結果として一つの値が得られます。
  2. 得られた値と、switch 文の中のcase ラベルの定数式が上から順に比較されます。(実際の実装ではより効率的な方法が取られることもありますが、概念的にはこの比較が行われます。)
  3. 制御式の値と最初に一致した case ラベルが見つかると、その case ラベルの直後にある文から処理が開始されます。
  4. case ラベルに続くコードが実行されていきます。
  5. 実行中に break; 文に遭遇すると、そこでその case ブロックの実行は中断され、switch 文全体を抜け出します。switch 文の直後の文に処理が移ります。
  6. もし、いずれの case ラベルとも一致するものが見つからなかった場合:
    • default: ブロックが存在すれば、default: の直後にある文から処理が開始されます。
    • default: ブロックが存在しなければ、switch 文の中のコードは何も実行されずに、switch 文全体を抜け出し、直後の文に処理が移ります。
  7. もし、case ブロックの処理中に break; に遭遇しなかった場合、その case ブロックのコードが終了しても、switch 文を抜け出さずに、そのまま次の case ラベル、default ラベルをスキップして、直後のコードの実行を続けます。これをフォールスルー (fall-through)と呼びます。これは意図的に利用されることもありますが、多くの場合は break; の書き忘れによるバグの原因となります。

このフォールスルーの挙動は、switch 文を理解する上で最も重要なポイントの一つです。

4. break; の重要性とフォールスルー (Fall-through)

switch 文の最も特徴的な挙動は、break; がない場合のフォールスルーです。これは初心者の方が switch 文で最もつまずきやすい点でもあります。

フォールスルーとは:

ある case に一致して処理が開始された後、その case ブロックの終わりに break; がない場合、switch 文全体を抜け出さずに、次の case ラベルや default ラベルを無視して、そのまま下のコードを実行し続けてしまう挙動です。処理は、次の break; に遭遇するか、switch 文の終わりに到達するまで止まりません。

具体例を見てみましょう。

“`c

include

int main() {
int num = 2;

switch (num) {
    case 1:
        printf("ケース 1\n");
        // break; // ここに break がない!

    case 2:
        printf("ケース 2\n");
        // break; // ここにも break がない!

    case 3:
        printf("ケース 3\n");
        break; // ここで break がある

    default:
        printf("デフォルト\n");
        break;
}

printf("switch文を抜けました。\n");

return 0;

}
“`

このコードを実行すると、num の値は 2 なので、case 2: の箇所から実行が開始されます。

しかし、case 2 のコードの最後に break; がありません。そのため、printf("ケース 2\n"); が実行された後、switch 文を抜け出さずに、そのまま次の case 3: のコードにフォールスルーします

case 3: のコードである printf("ケース 3\n"); が実行されます。そして、case 3 のコードの最後には break; があります。ここで switch 文全体を抜け出し、printf("switch文を抜けました。\n"); が実行されます。

したがって、このプログラムの出力は以下のようになります。

ケース 2
ケース 3
switch文を抜けました。

もし各 case の最後にきちんと break; が書かれていれば、出力は ケース 2switch文を抜けました。 だけになります。

“`c

include

int main() {
int num = 2;

switch (num) {
    case 1:
        printf("ケース 1\n");
        break; // break あり

    case 2:
        printf("ケース 2\n");
        break; // break あり

    case 3:
        printf("ケース 3\n");
        break; // break あり

    default:
        printf("デフォルト\n");
        break;
}

printf("switch文を抜けました。\n");

return 0;

}
出力:
ケース 2
switch文を抜けました。
“`

フォールスルーの意図的な利用:

フォールスルーは、意図的に利用されることもあります。最も一般的なのは、複数の異なる case ラベルに対して、同じ処理を行いたい場合です。

例えば、「入力された文字が母音かどうかを判定する」場合を考えます。

“`c

include

int main() {
char ch = ‘a’;

switch (ch) {
    case 'a':
    case 'e':
    case 'i':
    case 'o':
    case 'u':
    case 'A':
    case 'E':
    case 'I':
    case 'O':
    case 'U':
        printf("入力された文字 '%c' は母音です。\n", ch);
        break; // 複数の case ラベルがこの break に到達する

    default:
        printf("入力された文字 '%c' は母音ではありません。\n", ch);
        break;
}

return 0;

}
“`

この例では、case 'a': の後に break; がありません。したがって、ch'a' と一致した場合、そのまま下の case 'e': にフォールスルーします。case 'e': の後にも break; がありませんので、さらに下の case 'i': にフォールスルーします。このフォールスルーは case 'U': の直後の printf 文まで続き、最終的に break; によって switch 文を抜け出します。

このように、break; を書かずに連続して case ラベルを並べることで、「これらのいずれかの値に一致した場合、共通の処理を実行する」という意図を表現できます。

ただし、フォールスルーは意図しないバグの温床となりやすいため、意図的にフォールスルーさせる場合は、コメントなどでその意図を明確に示すことが強く推奨されます。例えば、// fall-through のようなコメントを付けます。

c
switch (ch) {
case 'a': // fall-through
case 'e': // fall-through
case 'i': // fall-through
case 'o': // fall-through
case 'u': // fall-through
case 'A': // fall-through
case 'E': // fall-through
case 'I': // fall-through
case 'O': // fall-through
case 'U':
printf("入力された文字 '%c' は母音です。\n", ch);
break;
// ... default ...
}

このようにコメントを付けておくと、他のプログラマーがコードを読んだときに、「あっ、これは break の書き忘れではなく、意図的にフォールスルーさせているのだな」と理解できます。

5. switch 文の制限とルール

switch 文を使う上で守らなければならないルールや制限があります。

  1. 制御式の型: switch (式) は、整数型(int, char, short, long, long long)、または列挙型 (enum) である必要があります。
    • float, double などの浮動小数点型は直接使用できません。
    • ポインタ型も直接使用できません。
    • 構造体や共用体も使用できません。
    • ただし、char 型は文字コードとして整数値なので使えます。また、bool 型(C99以降)も整数型として扱えるので使えます。
  2. ケースラベルの型: case 定数式:定数式 は、switch の制御式の型と互換性のあるコンパイル時に決定される整数定数である必要があります。
    • 変数を使うことはできません (case x: ... は不可)。
    • 関数呼び出しを使うことはできません (case func(): ... は不可)。
    • 浮動小数点定数を使うことはできません (case 1.5: ... は不可)。
    • 文字列リテラルを使うことはできません (case "hello": ... は不可)。
    • case 10:case 'A': (文字定数) や case 5 + 3: (定数式) は有効です。
  3. ケースラベルのユニーク性: 同じ switch 文の中に、同じ値を持つ case ラベルを複数書くことはできません
  4. default ラベル: default: ラベルは省略可能です。また、switch 文内に一つだけ記述できます。記述する位置はどこでも構いませんが、慣習として最後に置かれます。
  5. break; の位置: break; 文は、switch 文全体から抜け出すために使用します。通常は各 case ブロックの最後に置きますが、意図的なフォールスルーのためであれば省略できます。
  6. 変数宣言のスコープ: switch 文のブロック全体 { ... } は一つのスコープではありません。case ラベルや default ラベルは単なるジャンプ先を示しているだけです。もし、case ラベルの直後で変数を宣言し、その変数のスコープをその case ブロックだけに限定したい場合は、その case ブロックをさらに波括弧 {} で囲んで、新しいスコープを作成する必要があります。

c
// 例:変数のスコープ
switch (value) {
case 1:
int a = 10; // これはスイッチ文全体でアクセス可能(ただし case 1 から下にフォールスルーした場合のみ初期化されている)
printf("%d\n", a);
break;
case 2:
// ここでも変数 a にアクセスできてしまう可能性がある (未初期化かもしれないが)
printf("Value is 2\n");
{ // 新しいスコープを作成
int b = 20; // この b はこの {} ブロック内でのみ有効
printf("%d\n", b);
} // b のスコープはここで終わる
break;
default:
printf("Default case\n");
break;
}

このように、switch 文の内部で変数を宣言する際は、スコープを明確にするために {} を使うのが安全なプラクティスです。

6. switch 文と if-else if 文の比較

switch 文も if-else if 文も条件分岐を実現する手段ですが、それぞれ得意な状況が異なります。どちらを使うべきか判断できるようになることは重要です。

特徴 switch if-else if
判定基準 ある一つの式の値が、複数の離散的な定数値のどれと一致するか(等価比較) 真偽値を返す条件式(より複雑な比較や論理演算も可能)
適用範囲 整数型、文字型、列挙型などの離散値 整数型、浮動小数点型、ポインタ型など、あらゆる型の条件式に対応
条件の形式 case 定数式: の形式。定数値との等価比較のみ 条件式<, >, <=, >=, ==, !=, &&, || など)による柔軟な比較
コードの読みやすさ 多数の離散値との比較の場合、if-else if よりも簡潔で読みやすい傾向がある 複雑な条件や範囲指定の場合に強い。離散値多数の場合は冗長になることがある
実行性能 多くの case がある場合、コンパイラによっては効率的な分岐(ジャンプテーブルなど)を生成することがある。 基本的には線形に条件を評価していく。多数の条件では評価回数が増える可能性がある。
フォールスルー break; を省略するとフォールスルーが発生する フォールスルーの概念はない。各ブロックは独立して実行されるかスキップされる

どちらを選ぶべきか?

  • switch 文を使うべき場合:
    • 一つの整数型の変数や式が、少数の、または多数の、離散的な特定の定数値のどれかと一致するかを判定したい場合。
    • 例えば、エラーコード、コマンド番号、メニュー選択肢、キーボード入力文字などが特定の値と一致するかどうか。
    • コードの意図が「この値が X ならば、Y ならば、Z ならば…」と明確に表現できる場合。
  • if-else if 文を使うべき場合:
    • 条件が複雑である場合(複数の条件を &&|| で組み合わせるなど)。
    • 値の範囲に基づいて判定したい場合(例: 「0より大きく100以下」)。
    • 浮動小数点数や文字列など、switch の制御式として直接使えない型の値を判定したい場合。
    • 条件の数が少なく、単純な真偽判定で十分な場合。

例:

  • switch が適している例:
    • メニュー選択 (1: 開く, 2: 保存, 3: 終了)
    • 曜日判定 (1: 月, …, 7: 日)
    • エラーコード処理 (100: ファイルなし, 200: メモリ不足, …)
  • if-else if が適している例:
    • 点数による合否判定 (点数が60点以上なら合格)
    • 年齢による割引適用 (年齢が18歳未満または65歳以上なら割引)
    • ユーザー名のチェック (ユーザー名が空文字列でないか、特定の文字を含まないか)

どちらの文を使うかは、条件の性質やコードの読みやすさを考慮して判断します。多くの場合、離散的な値をチェックするには switch が、範囲や複雑な条件をチェックするには if-else if が適しています。

7. 具体的な使用例

いくつかの実用的な例を通して、switch 文の使い方をさらに深く理解しましょう。

例 1: 数値による簡単なメッセージ表示

ユーザーに1から3の数字を入力してもらい、それぞれ異なるメッセージを表示するプログラムです。それ以外の数字の場合はエラーメッセージを表示します。

“`c

include

int main() {
int choice;

printf("1 から 3 の数字を入力してください: ");
scanf("%d", &choice);

switch (choice) {
    case 1:
        printf("「開く」を選択しました。\n");
        break; // ここで switch から抜ける

    case 2:
        printf("「保存」を選択しました。\n");
        break; // ここで switch から抜ける

    case 3:
        printf("「終了」を選択しました。\n");
        break; // ここで switch から抜ける

    default: // 1, 2, 3 のいずれでもなかった場合
        printf("無効な入力です。\n");
        // default が最後なので break; は厳密には不要だが、書いても問題ない
        break; 
}

printf("プログラムを終了します。\n");

return 0;

}
“`

解説:

  1. int choice; でユーザーの入力を格納する変数を宣言します。
  2. printf でユーザーに入力を促し、scanf で整数値を受け取り choice に格納します。
  3. switch (choice) で、変数 choice の値を評価します。
  4. case 1:choice の値が 1 の場合。一致すれば printf("「開く」を選択しました。\n"); を実行後、break;switch 文全体を抜け出し、printf("プログラムを終了します。\n"); に進みます。
  5. case 2:case 3: も同様です。それぞれの値に一致した場合、対応するメッセージを表示して break; します。
  6. choice の値が 1, 2, 3 のいずれとも一致しなかった場合、default: ブロックのコードが実行されます。printf("無効な入力です。\n"); が表示されます。
  7. default の後に break; がありますが、これは defaultswitch 文の最後にあるため、なくても結果は同じです。しかし、習慣として付けておく人も多いです。
  8. いずれのケースでも、switch 文の処理が終わると、外側の printf("プログラムを終了します。\n"); が実行されます。

例 2: 文字によるコマンド処理

簡単なコマンド(’a’ で追加、’d’ で削除、’q’ で終了)を受け付けるプログラムです。

“`c

include

int main() {
char command;

printf("コマンドを入力してください ('a' for add, 'd' for delete, 'q' for quit): ");
scanf(" %c", &command); // %c の前のスペースは改行文字などをスキップするため

switch (command) {
    case 'a':
        printf("項目を追加します。\n");
        // ここに追加処理のコード
        break;

    case 'd':
        printf("項目を削除します。\n");
        // ここに削除処理のコード
        break;

    case 'q':
        printf("プログラムを終了します。\n");
        // ここに終了処理のコード
        break;

    default:
        printf("無効なコマンドです。\n");
        break;
}

printf("処理が完了しました。\n");

return 0;

}
“`

解説:

  1. char command; で文字入力を格納する変数を宣言します。
  2. scanf(" %c", &command); で文字を読み込みます。%c の前のスペースは、前回の入力で残った改行文字などの空白文字を読み飛ばすためによく使われます。
  3. switch (command) で変数 command の値を評価します。char 型は整数型の一種として switch の制御式に使えます。
  4. case 'a':, case 'd':, case 'q': のように、文字リテラルをケースラベルとして使用します。文字リテラルは対応する文字コード(ASCII値など)の整数定数として扱われます。
  5. それぞれの文字に一致した場合、対応するメッセージを表示し、break;switch 文を抜け出します。
  6. いずれの文字とも一致しなかった場合、default: ブロックが実行されます。
  7. switch 文の処理が終わると、printf("処理が完了しました。\n"); が実行されます。

例 3: 点数による簡単な評価 (フォールスルーの利用)

0点から100点の点数を入力してもらい、簡単な評価(A, B, C, D, F)を表示するプログラムです。評価基準は以下とします。

  • 90点以上: A
  • 80点以上90点未満: B
  • 70点以上80点未満: C
  • 60点以上70点未満: D
  • 60点未満: F

この例では、switch 文の制御式に整数除算を利用し、複数の case ラベルをまとめて扱うためにフォールスルー(またはラベルの連続記述)を活用します。

“`c

include

int main() {
int score;

printf("点数を入力してください (0-100): ");
scanf("%d", &score);

// 点数を 10 で割った整数値を使用
// 100点 -> 10
// 90-99点 -> 9
// 80-89点 -> 8
// ...
// 0-9点 -> 0
int grade_level = score / 10;

switch (grade_level) {
    case 10: // 100点の場合
    case 9:  // 90点台の場合
        printf("評価: A\n");
        break;

    case 8: // 80点台の場合
        printf("評価: B\n");
        break;

    case 7: // 70点台の場合
        printf("評価: C\n");
        break;

    case 6: // 60点台の場合
        printf("評価: D\n");
        break;

    // 0点台から50点台まで
    case 5: // fall-through
    case 4: // fall-through
    case 3: // fall-through
    case 2: // fall-through
    case 1: // fall-through
    case 0:
        printf("評価: F\n");
        break;

    default: // 0-100の範囲外の入力の場合
        printf("無効な点数です。\n");
        break;
}

return 0;

}
“`

解説:

  1. 入力された score10 で整数除算し、grade_level という変数に格納します。例えば、score95 なら grade_level9score60 なら grade_level6score59 なら grade_level5 となります。100点の場合は 10 になります。
  2. switch (grade_level) でこの grade_level の値を評価します。
  3. case 10:case 9: には、間にコードがありません。これは、grade_level10 または 9 のいずれかであれば、その後の printf("評価: A\n"); を実行するという意味になります。これは前述の複数の case ラベルに対する共通処理の例です。
  4. case 8:, case 7:, case 6: はそれぞれ単独のケースとして処理されます。
  5. case 5: から case 0: までは、コメントで // fall-through と示されているように、意図的に break; を省略しています。grade_level5 から 0 のいずれであっても、最終的に printf("評価: F\n"); に到達します。
  6. default: ブロックは、入力された score が0未満または100より大きい場合に、grade_level が負の値になったり10より大きい値になったりする場合に対応します。無効な点数として扱います。

この例のように、工夫次第で switch 文を柔軟に利用することができます。ただし、範囲指定など、複雑な条件判定にはやはり if-else if 文の方が適している場合が多いです。この例も if-else if で書く方が直感的かもしれません。どちらの手法が良いかは、ケースバイケースで判断することが重要です。

8. よくある間違いと注意点

初心者が switch 文を使う際によく犯す間違いや注意すべき点をまとめます。

  1. break; の書き忘れ: 最も多い間違いです。意図しないフォールスルーが発生し、予期しない処理が実行されてしまいます。各 case の最後に break; を書くことを忘れないようにしましょう。(意図的なフォールスルーの場合は、コメントで明示すること。)
  2. 制御式やケースラベルの型が不適切: switch は基本的に整数型と列挙型にしか使えません。浮動小数点数や文字列を直接使おうとするとエラーになります。
  3. ケースラベルに変数や非定数式を使う: case ラベルはコンパイル時に値が決まる定数でなければなりません。case value: ... のように変数を使うことはできません。
  4. 同じ値を持つケースラベルを複数書く: コンパイルエラーになります。各ケースラベルの値はユニークである必要があります。
  5. default ラベルの重複: default ラベルは switch 文内に一つしか書けません。
  6. default ブロック内の break;: 最後に位置する default ブロックの break; は技術的には不要ですが、習慣として付けておくことで、後から case を追加した際にフォールスルーを防ぐなどのメリットがあります。付けない場合は、その意図を明確にしましょう。
  7. 変数のスコープの問題: 前述のように、case ラベルの直後で宣言した変数は、switch ブロック全体で有効と見なされる可能性があります。意図しない共有や未初期化アクセスのリスクを避けるため、case ブロック内部で変数を宣言する際は {} で囲んで新しいスコープを作成することを検討しましょう。
  8. switch 文の後に実行されるコード: break;switch 文を抜け出した後は、switch 文の直後にあるコードが実行されます。switch 文の最後の casedefault に到達した場合も同様です(break; があれば)。この制御の流れを理解しておくことが重要です。

これらの点に注意して switch 文を使えば、バグを防ぎ、意図した通りにプログラムを動作させることができます。

9. 発展的なトピック

ここでは、switch 文に関連するもう少し応用的な内容に触れておきます。

9.1. 複数のケースラベルを連続して書く

これは前述のフォールスルーの意図的な利用と密接に関連しています。break; を書かずに複数の case ラベルを連続して並べることで、それらのいずれかに一致した場合に共通の処理を実行させることができます。

c
switch (day) {
case 1: // 月曜日
case 2: // 火曜日
case 3: // 水曜日
case 4: // 木曜日
case 5: // 金曜日
printf("平日です。\n");
break;
case 6: // 土曜日
case 7: // 日曜日
printf("休日です。\n");
break;
default:
printf("無効な曜日です。\n");
break;
}

このように、case 1: に一致した場合、printf 文まで break; に遭遇しないため、そのまま下のコードに進みます。そして case 2:case 3: … とラベルを飛び越えていき、最初の break; に到達すると switch 文を抜け出します。これは、day の値が 1, 2, 3, 4, 5 のいずれであっても「平日です。」と表示するコードとして機能します。同様に、6または7であれば「休日です。」と表示します。

この記法は、意図的なフォールスルーの最も一般的で推奨される形式です。各ラベルに対応する処理コードを書かずにラベルだけを並べることで、これらのラベルが論理的にグループ化されていることを明確に示せます。

9.2. 列挙型 (enum) と switch

C言語の列挙型 (enum) は、関連する整数定数に名前を付けるための機能です。switch 文の制御式は整数型または列挙型でなければならないため、列挙型は switch 文と非常に相性が良いです。

列挙型を使うことで、マジックナンバー(意味不明な数値)を避けて、コードの可読性を大幅に向上させることができます。

“`c

include

// 列挙型で色の名前を定義
enum Color {
RED, // 通常は 0
GREEN, // 通常は 1
BLUE // 通常は 2
};

int main() {
enum Color selected_color = GREEN;

switch (selected_color) {
    case RED:
        printf("選択された色は赤です。\n");
        break;
    case GREEN:
        printf("選択された色は緑です。\n");
        break;
    case BLUE:
        printf("選択された色は青です。\n");
        break;
    default:
        printf("不明な色です。\n");
        break;
}

return 0;

}
“`

解説:

  1. enum Color { RED, GREEN, BLUE };Color という列挙型を定義します。RED にはデフォルトで 0、GREEN には 1、BLUE には 2 という整数値が関連付けられます。
  2. enum Color selected_color = GREEN;Color 型の変数を宣言し、GREEN (値は 1) で初期化します。
  3. switch (selected_color) で、列挙型変数を制御式として使用します。列挙型変数は、その基になる整数値として評価されます。
  4. case RED:, case GREEN:, case BLUE: のように、列挙子がケースラベルとして使用されます。これらの列挙子は、対応する整数定数として扱われます。
  5. selected_color の値が GREEN (つまり 1) なので、case GREEN: に一致し、「選択された色は緑です。」と表示されます。

列挙型と switch 文を組み合わせることは、特定の状態やタイプを表すコードを書く際の、C言語における非常に一般的で推奨されるパターンです。コードの意味が明確になり、メンテナンス性が向上します。また、多くのコンパイラは、switch 文で列挙型を使用した場合、すべての列挙子が case ラベルで処理されているかどうかを警告してくれることがあります。これは、網羅性のチェックに役立ちます。

9.3. ネストされた switch

if 文と同様に、switch 文を別の switch 文の case ブロックの中に記述することも可能です。これを「ネストされた switch 文」と呼びます。

“`c

include

int main() {
int type = 1;
int code = 2;

switch (type) {
    case 1:
        printf("タイプ 1:\n");
        switch (code) { // ネストされた switch 文
            case 1:
                printf("  コード 1\n");
                break; // 内側の switch から抜ける
            case 2:
                printf("  コード 2\n");
                break; // 内側の switch から抜ける
            default:
                printf("  不明なコード\n");
                break; // 内側の switch から抜ける
        } // 内側の switch はここまで
        break; // 外側の switch から抜ける (type=1 のケースの最後)

    case 2:
        printf("タイプ 2:\n");
        // ... タイプ 2 の処理 ...
        break;

    default:
        printf("不明なタイプ\n");
        break;
}

return 0;

}
“`

解説:

  1. 外側の switch (type)type の値を評価します。type1 なので case 1: に一致します。
  2. case 1: のブロック内のコードが実行されます。まず printf("タイプ 1:\n"); が実行されます。
  3. 次に、そのブロック内にネストされた switch (code) 文が実行されます。
  4. 内側の switch 文で code の値が評価されます。code2 なので、内側の case 2: に一致します。
  5. 内側の printf(" コード 2\n"); が実行されます。
  6. 内側の break; が実行され、内側の switchから抜け出します。
  7. 内側の switch 文を抜け出した後、外側の case 1: のブロックの残りのコードが実行されます。この例では、すぐに外側の break; があります。
  8. 外側の break; が実行され、外側の switch全体から抜け出します。

ネストされた switch 文を使う際は、どの break; がどの switch 文に対応しているのかを明確に理解しておくことが重要です。インデントを正しく行うことで、可読性を保つように努めましょう。ただし、あまり深くネストしすぎるとコードが複雑になり読みづらくなる傾向があるため、注意が必要です。

10. switch 文のベストプラクティス

switch 文を効果的かつ安全に使うためのいくつかのベストプラクティスを紹介します。

  1. 常に default ラベルを含める: switch 文の制御式の値が予期しないものである可能性を考慮し、常に default ラベルを用意して、エラーメッセージの表示や適切なデフォルト処理を行うようにしましょう。これにより、プログラムの堅牢性が高まります。
  2. case の最後に break; を書く(意図的なフォールスルー以外): これが最も重要です。意図しないフォールスルーによるバグを防ぎます。意図的にフォールスルーさせる場合は、必ずコメント (// fall-through など) でその旨を明記しましょう。
  3. default ラベルは最後に配置する: C言語の仕様上はどこに置いても構いませんが、最後に置くのが一般的な慣習であり、コードの読みやすさが向上します。
  4. ケースラベルには定数または列挙子を使用する: 変数や複雑な式ではなく、明確な定数値や意味のある列挙子を使用しましょう。これにより、コードの意図が明確になります。
  5. case ブロック内で変数を宣言する場合は {} で囲む: 変数のスコープに関する問題を避け、コードの安全性を高めます。
  6. 適切なインデントを使用する: switch, case, default の各ラベルとそれぞれのブロックを適切にインデントすることで、コードの構造が視覚的に分かりやすくなり、読み間違いを防ぎます。
  7. 複雑な条件や範囲指定には if-else if を検討する: switch は離散的な値の等価比較に特化しています。それ以外の複雑な条件には if-else if 文の方が適している場合が多いです。状況に応じて最適な制御構造を選択しましょう。
  8. 列挙型 (enum) を積極的に使用する: 関連する定数群を扱う場合は、マジックナンバーの代わりに列挙型を使用し、switch 文と組み合わせることで、コードの意味が格段に分かりやすくなります。

これらのベストプラクティスを意識することで、switch 文を使ったコードをより安全で、読みやすく、保守しやすいものにすることができます。

11. まとめ

この記事では、C言語の switch 文について、基本的な使い方から応用的な側面までを詳しく解説しました。

  • switch 文は、一つの式が複数の離散的な定数値のどれと一致するかを判定し、対応するコードブロックを実行するための条件分岐構造です。
  • 主要な要素は switch (制御式), case 定数式:, break;, default: です。
  • 制御式とケースラベルには、整数型または列挙型の値(および互換性のある型)が使用できます。ケースラベルは定数でなければなりません。
  • break; 文は、一致した case ブロックの処理後、switch 文全体から抜け出すために不可欠です。
  • break; を省略するとフォールスルーが発生し、次の casedefault のコードが連続して実行されます。これは意図的に使うことも可能ですが、バグの一般的な原因となります。
  • default: ラベルは、いずれの case とも一致しなかった場合に実行される処理を指定します。省略可能ですが、予期しない入力に対応するために含めることが推奨されます。
  • switch 文は離散的な値の等価比較に、if-else if 文は範囲や複雑な条件の判定に適しています。状況に応じて適切な方を選択しましょう。
  • 列挙型 (enum) と switch 文の組み合わせは、コードの可読性と安全性を高めるための有効な手段です。
  • break; の書き忘れ、不適切な型の使用、ケースラベルの重複などがよくある間違いです。適切なベストプラクティスに従うことが重要です。

switch 文は、特にメニュー処理やステータスコードの判定など、特定のタスクにおいて if-else if よりも優れた可読性と効率を提供することができます。この記事を通じて switch 文の動作原理と正しい使い方を習得し、あなたのC言語プログラミングに役立ててください。

実際に様々なパターンで switch 文を使ったプログラムを書いてみることで、理解がさらに深まります。練習問題を解いたり、自分で簡単なプログラムを作成したりして、ぜひ積極的に活用してみてください。

これで、C言語の switch 文の基本と使い方についての詳細な解説を終わりにします。最後までお読みいただきありがとうございました。


コメントする

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

上部へスクロール