enum - 列挙型

列挙型enumを使うと、連続した整数定数を簡単に記述できます。定数名1は0、定数名2は1、定数名3は2になります。

enum {
  定数名1,
  定数名2,
  定数名3,
  ...
};

列挙型enumを使ったサンプルです。enumは、switch文と組み合わせて使うことが多いので、switch文のサンプルにしました。

#include <stdint.h>
#include <stdio.h>

enum {
  SPVM_OP_C_ID_IF,
  SPVM_OP_C_ID_UNLESS,
  SPVM_OP_C_ID_ELSIF,
  SPVM_OP_C_ID_ELSE,
  SPVM_OP_C_ID_FOR,
};

int main(void) {
  
  int32_t id = SPVM_OP_C_ID_UNLESS;
  
  switch (id) {
    case SPVM_OP_C_ID_IF: {
      printf("IF\n");
      break;
    }
    case SPVM_OP_C_ID_UNLESS: {
      printf("UNLESS\n");
      break;
    }
    case SPVM_OP_C_ID_ELSIF: {
      printf("ELSIF\n");
      break;
    }
    case SPVM_OP_C_ID_ELSE: {
      printf("ELSE\n");
      break;
    }
    case SPVM_OP_C_ID_FOR: {
      printf("FOR\n");
      break;
    }
    default: {
      printf("No Match\n");
    }
  }
}

列挙型の識別子は、識別子名であればなんでもよいですが、C言語には名前空間がないので、サンプルでは、名前空間を持っているかのような名前「SPVM_OP_C_XXX」(spvm_op.cのソースコードの中の定数「constant」定義という意味)を使用しています。

列挙型はマクロによる定数定義と意味的に同じ

列挙型はマクロによる定数定義と意味的に同じです。

// 列挙型を使った整数定義
enum {
  SPVM_OP_C_ID_IF,
  SPVM_OP_C_ID_UNLESS,
  SPVM_OP_C_ID_ELSIF,
}

// マクロを使った整数定義
#define SPVM_OP_C_ID_IF 0
#define SPVM_OP_C_ID_UNLESS 1
#define SPVM_OP_C_ID_ELSIF 2

意味的には同じですが、列挙型は、マクロとことなりプリプロセッサではなく、コンパイラで処理されることが特徴です。コンパイラによって、定数に展開されます。

C言語のベストプラクティスは、マクロを必要な部分以外では使わないということですので、連続した整数定数が欲しい場合は、列挙型を使うのがお勧めです。

列挙型の疑問

列挙型の疑問を列挙します。

列挙型で整数の開始値を変えることはできますか?

はい、できます。整数の開始値を変えるには「定数名 = 数値」のように書きます。次の低数値は、インクリメントされた値になります。

enum {
  SPVM_OP_C_ID_IF = 3,    # 3
  SPVM_OP_C_ID_UNLESS,    # 4
  SPVM_OP_C_ID_ELSIF,     # 5
  SPVM_OP_C_ID_ELSE = 10, # 10
  SPVM_OP_C_ID_FOR,       # 11
};

列挙型で浮動小数点を使うことはできますか?

できません。浮動小数点の定数を定義したい場合は、マクロを使用してください。

#define SPVM_OP_C_ID_IF 5.23

列挙型の識別子は大文字ですか?

C言語の一般的な慣習として、定数は大文字で書くのがよいと思われます。

列挙型はどの辺が型なのですか?

列挙型には、型のように名前をつけて、型のように扱うことができます。

enum SPVM_OP_C_ID_TYPE {
  SPVM_OP_C_ID_IF,
  SPVM_OP_C_ID_UNLESS,
  SPVM_OP_C_ID_ELSIF,
  SPVM_OP_C_ID_ELSE,
  SPVM_OP_C_ID_FOR,
};

ただし列挙型に代入したときの挙動はC言語仕様では定義されておらず、処理系依存ですので、型としての列挙型は、あまり使わない方がよいのではないかと考えます。

列挙型ではなく文字列を使ってはいけないのですか?

列挙型は、整数に対して識別子、つまり名前をつける機能です。それならば最初から、文字列を使ってはいけないのでしょうか?

動的な型を持つPerlのようなプログラミング言語であれば、この方法がベストだと思います。

if ($id eq 'if') {
  
}
elsif ($id eq 'unless') {
  
}

ただしC言語を使うのであれば、パフォーマンスを速くしたいという動機を持って使っているのでしょう。識別子がある数を超える場合(10個くらい?)は、switch文で整数判定して、ジャンプするのが、最速です。

またC言語の文字列比較は、strcmp関数を使う必要がありますし「\0」で文字列が終わっておらず、意図しない領域まで読み込む可能性もあります。

このようにC言語の文字列では、考えるべきことがたくさんあるので、列挙型で整数を定義しておくのが、やりやすいです。

分割コンパイルする場合は、列挙型はどこで定義しますか?

分割コンパイルを使ったヘッダとソースコードに分けてコンパイルする場合の構成としては、列挙型はヘッダファイルに記述します。

マクロ定数や列挙型などの定数定義は、ヘッダに記述します。

spvm_op.h

enum {
  SPVM_OP_C_ID_IF,
  SPVM_OP_C_ID_UNLESS,
  SPVM_OP_C_ID_ELSIF,
  SPVM_OP_C_ID_ELSE,
  SPVM_OP_C_ID_FOR,
};

spvm_op.c

#include <stdint.h>
#include <stdio.h>

#include "spvm_op.h"

int main(void) {
  
  int32_t id = SPVM_OP_C_ID_UNLESS;
  
  switch (id) {
    case SPVM_OP_C_ID_IF: {
      printf("IF\n");
      break;
    }
    case SPVM_OP_C_ID_UNLESS: {
      printf("UNLESS\n");
      break;
    }
    case SPVM_OP_C_ID_ELSIF: {
      printf("ELSIF\n");
      break;
    }
    case SPVM_OP_C_ID_ELSE: {
      printf("ELSE\n");
      break;
    }
    case SPVM_OP_C_ID_FOR: {
      printf("FOR\n");
      break;
    }
    default: {
      printf("No Match\n");
    }
  }
}

関連情報