まふゆちゃんの技術ブログ。

情報系の高校生・高専生・大学生を全力で応援するブログです。

【C言語】列挙型はいいぞ。enumの便利な使い方講座。

f:id:Mafuyu7se:20181111085643p:plain
こんにちは~・:*+.\( °ω° )/.:+
バーチャル高専女子の七瀬真冬です。

前回の記事「ポインタが分からない人のためのポインタ講座。」がなかなか好評だったようで嬉しいです!!

さてさて、今回のテーマは列挙型です♡

"enum"のことですね。"イーナム"と読みます。
列挙型の便利な使い方について解説するよ。
この記事を読み終えたら、きっと列挙型の使い方を友達に自慢したくなるはず!

ソフトウェア開発の現場では、よく"変更に強いコード(プログラム)を書け!!"と言われるのですが、プログラミング勉強中の人にとってはどういうコードが"変更に強い"のかよくわからないよね。

列挙型(enum)の便利な使い方を覚えれば、プログラミング初心者でもどういうコードが"変更に強い"のかをなんとなーく理解できるようになるよ!

■ 本記事の対象読者

・C言語習ったばかりで、列挙型なんて知らないよ!!
・列挙型は知ってるけど、適切な使い方がイマイチわからないよ!!
・"変更に強いコード"って言われても、全くイメージが湧かないよ!!

といった人たち向けになります。

あと、スマホ用のレイアウトで見るとソースコードの表示が崩れるので、
PCモードでの閲覧を推奨します!!


■ コードの実行環境

・OS:Windows 10
・開発環境:Visual Studio 2017

前回と一緒だよ。


スポンサーリンク



■ 列挙型とはなんぞや??

まず、列挙型の概念について説明するよ。
まふゆちゃんは優しいので、教科書読め!とか、ググれ!とか、そういうことは言いません。
この記事だけで、あなたが列挙型マスターになれるように全力でサポートします。

列挙型(enum)は、
"複数の整数にそれぞれ名前をつけて、一つのまとまりとして扱う"
ときに使われます。

以下のコードを見てね。
これが列挙型の定義方法です。
軽音楽部のバンドをイメージしてね。

typedef enum    // イカれたメンバーを紹介するぜ!
{
	BAND_GUITAR,     // ギター
	BAND_BASS,       // ベース
	BAND_DRUMS,      // ドラム
	BAND_KEYBOARD,   // キーボード
} BAND_MEMBERS;  // 以上、俺たちが"BAND_MEMBERS"だ!よろしくな!

最初の"typedef enum"は、列挙型を定義するときに必要な宣言です。
列挙型のイカれたメンバーを紹介するぜ!みたいな感じで捉えてください。
で、最後のBAND_MEMBERSって言うのは、自由に付けられる列挙型の名前です。
まぁバンド名とか、アイドルユニット名みたいなもんだ。

このような宣言のしかたをすると、一番上のBAND_GUITARから順番に、0,1,2,3...という整数が自動で割り振られます。
つまり、それぞれの要素に固有のIDが割り振られることになるね。

以下のコードを実行してみよう。

#include <stdio.h>

typedef enum
{
	BAND_GUITAR,     // ギター
	BAND_BASS,       // ベース
	BAND_DRUMS,      // ドラム
	BAND_KEYBOARD,   // キーボード
} BAND_MEMBERS;

int main(void)
{
	printf("BAND_GUITAR  の値は、%d です。\n", BAND_GUITAR);
	printf("BAND_BASS    の値は、%d です。\n", BAND_BASS);
	printf("BAND_DRUMS   の値は、%d です。\n", BAND_DRUMS);
	printf("BAND_KEYBOARDの値は、%d です。\n", BAND_KEYBOARD);

	return 0;
}

実行結果はこちら。
f:id:Mafuyu7se:20181110081801p:plain

コンピュータさんは、バンド内のメンバーさんたちに固有の数値を割り振ることで、区別しているってことになります。

■ 役に立つ使い方を紹介しよう。

