web-dev-qa-db-ja.com

Cで列挙名を文字列に変換する方法

Cで列挙子名を文字列に変換する可能性はありますか?

78
Misha

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をAppleに展開するのではなく、文字列化するためです。 xstrを使用することにより、最初にマクロ展開が行われ、次にその結果が文字列化されます。

詳細については、 Stringification を参照してください。

159
Terrence M

これがある状況では:

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];
}
22

これを直接達成する簡単な方法はありません。ただし、 P99 には、このようなタイプの関数を自動的に作成できるマクロがあります。

 P99_DECLARE_ENUM(color, red, green, blue);

ヘッダーファイル内

 P99_DEFINE_ENUM(color);

1つのコンパイルユニット(.cファイル)でトリックを行う必要があります。その例では、関数はcolor_getnameと呼ばれます。

15
Jens Gustedt

同じ仕事をしているCプリプロセッサのトリックを見つけましたwithout専用の配列文字列を宣言しています(ソース: 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"

9
Maschina

列挙型を検証せずにそのような関数はささいな危険です。 switchステートメントを使用することをお勧めします。別の利点は、値が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であるため、const char *を返す必要があります。

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);

2
QuentinUK

文字列配列をインスタンス化するために指定子を使用することに基づいた、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 };
1
Lars