C koşullu önişlemci yönergelerinde dizeler nasıl karşılaştırılır


92

C'de bunun gibi bir şey yapmalıyım. Sadece bir karakter kullanırsam çalışır, ama bir dizgeye ihtiyacım var. Bunu nasıl yapabilirim?

#define USER "jack" // jack or queen

#if USER == "jack"
#define USER_VS "queen"
#elif USER == "queen"
#define USER_VS "jack"
#endif

Neden strcmp'yi kullanamıyorsun?

@Brian: Evet, soruyu da okudum :-). Sadece strcmp'nin var olduğunu bildiğinden emin olmak istedim ve bu #define şeyi yapmak için bir neden düşünemediğim için yanıt aydınlatıcı olabilir.

2
Aynı şeyin sadece önişlemciler için değil, normal kod için de geçerli olduğunu belirtmek istedim. Basit bir değer işe yarayacaksa asla bir dizge kullanmayın. Dizelerin tamsayılardan veya numaralandırmalardan çok daha fazla yükü vardır ve bunları karşılaştırmaktan daha fazlasını yapmanız gerekmiyorsa, dizeler yanlış çözümdür.
swestrup

Sorunun istenen ve gerçek davranış hakkında biraz daha fazla bilgi içermesi kullanışlı olacaktır.
Brent Bradburn

Yanıtlar:


71

Önişlemci direktiflerinde tamamen değişken uzunluklu dizge karşılaştırmaları yapmanın bir yolu olduğunu düşünmüyorum. Belki de şunları yapabilirsiniz:

#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

Veya kodu biraz yeniden düzenleyebilir ve bunun yerine C kodunu kullanabilirsiniz.


3
Veya #define USER_VS (3 - USER)bu özel durumda yapabilirdi. :)
Jesse Chisholm

17

[GÜNCELLEME: 2018.05.03]

CAVEAT : Tüm derleyiciler C ++ 11 belirtimini aynı şekilde uygulamaz. Aşağıdaki kod, üzerinde test ettiğim derleyicide çalışıyor, ancak birçok yorumcu farklı bir derleyici kullanıyor.

Shafik Yaghmour'un cevabından alıntı: Derleme zamanında bir C dizgisinin uzunluğu hesaplanıyor . Bu gerçekten bir constexpr mi?

Sabit ifadelerin derleme sırasında değerlendirilmesi garanti edilmez, yalnızca taslak C ++ standardı bölüm 5.19'dan normatif olmayan bir alıntıya sahibiz.

[...]> [Not: Sabit ifadeler çeviri sırasında değerlendirilebilir. - son not]

Bu kelime candünyadaki tüm farkı yaratıyor.

Öyleyse, constexprderleyici yazarının şartnameyi yorumlamasına bağlı olarak, bu (veya herhangi bir) yanıta YMMV .

[GÜNCELLENMİŞ 2016.01.31]

Bazıları önceki cevabımdan hoşlanmadığı için OP'nin tüm yönünden kaçınarakcompile time string compare hedefe dizgi karşılaştırmalarına gerek kalmadan ulaştığı için, burada daha ayrıntılı bir cevap var.

Yapamazsın! C98 veya C99'da değil. C11'de bile değil. Hiçbir MAKRO manipülasyonu bunu değiştirmez.

İçinde const-expressionkullanılan tanımı #ifdizelere izin vermez.

Karakterlere izin verir, bu yüzden kendinizi karakterlerle sınırlarsanız, bunu kullanabilirsiniz:

#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

Yapabilirsin! C ++ 11'de. Karşılaştırma için bir derleme zamanı yardımcı işlevi tanımlarsanız.

// 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

Dolayısıyla, nihayetinde, USERve için son dize değerlerini seçme hedefinizi gerçekleştirme şeklinizi değiştirmeniz gerekecektir USER_VS.

C99'da zaman dizisi karşılaştırmalarını derleyemezsiniz, ancak dizeleri seçerek derleme zamanı yapabilirsiniz.