それじゃあ、さっき紹介したバンドを使って列挙型が役に立つ場面について説明するね。

さっき宣言した列挙型の"BAND_MEMBERS"だけど、ちょっと修正を入れましょう。

typedef enum
{
	BAND_GUITAR,     // ギター
	BAND_BASS,       // ベース
	BAND_DRUMS,      // ドラム
	BAND_KEYBOARD,   // キーボード

	BAND_MEMBERS_NUM  // これを新しく追加!!
} BAND_MEMBERS;

BAND_MEMBERS_NUM っていうのが増えたね。
実は、ここが今回の記事一番のポイントだったりします。
もう一度いいます。ここが今回の記事の一番大事な所です。
これが何を表しているのかというと、"バンドメンバーの人数"です。

ギター、ベース、ドラム、キーボードの4人がいるけど、それぞれに割り振られた数値は0,1,2,3になるよね。
なので、一番最後に追加した要素には4が割り振られます。
この最後の要素(4)を使えば、"列挙型の全メンバー数(4人)を表せる"ことになります。

f:id:Mafuyu7se:20181110152857p:plain

enumを宣言するときは、一番最後に"要素数を表す値"(今回の場合はバンドのメンバー数)を定義すると、何かと便利です。
実際に使うかどうかは置いといて、とりあえず定義しておくクセをつけるといいよ。

こいつが如何に便利な存在かは、この記事を読み進めればわかるよ。
とりあえず追加しておこうね。

じゃあ次。
以下のコードを見てみよう。
バンド内のメンバーそれぞれの名前を定義するよ。

// メンバーの名前を定義するよ。
// メンバーの人数(BAND_MEMBERS_NUM = 4) × 20文字分の配列を宣言しよう。
char bandMemberNames[BAND_MEMBERS_NUM][20]
{
	"HIRASAWA_YUI",       // ギター担当の平沢唯さん。
	"AKIYAMA_MIO",        // ベース担当の秋山澪さん。
	"TAINAKA_RITSU",      // ドラム担当の田井中律さん。
	"KOTOBUKI_TSUMUGI",   // キーボード担当の琴吹紬さん。
};

早速、BAND_MEMBERS_NUM(4のこと)が出てきたね。
別にBAND_MEMBERS_NUMなんて使わなくても、"数字の4をそのまま入れればよくね?"
と思った君は、まだまだ修行不足だ。

BAND_MEMBERS_NUMを使うメリットは、ズバリ"変更容易性"です。
つまりこれを使うと、記事冒頭で言った"変更に強いコード"にすることができます。

一つ例をあげよう。
このバンド、ボーカルがいないよね。
ところが今まさに、タイミングよくボーカル希望者が現れ、バンドに加入したとします。

すると列挙型の値は

// バンドのメンバーに固有の数値を割り振るよ。
typedef enum
{
	BAND_VOCAL,      // ボーカル(新規加入だぜ!!)
	BAND_GUITAR,     // ギター
	BAND_BASS,       // ベース
	BAND_DRUMS,      // ドラム
	BAND_KEYBOARD,   // キーボード

	BAND_MEMBERS_NUM 
} BAND_MEMBERS;

というふうになるよね。
このとき、一番上のボーカルさんに0が割り振られるので、ギターさんは1、ベースさんは2...といったふうに、1つずつズレることになります。
で、一番下のBAND_MEMBERS_NUMは当然"5"になるよね。
つまり、メンバー(新しい列挙型の要素)が増えるとメンバー数を表すBAND_MEMBERS_NUMも同時に増えて、適切な値になるということなんだ!!すごいでしょ!!

f:id:Mafuyu7se:20181110152900p:plain

これ、至極当たり前のことのように思えるけど、とっても大事なことなんです。
なんでかっていうと、例えば新しいメンバーの名前を定義するとき、

