Cで列挙子名を文字列に変換する可能性はありますか?
9 に答える
1つの方法は、プリプロセッサに作業を行わせることです。また、列挙型と文字列が同期していることを確認します。
#define FOREACH_FRUIT(FRUIT) \
FRUIT(apple) \
FRUIT(orange) \
FRUIT(grape) \
FRUIT(banana) \
#define GENERATE_ENUM(ENUM) ENUM,
#define GENERATE_STRING(STRING) #STRING,
enum FRUIT_ENUM {
FOREACH_FRUIT(GENERATE_ENUM)
};
static const char *FRUIT_STRING[] = {
FOREACH_FRUIT(GENERATE_STRING)
};
プリプロセッサが完了すると、次のようになります。
enum FRUIT_ENUM {
apple, orange, grape, banana,
};
static const char *FRUIT_STRING[] = {
"apple", "orange", "grape", "banana",
};
次に、次のようなことを行うことができます。
printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);
ユースケースが文字通り列挙型名を出力するだけの場合は、次のマクロを追加します。
#define str(x) #x
#define xstr(x) str(x)
次に、次のことを行います。
printf("enum apple as a string: %s\n", xstr(apple));
この場合、2レベルのマクロは不要に見えるかもしれませんが、Cでの文字列化の仕組みにより、必要になる場合があります。たとえば、列挙型で#defineを使用するとします。
#define foo apple
int main() {
printf("%s\n", str(foo));
printf("%s\n", xstr(foo));
}
出力は次のようになります。
foo
apple
これは、strが入力fooを拡張してアップルにするのではなく、文字列化するためです。xstrを使用すると、最初にマクロ展開が実行され、次にその結果が文字列化されます。
詳細については、文字列化を参照してください。
あなたがこれを持っている状況では:
enum fruit {
apple,
orange,
grape,
banana,
// etc.
};
私はこれを列挙型が定義されているヘッダーファイルに入れるのが好きです:
static inline char *stringFromFruit(enum fruit f)
{
static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };
return strings[f];
}
専用の配列文字列を宣言せずに同じ仕事をしているCプリプロセッサのトリックを見つけました(出典:http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en)。
順次列挙型
Stefan Ramの発明に続いて、(たとえば、インデックスを明示的に指定せずに)順次列挙型を次のenum {foo=-1, foo1 = 1}
ような天才的なトリックのように実現できます。
#include <stdio.h>
#define NAMES C(RED)C(GREEN)C(BLUE)
#define C(x) x,
enum color { NAMES TOP };
#undef C
#define C(x) #x,
const char * const color_name[] = { NAMES };
これにより、次の結果が得られます。
int main( void ) {
printf( "The color is %s.\n", color_name[ RED ]);
printf( "There are %d colors.\n", TOP );
}
色は赤です。
3色あります。
非順次列挙型
エラーコード定義を配列文字列にマップして、生のエラー定義をエラーコードに追加できるようにしたかったので(たとえば"The error is 3 (LC_FT_DEVICE_NOT_OPENED)."
)、それぞれの列挙値に必要なインデックスを簡単に決定できるようにコードを拡張しました:
#define LOOPN(n,a) LOOP##n(a)
#define LOOPF ,
#define LOOP2(a) a LOOPF a LOOPF
#define LOOP3(a) a LOOPF a LOOPF a LOOPF
#define LOOP4(a) a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP5(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP6(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP7(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP8(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LOOP9(a) a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF a LOOPF
#define LC_ERRORS_NAMES \
Cn(LC_RESPONSE_PLUGIN_OK, -10) \
Cw(8) \
Cn(LC_RESPONSE_GENERIC_ERROR, -1) \
Cn(LC_FT_OK, 0) \
Ci(LC_FT_INVALID_HANDLE) \
Ci(LC_FT_DEVICE_NOT_FOUND) \
Ci(LC_FT_DEVICE_NOT_OPENED) \
Ci(LC_FT_IO_ERROR) \
Ci(LC_FT_INSUFFICIENT_RESOURCES) \
Ci(LC_FT_INVALID_PARAMETER) \
Ci(LC_FT_INVALID_BAUD_RATE) \
Ci(LC_FT_DEVICE_NOT_OPENED_FOR_ERASE) \
Ci(LC_FT_DEVICE_NOT_OPENED_FOR_WRITE) \
Ci(LC_FT_FAILED_TO_WRITE_DEVICE) \
Ci(LC_FT_EEPROM_READ_FAILED) \
Ci(LC_FT_EEPROM_WRITE_FAILED) \
Ci(LC_FT_EEPROM_ERASE_FAILED) \
Ci(LC_FT_EEPROM_NOT_PRESENT) \
Ci(LC_FT_EEPROM_NOT_PROGRAMMED) \
Ci(LC_FT_INVALID_ARGS) \
Ci(LC_FT_NOT_SUPPORTED) \
Ci(LC_FT_OTHER_ERROR) \
Ci(LC_FT_DEVICE_LIST_NOT_READY)
#define Cn(x,y) x=y,
#define Ci(x) x,
#define Cw(x)
enum LC_errors { LC_ERRORS_NAMES TOP };
#undef Cn
#undef Ci
#undef Cw
#define Cn(x,y) #x,
#define Ci(x) #x,
#define Cw(x) LOOPN(x,"")
static const char* __LC_errors__strings[] = { LC_ERRORS_NAMES };
static const char** LC_errors__strings = &__LC_errors__strings[10];
この例では、Cプリプロセッサは次のコードを生成します。
enum LC_errors { LC_RESPONSE_PLUGIN_OK=-10, LC_RESPONSE_GENERIC_ERROR=-1, LC_FT_OK=0, LC_FT_INVALID_HANDLE, LC_FT_DEVICE_NOT_FOUND, LC_FT_DEVICE_NOT_OPENED, LC_FT_IO_ERROR, LC_FT_INSUFFICIENT_RESOURCES, LC_FT_INVALID_PARAMETER, LC_FT_INVALID_BAUD_RATE, LC_FT_DEVICE_NOT_OPENED_FOR_ERASE, LC_FT_DEVICE_NOT_OPENED_FOR_WRITE, LC_FT_FAILED_TO_WRITE_DEVICE, LC_FT_EEPROM_READ_FAILED, LC_FT_EEPROM_WRITE_FAILED, LC_FT_EEPROM_ERASE_FAILED, LC_FT_EEPROM_NOT_PRESENT, LC_FT_EEPROM_NOT_PROGRAMMED, LC_FT_INVALID_ARGS, LC_FT_NOT_SUPPORTED, LC_FT_OTHER_ERROR, LC_FT_DEVICE_LIST_NOT_READY, TOP };
static const char* __LC_errors__strings[] = { "LC_RESPONSE_PLUGIN_OK", "" , "" , "" , "" , "" , "" , "" , "" "LC_RESPONSE_GENERIC_ERROR", "LC_FT_OK", "LC_FT_INVALID_HANDLE", "LC_FT_DEVICE_NOT_FOUND", "LC_FT_DEVICE_NOT_OPENED", "LC_FT_IO_ERROR", "LC_FT_INSUFFICIENT_RESOURCES", "LC_FT_INVALID_PARAMETER", "LC_FT_INVALID_BAUD_RATE", "LC_FT_DEVICE_NOT_OPENED_FOR_ERASE", "LC_FT_DEVICE_NOT_OPENED_FOR_WRITE", "LC_FT_FAILED_TO_WRITE_DEVICE", "LC_FT_EEPROM_READ_FAILED", "LC_FT_EEPROM_WRITE_FAILED", "LC_FT_EEPROM_ERASE_FAILED", "LC_FT_EEPROM_NOT_PRESENT", "LC_FT_EEPROM_NOT_PROGRAMMED", "LC_FT_INVALID_ARGS", "LC_FT_NOT_SUPPORTED", "LC_FT_OTHER_ERROR", "LC_FT_DEVICE_LIST_NOT_READY", };
これにより、次の実装機能が実現します。
LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"
列挙型と文字列が同期していることを確認するためにプリプロセッサに依存する必要はありません。私にとって、マクロを使用すると、コードが読みにくくなる傾向があります。
列挙型と文字列の配列の使用
enum fruit
{
APPLE = 0,
ORANGE,
GRAPE,
BANANA,
/* etc. */
FRUIT_MAX
};
const char * const fruit_str[] =
{
[BANANA] = "banana",
[ORANGE] = "orange",
[GRAPE] = "grape",
[APPLE] = "apple",
/* etc. */
};
注:fruit_str
配列内の文字列は、列挙型アイテムと同じ順序で宣言する必要はありません。
それの使い方
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
コンパイル時チェックの追加
1つの文字列を忘れるのが怖い場合は、次のチェックを追加できます。
#define ASSERT_ENUM_TO_STR(sarray, max) \
typedef char assert_sizeof_##max[(sizeof(sarray)/sizeof(sarray[0]) == (max)) ? 1 : -1]
ASSERT_ENUM_TO_STR(fruit_str, FRUIT_MAX);
列挙型アイテムの量が配列内の文字列の量と一致しない場合、コンパイル時にエラーが報告されます。
これを直接達成する簡単な方法はありません。ただし、P99には、このようなタイプの関数を自動的に作成できるマクロがあります。
P99_DECLARE_ENUM(color, red, green, blue);
ヘッダーファイル内、および
P99_DEFINE_ENUM(color);
1つのコンパイルユニット(.cファイル)でトリックを実行する必要があります。その例では、関数が呼び出されcolor_getname
ます。
文字列配列をインスタンス化するための指定子の使用に基づく、Hokyoの「非順次列挙型」の回答のより簡単な代替手段:
#define NAMES C(RED, 10)C(GREEN, 20)C(BLUE, 30)
#define C(k, v) k = v,
enum color { NAMES };
#undef C
#define C(k, v) [v] = #k,
const char * const color_name[] = { NAMES };
列挙型を検証せずにそのような関数を実行するのは、ささいな危険です。switchステートメントを使用することをお勧めします。もう1つの利点は、値が1、2、4、8、16などであるフラグなど、値が定義されている列挙型に使用できることです。
また、すべての列挙型文字列を1つの配列にまとめます。-
static const char * allEnums[] = {
"Undefined",
"apple",
"orange"
/* etc */
};
ヘッダーファイルでインデックスを定義します:-
#define ID_undefined 0
#define ID_fruit_apple 1
#define ID_fruit_orange 2
/* etc */
これを行うと、たとえば、プログラムの国際バージョンを他の言語で作成する場合など、さまざまなバージョンを簡単に作成できます。
ヘッダーファイルでもマクロを使用する:-
#define CASE(type,val) case val: index = ID_##type##_##val; break;
switchステートメントを使用して関数を作成しconst char *
ます。文字列staticconsts:-であるため、これはaを返す必要があります。
const char * FruitString(enum fruit e){
unsigned int index;
switch(e){
CASE(fruit, apple)
CASE(fruit, orange)
CASE(fruit, banana)
/* etc */
default: index = ID_undefined;
}
return allEnums[index];
}
Windowsでプログラミングする場合、ID_値はリソース値にすることができます。
(C ++を使用している場合は、すべての関数に同じ名前を付けることができます。
string EnumToString(fruit e);
)。
私は通常これを行います:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))
列挙型をコピーし、Vimで正規表現を使用して、本体が更新される関数を作成することにしました。列挙型がコンパクトではないため、スイッチケースを使用します。これにより、最大限の柔軟性が得られます。正規表現をコード内のコメントとして保持しているので、コピーして貼り付けるだけです。
私の列挙型(短く、実際の列挙型ははるかに大きい):
enum opcode
{
op_1word_ops = 1024,
op_end,
op_2word_ops = 2048,
op_ret_v,
op_jmp,
op_3word_ops = 3072,
op_load_v,
op_load_i,
op_5word_ops = 5120,
op_func2_vvv,
};
列挙型をコピーする前の関数:
const char *get_op_name(enum opcode op)
{
// To update copy the enum and apply this regex:
// s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
switch (op)
{
}
return "Unknown op";
}
列挙型の内容をスイッチブラケット内に貼り付けます。
const char *get_op_name(enum opcode op)
{
// To update copy the enum and apply this regex:
// s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
switch (op)
{
op_1word_ops = 1024,
op_end,
op_2word_ops = 2048,
op_ret_v,
op_jmp,
op_3word_ops = 3072,
op_load_v,
op_load_i,
op_5word_ops = 5120,
op_func2_vvv,
}
return "Unknown op";
}
次に、VimでShift-Vを使用して行を選択し、を押し:
てから正規表現を貼り付け(WindowsではCtrl-V)、s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
Enterキーを押します。
const char *get_op_name(enum opcode op)
{
// To update copy the enum and apply this regex:
// s/\t\([^, ]*\).*$/\t\tcase \1: \treturn "\1";
switch (op)
{
case op_1word_ops: return "op_1word_ops";
case op_end: return "op_end";
case op_2word_ops: return "op_2word_ops";
case op_ret_v: return "op_ret_v";
case op_jmp: return "op_jmp";
case op_3word_ops: return "op_3word_ops";
case op_load_v: return "op_load_v";
case op_load_i: return "op_load_i";
case op_5word_ops: return "op_5word_ops";
case op_func2_vvv: return "op_func2_vvv";
}
return "Unknown op";
}
正規表現は最初の文字をスキップし、\t
その後に続くすべての文字を入れて、行の残りの部分と一致させてすべてを削除します。次に、列挙型ラベルであるため、フォーマットの行を再作成します。StackOverflowが4スペースの集計を使用しているのに対し、Vimでは8スペースの集計を使用しているため、この投稿では位置合わせが不十分に見えることに注意してください。スタイルの正規表現を編集することをお勧めします。,
\1
\1
case <label>: return "<label>";