Antworten:
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 .
#define GENERATE_ENUM(ENUM) PREFIX##ENUM,
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];
}
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.
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
.
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 ).
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.
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"
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.
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_str
Array müssen nicht in derselben Reihenfolge wie die Aufzählungselemente deklariert werden.
printf("enum apple as a string: %s\n", fruit_str[APPLE]);
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.
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);
)
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 };
Normalerweise mache ich das:
#define COLOR_STR(color) \
(RED == color ? "red" : \
(BLUE == color ? "blue" : \
(GREEN == color ? "green" : \
(YELLOW == color ? "yellow" : "unknown"))))