C 列挙型

提供:yonewiki
2022年10月25日 (火) 12:21時点におけるYo-net (トーク | 投稿記録)による版 (→‎列挙型)

C++へ戻る


※このページではC言語にも存在していたという意味で記事タイトルがC 列挙型になっていますが、
C++でも同様です。C++だけの機能がある場合は明記します。

列挙型

列挙型という名前自体が日本語としてもあまり使わない単語の型なので、なんともつかみどころのない型ですが、世界中のプログラマの中ではよく使われているという超人気の構文です。列挙って何?って感じですが、EnumrationとかEnumeratorといった単語を素直に日本語に訳したら列挙だったみたいな感じです。「(順序)番号割り当て型」くらいがちょうどよい日本語なのですが、これではあんまりにも幼稚な感じもあるというところなのかもしれません。


よくC言語マニュアルなんかでは


enum 列挙型名{識別子1,…,識別子n};


として宣言できると記述されています。全くそのとおりなのですが、何をしたいのかがよくわかりません。そして利用する際には


列挙型名 列挙変数名;


として定義することで利用ができるとあります。かくいう自分もこれを説明するのにどうやって説明するのがわかりやすいのか思いつきませんでしたので、さっそく具体的なサンプルコードを示して、それから解説することにしたいと思います。列挙型を理解するには辛抱強さが必要だと感じました。とにかく読み進めて下さい。

#pragma once
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[]){
  enum enumAction{
     ACTION_STOP,
     ACTION_START,
     ACTION_PAUSE,
     ACTION_RECORD,
     ACTION_RESTART,
     ACTION_NEXTCHAPTER,
     ACTION_PREVCHAPTER
  };
  enumAction enumActionSignal;
  enumActionSignal = ACTION_STOP;

  printf("%d\n",enumActionSignal);

  switch(enumActionSignal){
    case ACTION_STOP:
      printf("ACTION_STOP\n");
      break;
    case ACTION_START:
      printf("ACTION_START\n");
      break;
    case ACTION_PAUSE:
      printf("ACTION_PAUSE\n");
      break;
    case ACTION_RECORD:
      printf("ACTION_RECORD\n");
      break;
    case ACTION_RESTART:
      printf("ACTION_RESTART\n");
      break;
    case ACTION_NEXTCHAPTER:
      printf("ACTION_NEXTCHAPTER\n");
      break;
    case ACTION_PREVCHAPTER:
      printf("ACTION_PREVCHAPTER\n");
      break;
    default:
      printf("Unknown\n");
      break;
  }
}

上記プログラムの出力結果は以下です。

0
ACTION_STOP

で、列挙型って何?って感じのプログラムかもしれませんが、この程度の使い方でもあるわけでして、なんともシンプルかつ不可解な存在だと言えると思います。列挙型で大事なのはサンプルプログラムの17行目の使い方をするところです。


 enumActionSignal = ACTION_STOP;


この一行だけ見ると、なにかを代入しているんだけど、列挙子って奴は変数みたいになっちゃってるんだなって思うかもしれませんが、列挙子1から順番にACTION_STOP = 0という具合に整数が割り当てられていて、次に指定した列挙子2にはACTION_START = 1のような値を保持します。まとめると


ACTION_STOP = 0
ACTION_START = 1
ACTION_PAUSE = 2
ACTION_RECORD = 3
ACTION_RESTART = 4
ACTION_NEXTCHAPTER = 5
ACTION_PREVCHAPTER = 6


という具合に0~6までの順序番号を自分で好きな名前で定義した文字列ACTION_***に割り振ってくれます。これをenumAction型として列挙型というものを宣言して、


enumAction enumActionSignal;


としてenuAction型の変数enumActionSignalを作成しています。これでenumActionSignalは0~6までの整数が割り振られた列挙子を格納するための変数となったことになります。ここではACTION_*****の部分で動作について数字を割り振ったようなサンプルにしましたので、17行目では


 enumActionSignal = ACTION_STOP;


あたかも


 enumActionSignal = 0;//実際はこの代入は出来ない。


と代入したかのような動作をしてくれます。と、なんだかありがたそうに書きましたが、それだったらint型でも普通に代入できるじゃんと思うところですが、まったく意味合いは異なります。なにが大事かと言うと、列挙型で定義した変数にはenumActionSignal = 7;に対応する列挙子が用意されていないので、列挙型として定義していない値は代入できないことが重要な部分になっていることです。