// メンバーの名前を定義するよ。
// メンバーが増えたので、BAND_MEMBERS_NUMは勝手に4から5になったよ。
char bandMemberNames[BAND_MEMBERS_NUM][20]
{
	"NISHIDERA_GOUTA",     // ボーカル担当の西寺郷太さん。(追加するのはこれだけ!!)
	"HIRASAWA_YUI",        // ギター担当の平沢唯さん。
	"AKIYAMA_MIO",         // ベース担当の秋山澪さん。
	"TAINAKA_RITSU",       // ドラム担当の田井中律さん。
	"KOTOBUKI_TSUMUGI",    // キーボード担当の琴吹紬さん。
};

とするだけでよくなるのだ!!

仮に、
char bandMemberNames[BAND_MEMBERS_NUM][20]
のところを
char bandMemberNames[4][20]
と定義してた場合、
char bandMemberNames[5][20]
に書き直す手間が発生するよね。

プログラミング初心者さんは"それぐらい修正すりゃいいじゃん…"って思うかもしれませんが、実際の開発現場はとっても忙しいので、こういった手間が非常に嫌われるし、この修正を忘れてしまうとバグが発生する原因になったりするよ。
そして、バグの原因を突き止めるのに無駄な仕事が発生します。
多分、こういうコードを書くと上司に間違いなく指摘を喰らいます。というか怒られます。

"変更に強いコード"ってのは、言い換えると仕様変更があったときに、少しだけ変更すれば対応できるコードってことです。

最初のうちは難しいかもしれないけど、仕様変更があったときに、変更が少なくなるような設計にすることっていうのを常に意識してプログラミングをすると、周りに差をつけることができるぞ!!
これができると、先生や上司から褒められると思います。


スポンサーリンク



■ 変更に強いコード。

#include <stdio.h>

// バンドのメンバーに固有の数値を割り振るよ。
typedef enum
{
	BAND_VOCAL,      // ボーカル
	BAND_GUITAR,     // ギター
	BAND_BASS,       // ベース
	BAND_DRUMS,      // ドラム
	BAND_KEYBOARD,   // キーボード

	BAND_MEMBERS_NUM  // バンドメンバーの人数。
} BAND_MEMBERS;


// メンバーの名前の文字列を定義するよ。
// メンバーの人数(BAND_MEMBERS_NUM = 5) × 20文字分の配列を宣言しよう。
static char bandMemberNames[BAND_MEMBERS_NUM][20]
{
	"NISHIDERA_GOUTA",   // ボーカル担当の西寺郷太さん。
	"HIRASAWA_YUI",      // ギター担当の平沢唯さん。
	"AKIYAMA_MIO",       // ベース担当の秋山澪さん。
	"TAINAKA_RITSU",     // ドラム担当の田井中律さん。
	"KOTOBUKI_TSUMUGI",  // キーボード担当の琴吹紬さん。
};

// 担当パートの文字列を定義するよ。
// メンバーの人数(BAND_MEMBERS_NUM = 5) × 20文字分の配列を宣言しよう。
static char bandPartNames[BAND_MEMBERS_NUM][20]
{
	"Vocal", 
	"Guitar",  
	"Bass",   
	"Drums", 
	"Keyboard",
};

int main(void)
{
	int i;

	printf("イカれたメンバーを紹介するぜ!!\n\n");

	for (i = 0; i < BAND_MEMBERS_NUM; i++)
	{
		// ここでメンバーを全員紹介
		printf("%s 担当、%s!!\n", bandPartNames[i], bandMemberNames[i]);
	}

	printf("\n以上だ!!よろしくな!!\n");

	return 0;
}

このサンプルコード、実行するとこうなります。

f:id:Mafuyu7se:20181110155143p:plain

実はこのコード、とっても"変更に強いコード"です。

たとえば、バンド内でこういう方針転換があったとする。
・平沢唯さんは、ギター下手くそだからカスタネット担当に変更しよう!
・代わりに、ギターの上手い新入生の中野梓さんをギター担当にしよう!

どんな変更が必要になるか、ちょっと考えてみてね。


正解は、
・列挙型 BAND_MEMBERS に、カスタネット担当者の値を追加する。
・bandMemberNames内のギター担当者の名前を変更する。(平沢唯→中野梓)
・bandMemberNames内のカスタネット担当者として、平沢唯さんを追加する。
・bandPartNamesに、カスタネットを追加する。

この4つです。

つまり、定義しているデータの値をいじるだけ。
main関数内のロジック(処理内容)をいじる必要は一切ありません。
main関数内で、バンドメンバーの数(BAND_MEMBERS_NUM)だけループする処理になっているので、バンドメンバーの数が増えたり減ったりしても、自動的に対応できます。

今回はmain関数内のロジックはわりと単純だけど、実際の開発現場で使われるロジックはめちゃくちゃ複雑です。
ちょっといじるだけで思いがけないバグが発生することもあります。

仕様変更が発生したとき、複雑なロジック触らずに値だけをいじって済むようにしたいなぁ~と思ったら、この記事で学んだことを思い出して設計してみてね♡

最後に、バンド内の方針転換を反映したコードと実行結果を載せて終わりにします。

#include <stdio.h>

// バンドのメンバーに固有の数値を割り振るよ。
typedef enum
{
	BAND_VOCAL,      // ボーカル
	BAND_GUITAR,     // ギター
	BAND_BASS,       // ベース
	BAND_DRUMS,      // ドラム
	BAND_KEYBOARD,   // キーボード
	BAND_CASTANTES,  // カスタネット(新しく追加!!!)

	BAND_MEMBERS_NUM  // バンドメンバーの人数。
} BAND_MEMBERS;


// メンバーの名前の文字列を定義するよ。
// メンバーの人数(BAND_MEMBERS_NUM = 6) × 20文字分の配列を宣言しよう。
static char bandMemberNames[BAND_MEMBERS_NUM][20]
{
	"NISHIDERA_GOUTA",  // ボーカル担当の西寺郷太さん。
	"NAKANO_AZUSA",     // ギター担当の中野梓さん。(平沢唯さんから変更!!!)
	"AKIYAMA_MIO",      // ベース担当の秋山澪さん。
	"TAINAKA_RITSU",    // ドラム担当の田井中律さん。
	"KOTOBUKI_TSUMUGI", // キーボード担当の琴吹紬さん。
	"HIRASAWA_YUI",     // カスタネット担当の平沢唯さん。(ギターやめました!!!)
};

// 担当パートの文字列を定義するよ。
// メンバーの人数(BAND_MEMBERS_NUM = 6) × 20文字分の配列を宣言しよう。
static char bandPartNames[BAND_MEMBERS_NUM][20]
{
	"Vocal", 
	"Guitar",  
	"Bass",   
	"Drums", 
	"Keyboard",
	"Castanets",  // カスタネット(新しく追加!!!)
};

// main関数は一切いじる必要はありません!!!
int main(void)
{
	int i;

	printf("イカれたメンバーを紹介するぜ!!\n\n");

	for (i = 0; i < BAND_MEMBERS_NUM; i++)
	{
		// ここでメンバーを全員紹介
		printf("%s 担当、%s!!\n", bandPartNames[i], bandMemberNames[i]);
	}

	printf("\n以上だ!!よろしくな!!\n");

	return 0;
}

実行結果。
f:id:Mafuyu7se:20181110161140p:plain

もし余裕があったら、バンドメンバーや楽器パートを自分で追加して動かしてみてもいいよ!

■ まとめ

・列挙型は、複数の整数にそれぞれ名前をつけて、一つのまとまりとして扱うよ!
・列挙型の最後に、要素の総数を表す値を定義すると便利だよ!
・なるべくロジック(処理内容)をいじらずに、データの定義部分のみを変更して仕様変更に対応できるような設計にしよう!

以上!終わり!!閉廷!!!

ではでは・:*+.\( °ω° )/.:+

にほんブログ村 IT技術ブログへにほんブログ村
にほんブログ村 IT技術ブログ C/C++へにほんブログ村