Cách so sánh các chuỗi trong lệnh tiền xử lý có điều kiện C


92

Tôi phải làm một cái gì đó như thế này trong C. Nó chỉ hoạt động nếu tôi sử dụng một ký tự, nhưng tôi cần một chuỗi. Tôi có thể làm cái này như thế nào?

#define USER "jack" // jack or queen

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

Tại sao bạn không thể chỉ sử dụng strcmp?

@Brian: Vâng, tôi cũng đọc câu hỏi :-). Tôi chỉ muốn đảm bảo rằng anh ấy biết strcmp tồn tại và phản hồi có thể rất thú vị, vì tôi không thể nghĩ ra lý do để làm điều này #define.

2
Chỉ muốn đề cập rằng điều tương tự cũng xảy ra đối với mã thông thường, không chỉ bộ xử lý trước. Không bao giờ sử dụng một chuỗi khi một giá trị đơn giản sẽ làm được. Chuỗi có chi phí cao hơn nhiều so với số nguyên hoặc enum và nếu bạn không cần phải làm gì khác hơn là so sánh chúng, thì chuỗi là giải pháp sai.
swestrup

Sẽ rất hữu ích nếu câu hỏi bao gồm thêm một chút thông tin về hành vi mong muốn so với thực tế.
Brent Bradburn,

Câu trả lời:


69

Tôi không nghĩ rằng có cách nào để thực hiện so sánh chuỗi có độ dài thay đổi hoàn toàn trong các chỉ thị tiền xử lý. Bạn có thể làm như sau:

#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

Hoặc bạn có thể cấu trúc lại mã một chút và sử dụng mã C thay thế.


3
Hoặc anh ta có thể #define USER_VS (3 - USER)trong trường hợp cụ thể này. :)
Jesse Chisholm,

17

[CẬP NHẬT: 2018.05.03]

CAVEAT : Không phải tất cả các trình biên dịch đều triển khai đặc tả C ++ 11 theo cùng một cách. Đoạn mã dưới đây hoạt động trong trình biên dịch mà tôi đã thử nghiệm, trong khi nhiều người bình luận đã sử dụng trình biên dịch khác.

Trích dẫn câu trả lời của Shafik Yaghmour tại: Tính toán độ dài của chuỗi C tại thời điểm biên dịch. Đây có thực sự là một constexpr?

Các biểu thức hằng số không được đảm bảo sẽ được đánh giá tại thời điểm biên dịch, chúng tôi chỉ có một trích dẫn không chuẩn mực từ bản nháp C ++ tiêu chuẩn phần 5.19 Các biểu thức hằng số cho biết điều này mặc dù:

[...]> [Lưu ý: Các biểu thức hằng có thể được đánh giá trong quá trình dịch. — Ghi chú cuối]

Từ đó cantạo nên sự khác biệt trên thế giới.

Vì vậy, YMMV về câu trả lời này (hoặc bất kỳ) liên quan constexpr, tùy thuộc vào cách giải thích của người viết trình biên dịch về thông số kỹ thuật.

[CẬP NHẬT 2016.01.31]

Như một số không thích câu trả lời trước đó của tôi vì nó tránh toàn bộ compile time string comparekhía cạnh của OP bằng cách hoàn thành mục tiêu mà không cần so sánh chuỗi, đây là câu trả lời chi tiết hơn.

Bạn không thể! Không có trong C98 hoặc C99. Ngay cả trong C11 cũng không. Không có thao tác MACRO nào sẽ thay đổi điều này.

Định nghĩa của const-expressionđược sử dụng trong #ifchuỗi không cho phép.

Nó cho phép các ký tự, vì vậy nếu bạn giới hạn bản thân trong các ký tự, bạn có thể sử dụng điều này:

#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

Bạn có thể! Trong C ++ 11. Nếu bạn xác định một hàm trợ giúp thời gian biên dịch để so sánh.

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

Vì vậy, cuối cùng, bạn sẽ phải thay đổi cách bạn thực hiện mục tiêu của mình là chọn giá trị chuỗi cuối cùng cho USERUSER_VS.

Bạn không thể so sánh chuỗi thời gian biên dịch trong C99, nhưng bạn có thể thực hiện việc lựa chọn thời gian biên dịch của chuỗi.