Derleme zamanı karşılaştırmaları gerçekten yapmanız gerekiyorsa, o zaman C ++ 11'e veya bu özelliğe izin veren daha yeni varyantlara geçmeniz gerekir.

[ORİJİNAL CEVAP TAKİP ETMEKTEDİR]

Deneyin:

#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

GÜNCELLEME: ANSI belirtecinin yapıştırılması bazen bariz olandan daha azdır. ; -D

#Bir makronun önüne bir tek koymak , onun çıplak değeri yerine kendi değerinin bir dizgisine dönüştürülmesine neden olur.

##İki jeton arasına çift ​​koymak , bunların tek bir jetonda birleştirilmesine neden olur.

Yani, makro USER_VSgenişleme vardır jack_VSya queen_VSayarladığınız şekline bağlı olarak, USER.

Stringify makro S(...)adında makro değeri bir dizeye dönüştürülmüş olur böylece makro indirection kullanır. makronun adı yerine.

Böylece , nasıl ayarladığınıza bağlı olarak (veya ) USER##_VSolur .jack_VSqueen_VSUSER

Daha sonra, dizgeleştirme makrosu S(USER_VS)değeri olarak kullanıldığında USER_VS( jack_VSbu örnekte), S_(jack_VS)değerini ( queen) bir dizeye dönüştüren indireksiyon adımına geçirilir "queen".

Eğer ayarlarsanız USERiçin queendaha sonra nihai sonuç dizedir "jack".

Belirteç birleştirme için bkz: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html

Belirteç dizisi dönüşümü için bkz: https://gcc.gnu.org/onlinedocs/cpp/Stringification.html#Stringification

[Bir yazım hatasını düzeltmek için 2015.02.15 GÜNCELLENDİ.]


