Bài 4.4: Bài tập thực hành rẽ nhánh switch-case nâng cao trong C++

Chào mừng bạn đến với chuỗi bài viết về C++ của FullhouseDev! Hôm nay, chúng ta sẽ cùng nhau đào sâu hơn vào một trong những cấu trúc điều khiển cơ bản nhưng cực kỳ mạnh mẽ và linh hoạt: câu lệnh switch-case.

Ở các bài trước, có thể bạn đã làm quen với cách sử dụng if-else if-else để xử lý nhiều điều kiện. switch-case là một lựa chọn thay thế tuyệt vời khi bạn cần so sánh một biến duy nhất với nhiều giá trị hằng số khác nhau. Nó giúp code của bạn trông gọn gàng, dễ đọc và trong nhiều trường hợp, có thể tối ưu hơn về hiệu suất so với chuỗi if-else if dài.

Bài viết này không chỉ dừng lại ở cú pháp cơ bản, mà chúng ta sẽ cùng nhau thực hành các tình huống phức tạp hơn một chút, nơi mà việc áp dụng switch-case một cách khéo léo sẽ mang lại hiệu quả bất ngờ. Hãy cùng bắt đầu nào!

Nhắc Lại Ngắn Gọn Về switch-case Cơ Bản

Trước khi đi vào "nâng cao", hãy cùng điểm lại cấu trúc cơ bản của switch-case:

switch (expression) {
    case value1:
        // Code thực thi khi expression == value1
        // ...
        break; // Rất quan trọng để thoát khỏi switch
    case value2:
        // Code thực thi khi expression == value2
        // ...
        break;
    // ... nhiều case khác ...
    default:
        // Code thực thi khi expression không khớp với bất kỳ case nào ở trên
        // ...
        break; // break ở default là tùy chọn, nhưng nên dùng để đồng nhất
}
  • expression: Biểu thức hoặc biến mà bạn muốn so sánh. Kết quả của nó phải là một kiểu dữ liệu nguyên (integer type), ký tự (char), hoặc enum. Kể từ C++11, bạn có thể dùng enum class.
  • case valueN:: valueN là các hằng số (literal) hoặc biểu thức hằng (constant expression) mà bạn muốn so sánh expression với. Các giá trị này phải duy nhất.
  • break;: Dừng việc thực thi bên trong switch và thoát ra khỏi khối switch. Đây là điểm thường gây lỗi nếu quên!
  • default:: Khối code được thực thi khi expression không khớp với bất kỳ case nào. Nó là tùy chọn và có thể đặt ở bất kỳ đâu trong khối switch (nhưng thường đặt ở cuối).

Lưu ý quan trọng: Nếu bạn quên break, code sẽ "rơi xuống" (fall through) và thực thi các case tiếp theo cho đến khi gặp break hoặc kết thúc khối switch. Đôi khi đây là hành vi mong muốn (chúng ta sẽ xem ví dụ sau), nhưng thường thì đó là một lỗi logic.

Thực Hành "Nâng Cao" Với switch-case

Vậy thế nào là "nâng cao" ở đây? Không phải là cú pháp phức tạp, mà là cách chúng ta áp dụng switch-case để giải quyết các vấn đề thực tế một cách hiệu quả và thanh lịch hơn, đặc biệt là khi kết hợp nó với các cấu trúc khác hoặc xử lý các loại dữ liệu khác nhau một cách gián tiếp.

Hãy cùng đi qua một vài ví dụ minh họa sinh động:

Ví dụ 1: Xử lý Menu Tương Tác Đơn Giản

Một trong những ứng dụng phổ biến nhất của switch-case là xử lý các lựa chọn từ menu. Thay vì dùng nhiều if-else if, switch làm cho code trở nên cực kỳ rõ ràng.

#include <iostream>

