So konvertieren Sie Enum-Namen in Zeichenfolgen in c

92

Gibt es eine Möglichkeit, Enumeratornamen in C in Zeichenfolgen umzuwandeln?

Mischa
quelle

Antworten:

184

Eine Möglichkeit, den Präprozessor die Arbeit machen zu lassen. Außerdem wird sichergestellt, dass Ihre Aufzählungen und Zeichenfolgen synchron sind.

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

Nachdem der Präprozessor fertig ist, haben Sie:

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

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

Dann könnten Sie so etwas tun wie:

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

Wenn der Anwendungsfall buchstäblich nur den Namen der Aufzählung druckt, fügen Sie die folgenden Makros hinzu:

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

Dann mach:

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

In diesem Fall scheint das zweistufige Makro überflüssig zu sein. Aufgrund der Funktionsweise der Stringifizierung in C ist dies jedoch in einigen Fällen erforderlich. Angenommen, wir möchten ein #define mit einer Aufzählung verwenden:

#define foo apple

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

Die Ausgabe wäre:

foo
apple

Dies liegt daran, dass str die Eingabe foo stringifiziert, anstatt sie zu apple zu erweitern. Mit xstr wird zuerst die Makroerweiterung durchgeführt, dann wird das Ergebnis stringifiziert.

Weitere Informationen finden Sie unter Stringifizierung .

Terrence M.
quelle
1
Das ist perfekt, aber ich kann nicht verstehen, was wirklich passiert. : O
p0lAris
Wie konvertiert man im obigen Fall eine Zeichenfolge in eine Aufzählung?
p0lAris
Es gibt verschiedene Möglichkeiten, je nachdem, was Sie erreichen möchten.
Terrence M
5
Wenn Sie den Namespace nicht mit Apfel und Orange verschmutzen möchten, können Sie ihm#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
jsaak
1
Für diejenigen, die auf diesen Beitrag stoßen, wird diese Methode zur Verwendung einer Makroliste zum Auflisten verschiedener Elemente in einem Programm informell als "X-Makros" bezeichnet.
Lundin
27

In einer Situation, in der Sie dies haben:

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

Ich möchte dies in die Header-Datei einfügen, in der die Aufzählung definiert ist:

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

    return strings[f];
}
Richard J. Ross III
quelle
4
Für mein Leben kann ich nicht sehen, wie das hilft. Könnten Sie etwas erweitern, um es offensichtlicher zu machen?
David Heffernan
2
OK, wie hilft das? Wollen Sie damit sagen, dass es einfacher ist zu tippen enumToString(apple)als zu tippen "apple"? Es ist nicht so, dass es irgendwo irgendeine Art von Sicherheit gibt. Es sei denn, mir fehlt etwas, was Sie hier vorschlagen, ist sinnlos und es gelingt nur, Code zu verschleiern.
David Heffernan
2
OK, ich verstehe jetzt. Das Makro ist meiner Ansicht nach falsch und ich schlage vor, dass Sie es löschen.
David Heffernan
2
Kommentare sprechen über Makro. Wo ist es?
mk ..
2
Dies ist auch unpraktisch zu warten. Wenn ich eine neue Enumeration einfüge, muss ich mich daran erinnern, diese auch im Array an der richtigen Position zu duplizieren.
Fabio
14

Es gibt keinen einfachen Weg, dies direkt zu erreichen. Aber P99 hat Makros , die es Ihnen ermöglichen , diese Art von Funktion zu erstellen automatisch:

 P99_DECLARE_ENUM(color, red, green, blue);

in einer Header-Datei und

 P99_DEFINE_ENUM(color);

In einer Kompilierungseinheit (.c-Datei) sollte dann der Trick ausgeführt werden, in diesem Beispiel würde dann die Funktion aufgerufen color_getname.

Jens Gustedt
quelle
Wie ziehe ich diese Bibliothek ein?
JohnyTex
14

