Sử dụng if (0) để bỏ qua một trường hợp trong một công tắc có hoạt động không?


117

Tôi có một tình huống mà tôi muốn hai trường hợp trong một câu lệnh chuyển đổi C ++ để cả hai chuyển sang trường hợp thứ ba. Cụ thể, trường hợp thứ hai sẽ rơi vào trường hợp thứ ba, và trường hợp đầu tiên cũng sẽ rơi vào trường hợp thứ ba mà không chuyển qua trường hợp thứ hai.

Tôi đã có một ý tưởng ngu ngốc, đã thử nó, và nó đã thành công! Tôi quấn trường hợp thứ hai trong một if (0) {... }. Nó trông như thế này:

#ifdef __cplusplus
#  include <cstdio>
#else
#  include <stdio.h>
#endif

int main(void) {
    for (int i = 0; i < 3; i++) {
        printf("%d: ", i);
        switch (i) {
        case 0:
            putchar('a');
            // @fallthrough@
            if (0) {        // fall past all of case 1 (!)
        case 1:
            putchar('b');
            // @fallthrough@
            }
        case 2:
            putchar('c');
            break;
        }
        putchar('\n');
    }
    return 0;
}

Khi tôi chạy nó, tôi nhận được đầu ra mong muốn:

0: ac
1: bc
2: c

Tôi đã thử nó trong cả C và C ++ (cả hai đều có tiếng kêu), và nó đã làm điều tương tự.

Câu hỏi của tôi là: C / C ++ này có hợp lệ không? Nó phải làm những gì nó làm?


33
Có, điều này hợp lệ và hoạt động vì những lý do tương tự như lý do tại sao thiết bị của Duff lại làm như vậy.
dxiv

41
Lưu ý rằng mã như thế này sẽ giúp bạn thoát khỏi bất kỳ hướng dẫn mã nào trong một môi trường quan tâm thậm chí chỉ một chút về khả năng đọc và khả năng bảo trì.
Andrew Henle

16
Điều này thật kinh khủng. Còn kinh khủng hơn thiết bị của Duff, phiền bạn. Có liên quan, gần đây tôi cũng thấy một cái gì đó như thế switch(x) { case A: case B: do_this(); if(x == B) also_do_that(); ... }. Đó cũng là IMO, thật kinh khủng. Làm ơn, chỉ cần viết những thứ như vậy như thể các câu lệnh, ngay cả khi nó có nghĩa là bạn phải lặp lại một dòng ở hai nơi. Sử dụng các hàm và biến (và tài liệu!) Để giảm nguy cơ vô tình cập nhật sau này chỉ ở một nơi.
ilkkachu

48
:-) Đối với những người bị thương và gây tàn phế do nhìn vào mã mà, tôi không nói đó là một tốt ý tưởng. Trong thực tế, tôi đã nói đó là một ý tưởng ngu ngốc.
Mark Adler

