Enum adlarını c dizgesine dönüştürme


92

C'deki numaralandırıcı adlarını dizeye dönüştürme olasılığı var mı?

Yanıtlar:


188

Bir yol, önişlemcinin işi yapmasını sağlamak. Ayrıca numaralandırmalarınızın ve dizelerinizin senkronize olmasını sağlar.

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

Önişlemci tamamlandıktan sonra şunlara sahip olacaksınız:

enum FRUIT_ENUM {
    apple, orange, grape, banana,
};

static const char *FRUIT_STRING[] = {
    "apple", "orange", "grape", "banana",
};

O zaman şöyle bir şey yapabilirsiniz:

printf("enum apple as a string: %s\n",FRUIT_STRING[apple]);

Kullanım durumu tam anlamıyla yalnızca enum adını yazdırıyorsa, aşağıdaki makroları ekleyin:

#define str(x) #x
#define xstr(x) str(x)

O zaman yap:

printf("enum apple as a string: %s\n", xstr(apple));

Bu durumda, iki seviyeli makro gereksiz gibi görünebilir, ancak, C'deki sıkılaştırmanın nasıl çalıştığına bağlı olarak, bazı durumlarda gereklidir. Örneğin, bir #define ile bir enum kullanmak istediğimizi varsayalım:

#define foo apple

int main() {
    printf("%s\n", str(foo));
    printf("%s\n", xstr(foo));
}

Çıktı şu şekilde olacaktır:

foo
apple

Bunun nedeni, str'nin foo girdisini apple olacak şekilde genişletmek yerine dizgiselleştirmesidir. Xstr kullanılarak önce makro genişletme yapılır, sonra bu sonuç dizgeleştirilir.

Daha fazla bilgi için Kısaltmaya bakın .


1
Bu mükemmel, ama gerçekte ne olduğunu anlayamıyorum. : O
p0lAris

Ayrıca yukarıdaki durumda bir dizgeyi enum'a nasıl dönüştürürüm?
p0lAris

Neyi başarmaya çalıştığınıza bağlı olarak bunu yapmanın birkaç yolu var mı?
Terrence, M

5
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
Ad

1
Bu gönderiye rastlayanlar için, bir programdaki çeşitli öğeleri numaralandırmak için bir makro listesi kullanma yöntemi gayri resmi olarak "X makroları" olarak adlandırılır.
Lundin

27

Buna sahip olduğunuz bir durumda:

enum fruit {
    apple, 
    orange, 
    grape,
    banana,
    // etc.
};

Bunu numaralamanın tanımlandığı başlık dosyasına koymayı seviyorum:

static inline char *stringFromFruit(enum fruit f)
{
    static const char *strings[] = { "apple", "orange", "grape", "banana", /* continue for rest of values */ };

    return strings[f];
}

4
Hayatım boyunca bunun nasıl yardımcı olacağını göremiyorum. Daha açık hale getirmek için biraz genişler misiniz?
David Heffernan

2
Tamam, bu nasıl yardımcı olur? Bunu yazmak daha kolay olduğunu söylüyorsunuz enumToString(apple)tipine göre "apple"? Hiçbir yerde herhangi bir tip güvenliği yok. Burada önerdiğiniz bir şeyi kaçırmadıysam, anlamsızdır ve sadece kodu şaşırtmayı başarır.
David Heffernan

2
Tamam, şimdi anladım. Makro benim görüşüme göre sahte ve onu silmenizi öneririm.
David Heffernan

2
yorumlar makro hakkında konuşur. Nerede?
mk ..

2
Bunun sürdürülmesi de sakıncalıdır. Yeni bir enum eklersem, bunu da dizide, doğru konumda çoğaltmam gerektiğini hatırlamam gerekir.
Fabio

14

Bunu doğrudan başarmanın basit bir yolu yok. Ancak P99 , bu tür bir işlevi otomatik olarak oluşturmanıza izin veren makrolara sahiptir:

 P99_DECLARE_ENUM(color, red, green, blue);

bir başlık dosyasında ve

 P99_DEFINE_ENUM(color);

bir derleme biriminde (.c dosyası) hile yapmalı, bu örnekte işlev çağrılacaktır color_getname.


Bu kitaplığı nasıl içeri çekebilirim?
JohnyTex

14

Ben aynı işi yapan bir C önişlemci hile buldum olmadan (: Kaynak adanmış dizi dize ilan http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).

Sıralı numaralandırmalar

