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

Bài 2.5: Bài tập thực hành rẽ nhánh nâng cao trong C++
Chào mừng trở lại với series C++! Ở các bài trước, chúng ta đã làm quen với những công cụ rẽ nhánh cơ bản như if
và if/else
để đưa ra quyết định đơn giản dựa trên một điều kiện. Tuyệt vời! Nhưng trong thế giới thực của lập trình, các tình huống thường phức tạp hơn nhiều.
Điều gì xảy ra khi chúng ta cần kiểm tra nhiều điều kiện cùng lúc? Hoặc khi một quyết định phụ thuộc vào kết quả của một quyết định khác? Đó chính là lúc các kỹ thuật rẽ nhánh nâng cao phát huy tác dụng. Bài viết này sẽ đi sâu vào các phương pháp xử lý rẽ nhánh phức tạp hơn, giúp code của bạn mạnh mẽ và linh hoạt hơn.
Chúng ta sẽ cùng nhau thực hành với các khái niệm như rẽ nhánh lồng nhau, điều kiện phức hợp sử dụng các toán tử logic, và tìm hiểu sâu hơn về câu lệnh switch
.
1. Rẽ nhánh Lồng nhau (Nested Branching)
Khái niệm rẽ nhánh lồng nhau khá đơn giản: đó là việc đặt một câu lệnh rẽ nhánh (if
, if/else
, hoặc switch
) bên trong khối lệnh của một câu lệnh rẽ nhánh khác. Điều này cho phép bạn xử lý các quyết định phụ thuộc lẫn nhau hoặc có thứ bậc.
Hãy tưởng tượng bạn cần kiểm tra xem một người có đủ điều kiện tham gia một sự kiện đặc biệt hay không. Điều kiện đầu tiên là họ phải đủ tuổi, và chỉ khi họ đủ tuổi, bạn mới kiểm tra điều kiện thứ hai, ví dụ: họ có phải là cư dân của một khu vực cụ thể hay không.
Ví dụ 1.1: Kiểm tra tuổi và địa điểm
Chương trình này kiểm tra xem một người có đủ 18 tuổi hay không. Nếu đủ 18 tuổi, nó tiếp tục kiểm tra xem họ có sống ở "Hà Nội" hay không.
#include <iostream>
#include <string>
int main() {
int tuoi = 20;
string thanhPho = "Hanoi"; // Thử thay đổi thành "HCM" để xem kết quả
cout << "Dang kiem tra dieu kien..." << endl;
// Rẽ nhánh ngoài: Kiểm tra tuổi
if (tuoi >= 18) {
cout << "-> Da du tuoi." << endl;
// Rẽ nhánh lồng nhau bên trong: Kiểm tra thành phố (chi khi du tuoi)
if (thanhPho == "Hanoi") {
cout << "-> Va ban song o Ha Noi. -> **DU DIEU KIEN!**" << endl;
} else {
cout << "-> Nhung ban KHONG song o Ha Noi. -> Chua du dieu kien hoan toan." << endl;
}
} else {
cout << "-> Chua du tuoi. -> Chua du dieu kien." << endl;
}
return 0;
}
Giải thích:
- Câu lệnh
if (tuoi >= 18)
là rẽ nhánh ngoài. Khối lệnh bên trong nó chỉ được thực thi khi tuổi lớn hơn hoặc bằng 18. - Bên trong khối
if
đầu tiên, chúng ta có một câu lệnhif/else
khác (if (thanhPho == "Hanoi")
). Đây chính là rẽ nhánh lồng nhau. - Câu lệnh
else
choif
ngoài sẽ xử lý trường hợp người đó không đủ tuổi, và các kiểm tra lồng nhau bên trong sẽ không bao giờ được thực thi.
Rẽ nhánh lồng nhau rất hữu ích, nhưng hãy cẩn thận! Việc lồng quá sâu có thể khiến code trở nên khó đọc và khó quản lý. Đôi khi, việc kết hợp các điều kiện bằng toán tử logic là một lựa chọn tốt hơn.
2. Điều kiện phức hợp với Toán tử Logic
Thay vì lồng nhiều lớp if
, bạn có thể kết hợp nhiều điều kiện đơn giản thành một điều kiện phức hợp bằng cách sử dụng các toán tử logic:
&&
(AND): Điều kiện phức hợp đúng khi tất cả các điều kiện con đều đúng.||
(OR): Điều kiện phức hợp đúng khi ít nhất một trong các điều kiện con đúng.!
(NOT): Đảo ngược giá trị boolean của một điều kiện (đúng thành sai, sai thành đúng).
Việc sử dụng toán tử logic giúp làm phẳng cấu trúc rẽ nhánh, thường cải thiện khả năng đọc code, đặc biệt là khi các điều kiện không có mối quan hệ thứ bậc rõ ràng mà chỉ cần cùng nhau đáp ứng một tiêu chí nào đó.
Ví dụ 2.1: Sử dụng &&
(AND)
Kiểm tra xem một sinh viên có đủ điều kiện nhận học bổng hay không, dựa trên cả hai điều kiện: điểm trung bình (GPA) cao hơn 3.0 và đang tích cực theo học.
#include <iostream>
int main() {
double gpa = 3.8;
bool dangHoc = true; // Đang theo học
// Điều kiện phức hợp: GPA > 3.0 AND đang học
if (gpa > 3.0 && dangHoc) {
cout << "Ban du dieu kien nhan hoc bong." << endl;
} else {
cout << "Ban khong du dieu kien nhan hoc bong." << endl;
}
// Thử thay đổi gpa hoặc dangHoc để xem kết quả
gpa = 2.9;
dangHoc = true;
if (gpa > 3.0 && dangHoc) {
cout << "[Lan 2] Ban du dieu kien nhan hoc bong." << endl;
} else {
cout << "[Lan 2] Ban khong du dieu kien nhan hoc bong." << endl; // Kết quả sẽ là dòng này
}
return 0;
}
Giải thích:
Câu lệnh if (gpa > 3.0 && dangHoc)
chỉ đúng khi cả gpa > 3.0
là true
và dangHoc
là true
. Nếu một trong hai hoặc cả hai đều sai, điều kiện phức hợp sẽ sai.
Ví dụ 2.2: Sử dụng ||
(OR)
Kiểm tra xem một người có đủ điều kiện nhận vé giảm giá hay không, nếu họ hoặc là người cao tuổi (trên 65 tuổi) hoặc là trẻ em (dưới 12 tuổi).
#include <iostream>
int main() {
int tuoi = 8; // Thử thay đổi thành 70 hoặc 30
// Điều kiện phức hợp: Tuổi >= 65 OR Tuổi < 12
if (tuoi >= 65 || tuoi < 12) {
cout << "Ban du dieu kien mua ve giam gia." << endl;
} else {
cout << "Ban khong du dieu kien mua ve giam gia." << endl;
}
return 0;
}
Giải thích:
Câu lệnh if (tuoi >= 65 || tuoi < 12)
đúng nếu một trong hai điều kiện (tuoi >= 65
hoặc tuoi < 12
) là true
, hoặc cả hai đều true
(dù trong ví dụ này không thể xảy ra cả hai cùng lúc). Chỉ khi cả hai điều kiện con đều sai, điều kiện phức hợp mới sai.
Ví dụ 2.3: Sử dụng !
(NOT)
Kiểm tra xem một người dùng chưa đăng nhập hay không để hiển thị thông báo yêu cầu đăng nhập.
#include <iostream>
int main() {
bool daDangNhap = false; // Thử thay đổi thành true
// Điều kiện: chuaDangNhap (nghia la KHONG phai daDangNhap)
if (!daDangNhap) {
cout << "Vui long dang nhap de tiep tuc." << endl;
} else {
cout << "Chao mung ban tro lai!" << endl;
}
return 0;
}
Giải thích:
Toán tử !
đảo ngược giá trị boolean. !daDangNhap
sẽ là true
nếu daDangNhap
là false
, và ngược lại.
Bạn có thể kết hợp các toán tử logic này để tạo ra các điều kiện phức tạp hơn nữa, ví dụ: (tuoi >= 18 && gioiTinh == "Nu") || (tuoi >= 20 && gioiTinh == "Nam")
. Khi kết hợp nhiều toán tử, việc sử dụng dấu ngoặc đơn ()
để nhóm các điều kiện con là rất quan trọng để đảm bảo thứ tự ưu tiên và làm cho code dễ hiểu hơn.
3. Câu lệnh switch
Nâng cao
Chúng ta đã biết switch
rất hữu ích khi bạn cần so sánh một biến với nhiều giá trị hằng khác nhau. Tuy nhiên, có một vài điểm "nâng cao" hoặc cần lưu ý khi sử dụng switch
:
- Fall-through: Mặc định, sau khi một
case
được thực thi, luồng chương trình sẽ tiếp tục chạy xuống cáccase
tiếp theo cho đến khi gặpbreak;
hoặc kết thúcswitch
. Đây gọi là "fall-through". Thông thường, bạn sẽ muốn dùngbreak;
ở cuối mỗicase
để ngăn chặn điều này, chỉ thực thi khối lệnh tương ứng vớicase
khớp đầu tiên. Tuy nhiên, đôi khi fall-through lại hữu ích (dù ít gặp). default
: Khốidefault
sẽ được thực thi nếu không cócase
nào khớp với giá trị của biến. Việc sử dụngdefault
rất quan trọng để xử lý các trường hợp không mong muốn hoặc không được định nghĩa rõ ràng.
Ví dụ 3.1: Sử dụng switch
với break
và default
(Cách dùng phổ biến)
Chương trình kiểm tra số ngày trong một tháng nhất định (bỏ qua năm nhuận cho đơn giản).
#include <iostream>
int main() {
int month = 2; // Thử thay đổi số tháng
cout << "Thang " << month << " co ";
switch (month) {
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
cout << "31 ngay.";
break; // Ngừng tại đây sau khi in
case 4:
case 6:
case 9:
case 11:
cout << "30 ngay.";
break; // Ngừng tại đây
case 2:
cout << "28 hoac 29 ngay (tuy nam nhuan).";
break; // Ngừng tại đây
default: // Nếu không khớp với bất kỳ case nào trên
cout << "so thang khong hop le.";
}
cout << endl; // In xuống dòng cuối cùng
return 0;
}
Giải thích:
- Chúng ta gom các
case
có cùng kết quả (31 ngày, 30 ngày) lại với nhau. Nhờ có fall-through, nếumonth
là 1, nó khớpcase 1:
, nhưng không cóbreak
, nên nó tiếp tục chạy xuốngcase 3:
, rồicase 5:
,... cho đến khi gặpbreak;
saucase 12:
. Đây là một trong số ít trường hợp fall-through được sử dụng một cách có chủ đích. - Câu lệnh
break;
rất quan trọng để thoát khỏiswitch
sau khi tìm thấycase
phù hợp và thực thi xong khối lệnh của nó. - Khối
default:
xử lý mọi giá trịmonth
không nằm trong khoảng 1 đến 12.
Ví dụ 3.2: switch
với enum
Sử dụng enum
(kiểu dữ liệu liệt kê) với switch
giúp code rõ ràng và dễ quản lý hơn khi làm việc với các tập hợp giá trị có tên.
#include <iostream>
// Định nghĩa kiểu enum cho các ngày trong tuần
enum class Day {
Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
};
int main() {
Day today = Day::Wednesday;
switch (today) {
case Day::Sunday:
cout << "Hom nay la Chu Nhat. Chuc ban cuoi tuan vui ve!" << endl;
break;
case Day::Saturday:
cout << "Hom nay la Thu Bay. Chuc ban cuoi tuan vui ve!" << endl;
break;
case Day::Monday:
case Day::Tuesday:
case Day::Wednesday:
case Day::Thursday:
case Day::Friday:
cout << "Hom nay la ngay trong tuan. Co len nhe!" << endl;
break;
// Khong can default o day neu chung ta chac chan bien thuoc enum
// Nhung them default co the giup debug
default:
cout << "Gia tri ngay khong hop le." << endl;
break;
}
return 0;
}
Giải thích:
- Chúng ta định nghĩa một
enum class Day
với các tên gọi cho các ngày trong tuần. - Biến
today
có kiểuDay
. - Trong
switch
, chúng ta so sánhtoday
với các giá trị hằng củaenum
(ví dụ:Day::Wednesday
). Điều này giúp code dễ đọc hơn rất nhiều so với việc sử dụng các số nguyên magic number (ví dụ: 0 cho Chủ Nhật, 1 cho Thứ Hai...). - Tương tự ví dụ trước, chúng ta gom các ngày trong tuần lại để có cùng thông báo.
Sử dụng switch
thường thích hợp hơn một chuỗi if-else if
dài khi bạn so sánh một biến với nhiều giá trị hằng rời rạc. Nó thường rõ ràng hơn và compiler có thể tối ưu tốt hơn trong một số trường hợp. Tuy nhiên, nếu bạn cần kiểm tra các điều kiện phức tạp hơn, các khoảng giá trị, hoặc kết hợp nhiều biến, thì if/else if
là lựa chọn phù hợp hơn.
4. Toán tử Điều kiện (Conditional Operator ? :
)
Toán tử điều kiện, hay còn gọi là toán tử ba ngôi (ternary operator), là một cách viết tắt ngắn gọn cho câu lệnh if/else
đơn giản mà kết quả là việc gán một giá trị hoặc trả về một biểu thức. Cú pháp của nó là:
điều_kiện ? giá_trị_nếu_đúng : giá_trị_nếu_sai;
Ví dụ 4.1: Xác định số lớn hơn
#include <iostream>
int main() {
int a = 15;
int b = 20;
// Sử dụng toán tử điều kiện để tìm giá trị lớn nhất
int max_value = (a > b) ? a : b;
cout << "Gia tri lon nhat giua " << a << " va " << b << " la: " << max_value << endl;
return 0;
}
Giải thích:
a > b
làđiều_kiện
.- Nếu
a > b
đúng (true), biểu thức toán tử điều kiện sẽ trả vềa
. - Nếu
a > b
sai (false), biểu thức sẽ trả vềb
. - Giá trị được trả về sau đó được gán cho biến
max_value
.
Đây tương đương với:
int max_value;
if (a > b) {
max_value = a;
} else {
max_value = b;
}
Nhưng rõ ràng là ngắn gọn hơn nhiều!
Ví dụ 4.2: Gán chuỗi dựa trên điều kiện
#include <iostream>
#include <string>
int main() {
int number = 7;
// Sử dụng toán tử điều kiện để gán chuỗi
string ket_qua = (number % 2 == 0) ? "so chan" : "so le";
cout << number << " la " << ket_qua << endl;
return 0;
}
Giải thích:
- Điều kiện
number % 2 == 0
kiểm tra xemnumber
có chia hết cho 2 hay không. - Nếu đúng, chuỗi
"so chan"
được chọn. - Nếu sai, chuỗi
"so le"
được chọn. - Chuỗi được chọn được gán vào biến
ket_qua
.
Toán tử điều kiện là một công cụ tuyệt vời để viết code ngắn gọn cho các quyết định đơn giản. Tuy nhiên, nó chỉ nên được dùng cho các trường hợp đơn giản. Nếu điều kiện hoặc giá trị trả về quá phức tạp, việc sử dụng if/else
truyền thống sẽ giúp code dễ đọc hơn rất nhiều. Đừng cố gắng lồng toán tử điều kiện vào nhau một cách phức tạp, nó sẽ trở thành "ác mộng" cho người đọc code!
5. Lời khuyên khi thực hành rẽ nhánh nâng cao
Bạn đã thấy các cách khác nhau để xử lý các tình huống rẽ nhánh phức tạp hơn. Dưới đây là một vài lời khuyên để bạn thực hành hiệu quả:
- Chọn đúng công cụ:
- Sử dụng
if/else if
cho các điều kiện phức tạp, các khoảng giá trị, hoặc khi thứ tự kiểm tra là quan trọng. - Sử dụng
switch
khi bạn so sánh một biến với nhiều giá trị hằng rời rạc. - Sử dụng toán tử điều kiện
? :
cho các quyết định đơn giản, thường là để gán giá trị.
- Sử dụng
- Ưu tiên khả năng đọc code: Đừng ngần ngại sử dụng dấu ngoặc đơn
()
trong các điều kiện phức hợp. Tránh lồngif
quá sâu. Code rõ ràng quan trọng hơn code quá "thông minh" hoặc quá ngắn gọn đến mức khó hiểu. - Sử dụng
default
trongswitch
: Điều này giúp chương trình của bạn mạnh mẽ hơn trước các đầu vào không mong muốn. - Thực hành, thực hành, thực hành: Cách tốt nhất để nắm vững các khái niệm này là áp dụng chúng vào các bài toán nhỏ.
Hãy thử thách bản thân với một số bài tập nhỏ để củng cố kiến thức:
- Viết chương trình kiểm tra xem một năm có phải là năm nhuận không. Năm nhuận là năm chia hết cho 400, hoặc chia hết cho 4 nhưng không chia hết cho 100. (Sử dụng toán tử logic!)
- Viết chương trình yêu cầu người dùng nhập điểm số (từ 0-100) và in ra xếp loại (A, B, C, D, F) dựa trên thang điểm nhất định. (Sử dụng
if-else if
). - Viết chương trình yêu cầu người dùng nhập một ký tự và kiểm tra xem đó là nguyên âm (
a
,e
,i
,o
,u
- không phân biệt hoa thường) hay phụ âm. (Sử dụngswitch
hoặcif
với toán tử||
).
Bài tập ví dụ: C++ Bài 2.A5: Chia hết
Cho hai số \(a\) và \(b\). Xác định trong hai số trên, có một số chia hết cho số còn lại hay không.
INPUT FORMAT
Đầu vào gồm hai số nguyên dương \(a\) và \(b (0 < a, b \leq 10^4)\);
OUTPUT FORMAT
In ra một dòng duy nhất là yes
nếu hai số chia hết cho nhau, ngược lại in ra no
.
Ví dụ 1:
Input
6 3
Ouput
yes
Ví dụ 2:
Input
6 4
Output
no
Giải thích ví dụ mẫu:
- Ví dụ 1: 6 chia hết cho 3 và ngược lại, nên kết quả là
yes
. - Ví dụ 2: 6 không chia hết cho 4 và 4 không chia hết cho 6, nên kết quả là
no
. <br>
- Bao gồm thư viện cần thiết: Để thực hiện nhập và xuất dữ liệu, bạn cần bao gồm thư viện
iostream
. - Khai báo biến: Cần hai biến kiểu số nguyên để lưu trữ hai số
a
vàb
từ đầu vào. Kiểuint
là phù hợp vì giới hạn củaa
vàb
là10^4
. - Đọc dữ liệu: Sử dụng
cin
để đọc giá trị của hai sốa
vàb
từ đầu vào chuẩn. - Kiểm tra điều kiện: Bài toán yêu cầu kiểm tra xem một trong hai số có chia hết cho số còn lại hay không.
- Để kiểm tra xem
a
có chia hết chob
hay không, sử dụng toán tử modulo%
. Điều kiện làa % b == 0
. - Để kiểm tra xem
b
có chia hết choa
hay không, điều kiện làb % a == 0
. - Vì chỉ cần một trong hai điều kiện trên đúng, bạn sẽ kết hợp chúng bằng toán tử logic
||
(OR). Điều kiện kiểm tra tổng quát sẽ là(a % b == 0) || (b % a == 0)
.
- Để kiểm tra xem
- Xuất kết quả:
- Sử dụng câu lệnh điều kiện
if
. Nếu điều kiện kiểm tra ở bước 4 là đúng, in ra dòng chữyes
bằngcout
. - Ngược lại (trong nhánh
else
), in ra dòng chữno
bằngcout
. - Đảm bảo thêm ký tự xuống dòng sau khi in (
endl
hoặc'\n'
) để đáp ứng định dạng đầu ra.
- Sử dụng câu lệnh điều kiện
- Cấu trúc chương trình: Đặt tất cả các bước trên vào hàm
int main()
tiêu chuẩn của C++.
Tóm lại, bạn sẽ đọc hai số, dùng toán tử modulo để kiểm tra hai khả năng chia hết, kết hợp chúng bằng OR, và dùng if/else
để in ra kết quả tương ứng.
Comments