Drukuj tekst zamiast wartości z C enum

89
avi
źródło
Oczekiwany wynik ma wypisać ciąg „Niedziela” itp.?
GalacticCowboy

Odpowiedzi:

105

Wyliczenia w C to liczby, które mają wygodne nazwy w kodzie. Nie są ciągami znaków, a nazwy przypisane do nich w kodzie źródłowym nie są wkompilowane w Twój program, więc nie są dostępne w czasie wykonywania.

Jedynym sposobem uzyskania tego, co chcesz, jest samodzielne napisanie funkcji, która tłumaczy wartość wyliczenia na ciąg. Np. (Zakładając tutaj, że przenosisz deklarację enum Dayspoza main):

Alternatywnie możesz użyć tablicy jako mapy, np

Ale tutaj prawdopodobnie chciałbyś przypisać Sunday = 0w wyliczeniu, aby było bezpieczne ... Nie jestem pewien, czy standard C wymaga, aby kompilatory rozpoczynały wyliczenia od 0, chociaż większość tak robi (jestem pewien, że ktoś skomentuje, aby to potwierdzić lub zaprzeczyć ).

Tyler McHenry
źródło
3
Aw, pokonałeś mnie w rozwiązaniu macierzy. : P Ale tak, wyliczenia zawsze zaczynają się od 0, chyba że określisz inną wartość.
casablanca
1
Gdybym polegał na używaniu wyliczeń jako indeksów, w rzeczywistości wolałbym jawnie ponumerować każdy z nich. Zgodnie ze standardami niepotrzebne, ale z mojego doświadczenia wynika, że ​​jako grupa kompilatorzy nie byli najlepsi w przestrzeganiu standardów.
jdmichal
3
Standard C mówi: „Jeśli pierwszy moduł wyliczający nie ma =, wartość jego stałej wyliczenia wynosi 0”. Ale to nie zaszkodzi, jeśli jest to wyraźne.
Michael Burr,
17
Nie zapominaj, że z C99 możesz to zrobić const char* dayNames[] = {[Sunday] = "Sunday", [Monday] = "Monday", [Tuesday] = "Tuesday", /* ... etc ... */ };. Wiesz, na wypadek, gdyby dni tygodnia zostały zmienione lub zdecydujesz, że poniedziałek jest pierwszym dniem tygodnia.
Tim Schaeffer,
2
@ user3467349 That (stringification preprocessor) po prostu zamienia symbol następujący po # na ciąg. Więc tak, #Monday zamieniłby się w „Monday”, ale Days TheDay = Monday; printf("%s", #TheDay);wypisałby „TheDay”.
Tyler McHenry
29

Używam czegoś takiego:

w pliku „EnumToString.h”:

następnie w dowolnym pliku nagłówkowym tworzysz deklarację wyliczenia, dzień enum.h

następnie w pliku o nazwie EnumToString.c:

następnie w main.c:

spowoduje to automatyczne wygenerowanie ciągów dla wszystkich wyliczeń zadeklarowanych w ten sposób i uwzględnionych w „EnumToString.c”

