Muszę zrobić coś takiego w C. Działa tylko wtedy, gdy używam znaku, ale potrzebuję ciągu. Jak mogę to zrobić?
#define USER "jack" // jack or queen
#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif
Muszę zrobić coś takiego w C. Działa tylko wtedy, gdy używam znaku, ale potrzebuję ciągu. Jak mogę to zrobić?
#define USER "jack" // jack or queen
#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif
Odpowiedzi:
Nie sądzę, aby w dyrektywach preprocesora można było całkowicie porównać łańcuchy o zmiennej długości. Możesz jednak wykonać następujące czynności:
#define USER_JACK 1
#define USER_QUEEN 2
#define USER USER_JACK
#if USER == USER_JACK
#define USER_VS USER_QUEEN
#elif USER == USER_QUEEN
#define USER_VS USER_JACK
#endif
Możesz też trochę zmienić kod i zamiast tego użyć kodu C.
#define USER_VS (3 - USER)
w tym konkretnym przypadku. :)
[AKTUALIZACJA: 2018.05.03]
PRZESTROGA : Nie wszystkie kompilatory implementują specyfikację C ++ 11 w ten sam sposób. Poniższy kod działa w kompilatorze, na którym testowałem, podczas gdy wielu komentujących używa innego kompilatora.
Cytując z odpowiedzi Shafika Yaghmoura z: Obliczanie długości łańcucha C w czasie kompilacji. Czy to naprawdę constexpr?
Nie ma gwarancji, że wyrażenia stałe zostaną ocenione w czasie kompilacji, mamy tylko nienormatywny cytat z wersji roboczej standardu C ++, sekcja 5.19. Wyrażenia stałe, które mówią tak:
[...]> [Uwaga: wyrażenia stałe można oceniać podczas tłumaczenia. — uwaga końcowa]
To słowo can
robi wielką różnicę na świecie.
Tak więc YMMV na tej (lub dowolnej) odpowiedzi obejmującej constexpr
, w zależności od interpretacji specyfikacji przez autora kompilatora.
[ZAKTUALIZOWANO 2016.01.31]
Ponieważ niektórym nie podobała się moja wcześniejsza odpowiedź, ponieważ pozwoliła uniknąć całego compile time string compare
aspektu PO poprzez osiągnięcie celu bez potrzeby porównywania ciągów, oto odpowiedź bardziej szczegółowa.
Nie możesz! Nie w C98 ani C99. Nawet w C11. Żadna ilość manipulacji MAKRO tego nie zmieni.
Definicja const-expression
użyta w #if
tagu nie zezwala na ciągi.
Pozwala na postacie, więc jeśli ograniczysz się do postaci, możesz użyć tego:
#define JACK 'J'
#define QUEEN 'Q'
#define CHOICE JACK // or QUEEN, your choice
#if 'J' == CHOICE
#define USER "jack"
#define USER_VS "queen"
#elif 'Q' == CHOICE
#define USER "queen"
#define USER_VS "jack"
#else
#define USER "anonymous1"
#define USER_VS "anonymous2"
#endif
#pragma message "USER IS " USER
#pragma message "USER_VS IS " USER_VS
Możesz! W C ++ 11. Jeśli zdefiniujesz funkcję pomocniczą czasu kompilacji dla porównania.
// compares two strings in compile time constant fashion
constexpr int c_strcmp( char const* lhs, char const* rhs )
{
return (('\0' == lhs[0]) && ('\0' == rhs[0])) ? 0
: (lhs[0] != rhs[0]) ? (lhs[0] - rhs[0])
: c_strcmp( lhs+1, rhs+1 );
}
// some compilers may require ((int)lhs[0] - (int)rhs[0])
#define JACK "jack"
#define QUEEN "queen"
#define USER JACK // or QUEEN, your choice
#if 0 == c_strcmp( USER, JACK )
#define USER_VS QUEEN
#elif 0 == c_strcmp( USER, QUEEN )
#define USER_VS JACK
#else
#define USER_VS "unknown"
#endif
#pragma message "USER IS " USER
#pragma message "USER_VS IS " USER_VS
Ostatecznie będziesz musiał zmienić sposób, w jaki osiągasz swój cel, jakim jest wybranie ostatecznych wartości ciągów dla USER
i USER_VS
.
Nie możesz porównywać ciągów czasu kompilacji w C99, ale możesz wybierać ciągi w czasie kompilacji.
Jeśli naprawdę musisz dokonywać porównań czasu kompilacji, musisz zmienić na C ++ 11 lub nowsze warianty, które pozwalają na tę funkcję.
[ORYGINALNA ODPOWIEDŹ NASTĘPUJE]
Próbować:
#define jack_VS queen
#define queen_VS jack
#define USER jack // jack or queen, your choice
#define USER_VS USER##_VS // jack_VS or queen_VS
// stringify usage: S(USER) or S(USER_VS) when you need the string form.
#define S(U) S_(U)
#define S_(U) #U
AKTUALIZACJA: Wklejanie tokenu ANSI jest czasami mniej niż oczywiste. ;-RE
Umieszczenie singla #
przed makrem powoduje, że zostaje ono zamienione na ciąg zawierający jego wartość, a nie samą wartość.
Umieszczenie ##
znaku podwójnego między dwoma tokenami powoduje ich połączenie w jeden token.
Tak więc makro USER_VS
ma rozszerzenie jack_VS
lub queen_VS
, w zależności od ustawienia USER
.
Stringify makro S(...)
używa makro zadnie więc wartość o nazwie makro zostanie przekształcona w ciąg. zamiast nazwy makra.
W ten sposób USER##_VS
staje się jack_VS
(lub queen_VS
), w zależności od tego, jak ustawisz USER
.
Później, gdy makro stringify jest używane jako S(USER_VS)
wartość USER_VS
( jack_VS
w tym przykładzie), jest przesyłane do kroku pośredniego, S_(jack_VS)
który konwertuje jego wartość ( queen
) na ciąg "queen"
.
Jeśli ustawisz USER
na, queen
wynikiem końcowym jest łańcuch "jack"
.
Aby uzyskać informacje o konkatenacji tokenów, zobacz: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html
Informacje na temat konwersji ciągów tokenu można znaleźć na stronie : https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification
[ZAKTUALIZOWANO 2015.02.15, aby poprawić literówkę]
#if 0 == c_strcmp( USER, JACK )
naconstexpr int comp1 = c_strcmp( USER, JACK );
#if 0 == comp1
#if
Twój przykład działa tylko dlatego, że USER to JACK. Gdyby USER był QUEEN, to powiedziałoby USER IS QUEEN
iUSER_VS IS QUEEN
constexpr
) z dyrektyw preprocesora.
Poniższe zadziałały dla mnie z clangiem. Umożliwia to, co pojawia się jako symboliczne porównanie wartości makra. #error xxx służy tylko do sprawdzenia, co naprawdę robi kompilator. Zastąpienie definicji cat przez #define cat (a, b) a ## b psuje wszystko.
#define cat(a,...) cat_impl(a, __VA_ARGS__)
#define cat_impl(a,...) a ## __VA_ARGS__
#define xUSER_jack 0
#define xUSER_queen 1
#define USER_VAL cat(xUSER_,USER)
#define USER jack // jack or queen
#if USER_VAL==xUSER_jack
#error USER=jack
#define USER_VS "queen"
#elif USER_VAL==xUSER_queen
#error USER=queen
#define USER_VS "jack"
#endif
Jak już wspomniano powyżej, preprocesor ISO-C11 nie obsługuje porównywania ciągów. Jednak problem przypisania makra z „wartością przeciwną” można rozwiązać za pomocą „wklejania tokenów” i „dostępu do tabeli”. Proste makro-rozwiązanie Concatenate / stringify Jessego zawodzi w gcc 5.4.0, ponieważ stringizacja jest wykonywana przed oceną konkatenacji (zgodnie z ISO C11). Można to jednak naprawić:
#define P_(user) user ## _VS
#define VS(user) P_ (user)
#define S(U) S_(U)
#define S_(U) #U
#define jack_VS queen
#define queen_VS jack
S (VS (jack))
S (jack)
S (VS (queen))
S (queen)
#define USER jack // jack or queen, your choice
#define USER_VS USER##_VS // jack_VS or queen_VS
S (USER)
S (USER_VS)
Pierwsza linia (makro P_()
) dodaje jedno pośrednie, aby następny wiersz (makro VS()
) zakończył konkatenację przed stringizacją (zobacz Dlaczego potrzebuję podwójnej warstwy pośredniej dla makr? ). Makra strunizacji ( S()
i S_()
) pochodzą od Jesse.
Tabela (makra jack_VS
i queen_VS
), która jest znacznie łatwiejsza do utrzymania niż konstrukcja if-then-else OP, pochodzi od Jessego.
Na koniec następny czteroliniowy blok wywołuje makra w stylu funkcji. Ostatni czteroliniowy blok pochodzi z odpowiedzi Jessego.
Przechowywanie kodu foo.c
i wywoływanie preprocesora gcc -nostdinc -E foo.c
daje:
# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"
"jack"
"USER_VS"
Dane wyjściowe są zgodne z oczekiwaniami. Ostatnia linia pokazuje, że USER_VS
makro nie jest rozwijane przed stringizacją.
#if (S(USER)=="jack")
- Otrzymuję błąd preprocesora podczas używania "
- error: invalid token at start of a preprocessor expression
.
Jeśli twoje łańcuchy są stałymi czasowymi kompilacji (tak jak w twoim przypadku), możesz użyć następującej sztuczki:
#define USER_JACK strcmp(USER, "jack")
#define USER_QUEEN strcmp(USER, "queen")
#if $USER_JACK == 0
#define USER_VS USER_QUEEN
#elif USER_QUEEN == 0
#define USER_VS USER_JACK
#endif
Kompilator może z wyprzedzeniem powiedzieć wynik strcmp i zastąpi strcmp jego wynikiem, dając w ten sposób #define, który można porównać z dyrektywami preprocesora. Nie wiem, czy jest jakakolwiek różnica między kompilatorami / zależność od opcji kompilatora, ale zadziałało to dla mnie na GCC 4.7.2.
EDYCJA: po dalszym badaniu wygląda na to, że jest to rozszerzenie łańcucha narzędzi, a nie rozszerzenie GCC, więc weź to pod uwagę ...
$
jakiegoś rozszerzenia preprocesora?
Odpowiedź Patricka i Jessego Chisholma skłoniła mnie do wykonania następujących czynności:
#define QUEEN 'Q'
#define JACK 'J'
#define CHECK_QUEEN(s) (s==QUEEN)
#define CHECK_JACK(s) (s==JACK)
#define USER 'Q'
[... later on in code ...]
#if CHECK_QUEEN(USER)
compile_queen_func();
#elif CHECK_JACK(USER)
compile_jack_func();
#elif
#error "unknown user"
#endif
Zamiast #define USER 'Q'
#define USER QUEEN
powinien również działać, ale nie został przetestowany działa również i może być łatwiejszy w obsłudze.
EDYCJA: Zgodnie z komentarzem @ Jean-François Fabre dostosowałem swoją odpowiedź.
(s==QUEEN?1:0)
by (s==QUEEN)
nie potrzebna trójskładnik, wynik jest już wartością logiczną
#define USER_IS(c0,c1,c2,c3,c4,c5,c6,c7,c8,c9)\
ch0==c0 && ch1==c1 && ch2==c2 && ch3==c3 && ch4==c4 && ch5==c5 && ch6==c6 && ch7==c7 ;
#define ch0 'j'
#define ch1 'a'
#define ch2 'c'
#define ch3 'k'
#if USER_IS('j','a','c','k',0,0,0,0)
#define USER_VS "queen"
#elif USER_IS('q','u','e','e','n',0,0,0)
#define USER_VS "jack"
#endif
jest to w zasadzie statyczna tablica znaków o stałej długości, inicjowana ręcznie zamiast statycznej tablicy znaków o zmiennej długości, inicjalizowana automatycznie, zawsze kończąca się kończącym znakiem o wartości null
Nie możesz tego zrobić, jeśli USER jest zdefiniowany jako ciąg znaków w cudzysłowie.
Ale możesz to zrobić, jeśli USER to po prostu JACK lub QUEEN, Joker lub cokolwiek.
Istnieją dwie sztuczki do wykorzystania:
#define JACK
czegośZacznijmy więc od:
#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
Teraz, jeśli napiszę JACK_QUEEN_OTHER(USER)
, a USER to JACK, preprocesor zamieni to wEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)
Krok drugi to konkatenacja:
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
Teraz JACK_QUEEN_OTHER(USER)
staje sięEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)
Daje to możliwość dodania liczby przecinków w zależności od tego, czy ciąg pasuje, czy nie:
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x
Jeśli USER to JACK, zmienia JACK_QUEEN_OTHER(USER)
się naEXPANSION2(x,x,x, 1, 2, 3)
Jeśli USER to QUEEN, zmienia JACK_QUEEN_OTHER(USER)
się naEXPANSION2(x,x, 1, 2, 3)
Jeśli USER to inny, JACK_QUEEN_OTHER(USER)
staje sięEXPANSION2(ReSeRvEd_other, 1, 2, 3)
W tym momencie wydarzyło się coś krytycznego: czwarty argument makra EXPANSION2 to 1, 2 lub 3, w zależności od tego, czy pierwotnym przekazanym argumentem była walet, dama, czy cokolwiek innego. Więc wszystko, co musimy zrobić, to go wyłowić. Z rozwlekłych powodów do ostatniego kroku będziemy potrzebować dwóch makr; będą to EXPANSION2 i EXPANSION3, nawet jeśli jeden z nich wydaje się niepotrzebny.
Łącząc to wszystko, mamy 6 makr:
#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)
#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)
#define EXPANSION2(a, b, c, d, ...) EXPANSION3(a, b, c, d)
#define EXPANSION3(a, b, c, d, ...) d
#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x
Możesz ich używać w ten sposób:
int main() {
#if JACK_QUEEN_OTHER(USER) == 1
printf("Hello, Jack!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 2
printf("Hello, Queen!\n");
#endif
#if JACK_QUEEN_OTHER(USER) == 3
printf("Hello, who are you?\n");
#endif
}
Obowiązkowy link godbolt: https://godbolt.org/z/8WGa19
To proste, myślę, że możesz po prostu powiedzieć
#define NAME JACK
#if NAME == queen