Trong bài này chúng ta sẽ học lệnh rẽ nhánh C++: if, else if, else và switch case, cùng cú pháp if có khởi tạo của C++17. Rẽ nhánh là thứ giúp chương trình “biết suy nghĩ”: còn sách thì cho mượn, hết sách thì báo, chọn menu số 1 thì làm việc này, số 2 thì làm việc kia.
Bài 6 trong Serie Lập trình C++ từ A tới Z
Lệnh rẽ nhánh là gì?
Tới giờ các chương trình của chúng ta chạy thẳng từ trên xuống dưới, dòng nào cũng được chạy. Lệnh rẽ nhánh (branching statement) cho phép chọn chạy đoạn code nào dựa trên một điều kiện.
Giống như đi đường gặp ngã ba: đèn xanh thì đi thẳng, đèn đỏ thì dừng lại. Điều kiện ở đây chính là các biểu thức so sánh và logic như soSach > 0 hay diem >= 5 && diem < 8 mà chúng ta đã học ở bài toán tử trong C++.
C++ có 2 lệnh rẽ nhánh chính: if dùng cho mọi loại điều kiện, switch dùng khi so một giá trị với nhiều hằng số.
Lệnh if và else
Cú pháp cơ bản:
if (dieu_kien)
{
// chay khi dieu_kien dung
}
else
{
// chay khi dieu_kien sai
}
Phần else là tùy chọn. Nếu có nhiều trường hợp thì nối thêm các else if. Ví dụ xếp loại học lực theo điểm trung bình:
#include <iostream>
int main()
{
double diem;
std::cout << "Nhap diem trung binh: ";
std::cin >> diem;
if (diem < 0 || diem > 10)
std::cout << "Diem khong hop le\n";
else if (diem >= 8.0)
std::cout << "Gioi\n";
else if (diem >= 6.5)
std::cout << "Kha\n";
else if (diem >= 5.0)
std::cout << "Trung binh\n";
else
std::cout << "Yeu\n";
return 0;
}
Chạy thử với 3 giá trị điểm:
Chuỗi if / else if được kiểm tra lần lượt từ trên xuống. Gặp điều kiện đúng đầu tiên thì chạy nhánh đó và bỏ qua toàn bộ phần còn lại:
Nhờ vậy nhánh “Khá” chỉ cần viết diem >= 6.5, không cần thêm && diem < 8.0. Điểm từ 8 trở lên đã bị nhánh “Giỏi” lấy mất từ trước rồi. Cũng vì vậy mà thứ tự các nhánh rất quan trọng: đảo nhánh >= 5.0 lên trước nhánh >= 8.0 thì điểm 9 cũng bị xếp loại Trung bình.
Nhánh kiểm tra điểm không hợp lệ mình đặt lên đầu tiên. Đây là thói quen tốt: loại bỏ dữ liệu sai trước, phần còn lại chỉ phải xử lý dữ liệu đúng.
Luôn dùng ngoặc nhọn cho thân if
Khi thân if chỉ có 1 câu lệnh, các bạn được phép bỏ ngoặc nhọn như ví dụ trên. Nhưng chính chỗ được phép này lại gây ra một lỗi rất hay gặp:
#include <iostream>
int main()
{
int soSachConLai = 0;
if (soSachConLai > 0)
std::cout << "Cho muon sach\n";
soSachConLai = soSachConLai - 1;
std::cout << "So sach con lai: " << soSachConLai << '\n';
return 0;
}
Hết sách nên không cho mượn là đúng, nhưng số sách lại thành âm 1. Dòng trừ sách được thụt lề trông như nằm trong if, nhưng thiếu ngoặc nhọn thì if chỉ điều khiển đúng 1 câu lệnh ngay sau nó. Dòng trừ sách luôn được chạy.
Lưu ý: Thụt lề chỉ để người đọc nhìn, trình biên dịch không quan tâm. Hãy luôn viết ngoặc nhọn { } cho thân if, else và vòng lặp, kể cả khi chỉ có 1 dòng. Sau này thêm dòng thứ hai vào sẽ không bao giờ dính lỗi này. g++ có cảnh báo -Wmisleading-indentation khi bật -Wall, nhưng không phải trường hợp nào cũng bắt được.
Trong bài này mình bỏ ngoặc ở vài ví dụ chỉ để code ngắn gọn khi minh họa. Còn trong chương trình thật, các bạn cứ viết ngoặc đầy đủ.
Lệnh switch case
Khi cần so sánh một biến với nhiều giá trị cố định, viết switch sẽ gọn và dễ đọc hơn một dãy if / else if. Ví dụ menu chính của chương trình thư viện:
#include <iostream>
int main()
{
int luaChon;
std::cout << "===== THU VIEN =====\n";
std::cout << "1. Xem danh sach sach\n";
std::cout << "2. Muon sach\n";
std::cout << "3. Tra sach\n";
std::cout << "0. Thoat\n";
std::cout << "Chon chuc nang: ";
std::cin >> luaChon;
switch (luaChon)
{
case 1:
std::cout << "-> Hien thi danh sach sach\n";
break;
case 2:
case 3:
std::cout << "-> Nhap ma sach can " << (luaChon == 2 ? "muon" : "tra") << '\n';
break;
case 0:
std::cout << "-> Tam biet!\n";
break;
default:
std::cout << "-> Lua chon khong hop le\n";
break;
}
return 0;
}
Cách switch hoạt động:
- Tính giá trị trong ngoặc
switch (luaChon), rồi nhảy tới nhãncasecó giá trị bằng nó. - Chạy từ nhãn đó trở xuống cho tới khi gặp
breakthì thoát khỏiswitch. - Không có
casenào khớp thì nhảy tớidefault. Phầndefaultlà tùy chọn nhưng nên có, để xử lý lựa chọn sai.
case 2: và case 3: viết liền nhau không có lệnh ở giữa, nghĩa là 2 lựa chọn này dùng chung một đoạn xử lý. Đây là cách gộp case có chủ đích.
Quên break trong switch case
Chính cơ chế “chạy xuống tới khi gặp break” gây ra lỗi phổ biến nhất của switch:
#include <iostream>
int main()
{
int luaChon = 1;
switch (luaChon)
{
case 1:
std::cout << "Muon sach\n";
case 2:
std::cout << "Tra sach\n";
break;
default:
std::cout << "Lua chon khong hop le\n";
}
return 0;
}
Chọn 1 là mượn sách, nhưng chương trình làm luôn cả việc trả sách. Thiếu break sau case 1 nên code “rơi” tiếp xuống case 2, gọi là fall through.
Lưu ý: Mỗi case có lệnh xử lý thì phải kết thúc bằng break, kể cả case cuối cùng. Cờ -Wextra sẽ cảnh báo this statement may fall through. Nếu thật sự muốn rơi xuống case dưới, các bạn ghi rõ [[fallthrough]]; ở cuối case để cả g++ lẫn người đọc code biết đó là cố ý.
Giới hạn của switch
switch chỉ làm việc với số nguyên, ký tự char và kiểu enum. Các trường hợp sau phải dùng if:
- So sánh với
std::string:switch (tenSach)sẽ báo lỗi biên dịch. - So sánh số thực
double. - So sánh theo khoảng như
diem >= 8.0. Mỗicasechỉ là một giá trị hằng cụ thể.
if có khởi tạo trong C++17
Nhiều khi chúng ta tạo một biến chỉ để dùng trong điều kiện if. C++17 cho phép khai báo biến đó ngay trong if, ngăn cách với điều kiện bằng dấu ;:
#include <iostream>
int main()
{
constexpr int SO_NGAY_MUON_TOI_DA = 14;
int ngayMuon = 3, ngayTra = 21;
if (int soNgay = ngayTra - ngayMuon; soNgay > SO_NGAY_MUON_TOI_DA)
std::cout << "Qua han " << soNgay - SO_NGAY_MUON_TOI_DA << " ngay\n";
else
std::cout << "Tra dung han, muon " << soNgay << " ngay\n";
// soNgay khong ton tai o day nua
return 0;
}
Biến soNgay dùng được trong cả nhánh if lẫn else, nhưng ra khỏi lệnh if là bị hủy. Như mình đã nói ở bài hằng và biến C++, phạm vi biến càng hẹp thì càng ít chỗ sửa nhầm. Đây cũng là lý do bản C++17 được dùng làm chuẩn trong serie này.
Nên dùng if hay switch?
| Tình huống | Nên dùng |
|---|---|
| So 1 biến với nhiều giá trị cố định (menu, mã lệnh) | switch |
Điều kiện theo khoảng (diem >= 8.0) |
if / else if |
Điều kiện ghép nhiều biến (conSach && theHopLe) |
if |
| So sánh chuỗi, số thực | if |
| Chỉ chọn 1 trong 2 giá trị để gán | Toán tử ba ngôi ? : |
Bài tập
- Nhập 3 số nguyên, in ra số lớn nhất chỉ dùng
if / else. - Nhập tháng (1–12), dùng
switchin ra tháng đó có bao nhiêu ngày (coi tháng 2 có 28 ngày). Gợi ý: gộp các case có cùng số ngày. - Viết lại chương trình xếp loại học lực, nhưng đổi thứ tự các nhánh thành
>= 5.0,>= 6.5,>= 8.0. Nhập điểm 9 và giải thích kết quả. - Thêm vào menu thư viện chức năng “4. Tim sach”. Thử bỏ
breakcủa case 4 và đọc cảnh báo của g++.
Lời giải sẽ có trong bài tổng hợp bài tập C++ cơ bản nhé.
Kết
Lệnh rẽ nhánh C++ chỉ có vài cú pháp, nhưng 2 lỗi trong bài này là thiếu ngoặc nhọn sau if và quên break trong switch xuất hiện ở cả code của người đã đi làm lâu năm. Viết ngoặc nhọn đầy đủ và để ý cảnh báo của g++ là đủ để tránh cả 2.
Nếu đã học cấu trúc rẽ nhánh if else và switch case trong C, các bạn sẽ thấy C++ giống hệt, chỉ thêm if có khởi tạo và [[fallthrough]]. Bài tiếp theo chúng ta sẽ học vòng lặp trong C++ để menu thư viện chạy lặp lại cho tới khi người dùng chọn thoát. Có thắc mắc gì các bạn để lại bình luận nhé.