Nếu bạn thực sự phải so sánh thời gian biên dịch, thì bạn cần thay đổi sang C ++ 11 hoặc các biến thể mới hơn cho phép tính năng đó.

[CÂU TRẢ LỜI GỐC SAU]

Thử:

#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

CẬP NHẬT: Việc dán mã thông báo ANSI đôi khi ít rõ ràng hơn. ;-D

Việc đặt một đơn vị #trước macro khiến nó bị thay đổi thành một chuỗi giá trị của nó, thay vì giá trị trần của nó.

Đặt một dấu đôi ##giữa hai mã thông báo khiến chúng được nối thành một mã thông báo duy nhất.

Vì vậy, macro USER_VScó sự mở rộng jack_VShoặc queen_VS, tùy thuộc vào cách bạn đặt USER.

Các stringify vĩ mô S(...)sử dụng gián tiếp vĩ mô vì vậy giá trị của macro tên được chuyển đổi thành một chuỗi. thay vì tên của macro.

Do đó USER##_VStrở thành jack_VS(hoặc queen_VS), tùy thuộc vào cách bạn đặt USER.

Sau đó, khi macro stringify được sử dụng làm S(USER_VS)giá trị của USER_VS( jack_VStrong ví dụ này) được chuyển đến bước S_(jack_VS)chuyển hướng sẽ chuyển đổi giá trị của nó ( queen) thành một chuỗi "queen".

Nếu bạn đặt USERthành queenthì kết quả cuối cùng là chuỗi "jack".

Để nối mã thông báo, hãy xem: https://gcc.gnu.org/onlineocs/cpp/Concatenation.html

Để chuyển đổi chuỗi mã thông báo, hãy xem: https://gcc.gnu.org/onlineocs/cpp/Stringification.html#Stringification

[CẬP NHẬT 2015.02.15 để sửa lỗi đánh máy.]


