C言語を学び始めると、まず最初に出てくるのがif文による条件分岐です。
しかし、分岐の種類が多くなるとコードが長くなり、どの条件でどの処理が行われるのかが分かりにくくなってしまいます。
そこで登場するのがswitch文です。
switch文を使うと、多くの条件をスッキリ整理して書くことができ、読みやすさとメンテナンス性が大きく向上します。
本記事では、C言語初心者の方にも分かるように、switch文の基本から実用的なサンプルまで詳しく解説します。
switch文とは?多くの条件分岐をスッキリ書くための基本を解説
switch文とは何か
switch文とは、ある1つの式の値に応じて、複数の候補の中から実行する処理を振り分ける構文です。
日本語でイメージすると「この値が1ならAの処理、2ならBの処理、それ以外ならCの処理」というような振り分けを、見通しよく書くための仕組みです。
if文でも同じことはできますが、条件がたくさんある場合にはswitch文の方が読みやすくなります。
特に、値のパターンがあらかじめ決まっている場合に力を発揮します。
if文との違い
if文とswitch文はどちらも条件分岐を行う構文ですが、考え方に違いがあります。
- if文は「条件式が真(true)かどうか」を順番に評価していきます。
- switch文は「ある式の値が、どの
caseの値と一致するか」を調べます。
if文の例とswitch文の例を比較してみます。
if文の例
#include <stdio.h>
int main(void) {
int n = 2;
// if文で書いた場合
if (n == 1) {
printf("1です\n");
} else if (n == 2) {
printf("2です\n");
} else if (n == 3) {
printf("3です\n");
} else {
printf("1〜3ではありません\n");
}
return 0;
}
switch文の例
#include <stdio.h>
int main(void) {
int n = 2;
// switch文で書いた場合
switch (n) {
case 1:
printf("1です\n");
break; // ここでswitch文を抜ける
case 2:
printf("2です\n");
break;
case 3:
printf("3です\n");
break;
default:
// 上のどのcaseにも当てはまらない場合
printf("1〜3ではありません\n");
break;
}
return 0;
}
出力結果はどちらも同じですが、switch文の方が「値ごとの処理」が縦に並び、パターンがひと目で分かるようになります。
switch文で使える式の種類
switch文の括弧の中には、整数値に評価される式を書く必要があります。
C言語では、switch文で使える型は主に次の通りです。
| 使用できる型の例 | 説明 |
|---|---|
int | 一般的な整数型です。最もよく使われます。 |
char | 文字型ですが、実体は整数として扱われるため使用可能です。 |
enum | 列挙型です。状態やモードを表すのに便利です。 |
short / long など | 整数型であれば基本的に使用できます。 |
浮動小数点型(float, double)はswitchでは使えません。
また、ポインタ型や構造体なども使えないため注意してください。
例えば、文字入力を判定する次のような記述は正しく動作します。
char c = 'a';
switch (c) {
case 'a':
/* 何らかの処理 */
break;
case 'b':
/* 何らかの処理 */
break;
}
このように、最終的に整数として解釈できる値であればswitch文の式に使うことができます。
基本構文と書き方
switch文の基本的な構文は次のようになります。
switch (式) {
case 定数式1:
// 処理1
break;
case 定数式2:
// 処理2
break;
// 必要なだけcaseを追加できる
default:
// どのcaseにも当てはまらない場合の処理
break;
}
ここで重要なポイントを整理します。
1つ目は、caseの後ろに書けるのは「定数式」だけという点です。
具体的には、次のような値が使えます。
- リテラル値(例:
0,1,'a') - マクロ定数(例:
#define VALUE 10で定義したVALUE) - 列挙定数(例:
enum Color { RED, BLUE };のREDなど)
2つ目は、各caseの最後にbreakを書くのが基本だという点です。
breakを書くことで、そのcaseの処理が終わった時点でswitch文から抜けます。
これを忘れると、後続のcaseまで処理が流れてしまい、意図しない動作になります。
3つ目は、defaultラベルは「どのcaseにも一致しなかったとき」に実行されるという点です。
defaultは省略することも可能ですが、初心者のうちはできるだけ書いておくと、想定外の値が来たときにも対応しやすくなります。
switch文で処理が実行される流れ
switch文の処理の流れを、1ステップずつ確認してみます。
- switchの括弧内の式が1回だけ評価されます。
- その値と一致する
caseラベルを上から順に探します。 - 一致するcaseが見つかったら、その行から下の処理を実行します。
breakに到達した時点でswitch文を抜けます。- 一致するcaseが1つもなければ、
defaultラベルがあればそこから実行し、なければ何も実行されません。
この「一致したcaseから下に向かって処理が流れる」という性質が、breakの書き忘れによるバグの原因にもなります。
これについては後の章で詳しく説明します。
複数のcaseに同じ処理を書くパターン
switch文の便利なテクニックとして、複数のcaseをまとめて同じ処理を行うという書き方があります。
これは、breakを書かずにcaseラベルを続けて書くことで実現できます。
例えば、1〜3は「小さい」、4〜6は「大きい」と判定するプログラムを考えてみます。
#include <stdio.h>
int main(void) {
int n;
printf("1〜6の数字を入力してください: ");
scanf("%d", &n);
switch (n) {
case 1:
case 2:
case 3:
// 上の3つのcaseはすべてここに流れ着きます
printf("小さい値です(1〜3)\n");
break;
case 4:
case 5:
case 6:
printf("大きい値です(4〜6)\n");
break;
default:
printf("1〜6の範囲外です\n");
break;
}
return 0;
}
出力例1(入力: 2)
1〜6の数字を入力してください: 2
小さい値です(1〜3)
出力例2(入力: 5)
1〜6の数字を入力してください: 5
大きい値です(4〜6)
この書き方では、case 1, 2, 3の後にbreakを書かず、3つのラベルを1つの処理ブロックに「合流」させています。
複数の値に対して同じ処理を行いたいときに、とてもよく使われるパターンです。
もうごちゃごちゃしない!if文だらけのコードをswitch文で読みやすくするコツ
else ifが増えたときの問題点
C言語を学び始めると、多くの人が次のような「else ifだらけ」のコードを書いた経験を持ちます。
if (mode == 0) {
/* 処理A */
} else if (mode == 1) {
/* 処理B */
} else if (mode == 2) {
/* 処理C */
} else if (mode == 3) {
/* 処理D */
} else {
/* エラー処理 */
}
条件が増えると、縦に長くなるだけでなく、どの値に対応しているかを一行ずつ読まないと分からないという問題が出てきます。
さらに、途中に別の条件を追加するときに括弧やelseの位置を間違えやすくなり、バグの原因にもなります。
switch文に書き換えるとスッキリする典型パターン
上のような「同じ変数を、特定の値と比較するだけ」の場合は、switch文に書き換えると非常にスッキリします。
#include <stdio.h>
int main(void) {
int mode = 2;
switch (mode) {
case 0:
printf("処理Aを実行します\n");
break;
case 1:
printf("処理Bを実行します\n");
break;
case 2:
printf("処理Cを実行します\n");
break;
case 3:
printf("処理Dを実行します\n");
break;
default:
printf("不正なモードです\n");
break;
}
return 0;
}
出力結果(この例ではmodeが2)
処理Cを実行します
switch文を使うと、「modeの値ごとの処理一覧」という構造が明確になり、後から読む人にとっても理解しやすくなります。
値の種類が決まっているときにswitch文を使うメリット
switch文が特に向いているのは、「とりうる値のパターンがあらかじめ決まっている」場合です。
例えば次のようなケースです。
- メニュー番号(1〜5など)
- モード番号(0〜3など)
- 状態を表す列挙型(enum)
- キーボードから入力される特定の文字(
'y'や'n'など)
このような場合、switch文を使うメリットは次の通りです。
1つ目は、全てのパターンが一覧になって見えることです。
漏れや重複がないかが確認しやすくなります。
2つ目は、新しいパターンを追加しやすいことです。
必要な場所にcaseを1つ追加するだけで済み、if文よりも安全に修正できます。
3つ目は、実装ミスをコンパイラが検出しやすくなることです。
たとえば、同じcase値を2回書いた場合にはコンパイルエラーになります。
if文ではこうしたミスをコンパイラが検出してくれません。
ネストしたif文をswitch文でフラットに書くコツ
条件分岐が増えてくると、次のようなネストしたif文を書いてしまうことがあります。
if (mode == 0) {
if (level == 1) {
/* 処理 ... */
} else if (level == 2) {
/* 処理 ... */
}
} else if (mode == 1) {
if (level == 1) {
/* 処理 ... */
} else if (level == 2) {
/* 処理 ... */
}
}
このようなコードは、インデントが深くなり、どの条件がどの処理に対応しているのかが分かりづらくなります。
こうした場合には、条件を1つの値にまとめてswitch文で判定するというテクニックがあります。
例えば、modeとlevelを1つの番号に変換する方法です。
#include <stdio.h>
int main(void) {
int mode = 1;
int level = 2;
// modeとlevelの組み合わせを1つの番号にする
int key = mode * 10 + level; // 例: mode=1, level=2 → 12
switch (key) {
case 0 * 10 + 1: // mode=0, level=1
printf("mode=0, level=1 の処理\n");
break;
case 0 * 10 + 2: // mode=0, level=2
printf("mode=0, level=2 の処理\n");
break;
case 1 * 10 + 1: // mode=1, level=1
printf("mode=1, level=1 の処理\n");
break;
case 1 * 10 + 2: // mode=1, level=2
printf("mode=1, level=2 の処理\n");
break;
default:
printf("未知の組み合わせです\n");
break;
}
return 0;
}
出力例(この例ではmode=1, level=2)
mode=1, level=2 の処理
このように、複数条件の組み合わせを1つの整数値に変換してしまうことで、switch文を使ってフラットな構造にできます。
ただし、この方法は少しトリッキーなので、まずは単純な使い方に慣れてから活用するとよいです。
ラベル名や値の決め方
switch文では、どの値がどの意味を持つかを分かりやすくすることが重要です。
数値をそのまま使うと、後からコードを読んだときに意味が分かりづらくなります。
そこでおすすめなのが、マクロ定数やenum(列挙型)を使って名前を付ける方法です。
#include <stdio.h>
// メニュー番号に意味のある名前を付ける
#define MENU_START 1
#define MENU_OPTION 2
#define MENU_EXIT 3
int main(void) {
int menu = MENU_OPTION;
switch (menu) {
case MENU_START:
printf("ゲームを開始します\n");
break;
case MENU_OPTION:
printf("オプションを表示します\n");
break;
case MENU_EXIT:
printf("終了します\n");
break;
default:
printf("不正なメニューです\n");
break;
}
return 0;
}
このように意味のある名前を使うことで、「MENU_OPTIONとは2のことだ」といった数字の覚え方をしなくて済み、コードの意図がはるかに分かりやすくなります。
switch文とif文を使い分ける判断基準
switch文とif文はどちらも条件分岐に使えますが、用途に応じて使い分けることが大切です。
判断基準を整理します。
switch文を使うとよい場面は次のような場合です。
- 1つの変数(または式)の値に応じて処理を分けたい。
- その値の候補が限定されている(例: 0〜5、’y’か’n’など)。
- 各値に対する処理を一覧として見やすくしたい。
一方で、if文を使う方が適している場面もあります。
- 条件式が「範囲」や「複雑な論理式」の場合(例:
x > 10 && x < 20など)。 - 複数の変数を組み合わせた複雑な条件が必要な場合。
- switchでは扱えない型(
doubleなど)を判定したい場合。
「1つの整数(または文字)の値の違いで分岐するだけ」ならswitch、「それ以外」なら基本的にはifと考えるとよいです。
break・defaultって何?switch文でよくあるつまづきポイントをやさしく整理
break文の役割
switch文の中で使われるbreak文には、「今実行しているswitch文から抜ける」という役割があります。
ループの中で使うbreakと同じ構文ですが、switch内で使う場合は「それ以降のcaseを実行しないようにするためのもの」と理解すると分かりやすいです。
次の短い例で、breakありとなしの違いを確認してみます。
#include <stdio.h>
int main(void) {
int n = 1;
switch (n) {
case 1:
printf("case 1\n");
break; // ここでswitch文を抜ける
case 2:
printf("case 2\n");
break;
default:
printf("default\n");
break;
}
return 0;
}
このコードではcase 1の中でbreakを実行しているため、「case 1」だけが表示されてswitch文を抜けます。
case 1
breakを書き忘れたときに起こるバグ
初心者が最もつまずきやすいのが、breakの書き忘れです。
breakを忘れると、switch文は次のcase以降の処理も連続して実行してしまいます。
これを「フォールスルー(fall through)」と呼びます。
例を見てみます。
#include <stdio.h>
int main(void) {
int n = 1;
switch (n) {
case 1:
printf("case 1\n");
// breakを書き忘れている!
case 2:
printf("case 2\n");
break;
default:
printf("default\n");
break;
}
return 0;
}
本来は「case 1」だけ表示したいのに、実行してみると次のようになります。
case 1
case 2
これは、case 1に一致して「case 1\n」が実行されたあと、breakがないため、そのままcase 2の処理に流れ込んでしまった結果です。
多くの場合、この挙動はバグの原因になります。
そのため、特別な理由がない限り、各caseの末尾には必ずbreakを書くように習慣付けてください。
あえてbreakを書かないテクニックと注意点
一方で、先ほど紹介した「複数のcaseで同じ処理をしたい」ときには、意図的にbreakを書かないことがあります。
もう一度、典型例を示します。
switch (n) {
case 1:
case 2:
case 3:
printf("1〜3のどれかです\n");
break;
case 4:
printf("4です\n");
break;
}
この場合、case 1に一致したときは、次のような流れになります。
case 1が見つかる。- breakがないのでそのまま下に処理が進む。
case 2とcase 3はラベルであり、特別な処理はない。- printfの行に到達して処理を実行。
- breakでswitch文から抜ける。
このような「意図的なフォールスルー」を使うと、同じ処理を複数回書かずに済むという利点があります。
ただし、
- コメントで「ここは意図的にbreakを書いていない」ことを明示する。
- 複雑になりすぎないようにする。
といった注意が必要です。
複雑なフォールスルーは、かえって読みづらくなる原因になります。
defaultラベルの意味
defaultラベルは、どのcaseにも一致しなかったときに実行される「最後の受け皿」です。
if文でいうところのelseに相当します。
defaultを使うことで、次のような利点があります。
- 想定外の値が来たときにエラーメッセージを出せる。
- 値の漏れがあっても、何らかの処理を行える。
- デバッグ時に「ここに来るはずのない値」を検出しやすくなる。
例えば、メニュー番号が1〜3のはずなのに、それ以外の値が来たときに警告を出す、といった用途です。
defaultを書く位置と動作
defaultラベルは、switch文の中ならどこに書いても構文上は問題ありません。
ただし、読みやすさの観点から、一番下に書くのが一般的です。
switch (n) {
case 1:
/* ... */
break;
case 2:
/* ... */
break;
default:
/* ... */
break;
}
なお、defaultが途中にあっても、一致するcaseがあればそちらが優先されます。
defaultは「どのcaseにも一致しなかった場合」にだけ実行されるという点に注意してください。
switch (n) {
default:
printf("default\n");
break;
case 1:
printf("1\n");
break;
}
このコードでnが1のときは、「1」と表示されます。
「defaultが上にあるから優先される」ということはありません。
switch文でよくあるコンパイルエラーとその理由
switch文で初心者がよく遭遇するコンパイルエラーには、いくつか典型的なパターンがあります。
それぞれ理由を知っておくと、エラーを素早く解決できます。
1つ目は、caseラベルに変数を使ってしまうケースです。
int x = 1;
switch (n) {
case x: // エラー: xは変数なので定数式ではない
/* ... */
break;
}
caseの後ろには定数式しか書けず、変数は使えません。
2つ目は、同じ値のcaseを重複して書いてしまうケースです。
switch (n) {
case 1:
/* ... */
break;
case 1: // エラー: 重複したcaseラベル
/* ... */
break;
}
このように同じ値のcaseがあると、どちらを使えばよいかコンパイラが決められないため、エラーになります。
3つ目は、switchの式に不正な型を使うケースです。
double d = 1.0;
switch (d) { // エラー: double型は使えない
case 1:
/* ... */
break;
}
前述したように、switchの式には整数型しか使えません。
このような場合はif文を使う必要があります。
ネストしたswitch文を書くときの注意点
switch文の中に、さらに別のswitch文を書くことも可能です。
これをネストしたswitch文と呼びます。
#include <stdio.h>
int main(void) {
int category = 1;
int type = 2;
switch (category) {
case 1:
printf("カテゴリ1です\n");
// 内側のswitch文
switch (type) {
case 1:
printf("タイプ1です\n");
break;
case 2:
printf("タイプ2です\n");
break;
default:
printf("不明なタイプです\n");
break;
}
break; // 外側のswitchのbreak
default:
printf("不明なカテゴリです\n");
break;
}
return 0;
}
カテゴリ1です
タイプ2です
ネストしたswitch文を書くときの注意点は、どのbreakがどのswitchに対応しているかを意識することです。
上の例では、
- 内側のswitch用のbreak
- 外側のswitch用のbreak
がそれぞれ存在します。
インデントやコメントを丁寧に書き、どのbreakがどこを抜けるのかを分かりやすくしておくことが大切です。
メニュー選択や成績判定で学ぶ!switch文の実用的なサンプルコード集
数値入力によるメニュー選択のサンプルコード
まずは、最も典型的なswitch文の使い方として、数値メニューの例を見てみます。
#include <stdio.h>
int main(void) {
int menu;
printf("メニューを選択してください\n");
printf("1: 足し算\n");
printf("2: 引き算\n");
printf("3: 終了\n");
printf("番号を入力: ");
scanf("%d", &menu);
switch (menu) {
case 1:
printf("足し算モードを選びました\n");
// 実際の足し算処理はここに書く
break;
case 2:
printf("引き算モードを選びました\n");
// 実際の引き算処理はここに書く
break;
case 3:
printf("終了を選びました\n");
break;
default:
// 1〜3以外の数字が入力された場合
printf("不正な番号です\n");
break;
}
return 0;
}
実行例1(入力: 1)
メニューを選択してください
1: 足し算
2: 引き算
3: 終了
番号を入力: 1
足し算モードを選びました
実行例2(入力: 9)
メニューを選択してください
1: 足し算
2: 引き算
3: 終了
番号を入力: 9
不正な番号です
このように、switch文は「番号で選ぶメニュー」との相性がとてもよいです。
文字入力(char)で操作を切り替えるサンプルコード
次に、文字入力をswitchで判定する例を見てみます。
ここでは、'a'で左移動、'd'で右移動といった簡単なコマンドの例にします。
#include <stdio.h>
int main(void) {
char command;
printf("コマンドを入力してください(a: 左, d: 右, q: 終了): ");
// 先頭に空白を付けることで、直前の改行文字を読み飛ばす
scanf(" %c", &command);
switch (command) {
case 'a':
printf("左に移動します\n");
break;
case 'd':
printf("右に移動します\n");
break;
case 'q':
printf("終了します\n");
break;
default:
printf("不明なコマンドです\n");
break;
}
return 0;
}
実行例1(入力: a)
コマンドを入力してください(a: 左, d: 右, q: 終了): a
左に移動します
実行例2(入力: x)
コマンドを入力してください(a: 左, d: 右, q: 終了): x
不明なコマンドです
ここでは、scanf(" %c", &command);のようにフォーマット文字列の先頭に空白を入れている点がポイントです。
これにより、直前の改行などの余分な空白文字を読み飛ばすことができます。
成績判定(A~F)をswitch文で実装するサンプルコード
次は、テストの点数から成績ランク(A〜F)を判定する例です。
switch文では範囲判定が直接できないため、工夫して実装します。
ここでは、点数(0〜100)を10で割った値を使うことで、「10点刻み」の判定を行います。
#include <stdio.h>
int main(void) {
int score;
int rank_key; // 成績判定用のキー
printf("点数を入力してください(0〜100): ");
scanf("%d", &score);
if (score < 0 || score > 100) {
printf("不正な点数です\n");
return 0;
}
// 例: 95点 → 9, 82点 → 8, 67点 → 6
rank_key = score / 10;
switch (rank_key) {
case 10: // 100点はここに入る
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:
// 0〜59点
printf("成績: F\n");
break;
}
return 0;
}
実行例1(入力: 95)
点数を入力してください(0〜100): 95
成績: A
実行例2(入力: 72)
点数を入力してください(0〜100): 72
成績: C
この例では、scoreを10で割った整数値を利用し、複数の点数をグループにまとめています。
switchでは範囲比較ができない代わりに、「計算してケース数を減らす」という工夫がよく使われます。
switch文で曜日や月名を判定するサンプルコード
曜日や月のような決まった種類のデータも、switch文と相性がよい題材です。
ここでは、1〜7の数値を曜日名に変換する例を示します。
#include <stdio.h>
int main(void) {
int day;
printf("曜日を数字で入力してください(1:月〜7:日): ");
scanf("%d", &day);
switch (day) {
case 1:
printf("月曜日です\n");
break;
case 2:
printf("火曜日です\n");
break;
case 3:
printf("水曜日です\n");
break;
case 4:
printf("木曜日です\n");
break;
case 5:
printf("金曜日です\n");
break;
case 6:
printf("土曜日です\n");
break;
case 7:
printf("日曜日です\n");
break;
default:
printf("1〜7の範囲で入力してください\n");
break;
}
return 0;
}
実行例(入力: 6)
曜日を数字で入力してください(1:月〜7:日): 6
土曜日です
同様に、月(1〜12)に対して月名や日数を表示するプログラムも、switch文で素直に書くことができます。
エラーコードに応じてメッセージを切り替えるサンプルコード
実際のプログラムでは、エラーコードに応じてメッセージを切り替える場面が多くあります。
ここでもswitch文はとても便利です。
#include <stdio.h>
// エラーコードにわかりやすい名前を付ける
#define ERR_OK 0
#define ERR_FILE_NOT_FOUND 1
#define ERR_PERMISSION 2
#define ERR_DISK_FULL 3
int main(void) {
int err_code;
printf("エラーコードを入力してください(0〜3): ");
scanf("%d", &err_code);
switch (err_code) {
case ERR_OK:
printf("エラーは発生していません\n");
break;
case ERR_FILE_NOT_FOUND:
printf("エラー: ファイルが見つかりません\n");
break;
case ERR_PERMISSION:
printf("エラー: 権限がありません\n");
break;
case ERR_DISK_FULL:
printf("エラー: ディスク容量が不足しています\n");
break;
default:
printf("不明なエラーコードです\n");
break;
}
return 0;
}
実行例(入力: 2)
エラーコードを入力してください(0〜3): 2
エラー: 権限がありません
このように、エラーコードとメッセージの対応表としてswitch文を使うと、とても見通しのよい実装になります。
サンプルコードから学ぶswitch文の書き方のコツ
ここまでのサンプルから、switch文を書くときの実用的なコツをいくつか整理します。
1つ目は、caseの末尾には必ずbreakを書くことです。
複数のcaseをまとめる場合以外は、書き忘れないようにしましょう。
2つ目は、defaultはできるだけ書くことです。
不正な入力や想定外の状態が発生したときに、エラーメッセージやデバッグ用の表示を出すことで、不具合の発見が容易になります。
3つ目は、マクロ定数やenumを使って「魔法の数字」を減らすことです。
caseに直接1や2などを書くと、後から見たときに意味が分からなくなりがちです。
MENU_STARTやERR_FILE_NOT_FOUNDのような名前を付けることで、可読性が大きく向上します。
4つ目は、似たような処理をまとめるときにはフォールスルーを活用することです。
複数のcaseラベルを1つの処理ブロックに合流させる方法は、パターンが多いときにもすっきりとしたコードを書ける強力なテクニックです。
まとめ
switch文は、1つの値に応じて処理を分けるための、見通しのよい条件分岐構文です。
if文と同じことも実現できますが、値のパターンが限られている場合には、switch文の方がコードの意図が分かりやすくなります。
caseには定数式だけを書き、各caseの末尾にはbreakを入れること、defaultで想定外の値を扱うことが、バグを避けるための基本的なポイントです。
メニュー選択や成績判定、エラーコードの処理など、日常的なプログラムの中で積極的にswitch文を使ってみることで、その便利さと表現力に自然と慣れていくことができます。
