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; // <-- RẤT QUAN TRỌNG!
case constant_value2:
// Khối lệnh thực thi nếu expression == constant_value2
// ...
break; // <-- RẤT QUAN TRỌNG!
// ... 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; // break ở đây thường không cần thiết nhưng cũng không hại gì
}
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.expression
là 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áccase
phía dưới. Quan trọng:expression
phả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_valueX
phả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_valueX
nếu giá trị củaexpression
khớ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áccase
tiếp theo (hiện tượng "fall-through"), cho đến khi gặpbreak
hoặ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 saudefault
sẽ được thực thi khi giá trị củaexpression
không khớp với bất kỳconstant_value
nào trong cáccase
đã liệt kê.default
thườ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
expression
trong 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_value
trong từngcase
. - Ngay khi tìm thấy một
case
có 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
case
tiế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ó
case
nà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ó
case
nà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ốiswitch
và 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 lua_chon = 1;
cout << "Ban da chon so " << lua_chon << ".\n";
switch (lua_chon) {
case 1:
cout << "Executing case 1...\n";
// OH NO! Quên break ở đây!
case 2:
cout << "Executing case 2...\n";
break; // Gap break, thoat switch
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ị1
vớ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_trong_tuan;
cout << "Nhap mot so (1-7): ";
cin >> ngay_trong_tuan;
switch (ngay_trong_tuan) {
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";
// break; // break ở đây không cần thiết vì default là cuối cùng
}
return 0;
}
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ó
case
nào khớp. - Chương trình nhảy đến
default
và 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 diem_chu;
cout << "Nhap diem chu cua ban (A, B, C, D, F): ";
cin >> diem_chu;
switch (diem_chu) {
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;
}
Giải thích code:
- Biến
diem_chu
kiểuchar
được sử dụng làm biểu thức trongswitch
. - Các nhãn
case
sử 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 ky_tu;
cout << "Nhap mot ky tu chu cai: ";
cin >> ky_tu;
switch (ky_tu) {
case 'a':
case 'e':
case 'i':
case 'o':
case 'u':
case 'A': // Thêm cả trường hợp chữ hoa
case 'E':
case 'I':
case 'O':
case 'U':
cout << ky_tu << " la mot nguyen am.\n";
break; // Break sau khi đã kiểm tra tất cả nguyên âm
default:
cout << ky_tu << " la mot phu am (hoac khong phai chu cai).\n";
// break; // break không cần thiết
}
return 0;
}
Giải thích code:
- Nếu
ky_tu
là '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_tu
không khớp với bất kỳ nguyên âm nào, chương trình nhảy đếndefault
và 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-case
khi:- 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-else
khi:- 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
switch
và các giá trị trongcase
phả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-else
hoặc cấu trúc khác sẽ phù hợp hơn) trongswitch-case
theo cách thông thường. - Giá trị
case
phải là hằng: Giá trị saucase
phả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
case
của cùng một khốiswitch
phải là duy nhất. Trình biên dịch sẽ báo lỗi nếu có haicase
cùng giá trị. - Khối lệnh trong
case
: Mộtcase
có 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 = 0
vàb = 1
, phương trình không có nghiệm, nên inVO NGHIEM
. - Ví dụ 2: Với
a = 1
vàb = 2
, phương trình có nghiệm-2.00
, nên inPT CO NGHIEM
vàX = -2.00
. - Ví dụ 3: Với
a = 0
vàb = 0
, phương trình có vô số nghiệm, nên inVO SO NGHIEM
. <br>
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
a
khá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ì
a
vàb
là số nguyên, phép chia-b / a
có 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
a
bằ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
b
cũ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
b
khá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ịx
nà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:
iostream
cho việc nhập xuất vàiomanip
cho 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ủaa
vàb
. - Sử dụng
cin
để đọc hai số nguyêna
vàb
từ input. - Sử dụng cấu trúc điều khiển
if-else if-else
hoặ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
else
cho trường hợp còn lại khia == 0
(tức làb != 0
). - Sử dụng
else
cho 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ịx
bằng cách chia-b
choa
. Đảm bảo sử dụng kiểu dữ liệudouble
cho 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 chocout
bằng cách sử dụngfixed
và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
).
Comments