Einfache Möglichkeit, Variablen von Enum-Typen als Zeichenfolge in C zu verwenden?
Lesezeit: 9 Minuten
zxcv
Folgendes versuche ich zu tun:
typedef enum { ONE, TWO, THREE } Numbers;
Ich versuche, eine Funktion zu schreiben, die einen Switch-Fall ähnlich dem folgenden ausführen würde:
char num_str[10];
int process_numbers_str(Numbers num) {
switch(num) {
case ONE:
case TWO:
case THREE:
{
strcpy(num_str, num); //some way to get the symbolic constant name in here?
} break;
default:
return 0; //no match
return 1;
}
Anstatt in jedem Fall zu definieren, gibt es eine Möglichkeit, es mit der Enum-Variablen festzulegen, wie ich es oben versuche?
Suma
Die Technik von Etwas sowohl zu einem C-Bezeichner als auch zu einem String machen? können hier verwendet werden.
Wie bei solchen Präprozessor-Sachen üblich, kann das Schreiben und Verstehen des Präprozessor-Teils schwierig sein und beinhaltet das Übergeben von Makros an andere Makros und die Verwendung von #- und ##-Operatoren, aber die Verwendung ist wirklich einfach. Ich finde diesen Stil sehr nützlich für lange Aufzählungen, bei denen es sehr mühsam sein kann, dieselbe Liste zweimal zu pflegen.
Werkscode – nur einmal eingegeben, normalerweise in der Kopfzeile versteckt:
enumFactory.h:
// expansion macro for enum value definition
#define ENUM_VALUE(name,assign) name assign,
// expansion macro for enum to string conversion
#define ENUM_CASE(name,assign) case name: return #name;
// expansion macro for string to enum conversion
#define ENUM_STRCMP(name,assign) if (!strcmp(str,#name)) return name;
/// declare the access function and define enum values
#define DECLARE_ENUM(EnumType,ENUM_DEF) \
enum EnumType { \
ENUM_DEF(ENUM_VALUE) \
}; \
const char *GetString(EnumType dummy); \
EnumType Get##EnumType##Value(const char *string); \
/// define the access function names
#define DEFINE_ENUM(EnumType,ENUM_DEF) \
const char *GetString(EnumType value) \
{ \
switch(value) \
{ \
ENUM_DEF(ENUM_CASE) \
default: return ""; /* handle input error */ \
} \
} \
EnumType Get##EnumType##Value(const char *str) \
{ \
ENUM_DEF(ENUM_STRCMP) \
return (EnumType)0; /* handle input error */ \
} \
Die Technik kann leicht erweitert werden, sodass XX-Makros mehr Argumente akzeptieren, und Sie können auch mehr Makros vorbereiten, um XX für unterschiedliche Anforderungen zu ersetzen, ähnlich den drei, die ich in diesem Beispiel bereitgestellt habe.
Vergleich zu X-Makros mit #include / #define / #undef
Während dies X-Makros ähnlich ist, die andere erwähnt haben, denke ich, dass diese Lösung eleganter ist, da sie kein #undefing erfordert, wodurch Sie mehr von dem komplizierten Zeug verbergen können, das sich in der Header-Datei befindet – die Header-Datei ist etwas, das Sie überhaupt nicht berühren, wenn Sie eine neue Aufzählung definieren müssen, daher ist die neue Aufzählungsdefinition viel kürzer und sauberer.
Ich bin mir nicht sicher, wie Sie sagen können, dass dies besser/schlechter als X-Makros ist – das ist x-Makros. Das SOME_ENUM(XX) ist genau ein X-Makro (um genau zu sein, das “Benutzerformular”, das die XX Funktion statt Nutzung #def#undef) und dann wird wiederum das ganze X-MACRO an DEFINE_ENUM übergeben, das es verwendet. Um nichts von der Lösung wegzunehmen – es funktioniert gut. Nur um klarzustellen, dass es sich um eine Verwendung von X-Makros handelt.
– BeeOnRope
14. Februar 2017 um 19:49 Uhr
@BeeOnRope Der von Ihnen festgestellte Unterschied ist erheblich und unterscheidet diese Lösung von idiomatisch X-Makros (z Beispiele von Wikipedia). Der Vorteil des Passierens XX über re-#defineDer Unterschied besteht darin, dass das erstere Muster in Makroerweiterungen verwendet werden kann. Beachten Sie, dass die einzigen anderen Lösungen, die so prägnant sind wie diese, alle die Erstellung und mehrfache Einbindung einer separaten Datei erfordern, um eine neue Aufzählung zu definieren.
– pmttavara
29. Dezember 2017 um 3:48 Uhr
Ein weiterer Trick besteht darin, den Aufzählungsnamen zu verwenden wie den Makronamen. Du kannst einfach schreiben #define DEFINE_ENUM(EnumType) ...ersetzen ENUM_DEF(...) mit EnumType(...)und lassen Sie den Benutzer sagen #define SomeEnum(XX) .... Der C-Präprozessor wird kontextabhängig erweitert SomeEnum in den Makroaufruf, wenn Klammern folgen, und andernfalls in ein reguläres Token. (Das führt natürlich zu Problemen, wenn der Benutzer gerne verwendet SomeEnum(2) um in den Enum-Typ umzuwandeln, anstatt (SomeEnum)2 oder static_cast<SomeEnum>(2).)
– pmttavara
29. Dezember 2017 um 3:57 Uhr
@pmttavara – sicher, wenn eine schnelle Suche ein Hinweis darauf ist, dass die häufigste Verwendung von x-Makros einen festen inneren Makronamen zusammen mit verwendet #define und #undef. Sind Sie jedoch nicht damit einverstanden, dass das “Benutzerformular” (empfohlen für z Dieser Artikel) ist eine Art x-Makro? Ich habe es sicherlich auch immer ein X-Makro genannt und in den C-Codebasen, in denen ich in letzter Zeit war, ist es die häufigste Form (das ist offensichtlich eine voreingenommene Beobachtung). Möglicherweise habe ich das OP jedoch falsch analysiert.
– BeeOnRope
29. Dezember 2017 um 19:27 Uhr
@BeeOnRope Der aktuelle Wortlaut ist ein Ergebnis der Bearbeitung, da Sie mich damals davon überzeugt haben, dass dies ein X-Makro ist, auch wenn es damals vielleicht eine weniger verwendete Form war (oder zumindest eine weniger in Artikeln erwähnte).
– Suma
29. Dezember 2017 um 20:19 Uhr
// Define your enumeration like this (in say numbers.h);
ENUM_BEGIN( Numbers )
ENUM(ONE),
ENUM(TWO),
ENUM(FOUR)
ENUM_END( Numbers )
// The macros are defined in a more fundamental .h file (say defs.h);
#define ENUM_BEGIN(typ) enum typ {
#define ENUM(nam) nam
#define ENUM_END(typ) };
// Now in one and only one .c file, redefine the ENUM macros and reinclude
// the numbers.h file to build a string table
#undef ENUM_BEGIN
#undef ENUM
#undef ENUM_END
#define ENUM_BEGIN(typ) const char * typ ## _name_table [] = {
#define ENUM(nam) #nam
#define ENUM_END(typ) };
#undef NUMBERS_H_INCLUDED // whatever you need to do to enable reinclusion
#include "numbers.h"
// Now you can do exactly what you want to do, with no retyping, and for any
// number of enumerated types defined with the ENUM macro family
// Your code follows;
char num_str[10];
int process_numbers_str(Numbers num) {
switch(num) {
case ONE:
case TWO:
case THREE:
{
strcpy(num_str, Numbers_name_table[num]); // eg TWO -> "TWO"
} break;
default:
return 0; //no match
return 1;
}
// Sweet no ? After being frustrated by this for years, I finally came up
// with this solution for my most recent project and plan to reuse the idea
// forever
Dafür wurde cpp gemacht. +1.
– Derrick Turk
12. Mai 2010 um 21:25 Uhr
Dies ist eine gute Antwort, es scheint das Beste zu sein, was man ohne Spezialwerkzeuge tun kann, und ich habe so etwas schon einmal gemacht. aber es fühlt sich immer noch nie wirklich “richtig” an und ich mache es nie wirklich gerne …
– Michael Burr
13. Mai 2010 um 22:41 Uhr
Kleine Veränderung: #define ENUM_END(typ) }; extern const char * typ ## _name_table[]; in defs.h file – Dies deklariert Ihre Namenstabelle in den Dateien, die Sie verwenden. (Kann aber keine gute Möglichkeit finden, die Tabellengröße zu deklarieren.) Außerdem würde ich persönlich das abschließende Semikolon weglassen, aber die Vorzüge sind so oder so umstritten.
– Chris Lutz
23. Dezember 2010 um 8:31 Uhr
@ Bill, warum sich die Mühe machen typ in der Schlange #define ENUM_END(typ) };?
– Schrittmacher
15. Mai 2015 um 23:46 Uhr
Dies funktioniert nicht, wenn ich möchte, dass mein Makro als “EINS = 5” definiert wird.
– UKMonkey
13. September 2017 um 13:03 Uhr
sk.
Es gibt keine integrierte Lösung. Der einfachste Weg ist mit einem Array von char* wobei der int-Wert der Aufzählung auf eine Zeichenfolge verweist, die den beschreibenden Namen dieser Aufzählung enthält. Wenn Sie eine spärliche haben enum (einer, der nicht bei 0 beginnt oder Lücken in der Nummerierung hat), wo einige der int Mappings hoch genug sind, um ein Array-basiertes Mapping unpraktisch zu machen, dann könnten Sie stattdessen eine Hash-Tabelle verwenden.
Wenn es sich tatsächlich um eine linear inkrementierende Liste handelt, können Sie einfach das Makro-Tool Ihres Editors verwenden, um jeden der Namen aufzuzeichnen und in eine Zeichenfolge aufzulösen. Es ist nur wenig zusätzliches Tippen erforderlich, und Sie ersparen sich von vornherein die Notwendigkeit von Definitionen. Ich klicke beim letzten der kopierten Makros auf „Aufzeichnen“, füge danach ein Zitat hinzu und fahre an derselben Stelle in der nächsten Zeile fort. Ich drücke Stopp. Ich drücke X-mal ausführen und mache so viele es gibt (oder nur einen einzigen Schritt). Ich kann es dann in ein String-Array packen.
– Benutzer2262111
1. Mai 2019 um 18:54 Uhr
Es gibt definitiv einen Weg, dies zu tun – verwenden X()-Makros. Diese Makros verwenden den C-Präprozessor, um Aufzählungen, Arrays und Codeblöcke aus einer Liste von Quelldaten zu erstellen. Sie müssen nur neue Elemente zu #define hinzufügen, die das X()-Makro enthalten. Die switch-Anweisung würde automatisch expandieren.
Ihr Beispiel kann wie folgt geschrieben werden:
// Source data -- Enum, String
#define X_NUMBERS \
X(ONE, "one") \
X(TWO, "two") \
X(THREE, "three")
...
// Use preprocessor to create the Enum
typedef enum {
#define X(Enum, String) Enum,
X_NUMBERS
#undef X
} Numbers;
...
// Use Preprocessor to expand data into switch statement cases
switch(num)
{
#define X(Enum, String) \
case Enum: strcpy(num_str, String); break;
X_NUMBERS
#undef X
default: return 0; break;
}
return 1;
Es gibt effizientere Möglichkeiten (dh die Verwendung von X-Makros zum Erstellen eines String-Arrays und eines Enum-Index), aber dies ist die einfachste Demo.
Sockel
Ich weiß, dass Sie ein paar gute, solide Antworten haben, aber kennen Sie den #-Operator im C-Präprozessor?
Der Vorteil dieser Methode besteht darin, dass die Definitionen der Aufzählungen und Zeichenfolgen nahe beieinander liegen. eine switch-Anweisung in einer Funktion zu haben, trennt sie. Dies bedeutet, dass Sie das eine ohne das andere weniger wahrscheinlich ändern werden.
char const *kConstStr[]
– Anne van Rossum
15. August 2018 um 10:04 Uhr
KUSS. Sie werden mit Ihren Enums alle möglichen anderen Switch/Case-Dinge machen, also warum sollte das Drucken anders sein? Das Vergessen eines Falls in Ihrer Druckroutine ist keine große Sache, wenn Sie bedenken, dass es ungefähr 100 andere Orte gibt, an denen Sie einen Fall vergessen können. Kompilieren Sie einfach -Wall, das vor nicht erschöpfenden Fallübereinstimmungen warnt. Verwenden Sie nicht “Standard”, da dies den Wechsel erschöpft und Sie keine Warnungen erhalten. Lassen Sie stattdessen den Schalter beenden und behandeln Sie den Standardfall wie folgt …
const char *myenum_str(myenum e)
{
switch(e) {
case ONE: return "one";
case TWO: return "two";
}
return "invalid";
}
14205400cookie-checkEinfache Möglichkeit, Variablen von Enum-Typen als Zeichenfolge in C zu verwenden?yes