5
@JesseChisholm, C ++ 11 sürümünüzü kontrol ettiniz mi? GCC 4.8.1, 4.9.1, 5.3.0 üzerinde çalışmasını sağlayamıyorum. {{Göstergeden önce eksik ikili operatör "("}} {{#if 0 == c_strmp / * burada * / (KULLANICI,
KRALİÇ

3
@JesseChisholm Bu yüzden değiştirirseniz C ++ 11 örnek derlemek başardı #if 0 == c_strcmp( USER, JACK )içinconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov

4
@JesseChisholm, hmm, hala şans yok. Herhangi bir constexpr değişkeni sıfıra eşittir. Örneğiniz #ifyalnızca KULLANICI JACK olduğu için çalışır. KULLANICI QUEEN olsaydı, derdi USER IS QUEENveUSER_VS IS QUEEN
Dmitriy Elisov

9
Bu cevabın bu c ++ 11 kısmı yanlış. constexprÖnişlemci yönergelerinden işlevleri (hatta ) çağıramazsınız .
interjay

8
Bu apaçık yanlış cevap, ona referans veren birini zaten yanlış yönlendirdi. Önişlemciden bir constexpr işlevi çağıramazsınız; constexpr bile çeviri faz 7. Ön İşleme kadar anahtar kelime olarak kabul edilmez dönüşüm aşamasından 4'te yapılır
H Walters

10

Aşağıdaki clang ile benim için çalıştı. Sembolik makro değer karşılaştırması olarak görünenlere izin verir. #error xxx sadece derleyicinin gerçekte ne yaptığını görmektir. Değiştirme kedi ile tanımını #define kedi (a, b) b ## sonları şeyler.

#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

Bunun kötü mü, zekice mi yoksa her ikisi mi olduğundan emin değilim, ama tam olarak aradığım buydu - teşekkürler! Bir başka yardımcı numara da xUSER_ makrolarınızı 1'den başlayarak # tanımlamaktır. Ardından, KULLANICI'nın yanlışlıkla nasıl işleyeceğini bilmediğiniz bir şeye ayarlandığı durumları yakalamak için #elsif listenizin sonuna bir #else cümlesi ekleyebilirsiniz. (Aksi takdirde, 0'dan numaralandırırsanız, 0 durumu sizin için geçerli olur, çünkü bu, önişlemcinin tanımlanmamış semboller için varsayılan sayısal değeridir.)
18'de

8

Dizeler yerine sayısal değerler kullanın.

Son olarak, JACK veya QUEEN sabitlerini bir dizgeye dönüştürmek için dizge oluşturma (ve / veya belirtme) operatörlerini kullanın.


2

Daha önce de ISO-C11, yukarıda belirtilen önişlemci yok değil dize karşılaştırma destekler. Ancak, "zıt değerli" bir makro atama sorunu, "simge yapıştırma" ve "tablo erişimi" ile çözülebilir. Jesse'nin basit birleştirme / dizgeleştirme makro çözümü gcc 5.4.0 ile başarısız olur çünkü dizgeleştirme, birleştirme değerlendirmesinden önce yapılır (ISO C11'e uygun olarak). Ancak şu düzeltilebilir:

#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)

İlk satır (makro P_()) , dizgeleştirmeden önce bir sonraki satırın (makro VS()) birleştirmeyi bitirmesine izin vermek için bir yönlendirme ekler (bkz . Makrolar için neden çift yönlü yönlendirmeye ihtiyacım var? ). Telleştirme makroları ( ve ) Jesse'den alınmıştır .S()S_()

Tablo (makrolar jack_VSvequeen_VSOP'nin eğer-öyleyse-yapısına göre bakımı çok daha kolay olan ) Jesse'den alınmıştır.

Son olarak, sonraki dört satırlı blok, işlev tarzı makroları çağırır. Son dört satırlık blok Jesse'nin cevabından.

Kodu saklamak foo.cve önişlemciyi çağırmak gcc -nostdinc -E foo.cşunları verir:

# 1 "foo.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 1 "foo.c"
# 9 "foo.c"
"queen"
"jack"
"jack"
"queen"



"jack"
"USER_VS"

Çıktı beklendiği gibi. Bu son satırı gösterir USER_VSmakro edilir değil stringization önce genişletti.


Koşullu bir derleme yapmak için oluşturulan dizgiyi gerçekten karşılaştırmaya çalışıncaya kadar bu güzel çalışıyor : #if (S(USER)=="jack")- "- error: invalid token at start of a preprocessor expression.
ysap

1

Dizeleriniz derleme zamanı sabitleriyse (sizin durumunuzdaki gibi) aşağıdaki numarayı kullanabilirsiniz:

#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

Derleyici, strcmp'nin sonucunu önceden söyleyebilir ve strcmp'yi sonucuyla değiştirir, böylece size önişlemci yönergeleriyle karşılaştırılabilecek bir #define verir. Derleyiciler / derleyici seçeneklerine bağımlılık arasında herhangi bir fark olup olmadığını bilmiyorum, ama benim için GCC 4.7.2'de çalıştı.

DÜZENLEME: Daha fazla araştırmanın ardından, bu GCC uzantısı değil, bir araç zinciri uzantısı gibi görünüyor, bu yüzden bunu dikkate alın ...


7
Bu kesinlikle standart C değil ve herhangi bir derleyici ile nasıl çalışacağını anlamıyorum. Derleyici bazen ifadelerin sonuçlarını (hatta satır içi iseler işlev çağrıları) söyleyebilir, ancak ön işlemciyi söyleyemez. Bir $tür ön işlemci uzantısı mı kullanıyorsunuz?
ugoren

3
Görünüşe göre '#if $ USER_JACK == 0' sözdizimi çalışıyor, en azından yerel Android kodu (JNI) oluşturmak için kullanılan GNU C ++ ile ... Bunu bilmiyordum, ama çok faydalı, bize anlattığınız için teşekkür ederim o!
gregko

6
Bunu GCC 4.9.1'de denedim ve bunun sizin düşündüğünüz şeyi yapacağına inanmıyorum. Kod derlenirken size beklenen sonucu vermez. '$' bir değişken adı olarak kabul edilir. Dolayısıyla, önişlemci '$ USER_JACK' değişkenini arıyor, bulamıyor ve 0 varsayılan değerini veriyor. Bu nedenle, strcmp
Vitali

1

Patrick ve Jesse Chisholm'un cevabı bana şunları yaptırdı:

#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

Onun yerine #define USER 'Q' #define USER QUEEN da çalışmalı ama test edilmedi ayrıca çalışır ve kullanımı daha kolay olabilir.

DÜZENLEME: @ Jean-François Fabre'nin yorumuna göre cevabımı uyarladım.


değişim (s==QUEEN?1:0)tarafından (s==QUEEN)size sonuç zaten bir boolean, ve üçlü gerekmez
Jean-François Fabre

0
#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

temelde, değişken uzunluklu bir statik karakter dizisi yerine manuel olarak başlatılan sabit uzunluklu bir statik karakter dizisi, otomatik olarak her zaman sonlandırıcı bir boş karakter ile biten


0

USER, tırnak içine alınmış bir dize olarak tanımlanmışsa bunu yapamazsınız.

Ama olabilir KULLANICI sadece JACK veya QUEEN veya Joker ya da her türlü ise böyle yapar.

Kullanılacak iki numara var:

  1. Bir tanımlayıcıyı başka bir tanımlayıcıyla sadece karakterlerini birleştirerek birleştirdiğiniz belirteç ekleme. Bu, bir #define JACKşeye gerek kalmadan JACK ile karşılaştırma yapmanıza olanak tanır
  2. değişken sayıda bağımsız değişken içeren makroları işlemenizi sağlayan değişken makro genişletme. Bu, belirli tanımlayıcıları, dize karşılaştırmanız haline gelecek olan çeşitli virgül sayılarına genişletmenize olanak tanır.

Öyleyse şununla başlayalım:

#define JACK_QUEEN_OTHER(u) EXPANSION1(ReSeRvEd_, u, 1, 2, 3)

Şimdi, JACK_QUEEN_OTHER(USER)yazarsam ve USER JACK ise, önişlemci bunuEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

İkinci adım birleştirme:

#define EXPANSION1(a, b, c, d, e) EXPANSION2(a##b, c, d, e)

Şimdi JACK_QUEEN_OTHER(USER)olurEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Bu, bir dizenin eşleşip eşleşmediğine göre bir dizi virgül ekleme fırsatı verir:

#define ReSeRvEd_JACK x,x,x
#define ReSeRvEd_QUEEN x,x

KULLANICI JACK ise JACK_QUEEN_OTHER(USER),EXPANSION2(x,x,x, 1, 2, 3)

KULLANICI KRALİÇ ise, JACK_QUEEN_OTHER(USER)olurEXPANSION2(x,x, 1, 2, 3)

USER başka biriyse, JACK_QUEEN_OTHER(USER)olurEXPANSION2(ReSeRvEd_other, 1, 2, 3)

Bu noktada kritik bir şey oldu: EXPANSION2 makrosunun dördüncü argümanı, geçirilen orijinal argümanın vale, kız veya başka bir şey olmasına bağlı olarak 1, 2 veya 3'tür. Yani tek yapmamız gereken onu seçmek. Uzun soluklu nedenlerden dolayı, son adım için iki makroya ihtiyacımız olacak; Gereksiz görünse bile bunlar EXPANSION2 ve EXPANSION3 olacaktır.

Hepsini bir araya getirirsek, şu 6 makroya sahibiz:

#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

Ve bunları şu şekilde kullanabilirsiniz:

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
}

Zorunlu godbolt bağlantısı: https://godbolt.org/z/8WGa19


-5

Basit, sanırım sadece söyleyebilirsin

#define NAME JACK    
#if NAME == queen 
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.