Stefan Ram'ın icadının ardından, sıralı numaralandırmalar (örneğin, indeksi açıkça belirtmeden enum {foo=-1, foo1 = 1}) şu dahi hüner gibi gerçekleştirilebilir:

#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 };

Bu, aşağıdaki sonucu verir:

int main( void )  { 
    printf( "The color is %s.\n", color_name[ RED ]);  
    printf( "There are %d colors.\n", TOP ); 
}

Renk KIRMIZI.
3 renk var.

Sıralı olmayan numaralandırmalar

Hata kodu tanımlarını dizi dizesi olarak eşlemek istediğimden, ham hata tanımını hata koduna ekleyebiliyorum (örneğin "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), ilgili enum değerleri için gerekli dizini kolayca belirleyebileceğiniz şekilde kodu genişlettim :

#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];

Bu örnekte, C ön işlemcisi aşağıdaki kodu oluşturacaktır :

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", };

Bu, aşağıdaki uygulama yetenekleriyle sonuçlanır:

LC_errors__strings [-1] ==> LC_errors__strings [LC_RESPONSE_GENERIC_ERROR] ==> "LC_RESPONSE_GENERIC_ERROR"


Güzel. Bu tam olarak aradığım ve kullandığım şeydi. Aynı hatalar :)
mrbean

6

Numaralandırmalarınızın ve dizelerinizin senkronize olduğundan emin olmak için ön işlemciye güvenmenize gerek yoktur. Bana göre makroları kullanmak kodu okumayı zorlaştırıyor.

Enum ve Dizelerden Oluşan Bir Dizi Kullanma

enum fruit                                                                   
{
    APPLE = 0, 
    ORANGE, 
    GRAPE,
    BANANA,
    /* etc. */
    FRUIT_MAX                                                                                                                
};   

const char * const fruit_str[] =
{
    [BANANA] = "banana",
    [ORANGE] = "orange",
    [GRAPE]  = "grape",
    [APPLE]  = "apple",
    /* etc. */  
};

Not: Dizideki dizelerin fruit_strenum öğeleriyle aynı sırada bildirilmesi gerekmez.

Bu nasıl kullanılır

printf("enum apple as a string: %s\n", fruit_str[APPLE]);

Derleme Süresi Kontrolü Ekleme

Bir dizeyi unutmaktan korkuyorsanız, aşağıdaki kontrolü ekleyebilirsiniz:

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

Enum öğelerinin miktarı dizideki dizelerin miktarıyla eşleşmezse, derleme sırasında bir hata bildirilir.


2

Numaralamayı doğrulamadan böyle bir işlev önemsiz bir tehlikedir. Bir switch deyimi kullanmanızı öneririm. Diğer bir avantaj, bunun, tanımlanmış değerlere sahip numaralandırmalar için kullanılabilmesidir, örneğin değerlerin 1,2,4,8,16 vb. Olduğu bayraklar için.

Ayrıca tüm sıralama dizelerinizi tek bir dizide bir araya getirin: -

static const char * allEnums[] = {
    "Undefined",
    "apple",
    "orange"
    /* etc */
};

indisleri bir başlık dosyasında tanımlayın: -

#define ID_undefined       0
#define ID_fruit_apple     1
#define ID_fruit_orange    2
/* etc */

Bunu yapmak, örneğin programınızın uluslararası sürümlerini diğer dillerle yapmak istiyorsanız, farklı sürümler üretmeyi kolaylaştırır.

Başlık dosyasında da bir makro kullanarak: -

#define CASE(type,val) case val: index = ID_##type##_##val; break;

Switch deyimiyle bir işlev yapın, bu bir döndürmelidir const char *çünkü dizeler statik sabitler: -

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 ile programlama yapılıyorsa, ID_ değerleri kaynak değerleri olabilir.

(C ++ kullanılıyorsa, tüm işlevler aynı ada sahip olabilir.

string EnumToString(fruit e);

)


2

Dize dizisini başlatmak için tasarımcıları kullanmaya dayalı Hokyo'nun "Sıralı Olmayan numaralandırmalar" yanıtına daha basit bir alternatif:

#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 };

-2

Genellikle bunu yaparım:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   

Bu kötü bir cevap değil. Anlaşılır, basit ve anlaşılması kolaydır. Başkalarının kodunuzu hızlı bir şekilde okuması ve anlaması gereken sistemler üzerinde çalışıyorsanız, netlik çok önemlidir. Bir kodlama standardında ayrıntılı olarak yorumlanmadıkça veya tanımlanmadıkça, önişlemci hilelerini kullanmanızı tavsiye etmem.
nielsen
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.