Bài 2.2: Cấu trúc rẽ nhánh switch-case trong C++
Bài 2.2: Cấu trúc rẽ nhánh switch-case trong C++
Chào mừng các bạn quay trở lại với loạt bài viết về Lập trình C++ cơ bản trên FullhouseDev!
Trong các bài học trước, chúng ta đã làm quen với cấu trúc rẽ nhánh if-else mạnh mẽ, cho phép chương trình đưa ra quyết định dựa trên các điều kiện khác nhau. Hôm nay, chúng ta sẽ khám phá một công cụ rẽ nhánh khác, cực kỳ hữu ích khi bạn cần xử lý nhiều trường hợp cụ thể dựa trên giá trị của một biểu thức duy nhất. Đó chính là cấu trúc switch-case.
Vậy switch-case là gì và tại sao chúng ta lại cần nó khi đã có if-else? Hãy cùng tìm hiểu nhé!
switch-case là gì?
Cấu trúc switch-case trong C++ được thiết kế để làm cho việc kiểm tra bằng nhau một biến hoặc một biểu thức với nhiều giá trị hằng khác nhau trở nên rõ ràng và dễ đọc hơn so với việc sử dụng chuỗi dài các câu lệnh if-else if-else.
Hãy tưởng tượng bạn cần viết một chương trình yêu cầu người dùng nhập một số từ 1 đến 7 để hiển thị ngày trong tuần. Bạn có thể dùng if-else if-else, nhưng với switch-case, code sẽ trực quan hơn rất nhiều.
Cú pháp cơ bản của switch-case
Cấu trúc switch-case có cú pháp như sau:
switch (expression) {
case constant_value1:
// Khối lệnh thực thi nếu expression == constant_value1
// ...
break;
case constant_value2:
// Khối lệnh thực thi nếu expression == constant_value2
// ...
break;
// ... Có thể có nhiều case khác
case constant_valueN:
// Khối lệnh thực thi nếu expression == constant_valueN
// ...
break;
default:
// Khối lệnh thực thi nếu expression không khớp với bất kỳ case nào
// ...
// break;
}
Hãy cùng phân tích các thành phần chính:
switch (expression): Đây là điểm bắt đầu của cấu trúc.expressionlà một biểu thức mà giá trị của nó sẽ được so sánh với các giá trị trong cáccasephía dưới. Quan trọng:expressionphải có kiểu dữ liệu là kiểu nguyên (integral type), ví dụ nhưint,char,enum,... hoặc các kiểu dữ liệu có thể được chuyển đổi ngầm định sang kiểu nguyên.case constant_valueX:: Đây là một nhãn (label).constant_valueXphải là một hằng số nguyên (constant integral expression). Chương trình sẽ nhảy đến khối lệnh ngay sau nhãncase constant_valueXnếu giá trị củaexpressionkhớp chính xác vớiconstant_valueX.break;: Đây là một từ khóa cực kỳ quan trọng. Khi câu lệnhbreak;được thực thi, chương trình sẽ thoát ra khỏi toàn bộ khốiswitch, tiếp tục thực thi các lệnh ngay sau dấu đóng ngoặc nhọn}củaswitch. Nếu thiếubreak, chương trình sẽ tiếp tục thực thi các lệnh trong cáccasetiếp theo (hiện tượng "fall-through"), cho đến khi gặpbreakhoặc kết thúc khốiswitch. Chúng ta sẽ nói kỹ hơn về điều này sau.default:: Đây là nhãn tùy chọn. Khối lệnh saudefaultsẽ được thực thi khi giá trị củaexpressionkhông khớp với bất kỳconstant_valuenào trong cáccaseđã liệt kê.defaultthường được đặt ở cuối khốiswitch, nhưng về mặt cú pháp, nó có thể đặt ở bất kỳ đâu.
switch-case Hoạt động như thế nào?
Khi chương trình gặp một câu lệnh switch:
- Biểu thức
expressiontrong dấu ngoặc đơnswitch(...)được tính toán giá trị. - Giá trị này lần lượt được so sánh với các
constant_valuetrong từngcase. - Ngay khi tìm thấy một
casecó giá trị khớp, chương trình sẽ nhảy (jump) đến dòng code ngay sau nhãncaseđó và bắt đầu thực thi. - Chương trình sẽ tiếp tục thực thi tất cả các câu lệnh từ điểm nhảy đó trở xuống, bỏ qua việc kiểm tra các nhãn
casetiếp theo, cho đến khi:- Gặp câu lệnh
break;. - Hoặc kết thúc khối
switch. - Hoặc gặp câu lệnh nhảy khác như
return.
- Gặp câu lệnh
- Nếu không có
casenào khớp với giá trị củaexpression, và có nhãndefault, chương trình sẽ nhảy đến khối lệnh saudefault. - Nếu không có
casenào khớp và cũng không có nhãndefault, chương trình sẽ không làm gì cả bên trong khốiswitchvà tiếp tục thực thi lệnh ngay sau khốiswitch.
Tầm quan trọng của break (và hiện tượng Fall-through)
Hãy nhấn mạnh lại: Từ khóa break là linh hồn của hầu hết các trường hợp sử dụng switch-case thông thường. Nó giúp bạn chỉ thực thi khối lệnh tương ứng với case khớp và sau đó thoát ra.
Nếu bạn quên đặt break; ở cuối một khối case, chương trình sẽ không thoát khỏi switch sau khi thực thi khối lệnh đó. Thay vào đó, nó sẽ "rơi xuyên qua" (fall-through) và tiếp tục thực thi các lệnh trong case tiếp theo (và tiếp theo nữa, cho đến khi gặp break hoặc kết thúc switch).
Hiện tượng "fall-through" này có thể có chủ đích trong một số trường hợp nhất định (ví dụ: để gộp nhiều case thực hiện cùng một hành động), nhưng thông thường nó là nguyên nhân của lỗi logic không mong muốn.
Hãy xem ví dụ về fall-through:
#include <iostream>
int main() {
int lc = 1;
cout << "Ban da chon so " << lc << ".\n";
switch (lc) {
case 1:
cout << "Executing case 1...\n";
case 2:
cout << "Executing case 2...\n";
break;
case 3:
cout << "Executing case 3...\n";
break;
default:
cout << "Executing default...\n";
}
cout << "Ket thuc switch.\n";
return 0;
}
Giải thích code:
- Biến
lua_chonđược gán giá trị1. - Câu lệnh
switch (lua_chon)so sánh giá trị1với cáccase. - Nó khớp với
case 1:. Chương trình nhảy đến đó. - In ra "Executing case 1...".
- Không có
break;! Chương trình không thoát, mà tiếp tục thực thi các lệnh trongcase 2. - In ra "Executing case 2...".
- Gặp
break;trongcase 2. Chương trình thoát khỏi toàn bộ khốiswitch. - In ra "Ket thuc switch.".
Output của chương trình này sẽ là:
Ban da chon so 1.
Executing case 1...
Executing case 2...
Ket thuc switch.
Bạn thấy đấy, vì thiếu break ở case 1, code của case 2 cũng bị thực thi! Đây là một lỗi rất phổ biến khi mới làm quen với switch-case. Hãy luôn nhớ đặt break; ở cuối mỗi case trừ khi bạn thực sự muốn fall-through có chủ đích.
Sử dụng default Case
Nhãn default hoạt động như một "ngăn chứa" cho tất cả các trường hợp mà expression không khớp với bất kỳ constant_value nào trong các case đã liệt kê. Nó tương tự như mệnh đề else trong cấu trúc if-else if-else.
Việc sử dụng default là một thực hành tốt vì nó giúp xử lý các giá trị không mong muốn hoặc không hợp lệ, làm cho chương trình của bạn robust hơn.
Ví dụ với default:
#include <iostream>
int main() {
int ngay;
cout << "Nhap mot so (1-7): ";
cin >> ngay;
switch (ngay) {
case 1:
cout << "Hom nay la Chu Nhat.\n";
break;
case 2:
cout << "Hom nay la Thu Hai.\n";
break;
case 3:
cout << "Hom nay la Thu Ba.\n";
break;
case 4:
cout << "Hom nay la Thu Tu.\n";
break;
case 5:
cout << "Hom nay la Thu Nam.\n";
break;
case 6:
cout << "Hom nay la Thu Sau.\n";
break;
case 7:
cout << "Hom nay la Thu Bay.\n";
break;
default:
cout << "So ban nhap khong hop le! Vui long nhap so tu 1 den 7.\n";
}
return 0;
}
Output:
Nhap mot so (1-7): 3
Hom nay la Thu Ba.
---
Nhap mot so (1-7): 8
So ban nhap khong hop le! Vui long nhap so tu 1 den 7.
Giải thích code:
- Chương trình yêu cầu người dùng nhập một số.
- Giá trị nhập vào được đưa vào
switch. - Nếu người dùng nhập 1, 2, ..., 7, chương trình sẽ in ra ngày tương ứng và thoát nhờ
break;. - Nếu người dùng nhập một số khác (ví dụ: 0, 8, -5), không có
casenào khớp. - Chương trình nhảy đến
defaultvà in ra thông báo lỗi.
Các Ví dụ Minh Họa Khác
Ví dụ 1: Phân loại Điểm số bằng char
switch-case không chỉ hoạt động với int, nó còn hoạt động tốt với char vì char cũng là một kiểu nguyên.
#include <iostream>
int main() {
char dc;
cout << "Nhap diem chu cua ban (A, B, C, D, F): ";
cin >> dc;
switch (dc) {
case 'A':
cout << "Xuat sac!\n";
break;
case 'B':
cout << "Gioi!\n";
break;
case 'C':
cout << "Kha.\n";
break;
case 'D':
cout << "Trung binh.\n";
break;
case 'F':
cout << "Truot.\n";
break;
default:
cout << "Ky tu diem khong hop le.\n";
}
return 0;
}
Output:
Nhap diem chu cua ban (A, B, C, D, F): B
Gioi!
---
Nhap diem chu cua ban (A, B, C, D, F): G
Ky tu diem khong hop le.
Giải thích code:
- Biến
diem_chukiểucharđược sử dụng làm biểu thức trongswitch. - Các nhãn
casesử dụng các ký tự hằng ('A', 'B', v.v.) để so sánh. - Chương trình in ra kết quả tương ứng dựa trên điểm chữ được nhập.
Ví dụ 2: Sử dụng Fall-through Có Chủ Đích
Như đã đề cập, đôi khi bạn muốn nhiều case thực hiện cùng một khối lệnh. Thay vì lặp lại code, bạn có thể sử dụng fall-through bằng cách không đặt break; ở cuối các case đó.
Ví dụ: Phân loại xem một ký tự là nguyên âm hay phụ âm (đơn giản, không xét các trường hợp đặc biệt).
#include <iostream>
int main() {
char kt;
cout << "Nhap mot ky tu chu cai: ";
cin >> kt;
switch (kt) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'A':
case 'E':
case 'I':
case 'O':
case 'U':
cout << kt << " la mot nguyen am.\n";
break;
default:
cout << kt << " la mot phu am (hoac khong phai chu cai).\n";
}
return 0;
}
Output:
Nhap mot ky tu chu cai: O
O la mot nguyen am.
---
Nhap mot ky tu chu cai: t
t la mot phu am (hoac khong phai chu cai).
Giải thích code:
- Nếu
ky_tulà 'a', nó khớp vớicase 'a':. Không cóbreak, nên chương trình fall-through xuốngcase 'e':, rồi 'i', 'o', 'u', 'A', 'E', 'I', 'O', 'U'. Cuối cùng, nó gặpbreak;saucase 'U':và thực thicout << ky_tu << " la mot nguyen am.\n";một lần duy nhất. - Nếu
ky_tukhông khớp với bất kỳ nguyên âm nào, chương trình nhảy đếndefaultvà in ra thông báo đó là phụ âm (hoặc không phải chữ cái). - Cách này giúp tránh lặp lại câu lệnh
cout << ky_tu << " la mot nguyen am.\n";cho mỗi nguyên âm.
So sánh switch-case và if-else if-else
Khi nào nên dùng switch-case và khi nào nên dùng if-else if-else?
Sử dụng
switch-casekhi:- Bạn đang so sánh một biến duy nhất hoặc một biểu thức với nhiều giá trị hằng số nguyên (integral constants) khác nhau.
- Code của bạn sẽ rõ ràng và dễ đọc hơn rất nhiều khi có nhiều hơn một vài trường hợp để kiểm tra sự bằng nhau.
Sử dụng
if-else if-elsekhi:- Bạn cần kiểm tra các điều kiện phức tạp (ví dụ:
x > 10 && y < 20). - Bạn cần kiểm tra các khoảng giá trị (ví dụ:
diem >= 8.0 && diem < 9.0). - Bạn cần so sánh với các giá trị không phải hằng số nguyên (ví dụ: chuỗi
string, số thựcdouble). - Các điều kiện kiểm tra dựa trên nhiều biến khác nhau.
- Bạn cần kiểm tra các điều kiện phức tạp (ví dụ:
Nói cách khác, switch-case là một công cụ chuyên biệt, tối ưu cho việc rẽ nhánh dựa trên sự bằng nhau của một giá trị duy nhất với các hằng số, còn if-else if-else là cấu trúc rẽ nhánh tổng quát hơn, có thể xử lý mọi loại điều kiện.
Những Điều Cần Lưu Ý Khi Sử Dụng switch-case
- Kiểu dữ liệu: Biểu thức trong
switchvà các giá trị trongcasephải là kiểu nguyên hoặc có thể chuyển đổi sang kiểu nguyên. Bạn không thể sử dụng trực tiếpfloat,double,string(trừ C++11+ với cách tiếp cận phức tạp hơn hoặc các thủ thuật, nhưng thườngif-elsehoặc cấu trúc khác sẽ phù hợp hơn) trongswitch-casetheo cách thông thường. - Giá trị
casephải là hằng: Giá trị saucasephải là một hằng số nguyên (một giá trị cố định được biết tại thời điểm biên dịch). Bạn không thể sử dụng một biến (case bien_x:) hoặc một biểu thức phức tạp (case a + b:) làm nhãncase. - Tính duy nhất: Tất cả các giá trị trong các nhãn
casecủa cùng một khốiswitchphải là duy nhất. Trình biên dịch sẽ báo lỗi nếu có haicasecùng giá trị. - Khối lệnh trong
case: Mộtcasecó thể chứa nhiều câu lệnh. Bạn không cần phải đặt các câu lệnh này trong dấu ngoặc nhọn{}trừ khi bạn muốn khai báo một biến cục bộ chỉ tồn tại trongcaseđó (nhưng ngay cả khi đó, tốt nhất nên đặt toàn bộ khối code trong{}để tránh lỗi). Tuy nhiên, đối với cáccaseđơn giản chỉ có vài dòng, việc bỏ qua{}là phổ biến.
Bài tập ví dụ: C++ Bài 2.A2: Phương Trình Bậc Nhất
Hãy lập trình giải phương trình \(ax+b = 0\) với \(a\) và \(b\) nguyên nhập vào từ bàn phím. Kết quả làm tròn 2 chữ số thập phân.
INPUT FORMAT
Dòng đầu tiên chứa giá trị của hai số nguyên \(a, b\)
OUTPUT FORMAT
In ra kết quả của bài toán theo ví dụ bên dưới.
Ví dụ 1:
Input
0 1
Ouput
VO NGHIEM
Ví dụ 2:
Input
1 2
Output
PT CO NGHIEM
X = -2.00
Ví dụ 3:
Input
0 0
Output
VO SO NGHIEM
Giải thích ví dụ mẫu:
- Ví dụ 1: Với
a = 0vàb = 1, phương trình không có nghiệm, nên inVO NGHIEM. - Ví dụ 2: Với
a = 1vàb = 2, phương trình có nghiệm-2.00, nên inPT CO NGHIEMvàX = -2.00. - Ví dụ 3: Với
a = 0vàb = 0, phương trình có vô số nghiệm, nên inVO SO NGHIEM.
Bài toán yêu cầu giải phương trình ax + b = 0 với a và b là các số nguyên nhập từ bàn phím. Ta cần xét các trường hợp có thể xảy ra dựa vào giá trị của a:
Trường hợp
akhác 0 (a != 0):- Phương trình có dạng
ax = -b. - Trong trường hợp này, phương trình có nghiệm duy nhất là
x = -b / a. - Vì
avàblà số nguyên, phép chia-b / acó thể cho kết quả không nguyên. Do đó, bạn cần thực hiện phép chia này với kiểu dữ liệu dấu phẩy động (nhưdouble) để có kết quả chính xác. Lưu ý ép kiểu (cast) ít nhất một trong hai toán hạng trước khi chia để tránh phép chia số nguyên. - Kết quả cần làm tròn đến 2 chữ số thập phân. Bạn sẽ cần sử dụng các công cụ định dạng output của C++.
- In ra "PT CO NGHIEM" trên một dòng, sau đó in "X = " theo định dạng đã làm tròn.
- Phương trình có dạng
Trường hợp
abằng 0 (a == 0):- Khi
a = 0, phương trình trở thành0 * x + b = 0, tức làb = 0. - Xét tiếp giá trị của
b:- Nếu
bcũng bằng 0 (b == 0): Phương trình trở thành0 = 0. Điều này đúng với mọi giá trị củax. Phương trình có vô số nghiệm.- In ra "VO SO NGHIEM".
- Nếu
bkhác 0 (b != 0): Phương trình trở thànhb = 0, nhưng ta đang xét trường hợpb != 0. Điều này là vô lý, không có giá trịxnào thỏa mãn. Phương trình vô nghiệm.- In ra "VO NGHIEM".
- Nếu
- Khi
Hướng dẫn các bước thực hiện:
- Bao gồm các thư viện cần thiết:
iostreamcho việc nhập xuất vàiomanipcho việc định dạng output (làm tròn số thập phân). - Trong hàm
main, khai báo hai biến kiểuintđể lưu giá trị củaavàb. - Sử dụng
cinđể đọc hai số nguyênavàbtừ input. - Sử dụng cấu trúc điều khiển
if-else if-elsehoặc lồngifđể xử lý các trường hợp:- Kiểm tra
if (a == 0). - Bên trong khối
if (a == 0), kiểm tra tiếpif (b == 0). - Sử dụng
elsecho trường hợp còn lại khia == 0(tức làb != 0). - Sử dụng
elsecho trường hợpa != 0.
- Kiểm tra
- Trong từng nhánh của cấu trúc điều khiển, in ra kết quả tương ứng ("VO SO NGHIEM", "VO NGHIEM", hoặc nghiệm duy nhất).
- Khi in nghiệm duy nhất (
a != 0), tính giá trịxbằng cách chia-bchoa. Đảm bảo sử dụng kiểu dữ liệudoublecho phép chia (ví dụ:double x = static_cast<double>(-b) / a;hoặc đơn giản hơndouble x = (double)-b / a;). - Trước khi in giá trị
x, thiết lập định dạng output chocoutbằng cách sử dụngfixedvàsetprecision(2)từ thư việniomanip. - In chuỗi "PT CO NGHIEM" rồi xuống dòng.
- In chuỗi "X = " rồi in giá trị
xđã được định dạng. - Kết thúc chương trình.
Lưu ý:
- Sử dụng
cout << ... << endl;hoặccout << ... << '\n';để xuống dòng sau mỗi thông báo hoặc giá trị in ra, đảm bảo đúng định dạng output. - Ưu tiên sử dụng các thành phần trong namespace
std(cin,cout,fixed,setprecision).
#include <iostream>
#include <iomanip> // Để sử dụng fixed và setprecision
int main() {
int a, b;
cin >> a >> b; // Đọc giá trị a và b
if (a == 0) { // Trường hợp a = 0
if (b == 0) { // Nếu b cũng bằng 0
cout << "VO SO NGHIEM\n";
} else { // Nếu b khác 0
cout << "VO NGHIEM\n";
}
} else { // Trường hợp a khác 0
double x = static_cast<double>(-b) / a; // Tính nghiệm x
cout << "PT CO NGHIEM\n";
cout << "X = " << fixed << setprecision(2) << x << "\n";
}
return 0;
}
Output (theo ví dụ mẫu): Input:
0 1
Output:
VO NGHIEM
Input:
1 2
Output:
PT CO NGHIEM
X = -2.00
Input:
0 0
Output:
VO SO NGHIEM
Comments