列挙型(列挙体)

コードの可読性を上げる

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


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

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

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

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

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


#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行目が列挙型の定義です。
この「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
};

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

列挙型変数とtypedef

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


enum Gender
{
    MALE,
    FEMALE
};

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

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


typedef enum
{
    MALE,
    FEMALE
} Gender;

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

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

列挙型の使用

列挙型で定義した文字列(列挙子)は、コード中でそのまま記述することができるようになります。
最初のサンプルコードでは、性別を表すために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による方法もあります。
これらを用いてもマジックナンバーの解消は可能です。
C言語では、これらを使用する場合と列挙体を使用する場合とではそれほど大きな違いはありません。

列挙型変数が使用できる

列挙型を使用するメリットは、定義した列挙型をデータ型のように使用できる点です。
つまり列挙型の変数を宣言できる点です。
ただの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

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


//C++の場合

enum Gender gender = MALE;

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

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

列挙型変数から整数型への変換はそのまま行うことができます。
(C++でもキャストは不要)


enum Gender gender = MALE;

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

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