C言語 switch文の基本と使い方【初心者向け解説】
C言語を学ぶ上で、プログラムの流れを制御する「制御構造」は非常に重要です。これまでに if
文を使った条件分岐を学んだ方にとって、次に理解しておきたいのが switch
文です。switch
文も条件分岐の一つですが、if
文とは異なる特徴を持ち、特定の状況下で非常に効果的な記述方法となります。
この記事では、C言語の switch
文について、その基本的な概念から、具体的な使い方、if-else if
文との比較、よくある間違い、そしてより発展的な使い方まで、初心者の方でもしっかりと理解できるように詳細かつ丁寧に解説していきます。約5000語のボリュームで、switch
文のあらゆる側面を網羅することを目指します。
さあ、C言語の switch
文の世界に入り込みましょう!
1. なぜ条件分岐が必要なのか? switch
文とは何か?
プログラムは通常、書かれたコードを上から順番に実行していきます。しかし、実際のアプリケーションでは、様々な状況に応じて処理を変える必要が頻繁にあります。例えば:
- ユーザーが入力した値によって、表示するメッセージを変えたい。
- ある変数の状態に応じて、異なる計算を行いたい。
- エラーの種類ごとに、異なるエラー処理を実行したい。
このような「〜ならばAの処理、そうでなければBの処理」といった判断を行い、プログラムの実行経路を分ける仕組みを「条件分岐」と呼びます。
C言語には、条件分岐を実現するための主要な方法がいくつかあります。
if
文 /if-else
文 /if-else if
文: 条件式が真(True)か偽(False)かに基づいて処理を分けます。特に、特定の範囲や複雑な条件(かつ〜、あるいは〜など)を判定するのに適しています。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
文がどのように実行されるかをステップごとに見ていきましょう。
- まず、
switch (式)
の中の制御式が評価されます。その結果として一つの値が得られます。 - 得られた値と、
switch
文の中の各case
ラベルの定数式が上から順に比較されます。(実際の実装ではより効率的な方法が取られることもありますが、概念的にはこの比較が行われます。) - 制御式の値と最初に一致した
case
ラベルが見つかると、そのcase
ラベルの直後にある文から処理が開始されます。 case
ラベルに続くコードが実行されていきます。- 実行中に
break;
文に遭遇すると、そこでそのcase
ブロックの実行は中断され、switch
文全体を抜け出します。switch
文の直後の文に処理が移ります。 - もし、いずれの
case
ラベルとも一致するものが見つからなかった場合:default:
ブロックが存在すれば、default:
の直後にある文から処理が開始されます。default:
ブロックが存在しなければ、switch
文の中のコードは何も実行されずに、switch
文全体を抜け出し、直後の文に処理が移ります。
- もし、
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;
が書かれていれば、出力は ケース 2
と 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;
}
出力:
ケース 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
文を使う上で守らなければならないルールや制限があります。
- 制御式の型:
switch (式)
の式
は、整数型(int
,char
,short
,long
,long long
)、または列挙型 (enum
) である必要があります。float
,double
などの浮動小数点型は直接使用できません。- ポインタ型も直接使用できません。
- 構造体や共用体も使用できません。
- ただし、
char
型は文字コードとして整数値なので使えます。また、bool
型(C99以降)も整数型として扱えるので使えます。
- ケースラベルの型:
case 定数式:
の定数式
は、switch
の制御式の型と互換性のある、コンパイル時に決定される整数定数である必要があります。- 変数を使うことはできません (
case x: ...
は不可)。 - 関数呼び出しを使うことはできません (
case func(): ...
は不可)。 - 浮動小数点定数を使うことはできません (
case 1.5: ...
は不可)。 - 文字列リテラルを使うことはできません (
case "hello": ...
は不可)。 case 10:
やcase 'A':
(文字定数) やcase 5 + 3:
(定数式) は有効です。
- 変数を使うことはできません (
- ケースラベルのユニーク性: 同じ
switch
文の中に、同じ値を持つcase
ラベルを複数書くことはできません。 default
ラベル:default:
ラベルは省略可能です。また、switch
文内に一つだけ記述できます。記述する位置はどこでも構いませんが、慣習として最後に置かれます。break;
の位置:break;
文は、switch
文全体から抜け出すために使用します。通常は各case
ブロックの最後に置きますが、意図的なフォールスルーのためであれば省略できます。- 変数宣言のスコープ:
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;
}
“`
解説:
int choice;
でユーザーの入力を格納する変数を宣言します。printf
でユーザーに入力を促し、scanf
で整数値を受け取りchoice
に格納します。switch (choice)
で、変数choice
の値を評価します。case 1:
はchoice
の値が1
の場合。一致すればprintf("「開く」を選択しました。\n");
を実行後、break;
でswitch
文全体を抜け出し、printf("プログラムを終了します。\n");
に進みます。case 2:
、case 3:
も同様です。それぞれの値に一致した場合、対応するメッセージを表示してbreak;
します。choice
の値が1
,2
,3
のいずれとも一致しなかった場合、default:
ブロックのコードが実行されます。printf("無効な入力です。\n");
が表示されます。default
の後にbreak;
がありますが、これはdefault
がswitch
文の最後にあるため、なくても結果は同じです。しかし、習慣として付けておく人も多いです。- いずれのケースでも、
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;
}
“`
解説:
char command;
で文字入力を格納する変数を宣言します。scanf(" %c", &command);
で文字を読み込みます。%c
の前のスペースは、前回の入力で残った改行文字などの空白文字を読み飛ばすためによく使われます。switch (command)
で変数command
の値を評価します。char
型は整数型の一種としてswitch
の制御式に使えます。case 'a':
,case 'd':
,case 'q':
のように、文字リテラルをケースラベルとして使用します。文字リテラルは対応する文字コード(ASCII値など)の整数定数として扱われます。- それぞれの文字に一致した場合、対応するメッセージを表示し、
break;
でswitch
文を抜け出します。 - いずれの文字とも一致しなかった場合、
default:
ブロックが実行されます。 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;
}
“`
解説:
- 入力された
score
を10
で整数除算し、grade_level
という変数に格納します。例えば、score
が95
ならgrade_level
は9
、score
が60
ならgrade_level
は6
、score
が59
ならgrade_level
は5
となります。100点の場合は10
になります。 switch (grade_level)
でこのgrade_level
の値を評価します。case 10:
とcase 9:
には、間にコードがありません。これは、grade_level
が10
または9
のいずれかであれば、その後のprintf("評価: A\n");
を実行するという意味になります。これは前述の複数のcase
ラベルに対する共通処理の例です。case 8:
,case 7:
,case 6:
はそれぞれ単独のケースとして処理されます。case 5:
からcase 0:
までは、コメントで// fall-through
と示されているように、意図的にbreak;
を省略しています。grade_level
が5
から0
のいずれであっても、最終的にprintf("評価: F\n");
に到達します。default:
ブロックは、入力されたscore
が0未満または100より大きい場合に、grade_level
が負の値になったり10より大きい値になったりする場合に対応します。無効な点数として扱います。
この例のように、工夫次第で switch
文を柔軟に利用することができます。ただし、範囲指定など、複雑な条件判定にはやはり if-else if
文の方が適している場合が多いです。この例も if-else if
で書く方が直感的かもしれません。どちらの手法が良いかは、ケースバイケースで判断することが重要です。
8. よくある間違いと注意点
初心者が switch
文を使う際によく犯す間違いや注意すべき点をまとめます。
break;
の書き忘れ: 最も多い間違いです。意図しないフォールスルーが発生し、予期しない処理が実行されてしまいます。各case
の最後にbreak;
を書くことを忘れないようにしましょう。(意図的なフォールスルーの場合は、コメントで明示すること。)- 制御式やケースラベルの型が不適切:
switch
は基本的に整数型と列挙型にしか使えません。浮動小数点数や文字列を直接使おうとするとエラーになります。 - ケースラベルに変数や非定数式を使う:
case
ラベルはコンパイル時に値が決まる定数でなければなりません。case value: ...
のように変数を使うことはできません。 - 同じ値を持つケースラベルを複数書く: コンパイルエラーになります。各ケースラベルの値はユニークである必要があります。
default
ラベルの重複:default
ラベルはswitch
文内に一つしか書けません。default
ブロック内のbreak;
: 最後に位置するdefault
ブロックのbreak;
は技術的には不要ですが、習慣として付けておくことで、後からcase
を追加した際にフォールスルーを防ぐなどのメリットがあります。付けない場合は、その意図を明確にしましょう。- 変数のスコープの問題: 前述のように、
case
ラベルの直後で宣言した変数は、switch
ブロック全体で有効と見なされる可能性があります。意図しない共有や未初期化アクセスのリスクを避けるため、case
ブロック内部で変数を宣言する際は{}
で囲んで新しいスコープを作成することを検討しましょう。 switch
文の後に実行されるコード:break;
でswitch
文を抜け出した後は、switch
文の直後にあるコードが実行されます。switch
文の最後のcase
やdefault
に到達した場合も同様です(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;
}
“`
解説:
enum Color { RED, GREEN, BLUE };
でColor
という列挙型を定義します。RED
にはデフォルトで 0、GREEN
には 1、BLUE
には 2 という整数値が関連付けられます。enum Color selected_color = GREEN;
でColor
型の変数を宣言し、GREEN
(値は 1) で初期化します。switch (selected_color)
で、列挙型変数を制御式として使用します。列挙型変数は、その基になる整数値として評価されます。case RED:
,case GREEN:
,case BLUE:
のように、列挙子がケースラベルとして使用されます。これらの列挙子は、対応する整数定数として扱われます。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;
}
“`
解説:
- 外側の
switch (type)
でtype
の値を評価します。type
は1
なのでcase 1:
に一致します。 case 1:
のブロック内のコードが実行されます。まずprintf("タイプ 1:\n");
が実行されます。- 次に、そのブロック内にネストされた
switch (code)
文が実行されます。 - 内側の
switch
文でcode
の値が評価されます。code
は2
なので、内側のcase 2:
に一致します。 - 内側の
printf(" コード 2\n");
が実行されます。 - 内側の
break;
が実行され、内側のswitch
文から抜け出します。 - 内側の
switch
文を抜け出した後、外側のcase 1:
のブロックの残りのコードが実行されます。この例では、すぐに外側のbreak;
があります。 - 外側の
break;
が実行され、外側のswitch
文全体から抜け出します。
ネストされた switch
文を使う際は、どの break;
がどの switch
文に対応しているのかを明確に理解しておくことが重要です。インデントを正しく行うことで、可読性を保つように努めましょう。ただし、あまり深くネストしすぎるとコードが複雑になり読みづらくなる傾向があるため、注意が必要です。
10. switch
文のベストプラクティス
switch
文を効果的かつ安全に使うためのいくつかのベストプラクティスを紹介します。
- 常に
default
ラベルを含める:switch
文の制御式の値が予期しないものである可能性を考慮し、常にdefault
ラベルを用意して、エラーメッセージの表示や適切なデフォルト処理を行うようにしましょう。これにより、プログラムの堅牢性が高まります。 - 各
case
の最後にbreak;
を書く(意図的なフォールスルー以外): これが最も重要です。意図しないフォールスルーによるバグを防ぎます。意図的にフォールスルーさせる場合は、必ずコメント (// fall-through
など) でその旨を明記しましょう。 default
ラベルは最後に配置する: C言語の仕様上はどこに置いても構いませんが、最後に置くのが一般的な慣習であり、コードの読みやすさが向上します。- ケースラベルには定数または列挙子を使用する: 変数や複雑な式ではなく、明確な定数値や意味のある列挙子を使用しましょう。これにより、コードの意図が明確になります。
case
ブロック内で変数を宣言する場合は{}
で囲む: 変数のスコープに関する問題を避け、コードの安全性を高めます。- 適切なインデントを使用する:
switch
,case
,default
の各ラベルとそれぞれのブロックを適切にインデントすることで、コードの構造が視覚的に分かりやすくなり、読み間違いを防ぎます。 - 複雑な条件や範囲指定には
if-else if
を検討する:switch
は離散的な値の等価比較に特化しています。それ以外の複雑な条件にはif-else if
文の方が適している場合が多いです。状況に応じて最適な制御構造を選択しましょう。 - 列挙型 (
enum
) を積極的に使用する: 関連する定数群を扱う場合は、マジックナンバーの代わりに列挙型を使用し、switch
文と組み合わせることで、コードの意味が格段に分かりやすくなります。
これらのベストプラクティスを意識することで、switch
文を使ったコードをより安全で、読みやすく、保守しやすいものにすることができます。
11. まとめ
この記事では、C言語の switch
文について、基本的な使い方から応用的な側面までを詳しく解説しました。
switch
文は、一つの式が複数の離散的な定数値のどれと一致するかを判定し、対応するコードブロックを実行するための条件分岐構造です。- 主要な要素は
switch (制御式)
,case 定数式:
,break;
,default:
です。 - 制御式とケースラベルには、整数型または列挙型の値(および互換性のある型)が使用できます。ケースラベルは定数でなければなりません。
break;
文は、一致したcase
ブロックの処理後、switch
文全体から抜け出すために不可欠です。break;
を省略するとフォールスルーが発生し、次のcase
やdefault
のコードが連続して実行されます。これは意図的に使うことも可能ですが、バグの一般的な原因となります。default:
ラベルは、いずれのcase
とも一致しなかった場合に実行される処理を指定します。省略可能ですが、予期しない入力に対応するために含めることが推奨されます。switch
文は離散的な値の等価比較に、if-else if
文は範囲や複雑な条件の判定に適しています。状況に応じて適切な方を選択しましょう。- 列挙型 (
enum
) とswitch
文の組み合わせは、コードの可読性と安全性を高めるための有効な手段です。 break;
の書き忘れ、不適切な型の使用、ケースラベルの重複などがよくある間違いです。適切なベストプラクティスに従うことが重要です。
switch
文は、特にメニュー処理やステータスコードの判定など、特定のタスクにおいて if-else if
よりも優れた可読性と効率を提供することができます。この記事を通じて switch
文の動作原理と正しい使い方を習得し、あなたのC言語プログラミングに役立ててください。
実際に様々なパターンで switch
文を使ったプログラムを書いてみることで、理解がさらに深まります。練習問題を解いたり、自分で簡単なプログラムを作成したりして、ぜひ積極的に活用してみてください。
これで、C言語の switch
文の基本と使い方についての詳細な解説を終わりにします。最後までお読みいただきありがとうございました。