Menu

Switch case trong C: case, break và fall-through

Cách câu lệnh switch trong C chọn một nhánh từ danh sách hằng số: nhãn case, vì sao break quan trọng, fall-through cố ý và vô tình, default, switch trên char và enum, và khi nào switch tốt hơn chuỗi else-if.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Một giá trị, nhiều hằng số

Một chuỗi else if liên tục so sánh cùng một biến với các hằng số là mẫu code có hẳn một câu lệnh riêng trong C:

if (choice == 1)      { /* ... */ }
else if (choice == 2) { /* ... */ }
else if (choice == 3) { /* ... */ }
else                  { /* ... */ }

switch diễn đạt điều đó trực tiếp hơn. Nó tính biểu thức một lần, rồi nhảy thẳng tới nhãn khớp.

Hãy đổi choice rồi chạy lại. Cấu trúc này cho thấy ngay tập giá trị hợp lệ, và đó mới là lý do thật sự để chọn nó ở đây.

Cú pháp và quy tắc của nhãn case

switch (integerExpression) {
    case CONSTANT_1:
        statements
        break;
    case CONSTANT_2:
        statements
        break;
    default:
        statements
        break;
}

Bốn ràng buộc mà C bắt buộc:

  • Biểu thức của switch phải có kiểu số nguyên: int, char, short, long hoặc một enum. Không phải float, không phải double, và không phải chuỗi.
  • Mọi nhãn case phải là biểu thức hằng biết được lúc biên dịch. case n: với n là biến sẽ báo lỗi; case 3 + 4: và case MAX: (một #define hoặc hằng enum) thì hợp lệ.
  • Không được có hai case cùng giá trị. Giá trị trùng là lỗi biên dịch, một lưới an toàn nhỏ nhưng thực sự hữu ích.
  • Một case không thể biểu diễn khoảng giá trị. case 1 ... 5: là phần mở rộng của GCC, không thuộc chuẩn C; hãy dùng if cho khoảng giá trị.

default là tùy chọn và có thể đặt ở bất cứ đâu trong khối, dù gần như ai cũng theo quy ước đặt nó cuối cùng. Không có default, một giá trị không khớp sẽ đơn giản bỏ qua toàn bộ switch.

break: vì sao không thể bỏ

Điều gây bất ngờ nhất về switch trong C là nhãn case là đích nhảy, không phải những chiếc hộp. Khi chương trình đã nhảy tới một nhãn, nó cứ thế chạy qua mọi câu lệnh bên dưới, vượt qua cả các nhãn case khác, cho đến khi gặp break hoặc ngoặc nhọn đóng.

Switch thứ nhất in ra one, two và three: nó khớp case 1 rồi chạy mọi thứ phía sau. Switch thứ hai chỉ in ra one. Hành vi đó gọi là fall-through, và quên break là bug switch kinh điển trong C: chương trình sai một cách âm thầm thay vì không biên dịch được.

Biên dịch với gcc -Wall -Wextra -Wimplicit-fallthrough và trình biên dịch sẽ chỉ ra mọi case rơi xuống case tiếp theo.

break ở case cuối cùng về mặt kỹ thuật là thừa, vì khối kết thúc ngay đó. Dù vậy hãy cứ viết nó, để sau này thêm một case mới bên dưới sẽ không âm thầm tạo ra fall-through.

Fall-through có chủ ý

Fall-through là một tính năng khi nó có chủ ý. Xếp chồng các nhãn không có câu lệnh nào ở giữa là cách thông thường để nói "các giá trị này làm cùng một việc":

Ba nhãn dùng chung một thân, không lặp code và không cần điều kiện || dài dòng. Trình biên dịch không cảnh báo dạng này, vì không có câu lệnh nào để rơi qua.

Fall-through có câu lệnh ở giữa, khi case 1 chạy rồi cố ý chạy tiếp vào case 2, hiếm gặp hơn và cần một chú thích (comment) rõ ràng, vì người đọc phải được báo rằng việc thiếu break là có chủ đích:

switch (level) {
    case 3:
        printf("verbose\n");
        /* fall through */
    case 2:
        printf("info\n");
        /* fall through */
    case 1:
        printf("errors\n");
        break;
}

/* fall through */ là chú thích mà cảnh báo của GCC nhận ra, nên nó cũng tắt luôn cảnh báo đó.

Switch trên ký tự

Vì char là kiểu số nguyên, phân loại ký tự là trường hợp rất phù hợp:

Chú ý case 'a': dùng dấu nháy đơn: một hằng ký tự, thực chất chỉ là giá trị số nguyên của ký tự đó. case "a": sẽ là một chuỗi và không biên dịch được.

Switch trên enum

Cặp đôi tốt nhất trong C là switch với enum. Nhãn case trở thành những cái tên dễ đọc, và trình biên dịch có thể cảnh báo khi switch không xử lý hết mọi giá trị của enum:

Hai điều đáng học theo ở đây. Mỗi case đều return, nên không cần break: return thoát hẳn khỏi hàm, đồng thời cũng kết thúc switch. Và không có default: với -Wswitch (có sẵn trong -Wall), bỏ default giúp trình biên dịch cảnh báo vào ngày ai đó thêm trạng thái thứ năm mà quên sửa hàm này. Một default sẽ che mất điều đó.

Khai báo biến bên trong switch

Thân của switch là một khối duy nhất, nên một khai báo trong một case sẽ nhìn thấy được, dù không nhất thiết đã được khởi tạo, ở các case phía sau. Nhảy qua một lệnh khởi tạo là lỗi trong C:

switch (n) {
    case 1:
        int x = 10;        // error in C: a label cannot precede a declaration this way
        printf("%d\n", x);
        break;
    case 2:
        printf("%d\n", x); // x exists here, but was never initialised
        break;
}

Hãy bọc case trong cặp ngoặc nhọn riêng khi nó cần biến cục bộ:

switch (n) {
    case 1: {
        int x = 10;
        printf("%d\n", x);
        break;
    }
    case 2:
        printf("two\n");
        break;
}

Switch hay if-else?

Hãy dùng switch khi tất cả các điều sau đều đúng: bạn kiểm tra một biểu thức, biểu thức đó có kiểu số nguyên, và bạn so sánh nó với các hằng số cố định. Xử lý menu, máy trạng thái dùng enum, phân loại ký tự và điều phối lệnh là những trường hợp điển hình. Lợi ích là code dễ đọc, kiểm tra giá trị trùng lặp, và trình biên dịch có thể cảnh báo về các giá trị enum chưa được xử lý.

Hãy dùng if-else khi phép kiểm tra liên quan đến khoảng giá trị (score >= 90), số thực, chuỗi (bạn cần strcmp), nhiều biến khác nhau, hoặc các điều kiện kết hợp bằng && và ||. Không điều nào trong số đó viết được thành nhãn case.

Hiệu năng hiếm khi là yếu tố quyết định. Trình biên dịch biến một switch dày đặc thành bảng nhảy và một switch thưa thành các phép so sánh, nên với vài case thì hai dạng không khác gì nhau. Hãy chọn dạng thể hiện đúng ý định.

Câu hỏi thường gặp

Câu lệnh switch trong C hoạt động thế nào?

switch (expr) tính một biểu thức số nguyên đúng một lần, nhảy tới nhãn case có hằng số khớp, và chạy từ đó cho đến khi gặp break hoặc hết khối switch. Nếu không có gì khớp, nó nhảy tới default nếu có, nếu không thì bỏ qua toàn bộ khối.

Vì sao cần break trong switch của C?

Vì nhãn case là đích nhảy, không phải các khối riêng biệt. Không có break, chương trình chạy thẳng vào các câu lệnh của case tiếp theo: hiện tượng này gọi là fall-through. Thiếu break là bug switch phổ biến nhất trong C, và gcc -Wimplicit-fallthrough sẽ cảnh báo nó.

Có thể dùng switch với chuỗi trong C không?

Không. switch trong C yêu cầu một biểu thức số nguyên, nên giá trị int, char và enum dùng được, còn chuỗi, số thực và khoảng giá trị thì không. Để rẽ nhánh theo chuỗi, hãy dùng strcmp trong chuỗi if-else, hoặc ánh xạ chuỗi sang một enum trước rồi switch trên enum đó.

Khi nào nên dùng switch thay cho if-else trong C?

Dùng switch khi bạn so sánh một số nguyên hoặc một ký tự với danh sách hằng số cố định: lệnh trong menu, trạng thái enum, phân loại ký tự. Dùng chuỗi if-else khi phép kiểm tra liên quan đến khoảng giá trị, nhiều biến, hoặc bất cứ điều gì khác ngoài so sánh bằng với một hằng số biết lúc biên dịch.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu