C言語基礎|列挙定数

「enum は“0からの連番”だけじゃない!列挙定数に値を付ければ、コードがもっと読みやすくなる」

列挙定数は「名前付きの整数」をきれいに並べる仕組み

列挙体(enum)を使うと、0, 1, 2…のような数字に 意味のある名前 を付けられます。
この「名前そのもの」が列挙定数です。

列挙定数は基本的に 0 から自動で連番になりますが、実はここからが本題で、

  • 途中の列挙定数だけ値を指定できる。
  • 指定しなかったものは「直前 + 1」になる。
  • 同じ値を重ねることもできる。
  • タグ名(列挙体タグ)なしで列挙定数だけ作ることもできる。

という、使い勝手の良いルールがあります。

この記事では、列挙定数の「値の付き方」と「便利な使いどころ」を、表と図と小さなサンプルでしっかり説明しますね。

列挙定数とは

列挙体の { } の中に並ぶ名前が列挙定数です。

列挙定数が並ぶ場所(イメージ)

enum weather { Sunny, Cloudy, Rainy, Quit };
             ↑      ↑       ↑      ↑
           列挙定数 列挙定数 列挙定数 列挙定数

図の説明
Sunny などは「名前」ですが、内部的には整数値として扱われます。
だから switch の case にも使いやすいんですね。

基本ルール:何も指定しないと 0 から連番

自動で割り当てられる値

宣言割り当て
enum weather { Sunny, Cloudy, Rainy, Quit };Sunny=0, Cloudy=1, Rainy=2, Quit=3

表の説明
最初が 0、次は 1…という「0からの連番」が基本です。

値を指定できる:= を付けたら、その値になる

列挙定数は、名前の後ろに = 数値 を書くと、値を自由に指定できます。

値指定ありの列挙体(例)

図の説明
Saga に 5 を指定したので、Nagasaki は「直前の 5 に +1」で 6 になります。
この “直前 + 1” ルールが、値指定の後でも続くのがポイントです。

値指定のルールまとめ

状況
列挙定数に = が付いている指定した値になる
= が付いていない直前の列挙定数 + 1 になる
先頭で = が無い0 になる

同じ値を重ねることもできる

列挙定数は、複数を同じ値にできます。

同じ値を共有する

enum namae { Asuka, Nara = 0 };

同値の列挙定数

列挙定数
Asuka0
Nara0

表の説明
「別名(エイリアス)」のように、同じ値に違う名前を付けたいときに使えます。
ただし、switch の case で同じ値を2回書くとエラーになりやすいので、使う場所は少し選びます。

列挙体タグを省略して、列挙定数だけ作る方法

タグ名(列挙体タグ)を付けずに、列挙定数だけを作ることもできます。

書式:タグなし enum

enum { JANUARY = 1, FEBRUARY, /* … */ DECEMBER };

この形だと、enum 型の変数は宣言できません
理由は単純で、「型の名前(タグ)がないから」です。

タグあり/なしの違い

変数を宣言できる?何に便利?
enum month { ... };できる変数の型として使いたい
enum { ... };できない定数セットだけ欲しい

表の説明
タグなし enum は「定数のまとまり」を作るのに便利です。
型としては使わないけど、case ラベルなどで使いたいときに活躍します。

switch の case で列挙定数が便利な理由

列挙定数は整数値なので、switch の case と相性が最高です。

switch での利用イメージ

int month;

switch (month) {
    case JANUARY:   1月の処理
    case FEBRUARY:  2月の処理
    ...
}

図の説明
数字を直書きするより、JANUARY のように意味が分かる名前にすると、読みやすさが上がります。

サンプルプログラム

「月番号を入力すると季節のメッセージを出す」プログラムです。
列挙定数はタグなし enum で作り、switch で活用します。

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

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

#include <stdio.h>

enum { JAN = 1, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC };

int main(void)
{
    int month;

    printf("月を入力してください(1〜12):");
    scanf("%d", &month);

    switch (month) {
    case MAR:
    case APR:
    case MAY:
        puts("春っぽい空気になってきました。");
        break;

    case JUN:
    case JUL:
    case AUG:
        puts("暑さ対策をしていきましょう。");
        break;

    case SEP:
    case OCT:
    case NOV:
        puts("過ごしやすい季節ですね。");
        break;

    case DEC:
    case JAN:
    case FEB:
        puts("あたたかくして過ごしましょう。");
        break;

    default:
        puts("1〜12の範囲で入力してください。");
        break;
    }

    return 0;
}

実行例

月を入力してください(1〜12):4
春っぽい空気になってきました。

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

enum の書式(タグあり)

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

何をする?
「名前付きの整数の集合」を持つ列挙型を作ります。タグ名は型を指すときに使います(enum タグ名 型)。

enum の書式(タグなし)

enum { 列挙定数 = 値, 列挙定数, ... };

何をする?
型名は作らず、列挙定数(定数セット)だけを作ります。switch の case などで便利です。

switch の書式

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

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

printf / scanf / puts の書式

  • printf
    printf("書式文字列", 値, ...); 画面に表示します。
  • scanf
    scanf("書式文字列", 変数のアドレス, ...); 入力を読み取り、変数に入れます。
  • puts
    puts("文字列"); 文字列を表示し、最後に改行も出します。

演習問題

演習8-4:バブルソートの走査を「先頭側から末尾側」に変える

バブルソートで、比較の走査を末尾→先頭ではなく、先頭→末尾にするように書き換えた関数を作成せよ。

解答例(先頭→末尾の走査版)

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

void bsort_forward(int a[], int n)
{
    for (int i = 0; i < n - 1; i++) {            // パスは n-1 回
        for (int j = 0; j < n - 1 - i; j++) {    // 先頭から末尾へ走査
            if (a[j] > a[j + 1]) {               // 右のほうが小さければ交換
                int tmp = a[j];
                a[j] = a[j + 1];
                a[j + 1] = tmp;
            }
        }
    }
}

解説

  • 先頭から進むので比較は a[j] と a[j+1]
  • 1パス終わると最大値が末尾側へ寄っていきます
  • i が増えるほど、末尾側の確定領域が増えるので n - 1 - i まででOKです

演習8-5:性別または季節を表す列挙体を作り、switch で表示せよ

季節を表す列挙体を定義し、入力(0〜3)に応じてメッセージを表示するプログラムを作成せよ。

解答例

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

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

#include <stdio.h>

enum season { Spring, Summer, Autumn, Winter };

int main(void)
{
    int n;
    enum season s;

    printf("0…春  1…夏  2…秋  3…冬:");
    scanf("%d", &n);

    if (n < Spring || n > Winter) {
        puts("0〜3で入力してください。");
        return 0;
    }

    s = (enum season)n;

    switch (s) {
    case Spring: puts("新しいことを始めたくなる季節です。"); break;
    case Summer: puts("水分補給を忘れずに!"); break;
    case Autumn: puts("散歩が気持ちいいですね。"); break;
    case Winter: puts("手を冷やさないようにしましょう。"); break;
    }

    return 0;
}

解説

  • enum season の列挙定数は 0〜3 の連番になる。
  • 入力範囲チェックをしてから、enum に代入すると安心
  • switch の case に列挙定数を書くと分岐が読みやすい。

enum の読み方(気楽でOK)

enum の元の単語 enumeration は、カタカナだと「イニュームレーション」に近い感じです。
ただ実際は、短縮語の発音は人によっていろいろで、厳密な正解があるというより「通じればOK」な世界です。気楽にいきましょう。