4
Lưu ý rằng các cấu trúc bên trong công tắc như thế này KHÔNG hoạt động tốt với RAII :(
Mooing Duck

Câu trả lời:


57

Có, điều này được phép, và nó làm những gì bạn muốn. Đối với một switchcâu lệnh, tiêu chuẩn C ++ cho biết :

Bản thân các nhãn trường hợp và mặc định không làm thay đổi luồng điều khiển, tiếp tục không bị cản trở trên các nhãn đó. Để thoát khỏi một công tắc, hãy xem ngắt.

[Lưu ý 1: Thông thường, câu lệnh con là chủ đề của chuyển đổi là từ ghép và trường hợp và các nhãn mặc định xuất hiện trên các câu lệnh cấp cao nhất có trong câu lệnh (phức hợp), nhưng điều này không bắt buộc. Các khai báo có thể xuất hiện trong câu lệnh con của một câu lệnh switch. - ghi chú cuối]

Vì vậy, khi ifcâu lệnh được đánh giá, luồng điều khiển sẽ tiến hành theo các quy tắc của một ifcâu lệnh, bất kể nhãn trường hợp can thiệp.


13
Như một trường hợp cụ thể của điều này, người ta có thể xem Boost.Coroutines để biết một tập hợp các macro hấp dẫn tận dụng quy tắc này (và một nửa tá trường hợp góc khác của C ++) để triển khai các coroutines sử dụng quy tắc C ++ 03. Chúng không được tạo thành một phần của ngôn ngữ cho đến khi C ++ 20, nhưng những macro này đã khiến chúng hoạt động ... miễn là bạn dùng thuốc kháng axit trước!
Cort Ammon

58

Vâng, điều này được cho là hoạt động. Các nhãn trường hợp cho một câu lệnh switch trong C gần giống như các nhãn goto (với một số lưu ý về cách chúng hoạt động với các câu lệnh switch lồng nhau). Đặc biệt, chúng không tự định nghĩa khối cho các câu lệnh mà bạn cho là "bên trong trường hợp", và bạn có thể sử dụng chúng để nhảy vào giữa khối giống như bạn có thể làm với goto. Khi nhảy vào giữa một khối, các lưu ý tương tự như với goto áp dụng liên quan đến việc nhảy qua khởi tạo các biến, v.v.

Với điều đó đã nói, trong thực tế, có lẽ rõ ràng hơn khi viết điều này bằng một câu lệnh goto, như trong:

    switch (i) {
    case 0:
        putchar('a');
        goto case2;
    case 1:
        putchar('b');
        // @fallthrough@
    case2:
    case 2:
        putchar('c');
        break;
    }

1
"Khi nhảy vào giữa một khối, các cảnh báo tương tự như với goto áp dụng liên quan đến việc nhảy qua khởi tạo các biến, v.v." Những cảnh báo đó sẽ là gì? Bạn không thể nhảy qua quá trình khởi tạo các biến.
Tiểu hành tinh có cánh

4
@AsteroidsWithWings, ý bạn là "không thể" từ góc độ tuân thủ tiêu chuẩn hay từ góc độ thực tế mà trình biên dịch sẽ không cho phép? Bởi vì GCC của tôi không cho phép nó ở chế độ C, với một cảnh báo. Tuy nhiên, nó không cho phép nó ở chế độ C ++.
ilkkachu

5
@quetzalcoatl gcc-9 và clang-6 đều cho phép mã này, cảnh báo về khả năng chưa được khởi tạo bee(trong chế độ C; trong C ++, chúng báo lỗi "không thể chuyển từ câu lệnh switch sang nhãn trường hợp này / jump bỏ qua khởi tạo biến").
Ruslan

7
Bạn biết mình đang làm sai khi sử dụng gotogiải pháp sạch hơn.
Konrad Rudolph

9
@KonradRudolph: hơi ác gotoý nhưng thực sự không có gì đáng phản đối nếu bạn không tạo cấu trúc điều khiển phức tạp từ nó theo cách thủ công. Toàn bộ "goto được coi là có hại" là về việc viết mã HLL (ví dụ C) trông giống như asm được phát ra bởi một trình biên dịch (jmps qua lại khắp nơi). Có rất nhiều cách sử dụng có cấu trúc tốt của goto như chỉ chuyển tiếp (về mặt khái niệm không khác so với breakhoặc sớm hơn return), chỉ không thử lại-vòng lặp (tránh che khuất đường dẫn luồng chung & bù đắp cho việc thiếu lồng nhau- continue), v.v.
R. GitHub DỪNG TRỢ GIÚP ICE

29

Như các câu trả lời khác đã đề cập, điều này về mặt kỹ thuật được tiêu chuẩn cho phép, nhưng nó rất khó hiểu và không rõ ràng đối với những người đọc mã trong tương lai.

Đây là lý do tại sao các switch ... casecâu lệnh thường nên được viết bằng các lệnh gọi hàm chứ không phải nhiều mã nội tuyến.

switch(i) {
case 0:
    do_zero_case(); do_general_stuff(); break;
case 1:
    do_one_case(); do_general_stuff(); break;
case 2:
    do_general_stuff(); break;
default:
    do_default_not_zero_not_one_not_general_stuff(); break;
}

Lưu ý rằng mã trong câu hỏi không do_general_stuffdành cho mã mặc định, chỉ dành cho trường hợp 2 (và 0 và 1). Tuy nhiên, nó không bao giờ chạy công tắc cho một iphạm vi ngoài 0..2.
Peter Cordes

@PeterCordes - lưu ý rằng mã trong câu trả lời không do_general_stuffdành cho mã mặc định, chỉ dành cho trường hợp 2 (và 0 và 1).
Pete Becker

Một gợi ý - có thể trường hợp mặc định nên được xóa hoặc đổi tên? Khá dễ dàng để bỏ lỡ các tên chức năng khác nhau.
I.Am.A.Guy

@PeteBecker: Ồ, IDK sao tôi lại bỏ lỡ điều đó generaldefaultlà những từ khác nhau. OTOH, một thực tế đã biết là con người thường vẫn có thể đọc một từ nếu các chữ cái ở giữa bị xáo trộn; chúng ta có xu hướng xem xét điểm bắt đầu và kết thúc, vì vậy chỉ có sự khác biệt ở giữa có lẽ không lý tưởng cho khả năng đọc lướt. Có lẽ loại bỏ do_tiền tố.
Peter Cordes

1
@supercat: "Sự thật nổi tiếng" không phải là thay thế các chữ cái ở giữa bằng các chữ cái khác nhau, mà chỉ là trộn thứ tự của các chữ cái đó. "Nếu cialm đúng trong graneel, bạn sẽ mặc dù là tihs raed."
Michael Karcher
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.