int main() {
    cout << "--- MENU CHUONG TRINH ---" << endl;
    cout << "1. Them moi" << endl;
    cout << "2. Hien thi" << endl;
    cout << "3. Tim kiem" << endl;
    cout << "4. Thoat" << endl;
    cout << "-------------------------" << endl;
    cout << "Nhap lua chon cua ban: ";

    int luaChon;
    cin >> luaChon;

    switch (luaChon) {
        case 1:
            cout << "* Ban da chon: Them moi *" << endl;
            // Goi ham xu ly them moi du lieu
            break;
        case 2:
            cout << "* Ban da chon: Hien thi *" << endl;
            // Goi ham xu ly hien thi du lieu
            break;
        case 3:
            cout << "* Ban da chon: Tim kiem *" << endl;
            // Goi ham xu ly tim kiem du lieu
            break;
        case 4:
            cout << "* Ban da chon: Thoat chuong trinh *" << endl;
            // Thuc hien cac buoc thoat (luu du lieu truoc khi thoat...)
            break;
        default:
            cout << "* Lua chon khong hop le. Vui long nhap lai. *" << endl;
            // Thong bao loi cho nguoi dung
            break;
    }

    return 0;
}
  • Giải thích:
    • Chúng ta hiển thị một menu với các tùy chọn được đánh số.
    • Người dùng nhập số tương ứng với lựa chọn của họ.
    • Câu lệnh switch (luaChon) kiểm tra giá trị của biến luaChon.
    • Mỗi case tương ứng với một tùy chọn số từ menu.
    • default xử lý trường hợp người dùng nhập một số không nằm trong các tùy chọn hợp lệ.
    • Việc sử dụng switch ở đây giúp code trực quan hơn nhiều so với việc viết 4-5 câu lệnh if-else if.
Ví dụ 2: Kết hợp với Vòng Lặp để Tạo Menu Chạy Liên Tục

Để làm cho menu ở ví dụ 1 hữu dụng hơn, chúng ta thường đặt switch bên trong một vòng lặp (ví dụ: while) để chương trình chạy liên tục cho đến khi người dùng chọn thoát.

#include <iostream>

int main() {
    int luaChon = 0; // Khoi tao gia tri de vao vong lap

    while (luaChon != 4) { // Vong lap tiep tuc cho den khi luaChon la 4
        cout << "\n--- MENU CHUONG TRINH ---" << endl;
        cout << "1. Them moi" << endl;
        cout << "2. Hien thi" << endl;
        cout << "3. Tim kiem" << endl;
        cout << "4. Thoat" << endl;
        cout << "-------------------------" << endl;
        cout << "Nhap lua chon cua ban: ";

        cin >> luaChon;

        switch (luaChon) {
            case 1:
                cout << "* Ban da chon: Them moi *" << endl;
                // Logic them moi
                break;
            case 2:
                cout << "* Ban da chon: Hien thi *" << endl;
                // Logic hien thi
                break;
            case 3:
                cout << "* Ban da chon: Tim kiem *" << endl;
                // Logic tim kiem
                break;
            case 4:
                cout << "* Ban da chon: Thoat chuong trinh *" << endl;
                // Logic thoat (vong lap se ket thuc sau khi case nay chay xong)
                break;
            default:
                cout << "* Lua chon khong hop le. Vui long nhap lai. *" << endl;
                // Logic xu ly loi
                break;
        }
    }

    cout << "Chuong trinh da ket thuc. Tam biet!" << endl;

    return 0;
}
  • Giải thích:
    • Chúng ta sử dụng vòng lặp while (luaChon != 4) để liên tục hiển thị menu và xử lý lựa chọn.
    • Mỗi lần lặp, chương trình chờ người dùng nhập lựa chọn.
    • switch xử lý lựa chọn như trước.
    • Khi người dùng nhập 4, case 4 được thực thi, và sau đó điều kiện của vòng while (luaChon != 4) trở thành false, làm vòng lặp kết thúc.
Ví dụ 3: Sử dụng Ký Tự (char) trong switch-case

Không chỉ số nguyên, switch-case cũng hoạt động tuyệt vời với kiểu ký tự (char). Điều này hữu ích khi bạn muốn người dùng nhập các lệnh đơn giản bằng chữ cái (ví dụ: 'a' để thêm, 'q' để thoát).

#include <iostream>
#include <cctype> // De su dung tolower

int main() {
    char command;

    cout << "Nhap lenh ('A' them, 'D' xoa, 'Q' thoat): ";
    cin >> command;

    // Chuyen ky tu sang chu thuong de xu ly ca 'A' va 'a'
    command = tolower(command);

    switch (command) {
        case 'a':
            cout << "Lenh them moi duoc thuc thi." << endl;
            break;
        case 'd':
            cout << "Lenh xoa duoc thuc thi." << endl;
            break;
        case 'q':
            cout << "Dang thoat chuong trinh." << endl;
            break;
        default:
            cout << "Lenh khong hop le." << endl;
            break;
    }

    return 0;
}
  • Giải thích:
    • Biến command kiểu char được sử dụng làm biểu thức cho switch.
    • Các case sử dụng các ký tự hằng (ví dụ: 'a').
    • Chúng ta sử dụng tolower() để chuyển ký tự nhập vào thành chữ thường. Điều này giúp case 'a' xử lý được cả 'a''A', làm cho chương trình thân thiện hơn với người dùng.