Vargas
źródło
4
Czytanie jest brzydkie, ale nie ma duplikatów danych. (W przeciwieństwie do wszystkich innych.) Jestem rozdarty, czy to lubić.
Kim Reece,
1
+1 za niesamowicie kreatywne rozwiązanie bez duplikowania danych i prawdopodobnie najlepszą konserwacją / elastycznością, ale ta! Myślę, że nadal wolę po prostu wybrać trasę const char * [].
manifest
4
Tak, łatwość konserwacji jest niesamowita! Naprawdę łatwo jest zaktualizować, gdy zmieniają się dni tygodnia! </sarcasm> Nawiasem mówiąc, nie jest to nawet przydatne do celów lokalizacyjnych, ponieważ mapowanie między angielskimi napisami a nazwami w programie jest teraz zakodowane na stałe przez próbę uniknięcia powielania. Przynajmniej w przypadku innych podejść możliwe jest tłumaczenie ciągów bez zmiany każdego wystąpienia w plikach źródłowych.
R .. GitHub PRZESTAŃ POMÓC NA LODZIE
1
Prawdopodobnie możesz go umiędzynarodowić, zmieniając instrukcje powrotu na return _(#element)i tym podobne (za pomocą czegoś takiego jak gettext) .
Vargas,
Kiedy preprocesor C jest tak przydatny, ale tak brzydki, zwykle zastępuję go prostym generatorem kodu lub niestandardowym preprocesorem w języku skryptowym. I faktycznie, mam skrypt Pythona, którego używałem dokładnie w tym celu w wielu projektach. Ale nie używam go tak często w dzisiejszych czasach - w wielu przypadkach można uciec po prostu używając ciągów znaków i nie przejmując się wyliczeniami (a nawet bardziej w C ++ lub ObjC).
abarnert
7

Sposób, w jaki zwykle to robię, polega na przechowywaniu reprezentacji ciągów w oddzielnej tablicy w tej samej kolejności, a następnie indeksowaniu tablicy z wartością wyliczenia:

Casablanka
źródło
4

enumw C nie działają tak, jak tego oczekujesz. Możesz myśleć o nich jak o gloryfikowanych stałych (z kilkoma dodatkowymi korzyściami związanymi z byciem zbiorem takich stałych), a tekst, który napisałeś na „niedzielę”, jest naprawdę przekształcany na liczbę podczas kompilacji, tekst jest ostatecznie odrzucone.

W skrócie: aby zrobić to, czego naprawdę chcesz, musisz zachować tablicę ciągów lub utworzyć funkcję do odwzorowania wartości wyliczenia na tekst, który chcesz wydrukować.

Mark Elliot
źródło
4

Wyliczenia w C są w zasadzie cukrem syntaktycznym dla nazwanych list wartości całkowitych z automatyczną sekwencją. To znaczy, gdy masz ten kod:

Twój kompilator faktycznie wypluwa to:

Dlatego wyprowadzanie wyliczenia C jako ciągu nie jest operacją, która ma sens dla kompilatora. Jeśli chcesz mieć dla nich ciągi czytelne dla człowieka, musisz zdefiniować funkcje do konwersji z wyliczeń na ciągi.

jdmichal
źródło
4

Oto bardziej przejrzysty sposób na zrobienie tego za pomocą makr:

Nie jestem pewien, czy są to całkowicie przenośne b / w preprocesory, ale działa z gcc.

To jest c99 btw, więc użyj go, c99 strictjeśli podłączysz go do (kompilatora online) ideone .

Tim Schaeffer
źródło
Uwielbiam to, jak „czyste” są makra :-).
mk12
3

Wiem, że spóźniłem się na imprezę, ale co powiesz na to?

W ten sposób, nie trzeba ręcznie zachować enumi na char*tablicy w synchronizacji. Jeśli jesteś podobny do mnie, istnieje szansa, że ​​później zmienisz wartość enum, a char*tablica wydrukuje nieprawidłowe ciągi. Może to nie być funkcja powszechnie obsługiwana. Ale afaik, większość współczesnych kompilatorów dnia C obsługuje ten wyznaczony początkowy styl.

Możesz przeczytać więcej o wyznaczonych inicjatorach tutaj .

czterdzieści dwa
źródło
1

Pytanie brzmi, czy chcesz napisać nazwę tylko raz.
Mam taki ider:

#define __ENUM(situation,num) \
    int situation = num;        const char * __##situation##_name = #situation;

    const struct {
        __ENUM(get_other_string, -203);//using a __ENUM Mirco make it ease to write, 
        __ENUM(get_negative_to_unsigned, -204);
        __ENUM(overflow,-205);
//The following two line showing the expanding for __ENUM
        int get_no_num = -201;      const char * __get_no_num_name = "get_no_num";
        int get_float_to_int = -202;        const char * get_float_to_int_name = "float_to_int_name";

    }eRevJson;
#undef __ENUM
    struct sIntCharPtr { int value; const char * p_name; };
