列挙型(列挙体)

コードの可読性を上げる

以下は構造体の説明で登場したコードです。

#include <stdio.h>

#define NAME_LENGTH 50

typedef struct
{
    char name[NAME_LENGTH];
    int age;
    char gender;
} Person;

void PrintPerson(Person *p)
{
    printf("name: %s\n", p->name);
    printf("age: %d\n", p->age);
    printf("gender: %d\n", p->gender);
}

int main()
{
    Person person[] = {
        { "A山B男", 20, 0 },
        { "C下D太", 18, 0 },
        { "E田F子", 21, 1 },
        { "G山H美", 19, 1 },
    };
    PrintPerson(&person[1]);

    getchar();
}

性別を表すメンバ変数genderは、0と1の整数値で男女を判別する「決まり」にしています。
しかし、0が男性で1が女性である、というのはこのコードを書いたプログラマだけが分かる決まりに過ぎず、他人がこのコードを読んだときに意図した通りに理解してくれるとは限りません。

このような、コード中に書かれる何を意味するかわかりにくい数値はマジックナンバーと呼ばれ、避けるべきとされています。

これを解決するには、#defineやconstで定数にする方法があります。
(定数とマクロを参照)
数値に名前を付けることで、ただの数値に意味を持たせることができます。

名前を付けたい値の数が多い場合には列挙型を使用するのが便利です。

列挙型は、コード上に登場する「どのような意味なのか、一見してわかりづらい整数値」に名前を付けて、コードを読みやすくすることができます。

#include <stdio.h>

#define NAME_LENGTH 50

enum Gender
{
    MALE,
    FEMALE
};

typedef struct
{
    char name[NAME_LENGTH];
    int age;
    enum Gender gender;
} Person;

void PrintPerson(Person *p)
{
    printf("name: %s\n", p->name);
    printf("age: %d\n", p->age);
    if(p->gender == MALE)
        printf("gender: 男\n");
    else
        printf("gender: 女\n");
}

int main()
{
    Person person[] = {
        { "A山B男", 20, MALE },
        { "C下D太", 18, MALE },
        { "E田F子", 21, FEMALE },
        { "G山H美", 19, FEMALE },
    };
    PrintPerson(&person[1]);

    getchar();
}

5~9行目が列挙型の定義です。
この「MALE」と「FEMALE」はコード中で定数として使用することができます。

書式

enum 列挙型名
{
    メンバ名 = 0,
    メンバ名 = 1,
    ...
};

列挙型の定義にはenumというキーワードを使用します。
列挙型の名前は自由に決定できます。
サンプルコードでは「Gender」が列挙型名です。
名前が必要ない場合は省略が可能です。

波括弧{}の中には、意味を持たせたい名前と数値を記述します。
このとき数値の指定は省略することができます。
この場合、先頭から順に0、1、2…と番号が自動的に割り振られます。

明示的に数値を指定すると以下のようになります。

//移動方法の分類
enum Transportation
{
    WALK    = 0,
    BYCICLE = 1,
    BUS     = 2,
    TRAIN   = 3,
    CAR     = 4
};

また、以下のように特定の項目だけ数値を指定することもできます。
この場合「BUS」は5となり、以降は6、7…と順番に割り振られます。

//移動方法の分類
enum Transportation
{
    WALK,     //0
    BYCICLE,  //1
    BUS = 5,  //5
    TRAIN,    //6
    CAR       //7
};

列挙型変数とtypedef

列挙型を定義すると、新しいデータ型として使用することができます。
15行目では、構造体Personのメンバ変数genderのデータ型として列挙型Genderを指定しています。

enum Gender
{
    MALE,
    FEMALE
};

int main()
{
    //「Gender」型の変数の宣言
    enum Gender gender;
}

構造体の時と同じく、以下のようにtypedefを使用すると変数の宣言時に「enum」キーワードを省略できるようになります。

typedef enum
{
    MALE,
    FEMALE
} Gender;

int main()
{
    //「Gender」型の変数の宣言
    Gender gender;
}

列挙型の使用

列挙型で定義した文字列(メンバ名)は、コード中でそのまま記述することができるようになります。
サンプルコード31~34行目では、性別を表すために0と1を記述していたのを「MALE」「FEMALE」という文字列で記述しています。
ただの0と1よりも、ずっと意味が分かりやすいコードとなります。

//こうだったのが
Person person = { "A山B男", 20, 0 };

//こう書ける
Person person = { "A山B男", 20, MALE };

さらに、22行目では列挙型の条件判定を行っています。

//こうだったのが
if(p->gender == 0)
    printf("gender: 男\n");
else
    printf("gender: 女\n");

//こう書ける
if(p->gender == MALE)
    printf("gender: 男\n");
else
    printf("gender: 女\n");

列挙型を使用しない場合は「なぜ0と比較して、真なら男と表示するのか」という意図が他人には伝わらないコードになるおそれがあります。

const、#defineとの違い

整数に名前を付ける方法としては、const定数と#defineによる方法もあります。
これらを用いてもマジックナンバーの解消は可能です。
C言語では、これらを使用する場合と列挙体を使用する場合とではそれほど大きな違いはありません。

列挙型を使用するメリットは、定義した列挙型をデータ型のように使用できる点です。
つまり列挙型の変数を宣言できる点です。
ただのchar型やint型の変数に名前を付けるよりも、データ型が限定されているので変数の意味がより明確になります。

また、定義の記述が楽というメリットもあります。
仮に上記の移動手段の定数の定義を#defineで行うと、

//移動方法の分類
#define WALK 0
#define BYCICLE 1
#define BUS 2
#define TRAIN 3
#define CAR 4

と記述することになります。
5個ぐらいなら何とかなりますが、もっと多くなると微妙に面倒な作業です。
(const定数でも同じです)

これが列挙型ならば、

//移動方法の分類
enum Transportation { WALK, BYCICLE, BUS TRAIN, CAR };

と、一行で収めることができます。
番号も自動で割り振られるので楽ができます。
(ただし、数が多いなら改行したほうが見やすいです)