5
@JesseChisholm, bạn đã kiểm tra phiên bản C ++ 11 của mình chưa? Tôi không thể làm cho nó hoạt động trên GCC 4.8.1, 4.9.1, 5.3.0. Nó nói {{thiếu nhà điều hành nhị phân trước thẻ "("}} vào {{# nếu 0 == c_strmp / * đây * / (USER, QUEEN)}}
Dmitriy Elisov

3
@JesseChisholm Vì vậy, tôi cố gắng biên dịch C ++ 11 ví dụ nếu tôi thay đổi #if 0 == c_strcmp( USER, JACK )đếnconstexpr int comp1 = c_strcmp( USER, JACK ); #if 0 == comp1
Dmitriy Elisov

4
@JesseChisholm, hmm, vẫn chưa gặp may. Mọi biến constexpr bằng 0 trong. #ifVí dụ của bạn chỉ hoạt động vì USER là JACK. Nếu USER là QUEEN, nó sẽ nói USER IS QUEENUSER_VS IS QUEEN
Dmitriy Elisov

9
Phần c ++ 11 của câu trả lời này là sai. Bạn không thể gọi các hàm (thậm chí constexpr) từ các lệnh tiền xử lý.
interjay

8
Câu trả lời sai rõ ràng này đã đánh lừa một người đã tham khảo nó. Bạn không thể gọi một hàm constexpr từ bộ tiền xử lý; constexpr thậm chí không được công nhận là một từ khóa cho đến khi giai đoạn dịch 7. tiền xử lý được thực hiện trong giai đoạn dịch 4.
H Walters

9

Sau đây làm việc cho tôi với tiếng kêu. Cho phép những gì xuất hiện dưới dạng so sánh giá trị macro tượng trưng. #error xxx chỉ để xem trình biên dịch thực sự làm gì. Thay thế định nghĩa mèo bằng #define cat (a, b) a ## b sẽ phá vỡ mọi thứ.

#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

Không chắc đây là điều ác, rực rỡ hay cả hai, nhưng nó chính xác là những gì tôi đang tìm kiếm - cảm ơn bạn! Một thủ thuật hữu ích nữa là # xác định các macro xUSER_ của bạn bắt đầu từ 1. Sau đó, bạn có thể thêm mệnh đề #else vào cuối danh sách #elsif của mình để tránh các trường hợp USER vô tình được đặt thành thứ mà bạn không biết cách xử lý. (Nếu không, nếu bạn đánh số từ 0 thì trường hợp 0 ​​sẽ trở thành catchall của bạn, vì đó là giá trị số mặc định của bộ tiền xử lý cho các ký hiệu không xác định.)
sclamage

8

Sử dụng giá trị số thay vì chuỗi.

Cuối cùng để chuyển đổi các hằng số JACK hoặc QUEEN thành một chuỗi, hãy sử dụng các toán tử stringize (và / hoặc tokenize).


2

Như đã nêu ở trên, bộ tiền xử lý ISO-C11 không hỗ trợ so sánh chuỗi. Tuy nhiên, vấn đề chỉ định macro với “giá trị đối nghịch” có thể được giải quyết bằng “dán mã thông báo” và “quyền truy cập bảng”. Giải pháp macro nối / xâu chuỗi đơn giản của Jesse không thành công với gcc 5.4.0 vì quá trình xâu chuỗi được thực hiện trước khi đánh giá quá trình nối (tuân theo ISO C11). Tuy nhiên, nó có thể được sửa:

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

Dòng đầu tiên (macro P_()) thêm một hướng dẫn để cho dòng tiếp theo (macro VS()) kết thúc quá trình nối trước khi xâu chuỗi (xem Tại sao tôi cần hai lớp hướng dẫn cho macro? ). Các macro chuỗi ( S()S_()) là của Jesse.

Bảng (macro jack_VSqueen_VS) dễ bảo trì hơn nhiều so với cấu trúc if-then-else của OP là của Jesse.

Cuối cùng, khối bốn dòng tiếp theo gọi các macro kiểu hàm. Khối bốn dòng cuối cùng là từ câu trả lời của Jesse.

Lưu trữ mã trong foo.cvà gọi bộ tiền xử lý gcc -nostdinc -E foo.cmang lại:

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



"jack"
"USER_VS"

Đầu ra như mong đợi. Dòng cuối cùng cho thấy rằng USER_VSmacro không được mở rộng trước khi chuỗi.


Điều này hoạt động tốt, cho đến khi tôi cố gắng thực sự so sánh chuỗi đã tạo, để thực hiện biên dịch có điều kiện: #if (S(USER)=="jack")- Tôi gặp lỗi bộ xử lý trước khi sử dụng dấu "- error: invalid token at start of a preprocessor expression.
ysap

1

Nếu chuỗi của bạn là hằng số thời gian biên dịch (như trong trường hợp của bạn), bạn có thể sử dụng thủ thuật sau:

#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

Trình biên dịch có thể cho biết trước kết quả của strcmp và sẽ thay thế strcmp bằng kết quả của nó, do đó cung cấp cho bạn một #define có thể được so sánh với các chỉ thị tiền xử lý. Tôi không biết liệu có bất kỳ sự khác biệt nào giữa các trình biên dịch / sự phụ thuộc vào các tùy chọn trình biên dịch hay không, nhưng nó đã hoạt động với tôi trên GCC 4.7.2.

CHỈNH SỬA: khi điều tra thêm, có vẻ như đây là một tiện ích mở rộng chuỗi công cụ, không phải tiện ích mở rộng GCC, vì vậy hãy xem xét điều đó ...


7
Đây chắc chắn không phải là chuẩn C và tôi không biết nó sẽ hoạt động như thế nào với bất kỳ trình biên dịch nào. Đôi khi, trình biên dịch có thể cho biết kết quả của các biểu thức (ngay cả các lệnh gọi hàm, nếu chúng là nội dòng), nhưng không cho trình xử lý trước. Bạn có đang sử dụng $một số loại tiện ích mở rộng tiền xử lý không?
ugoren

3
Có vẻ như cú pháp '#if $ USER_JACK == 0' hoạt động, ít nhất là với GNU C ++ được sử dụng để xây dựng mã Android gốc (JNI) ... Tôi không biết điều này, nhưng nó rất hữu ích, cảm ơn bạn đã cho chúng tôi biết về nó!
gregko,

6
Tôi đã thử điều này trên GCC 4.9.1 và tôi không tin rằng điều này sẽ làm được những gì bạn nghĩ. Trong khi mã sẽ được biên dịch, nó sẽ không cung cấp cho bạn kết quả như mong đợi. '$' được coi là một tên biến. Vì vậy, tiền xử lý đang tìm kiếm biến '$ USER_JACK', không tìm thấy nó và cho nó giá trị mặc định là 0. Như vậy, bạn sẽ luôn có USER_VS định nghĩa là bất kể USER_QUEEN strcmp
Vitali

1

Người trả lời bởi PatrickJesse Chisholm đã khiến tôi làm như sau:

#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

Thay vì #define USER 'Q' #define USER QUEEN cũng nên hoạt động nhưng không được kiểm tra cũng hoạt động và có thể dễ dàng xử lý hơn.

CHỈNH SỬA: Theo nhận xét của @ Jean-François Fabre, tôi đã điều chỉnh câu trả lời của mình.


thay đổi (s==QUEEN?1:0)bởi (s==QUEEN)bạn không cần số ba, kết quả đã là một boolean
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

Về cơ bản nó là một mảng char tĩnh có độ dài cố định được khởi tạo theo cách thủ công thay vì một mảng char tĩnh có độ dài thay đổi được khởi tạo tự động luôn kết thúc bằng một ký tự rỗng kết thúc


0

Bạn không thể làm điều đó nếu USER được định nghĩa là một chuỗi được trích dẫn.

Nhưng bạn có thể làm điều đó nếu USER chỉ là JACK hoặc QUEEN hoặc Joker hoặc bất cứ điều gì.

Có hai thủ thuật để sử dụng:

  1. Ghép mã thông báo, nơi bạn kết hợp một số nhận dạng với một số nhận dạng khác bằng cách chỉ nối các ký tự của chúng. Điều này cho phép bạn so sánh với JACK mà không cần phải #define JACKlàm gì đó
  2. mở rộng macro variadic, cho phép bạn xử lý macro với số lượng đối số thay đổi. Điều này cho phép bạn mở rộng các số nhận dạng cụ thể thành các số dấu phẩy khác nhau, chúng sẽ trở thành phép so sánh chuỗi của bạn.

Vì vậy, hãy bắt đầu với:

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

Bây giờ, nếu tôi viết JACK_QUEEN_OTHER(USER)và USER là JACK, bộ tiền xử lý sẽ biến điều đó thànhEXPANSION1(ReSeRvEd_, JACK, 1, 2, 3)

Bước hai là nối:

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

Bây giờ JACK_QUEEN_OTHER(USER)trở thànhEXPANSION2(ReSeRvEd_JACK, 1, 2, 3)

Điều này tạo cơ hội để thêm một số dấu phẩy tùy theo việc một chuỗi có khớp hay không:

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

Nếu USER bị JACK, JACK_QUEEN_OTHER(USER)trở thànhEXPANSION2(x,x,x, 1, 2, 3)

Nếu USER là QUEEN, JACK_QUEEN_OTHER(USER)trở thànhEXPANSION2(x,x, 1, 2, 3)

Nếu USER khác, JACK_QUEEN_OTHER(USER)trở thànhEXPANSION2(ReSeRvEd_other, 1, 2, 3)

Tại thời điểm này, một điều gì đó quan trọng đã xảy ra: đối số thứ tư cho macro EXPANSION2 là 1, 2 hoặc 3, tùy thuộc vào việc đối số ban đầu được truyền là jack, queen hay bất kỳ thứ gì khác. Vì vậy, tất cả những gì chúng ta phải làm là chọn nó ra. Vì lý do dài dòng, chúng ta sẽ cần hai macro cho bước cuối cùng; chúng sẽ là EXPANSION2 và EXPANSION3, mặc dù một cái có vẻ không cần thiết.

Kết hợp tất cả lại với nhau, chúng ta có 6 macro sau:

#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

Và bạn có thể sử dụng chúng như thế này:

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
}

Liên kết chốt chặn bắt buộc: https://godbolt.org/z/8WGa19


-5

Thật đơn giản, tôi nghĩ bạn chỉ có thể nói

#define NAME JACK    
#if NAME == queen 
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.