//This function transform it to string.
    inline const char * enumRevJsonGetString(int num) {
        sIntCharPtr * ptr = (sIntCharPtr *)(&eRevJson);
        for (int i = 0;i < sizeof(eRevJson) / sizeof(sIntCharPtr);i++) {
            if (ptr[i].value == num) {
                return ptr[i].p_name;
            }
        }
        return "bad_enum_value";
    }

używa struktury do wstawienia wyliczenia, tak aby drukarka do łańcucha mogła następować po każdej zdefiniowanej wartości wyliczenia.

int main(int argc, char *argv[]) {  
    int enum_test = eRevJson.get_other_string;
    printf("error is %s, number is %d\n", enumRevJsonGetString(enum_test), enum_test);

>error is get_other_string, number is -203

Różnica w stosunku do wyliczenia polega na tym, że program budujący nie może zgłosić błędu, jeśli liczby się powtarzają. jeśli nie lubisz pisać numeru, możesz __LINE__go zastąpić:

#define ____LINE__ __LINE__
#define __ENUM(situation) \
    int situation = (____LINE__ - __BASELINE -2);       const char * __##situation##_name = #situation;
constexpr int __BASELINE = __LINE__;
constexpr struct {
    __ENUM(Sunday);
    __ENUM(Monday);
    __ENUM(Tuesday);
    __ENUM(Wednesday);
    __ENUM(Thursday);
    __ENUM(Friday);
    __ENUM(Saturday);
}eDays;
#undef __ENUM
inline const char * enumDaysGetString(int num) {
    sIntCharPtr * ptr = (sIntCharPtr *)(&eDays);
    for (int i = 0;i < sizeof(eDays) / sizeof(sIntCharPtr);i++) {
        if (ptr[i].value == num) {
            return ptr[i].p_name;
        }
    }
    return "bad_enum_value";
}
int main(int argc, char *argv[]) {  
    int d = eDays.Wednesday;
    printf("day %s, number is %d\n", enumDaysGetString(d), d);
    d = 1;
    printf("day %s, number is %d\n", enumDaysGetString(d), d);
}

>day Wednesday, number is 3 >day Monday, number is 1

user8931844
źródło
0

Jestem nowy w tym, ale instrukcja przełącznika zdecydowanie zadziała

Suraj K. Thomas
źródło
Prawidłowe formatowanie (czytaj: wcięcie) jest niezbędne dla dosłownego kodu w odpowiedziach ...
p4010
0

Podoba mi się, że wyliczenie w nazwach dni. Aby ograniczyć pisanie, możemy wykonać następujące czynności:

Heng lou
źródło
0

Jest inne rozwiązanie: utwórz własną dynamiczną klasę wyliczania. Oznacza, że ​​masz funkcję structi jakąś funkcję do tworzenia nowego wyliczenia, które przechowuje elementy w a, structa każdy element ma ciąg znaków dla nazwy. Potrzebujesz także typu do przechowywania poszczególnych elementów, funkcji do ich porównywania i tak dalej. Oto przykład:

Funkcje wyliczania powinny znajdować się we własnej jednostce tłumaczeniowej, ale połączyłem je tutaj, aby było to prostsze.

Zaletą jest to, że to rozwiązanie jest elastyczne, działa na zasadzie DRY, możesz przechowywać informacje wraz z każdym elementem, możesz tworzyć nowe wyliczenia w trakcie działania, a także dodawać nowe elementy w trakcie działania. Wadą jest to, że jest to złożone, wymaga dynamicznej alokacji pamięci, nie może być używane w switch- case, wymaga więcej pamięci i jest wolniejsze. Pytanie brzmi, czy nie powinieneś używać języka wyższego poziomu w przypadkach, gdy tego potrzebujesz.

12431234123412341234123
źródło
-3

TheDay mapuje z powrotem do pewnego typu całkowitego. Więc:

Próbuje przeanalizować TheDay jako ciąg i albo wydrukuje śmieci, albo awarię.

printf nie jest bezpieczny dla typów i ufa, że ​​przekażesz mu odpowiednią wartość. Aby wydrukować nazwę wartości, musisz stworzyć metodę mapowania wartości wyliczenia na łańcuch - tabelę przeglądową, instrukcję gigantycznego przełącznika itp.

Michael
źródło