Ví dụ 4: Xử lý Nhiều Case Cùng Một Khối Lệnh (Fall-through có chủ đích)

Như đã đề cập, việc quên break có thể gây lỗi. Tuy nhiên, đôi khi bạn cố ý để code "rơi xuống" (fall-through) để thực hiện cùng một khối lệnh cho nhiều case. Điều này hữu ích khi nhiều giá trị đầu vào dẫn đến cùng một hành động.

Lưu ý: Khi sử dụng fall-through có chủ đích, nên thêm bình luận để người đọc code hiểu rằng đây không phải là lỗi quên break.

#include <iostream>

int main() {
    int day;

    cout << "Nhap mot ngay trong tuan (1-7): ";
    cin >> day;

    switch (day) {
        case 1: // Chu nhat
        case 7: // Thu bay
            cout << "Day la ngay cuoi tuan! Rat tuyet!" << endl;
            break; // Break sau khi xu ly ca case 1 va case 7
        case 2: // Thu 2
        case 3: // Thu 3
        case 4: // Thu 4
        case 5: // Thu 5
        case 6: // Thu 6
            cout << "Day la ngay trong tuan. Co gang lam viec nao!" << endl;
            break; // Break sau khi xu ly tu case 2 den case 6
        default:
            cout << "Du lieu nhap khong hop le." << endl;
            break;
    }

    return 0;
}
  • Giải thích:
    • Chúng ta muốn in ra thông báo "cuối tuần" cho cả ngày 1 (Chủ Nhật) và ngày 7 (Thứ Bảy). Bằng cách đặt case 7: ngay sau case 1: mà không có break, khi day là 1, code sẽ thực thi khối lệnh dưới case 7: (vì nó "rơi xuống").
    • Tương tự, các ngày từ 2 đến 6 đều "rơi xuống" đến case 6: và thực thi cùng một khối lệnh.
    • Đây là một cách ngắn gọn để xử lý nhiều giá trị đầu vào có cùng một hành động.
Ví dụ 5: Xử lý Giá Trị Nằm Trong Khoảng (Sử dụng switch-case một cách gián tiếp)

switch-case trong C++ không hỗ trợ trực tiếp các khoảng giá trị (như case 1..10:). Tuy nhiên, chúng ta có thể "lách" bằng cách biến đổi giá trị đầu vào thành một giá trị rời rạc (discrete value) trước khi đưa vào switch.

Ví dụ: Phân loại điểm số học sinh theo thang 10. Chúng ta có thể chia điểm cho 10 để nhóm chúng lại.

#include <iostream>

int main() {
    int score;

    cout << "Nhap diem so (0-10): ";
    cin >> score;

    // Kiem tra diem so co hop le trong khoang 0-10 khong
    if (score < 0 || score > 10) {
        cout << "Diem khong hop le!" << endl;
    } else {
        // Chia diem cho 10 de nhom:
        // 0-9 -> 0
        // 10  -> 1
        // (Note: Voi thang diem 10, cach chia nay hoi don gian,
        // mot cach khac co the la chia theo khoang 0-4, 5, 6, 7, 8, 9, 10)
        // Hay thu mot cach phan loai theo muc diem pho bien hon:
        int category;
        if (score >= 9) {
            category = 4; // Gioi (9-10)
        } else if (score >= 7) {
            category = 3; // Kha (7-8)
        } else if (score >= 5) {
            category = 2; // Trung binh (5-6)
        } else if (score >= 3) {
             category = 1; // Yeu (3-4)
        }
        else {
            category = 0; // Kem (0-2)
        }


        switch (category) {
            case 4:
                cout << "Xep loai: Gioi" << endl;
                break;
            case 3:
                cout << "Xep loai: Kha" << endl;
                break;
            case 2:
                cout << "Xep loai: Trung binh" << endl;
                break;
             case 1:
                cout << "Xep loai: Yeu" << endl;
                break;
            case 0:
                cout << "Xep loai: Kem" << endl;
                break;
            // default o day se khong bao gio xay ra vi da kiem tra range truoc
        }
    }

    return 0;
}
  • Giải thích:
    • Trực tiếp dùng switch (score) sẽ cần case 0:, case 1:, ..., case 10:, rất dài.
    • Cách "nâng cao" hơn là dùng một vài câu lệnh if-else if ban đầu để phân loại điểm số vào các nhóm (categories) rời rạc (0, 1, 2, 3, 4).
    • Sau đó, chúng ta dùng switch (category) để xử lý các nhóm rời rạc này.
    • Cách này không thay thế hoàn toàn if-else if cho việc kiểm tra khoảng, nhưng nó cho thấy cách bạn có thể kết hợp hoặc biến đổi dữ liệu để tận dụng lợi thế của switch trong các trường hợp phức tạp hơn.
Ví dụ 6: Sử dụng Enum với switch-case

Sử dụng enum (hoặc enum class từ C++11) với switch-case là một thực hành tốt (best practice) giúp code dễ đọc, dễ bảo trìan toàn hơn (trình biên dịch có thể cảnh báo nếu bạn quên xử lý một giá trị enum nào đó trong switch).

#include <iostream>

// Dinh nghia mot enum class cho cac trang thai
enum class Status {
    Pending,    // 0 (theo mac dinh)
    Processing, // 1
    Completed,  // 2
    Failed      // 3
};

int main() {
    Status currentStatus = Status::Processing; // Gia su trang thai hien tai

    switch (currentStatus) {
        case Status::Pending:
            cout << "Trang thai: Dang cho xu ly." << endl;
            break;
        case Status::Processing:
            cout << "Trang thai: Dang xu ly." << endl;
            break;
        case Status::Completed:
            cout << "Trang thai: Hoan thanh." << endl;
            break;
        case Status::Failed:
            cout << "Trang thai: That bai." << endl;
            break;
        // Khong can default neu da xu ly het cac gia tri enum va khong co gia tri nao khac co the xay ra
    }

    // Vi du khac, nhap tu nguoi dung (can chuyen doi tu int sang enum)
    int userInput;
    cout << "Nhap trang thai (0: Pending, 1: Processing, 2: Completed, 3: Failed): ";
    cin >> userInput;

    // Chuyen doi int sang enum (can than voi input khong hop le)
    Status userStatus = static_cast<Status>(userInput);

    switch (userStatus) {
        case Status::Pending:
            cout << "Ban da nhap: Pending." << endl;
            break;
        case Status::Processing:
            cout << "Ban da nhap: Processing." << endl;
            break;
        case Status::Completed:
            cout << "Ban da nhap: Completed." << endl;
            break;
        case Status::Failed:
            cout << "Ban da nhap: Failed." << endl;
            break;
        default: // Nen co default khi input den tu ben ngoai co the khong hop le
             cout << "Input trang thai khong hop le." << endl;
            break;
    }


    return 0;
}
  • Giải thích:
    • Chúng ta định nghĩa một enum class gọi là Status với các trạng thái có ý nghĩa.
    • Sử dụng biến kiểu Status trong switch làm cho code tự mô tả hơn nhiều so với việc sử dụng các số nguyên "ma thuật" (magic numbers) như 0, 1, 2, 3.
    • Các case sử dụng các thành viên của enum class (ví dụ: Status::Processing).
    • Ví dụ thứ hai cho thấy cách bạn có thể đọc input là số nguyên từ người dùng và chuyển đổi nó sang kiểu enum trước khi dùng trong switch. Luôn cẩn thận với input không hợp lệ và nên có default trong trường hợp này.

Các Lời Khuyên Khi Sử Dụng switch-case "Nâng Cao"

  1. Luôn luôn có default: Trừ khi bạn hoàn toàn chắc chắn rằng biến expression sẽ luôn khớp với một trong các case (ví dụ: xử lý tất cả các giá trị của một enum đã định nghĩa kỹ), hãy luôn bao gồm khối default để xử lý các trường hợp không mong muốn hoặc input không hợp lệ.
  2. Đừng quên break;: Đây là lỗi phổ biến nhất khi dùng switch. Hãy kiểm tra kỹ. Nếu bạn cố ý fall-through, hãy thêm bình luận (// fall-through hoặc tương tự) để làm rõ ý định.
  3. Giữ cho code trong case ngắn gọn: Mỗi khối case lý tưởng chỉ nên chứa một vài dòng lệnh hoặc gọi một hàm khác để thực hiện công việc phức tạp. Điều này giúp giữ cấu trúc switch sạch sẽ và dễ đọc.
  4. Sử dụng enum hoặc enum class: Đối với các tập hợp giá trị rời rạc có ý nghĩa (như trạng thái, loại đối tượng, mã lỗi), hãy định nghĩa chúng bằng enum và sử dụng trong switch.
  5. Hạn chế lồng switch: Mặc dù C++ cho phép, việc lồng các câu lệnh switch vào nhau có thể làm code rất khó hiểu và khó theo dõi. Thường có những cách tổ chức code tốt hơn cho các logic phức tạp.

Comments

There are no comments at the moment.