このようなすごく単純な動作ですが、列挙型の重要な働きである不要な値は保持できないという振る舞いがプログラムには大事であり、プログラマにとってはとても利用価値のある存在になっています。それはなぜかというと、関数の動作を変えたり、処理の動作を変えるにはif文やswitch文で数字と比較して、ある番号ならこの処理をするという記述をする際、番号だけをつかっていると、何の処理をするかが覚えにくいからです。ある関数では0はSTOP。ある関数では0はSTART。あるプログラマが作った関数では0は異常終了といった具合に処理の決め手となる数字のフラグの意味がことなるからです。このため、プログラマは列挙型で準備した列挙子に意味のある言葉やワード(単語)を割り当てて、誰が使っても使いやすい関数を作ろうとするのです。


このことが理解できなければ列挙型の説明を読んでも、なんだか複雑な構文の割には整数型となんら遜色ないし、覚えるだけ無駄なように感じてしまうかもしれません。列挙型は非常に重要な役割をもっていて、とても人気のある構文だという事を忘れないで欲しいです。


但し列挙型の悪いところは、列挙子を覚える必要があることにもあるし、関数の使い方を列挙子に依存することで、本来はただの整数のやりとりなのに隠ぺいされているがために、逆に整数の0はenumActionSignalという列挙変数ではなんていう名前の列挙子を使えばいいのかがわからないことにあります。人気のある関数の列挙子は覚えやすいし、めったに使わない関数の列挙子は覚えづらいし、ながったらしくて嫌になることもあります。デメリットへの対処法はenumの宣言部分を見るか?enumを使っている関数やクラスの利用説明書を見るか?ネットで調べるか?ということになります。よくできた頭のよいというか親切な関数作成者はenumで使われている列挙子を検索する関数を作っていたりもしますが、その検索関数の使い方がわからないという連鎖があったりするのも、これまたおかしです。わかりやすいと思って列挙子を使っているのが回りまわって時間を浪費してしまうケースもすくなくはありません。


列挙型変数への代入は列挙子でしか代入できませんが、if文やswitch文などで列挙子以外の値と比較演算をすることは可能です。


基本的に列挙子は、先頭から0が割り当てられint型の数値として、列挙変数へ数値を代入されていますので、enum型は4バイトの整数を保持する変数となっています。プログラム中でenum 列挙名型{…};とすることでユーザー指定型の型を作ったことになりますので、列挙子はプログラム中で新しい型として処理されます。プログラム処理系(C#とか)によっては列挙子の先頭は1からだったりします。列挙子に割り当てられる整数値は先頭の番号を変更することもできますし、途中からまた番号の割り振り直しを指定することもできます。


列挙子の最初の番号を65536に変更する場合は

  enum enumAction{
     ACTION_STOP = 65536,
     ACTION_START,
     ACTION_PAUSE,
     ACTION_RECORD,
     ACTION_RESTART,
     ACTION_NEXTCHAPTER,
     ACTION_PREVCHAPTER
  };

とすることで65536~65542までが順番に各列挙子の数値として割り振られます。


列挙子の最初の番号を再度ふりなおす場合は

  enum enumAction{
     ACTION_STOP,
     ACTION_START,
     ACTION_PAUSE,
     ACTION_RECORD,
     ACTION_RESTART,
     ACTION_NEXTCHAPTER,
     ACTION_PREVCHAPTER
     ACTION_STOP2 = 0,
     ACTION_START2,
     ACTION_PAUSE2,
     ACTION_RECORD2,
     ACTION_RESTART2,
     ACTION_NEXTCHAPTER2,
     ACTION_PREVCHAPTER2
  };

のように列挙子の記述の途中で…ACTION_STOP2 = 0,…のような記述を挟むことで、番号のふりなおしがされます。すべての列挙子に数値を指定して列挙子を作成することもできます。すべて列挙子に数字を指定するということは、順番に割り振られる列挙型のシステムを使うかどうかの違いだけになります。何度でも振り直しができるため、列挙子がもつ数字が重複することは、すごく当然のように許されています。


また、列挙名型を宣言した範囲内であれば、列挙子が持つint型の整数値を格納することができるため、

#pragma once
#include <iostream>

using namespace std;

int _tmain(int argc, _TCHAR* argv[]){
  enum enumAction{
     ACTION_STOP,
     ACTION_START,
     ACTION_PAUSE,
     ACTION_RECORD,
     ACTION_RESTART,
     ACTION_NEXTCHAPTER,
     ACTION_PREVCHAPTER
  };
  int nEnum;
  nEnum = ACTION_STOP;

  printf("%d\n",nEnum);
}

と上記のように列挙子をint型の変数へ代入することができます。


列挙型の変数はポインタとして扱われることもありますので、そのあたりの応用が必要になることもありますが、通常のポインタ操作となんらかわらないため、詳細はポインタの項目を参照して下さい。


C++へ戻る