列挙型(列挙体)

コードの可読性を上げる

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


#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は、01の整数値で男女を判別する「決まり」にしています。
しかし、0が男性で1が女性である、というのはこのコードを書いたプログラマだけが分かる決まりに過ぎず、他人がこのコードを読んだときに意図した通りに理解してくれるとは限りません。

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

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

このコードでは「0」と「1」という値自体には意味はなく、何かの方法でそれぞれを識別できれば良いわけです。
このような場合には列挙型(列挙体)を使用するのが便利です。


#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();
}

6~10行目が列挙型の定義です。
このMALEFEMALEはコード中で定数として使用することができます。

書式


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
};

値は重複してもエラー等にはなりませんが、数値で識別するという目的で使われるものなので、通常は避けるべきです。
ちなみに列挙型変数の実体はint型で、値はint型で扱える範囲である必要があります。

列挙型の使用

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


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

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

条件判定でも同様に定数を使用できます。


//こうだったのが
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による方法もあります。
これらを用いてもマジックナンバーの解消は可能ですが、列挙型には以下のメリットがあります。

列挙型変数が使用できる

列挙型は新しいデータ型を定義することになります。
(ユーザー定義型の一種)
つまり列挙型の変数を宣言できるので、char型やint型の変数に名前を付けるよりもコードの意味がより明確になります。

ただし、列挙型変数の実体はただの整数型変数です。
列挙型で定義した範囲以外の値を変数に代入することもできることに注意する必要があります。


#include <stdio.h>

enum Gender
{
	MALE,
	FEMALE
};

int main()
{
	enum Gender gender = MALE;
	printf("%d\n", gender);

	gender = 99;
	printf("%d\n", gender);

	getchar();
}
0
99

列挙型変数から整数型への変換はそのまま行うことができます。


enum Gender gender = MALE;
int n = gender;

C言語では列挙型変数にそのまま数値を代入できますが、C++ではキャストが必要となります。


//C++の場合

enum Gender gender = MALE;

//C++ではこれはダメ
gender = 99;

//キャストが必要
gender = (Gender)99;

//これはキャスト不要
int n = gender;

定義がちょっと楽

定義の記述が楽というメリットもあります。
仮に上記の移動手段の定数の定義を#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 };

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

列挙型変数とtypedef

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


typedef enum
{
    MALE,
    FEMALE
} Gender;

int main()
{
    //「Gender」型の変数の宣言
	//enumキーワードは要らない
    Gender gender;
}