C言語基礎|列挙体と名前空間

「enum は“定数の集まり”以上!ミスを減らし、名前の衝突も整理できる賢い道具」

列挙体と名前空間を知ると、Cのコードがグッと安全になる

列挙体(enum)は、単に「0,1,2…に名前を付ける」だけじゃありません。
実は、次のような“うれしい特徴”がいくつもあります。

  • マクロで定義するより短く書ける(ミスも減る)
  • 不正な値を代入したとき、コンパイラが警告してくれることがある。
  • デバッガで値が数字ではなく名前で見えることがある。
  • さらに、列挙体タグと変数名は別の名前空間なので、同じ綴りでも区別できる。

この記事では、月の例を「曜日」に置き換えたシンプルなプログラムを使って、
enum とマクロの違い、型としての良さ、デバッグの助け、そして「名前空間」まで、表と図でしっかり解説しますね。

enum とマクロの違い:定義の短さとミスの減り方

「1〜7の曜日」を表したいとします。マクロでやるとこうなりがちです。

マクロで曜日を作ると

#define MONDAY    1
#define TUESDAY   2
#define WEDNESDAY 3
#define THURSDAY  4
#define FRIDAY    5
#define SATURDAY  6
#define SUNDAY    7

図の説明
行数が増える上に、全部の値を手で書くので、うっかりミス(例えば FRIDAY を 6 にしてしまう等)が起きやすいです。

一方、enum ならこう書けます。

enum で曜日を作る

enum weekday { MONDAY = 1, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY };

説明
最初の MONDAY = 1 だけ正しく書けば、残りは「直前 + 1」で自動的に続きます。
値の手入力が減るので、ミスも減りやすいんです。

マクロと enum の比較

観点マクロenum
記述量多くなりがちまとまって短い
値の管理全部手で指定しがち先頭だけ指定→自動で増える
型の情報型ではない(ただの置換)列挙型という型になる
デバッガ表示数字のままになりがち名前で見えることがある

表の説明
enum は「値のセット」だけでなく「型」も作るのが強みです。ここが次の話につながります。

列挙型は“型”なので、不正な値に気づきやすい

例えば、enum animal が 0〜3 の範囲を表すとして、そこに 5 を入れるコードを書いたらどうなるでしょう。

不正値代入のイメージ

enum animal an;
an = 5;   // 変な値

説明
C言語は厳密に実行時チェックをしてくれるわけではありませんが、
コンパイラによっては「列挙型に範囲外っぽい値を入れてるよ」と警告を出してくれることがあります。
int だと“ただの数字”なので、こういうヒントが出にくいんですね。

int だけで書く場合との違い

書き方間違いに気づきやすい?理由
int x;
x = 5;
低い5 が正しいか判断できない
enum animal an;
an = 5;
上がる場合があるenum の意図から外れていると警告が出ることがある

表の説明
列挙型は“表したい集合が決まっている”という意図をコードに埋め込めるのが強いです。

デバッグが楽になる:数字ではなく名前で見えることがある

デバッガによっては、列挙型の値を数字ではなく列挙定数名で表示してくれます。

デバッガ表示のイメージ

selected = 0   →  Dog
selected = 1   →  Cat

図の説明
0 や 1 を見て意味を思い出す手間が減ります。
状態が多いプログラムほど、これが地味に効きます。

重要:enum で表せそうな集合は enum にしよう

ここまでの話をまとめると、次の方針がかなりおすすめです。

enum が向く集合

集合の種類enum にすると良い?
少数の選択肢曜日、月、季節、メニュー
状態の種類初期化中、実行中、終了
エラー種別OK, NG, TIMEOUT
大きい数値範囲点数、身長、温度

名前空間:列挙体タグ名と変数名は別の世界

ここからが「名前空間(name space)」の話です。
C言語では、識別子(名前)が全部同じ箱に入っているわけではなく、種類によって“別の箱”に分かれています。

そのため、列挙体タグと変数名は別の名前空間に属します。
結果として、同じ綴りでも区別できます。

同じ綴りでもOKな例

enum animal animal;

説明

  • 最初の animal は「列挙体タグ」
  • 後ろの animal は「変数名」
    同じ綴りでも、種類が違うので衝突しません。

Cでよく意識する名前空間(ざっくり)

種類備考
列挙体タグenum animal の animal変数名とは別
変数名int animal; の animalタグ名とは別
関数名int select(void); の select変数名と同じ空間になりやすい
列挙定数Dog, Cat など変数名と衝突しうるので注意

表の説明
ここがちょっとややこしいポイントです。
タグ名と変数名は別でも、列挙定数(Dogなど)は“普通の名前”として使われるので、変数名とぶつかる可能性があります。
そのため、列挙定数には ANIMAL_DOG みたいに接頭辞を付ける流儀もよく使われます。

サンプルプログラム

「列挙体の便利さ」と「名前空間で同名が使える」を一度に見られる、小さな例です。
テーマは「状態(mode)で表示メッセージを変える」です。

プロジェクト名:chap8-8-1 ソースファイル名:chap8-8-1.c

Visual Studio でこのプログラムを実行するには、SDLチェック設定を変更しておく必要があります。
1.プロジェクト名を右クリックして、「プロパティ」をクリックします。
2.「C/C++」→「全般」→「SDLチェック」を「いいえ」に切り替えて「OK」をクリックします。

#include <stdio.h>

enum mode { MODE_STOP, MODE_RUN, MODE_QUIT };

int main(void)
{
    enum mode mode;   // enum タグと同名でもOK(ここではタグ名は mode ではないので参考例)
    int n;

    do {
        printf("0…停止  1…実行  2…終了:");
        scanf("%d", &n);

        mode = (enum mode)n;

        switch (mode) {
        case MODE_STOP:
            puts("いったん停止します。");
            break;
        case MODE_RUN:
            puts("処理を開始します。");
            break;
        case MODE_QUIT:
            puts("終了します。おつかれさまでした。");
            break;
        default:
            puts("0〜2で入力してください。");
            break;
        }
    } while (mode != MODE_QUIT);

    return 0;
}

実行例

0…停止  1…実行  2…終了:1
処理を開始します。
0…停止  1…実行  2…終了:2
終了します。おつかれさまでした。

登場する命令・構文の書式と役割

enum の書式

enum タグ名 { 列挙定数, 列挙定数, ... };

何をする?
限られた値の集合を、名前付きで定義し、列挙型(enum タグ名 型)を作ります。

変数宣言の書式

enum タグ名 変数名;

何をする?
列挙型の変数を作ります。状態や選択肢の変数に向きます。

switch の書式

switch (式) {
case 値:
    文;
    break;
default:
    文;
}

何をする?
式の値で分岐します。列挙定数を case に書くと、意味が読みやすくなります。

printf / scanf / puts の書式

  • printf
    printf("書式文字列", 値, ...); 画面表示。
  • scanf
    scanf("書式文字列", 変数のアドレス, ...); 入力を読み取って変数へ格納。
  • puts
    puts("文字列"); 文字列を表示して改行。

まとめ:enum を使うと「安全」「読みやすい」「管理しやすい」

  • マクロより短く書けて、値の指定ミスも減らせる。
  • 型として扱えるので、間違いに気づきやすくなる(警告やデバッグ表示の助け)
  • 名前空間が分かれているので、タグ名と変数名は同じ綴りでも区別できる。
  • 迷ったら「enum で表せる集合は enum にする」が安定です。