Einfache Möglichkeit, Variablen von Enum-Typen als Zeichenfolge in C zu verwenden?

Lesezeit: 9 Minuten

Benutzeravatar von zxcv
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?

Benutzeravatar von Suma
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 */ \
  } \

Fabrik verwendet

someEnum.h:

#include "enumFactory.h"
#define SOME_ENUM(XX) \
    XX(FirstValue,) \
    XX(SecondValue,) \
    XX(SomeOtherValue,=50) \
    XX(OneMoreValue,=100) \

DECLARE_ENUM(SomeEnum,SOME_ENUM)

someEnum.cpp:

#include "someEnum.h"
DEFINE_ENUM(SomeEnum,SOME_ENUM)

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

Benutzeravatar von sk
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.

Benutzeravatar von plinth
Sockel

Ich weiß, dass Sie ein paar gute, solide Antworten haben, aber kennen Sie den #-Operator im C-Präprozessor?

Damit können Sie Folgendes tun:

#define MACROSTR(k) #k

typedef enum {
    kZero,
    kOne,
    kTwo,
    kThree
} kConst;

static char *kConstStr[] = {
    MACROSTR(kZero),
    MACROSTR(kOne),
    MACROSTR(kTwo),
    MACROSTR(kThree)
};

static void kConstPrinter(kConst k)
{
    printf("%s", kConstStr[k]);
}

  • char const *kConstStr[]

    – Anne van Rossum

    15. August 2018 um 10:04 Uhr

Benutzeravatar von paxdiablo
paxdiablo

C oder C++ bieten diese Funktionalität nicht, obwohl ich sie oft benötigt habe.

Der folgende Code funktioniert, obwohl er am besten für Nicht-Sparse-Aufzählungen geeignet ist.

typedef enum { ONE, TWO, THREE } Numbers;
char *strNumbers[] = {"one","two","three"};
printf ("Value for TWO is %s\n",strNumbers[TWO]);

Mit nicht spärlich meine ich nicht von der Form

typedef enum { ONE, FOUR_THOUSAND = 4000 } Numbers;

denn das hat riesige Lücken drin.

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

1420540cookie-checkEinfache Möglichkeit, Variablen von Enum-Typen als Zeichenfolge in C zu verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy