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

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ánhexpression
với. Các giá trị này phải duy nhất.break;
: Dừng việc thực thi bên trongswitch
và thoát ra khỏi khốiswitch
. Đây là điểm thường gây lỗi nếu quên!default:
: Khối code được thực thi khiexpression
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ốiswitch
(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ếnluaChon
. - 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ệnhif-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òngwhile
(luaChon != 4
) trở thànhfalse
, làm vòng lặp kết thúc.
- Chúng ta sử dụng vòng lặp
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ểuchar
được sử dụng làm biểu thức choswitch
. - 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úpcase 'a'
xử lý được cả'a'
và'A'
, làm cho chương trình thân thiện hơn với người dùng.
- Biến
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 saucase 1:
mà không cóbreak
, khiday
là 1, code sẽ thực thi khối lệnh dướicase 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.
- 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
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ầncase 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ủaswitch
trong các trường hợp phức tạp hơn.
- Trực tiếp dùng
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ì và 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
trongswitch
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ủaenum 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 trongswitch
. 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.
- Chúng ta định nghĩa một
Các Lời Khuyên Khi Sử Dụng switch-case "Nâng Cao"
- Luôn luôn có
default
: Trừ khi bạn hoàn toàn chắc chắn rằng biếnexpression
sẽ luôn khớp với một trong cáccase
(ví dụ: xử lý tất cả các giá trị của mộtenum
đã định nghĩa kỹ), hãy luôn bao gồm khốidefault
để xử lý các trường hợp không mong muốn hoặc input không hợp lệ. - Đừng quên
break;
: Đây là lỗi phổ biến nhất khi dùngswitch
. 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. - Giữ cho code trong
case
ngắn gọn: Mỗi khốicase
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úcswitch
sạch sẽ và dễ đọc. - Sử dụng
enum
hoặcenum 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ằngenum
và sử dụng trongswitch
. - Hạn chế lồng
switch
: Mặc dù C++ cho phép, việc lồng các câu lệnhswitch
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