Ich habe einen C-Präprozessor-Trick gefunden, der denselben Job ausführt, ohne eine dedizierte Array-Zeichenfolge zu deklarieren (Quelle: http://userpage.fu-berlin.de/~ram/pub/pub_jf47ht81Ht/c_preprocessor_applications_en ).

Sequentielle Aufzählungen

Nach der Erfindung von Stefan Ram können sequentielle Aufzählungen (ohne explizite Angabe des Index, z. B. enum {foo=-1, foo1 = 1}) wie dieser geniale Trick realisiert werden:

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

Dies ergibt das folgende Ergebnis:

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

Die Farbe ist ROT.
Es gibt 3 Farben.

Nicht sequentielle Aufzählungen

Da ich Fehlercode-Definitionen als Array-Zeichenfolge zuordnen wollte, damit ich die rohe Fehlerdefinition an den Fehlercode anhängen kann (z. B. "The error is 3 (LC_FT_DEVICE_NOT_OPENED)."), habe ich den Code so erweitert, dass Sie den erforderlichen Index für die jeweiligen Aufzählungswerte leicht ermitteln können ::

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

In diesem Beispiel generiert der C-Präprozessor den folgenden Code :

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

Dies führt zu den folgenden Implementierungsfunktionen:

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

Maschina
quelle
Nett. Genau dafür habe ich gesucht und es benutzt. Gleiche Fehler :)
mrbean
5

Sie müssen sich nicht auf den Präprozessor verlassen, um sicherzustellen, dass Ihre Aufzählungen und Zeichenfolgen synchron sind. Für mich ist die Verwendung von Makros schwieriger zu lesen.

Verwenden von Enum und einer Reihe von Zeichenfolgen

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

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

Hinweis: Die Zeichenfolgen im fruit_strArray müssen nicht in derselben Reihenfolge wie die Aufzählungselemente deklariert werden.

Wie man es benutzt

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

Hinzufügen einer Überprüfung der Kompilierungszeit

Wenn Sie Angst haben, eine Zeichenfolge zu vergessen, können Sie die folgende Prüfung hinzufügen:

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

Bei der Kompilierung wird ein Fehler gemeldet, wenn die Anzahl der Aufzählungselemente nicht mit der Anzahl der Zeichenfolgen im Array übereinstimmt.

Jyvet
quelle
2

Eine solche Funktion ohne Validierung der Aufzählung ist eine Kleinigkeit gefährlich. Ich schlage vor, eine switch-Anweisung zu verwenden. Ein weiterer Vorteil besteht darin, dass dies für Aufzählungen mit definierten Werten verwendet werden kann, z. B. für Flags mit den Werten 1,2,4,8,16 usw.

Fügen Sie außerdem alle Aufzählungszeichenfolgen in einem Array zusammen: -

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

Definieren Sie die Indizes in einer Header-Datei: -

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

Auf diese Weise können Sie leichter verschiedene Versionen erstellen, beispielsweise wenn Sie internationale Versionen Ihres Programms in anderen Sprachen erstellen möchten.

Verwenden eines Makros, auch in der Header-Datei: -

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

Machen Sie eine Funktion mit einer switch-Anweisung, dies sollte a zurückgeben, const char *da die Zeichenfolgen statische consts: -

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

Bei der Programmierung mit Windows können die ID_-Werte Ressourcenwerte sein.

(Wenn Sie C ++ verwenden, können alle Funktionen denselben Namen haben.

string EnumToString(fruit e);

)

QuentinUK
quelle
2

Eine einfachere Alternative zu Hokyos Antwort "Nicht-sequentielle Aufzählungen", basierend auf der Verwendung von Bezeichnern zum Instanziieren des Zeichenfolgenarrays:

#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 };
Lars
quelle
-2

Normalerweise mache ich das:

#define COLOR_STR(color)                            \
    (RED       == color ? "red"    :                \
     (BLUE     == color ? "blue"   :                \
      (GREEN   == color ? "green"  :                \
       (YELLOW == color ? "yellow" : "unknown"))))   
Gigilibala
quelle
1
Das ist einfach lächerlich
Massimo Callegari
Dies ist keine schlechte Antwort. Es ist klar, einfach und leicht zu verstehen. Wenn Sie an Systemen arbeiten, auf denen andere Personen Ihren Code schnell lesen und verstehen müssen, ist Klarheit sehr wichtig. Ich würde die Verwendung von Präprozessor-Tricks nur empfehlen, wenn sie in einem Codierungsstandard ausführlich kommentiert oder beschrieben sind.
Nielsen