C言語には列挙型 enum というものがあります。かなり便利な機能なのですが効果的に使われていることは少ないように思います。
そこで列挙型を正しく使うとどのようなご利益があるかを説明します。
マジックナンバーを使った例#
例題として、引数に応じて曜日を出力する関数を考えてみます。まずは良くない例として引数をマジックナンバー(直値)で指定する関数を挙げてみます。
動作はしますが、引数に唐突に数字が渡されるので可読性が低いです。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| int func(int day)
{
if (day == 0)
puts("Sunday.");
else if (day == 1)
puts("Monday.");
else if (day == 2)
puts("Tuesday.");
else if (day == 3)
puts("Wednesday.");
else if (day == 4)
puts("Thursday.");
else if (day == 5)
puts("Friday.");
else if (day == 6)
puts("Saturday.");
return 0;
}
int main(void)
{
func(0);
func(1);
func(2);
func(3);
func(4);
func(5);
func(6);
return 0;
}
|
マクロを使った例#
マジックナンバーを使うのはよろしくないと、一度は言われたことがあると思います。
そこで曜日を数値でなくマクロにしてみました。少しは可読性がよくなりました。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#define SUNDAY (0)
#define MONDAY (1)
#define TUESDAY (2)
#define WEDNESDAY (3)
#define THURSDAY (4)
#define FRIDAY (5)
#define SATURDAY (6)
int func(int day)
{
if (day == SUNDAY)
puts("Sunday.");
else if (day == MONDAY)
puts("Monday.");
else if (day == TUESDAY)
puts("Tuesday.");
else if (day == WEDNESDAY)
puts("Wednesday.");
else if (day == THURSDAY)
puts("Thursday.");
else if (day == FRIDAY)
puts("Friday.");
else if (day == SATURDAY)
puts("Saturday.");
return 0;
}
int main(void)
{
func(SUNDAY);
func(MONDAY);
func(TUESDAY);
func(WEDNESDAY);
func(THURSDAY);
func(FRIDAY);
func(SATURDAY);
return 0;
}
|
列挙型を使う#
こんどはマクロの代わりに列挙型を使ってみました。可読性はマクロと同等ですね。列挙型にメリットはあるのでしょうか。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
typedef enum {
sunday,
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
} day_t;
int func(day_t day)
{
if (day == sunday)
puts("Sunday.");
else if (day == monday)
puts("Monday.");
else if (day == tuesday)
puts("Tuesday.");
else if (day == wednesday)
puts("Wednesday.");
else if (day == thursday)
puts("Thursday.");
else if (day == friday)
puts("Friday.");
else if (day == saturday)
puts("Saturday.");
return 0;
}
int main(void)
{
func(sunday);
func(monday);
func(tuesday);
func(wednesday);
func(thursday);
func(friday);
func(saturday);
return 0;
}
|
列挙型は列挙子に勝手に数値を割り当ててくれるので、その手間が省けるというのはあります。
上記だとsundayから0から6まで割り当てられます。しかし、ほんとうのメリットはそこではありません。
関数の引数がint型から列挙型day_tに変更されています。
これがポイントです。int型なら0でも100でも1000でも渡すことができてしまいます。
関数に引数チェックを追加すれば実行時にそのような期待しない数値をはじくことはできますが、それはかなり遅いタイミングでのエラー回避です。
エラーを検出するのははやければ早いほど手戻りが減るので嬉しいです。
列挙型 day_t の場合はsundayからsaturdayの7個しか値がないので、不正な値をわたすことがそもそもできません。
正確にはやろうとおもえばできますが、コンパイル時に型の不一致で警告なりが出されます。つまり実際に動かす前に不正な引数をチェックできます。これが列挙型のメリットです。
おまけ#
上記の例では処理とデータの分離ができていません。データはデータ、処理は処理にわけておかないとメンテが大変です。
下の例ではデータは配列としてまとめてみました。処理はputs一行になりました。
列挙子を配列の添字に使うことには賛否あるかと思いますが、例として挙げておきます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
| typedef enum {
sunday,
monday,
tuesday,
wednesday,
thursday,
friday,
saturday,
} day_t;
int func(day_t day)
{
const static char *s[] = {
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
};
puts(s[(int) day]);
return 0;
}
int main(void)
{
func(sunday);
func(monday);
func(tuesday);
func(wednesday);
func(thursday);
func(friday);
func(saturday);
return 0;
}
|
まとめ#
今回は列挙型をつかうことのメリットの一例を挙げました。関数の引数を列挙型にすることで、不正な引数をコンパイル時に検出でき手戻りを削減できます。ぜひ列挙型を使っていきましょう。