Chuyển tới nội dung chính

BÀI 4. CẤU TRÚC RẼ NHÁNH CÓ ĐIỀU KIỆN

Mục tiêu​

Sau bài này, học viên:

  • Viết được if, if else, chuỗi else if, và if lồng nhau không dính bẫy else mồ côi.
  • Viết được switch đủ break, default, và biết khi nào cố ý rơi xuyên (fall-through).
  • Dùng được toán tử ?: cho lựa chọn biểu thức, không dùng nó để nhét cả chương trình vào một dòng.
  • Kết hợp được &&, ||, ! đúng đoản mạch và đúng luật De Morgan.
  • Thiết kế được nhánh theo đặc tả loại trừ: phương trình, lịch, thuế, thực đơn, kiểm tra dữ liệu.
  • Ở mức cao thủ: thay một số chuỗi so sánh bằng bảng quyết định, tránh so float bằng == trong điều kiện, và nhận ra switch trên số nguyên là nhảy theo giá trị chứ không phải dãy if viết tắt một cách ngây thơ.

Vòng lặp chưa là chủ đề. Nếu một bài cần lặp menu, có thể dùng một vòng tối thiểu; hệ thống vòng lặp nằm ở Bài 5.


1. Điều kiện trong C là gì​

C không có kiểu Boolean từ bản ANSI C89 theo nghĩa từ khóa bool. Mọi điều kiện được xét theo quy tắc: 0 là sai, mọi giá trị khác 0 là đúng. Từ C99 có _Bool và, với <stdbool.h>, có bool, true, false. Bên trong, true vẫn là 1 và false vẫn là 0.

Biểu thức quan hệ và logic cho ra int có giá trị 0 hoặc 1. Vì vậy có thể viết:

int la_nguoi_lon = tuoi >= 18;

Đừng viết if (la_nguoi_lon == true) như một nghi thức. if (la_nguoi_lon) đủ nghĩa. Càng không viết if (a = 0) khi định so sánh: đó là phép gán, biểu thức có giá trị 0, nhánh sai luôn luôn, và a bị phá. Bật cảnh báo sẽ thấy suggest parentheses around assignment used as truth value.


2. if, if else, chuỗi else if​

if (điều_kiện) {
khối khi đúng;
} else {
khối khi sai;
}

else gắn với if chưa có else gần nhất. Thiếu ngoặc là nguồn của else mồ côi:

if (a > 0)
if (b > 0)
printf("ca hai duong\n");
else
printf("a khong duong\n");

else này gắn với if (b > 0), không phải với if (a > 0). Hãy luôn dùng {} cho thân if, kể cả một dòng.

Hai lưu đồ dưới đây là cùng một đoạn chữ. Bên trái là cách máy hiểu khi thiếu ngoặc. Bên phải là cách người viết thường tưởng.

Ca a = -1, b = 4 làm lộ lệch: máy không in gì, người viết lại chờ câu “a không dương”. Vì vậy mọi thân if trong khóa này đều có {}.

Chuỗi else if diễn các trường hợp loại trừ theo thứ tự ưu tiên. Điều kiện sau chỉ được xét khi mọi điều kiện trước sai. Thứ tự vì vậy là một phần của thuật toán, không phải chuyện thẩm mỹ.

if lồng nhau hợp lệ khi nhánh bên trong chỉ có nghĩa bên trong nhánh ngoài. Nếu các trường hợp ngang hàng, else if hoặc switch đọc rõ hơn lồng bốn tầng.

goto cũng rẽ được, nhưng nó nhảy tới nhãn bất kỳ trong hàm, không còn một hình thoi với hai lối ra rõ. Bài này cấm goto. Mọi lựa chọn viết bằng if hoặc switch.

Một thân rỗng if (dieu_kien); với dấu chấm phẩy ngay sau ngoặc là một lệnh trống. Nhánh đúng không làm gì, câu lệnh phía dưới luôn chạy. Đó là lỗi rất dễ gõ.


3. switch​

switch (biểu_thức_nguyên) {
case hằng_1:
...
break;
case hằng_2:
case hằng_3:
...
break;
default:
...
break;
}

Biểu thức phải có kiểu nguyên (int, char, enum). Không switch trên float, không switch trên chuỗi. Mỗi case là một nhãn. Khi trùng giá trị, thực thi bắt đầu tại nhãn đó và chạy xuyên xuống dưới cho đến break, return, hoặc hết switch. Quên break không phải lỗi cú pháp. Đó là lỗi logic phổ biến nhất của switch.

default bắt mọi giá trị không khớp. Đặt default cuối và vẫn break, để thói quen không phụ thuộc vị trí.

Fall-through cố ý thì được, nhưng phải có chú thích /* fall through */ và phải là cùng một việc cho nhiều nhãn, như 'a' và 'A'.


4. Toán tử điều kiện​

kết_quả = điều_kiện ? giá_trị_nếu_đúng : giá_trị_nếu_sai;

Dùng khi cả hai nhánh là biểu thức cùng mục đích, ví dụ chọn số lớn hơn. Đừng lồng ba toán tử ?: trên một dòng. Khi có câu lệnh, hiệu ứng phụ, hoặc nhiều bước, hãy dùng if.

Độ ưu tiên của ?: thấp hơn quan hệ và logic, cao hơn gán. Khi nghi ngờ, thêm ngoặc.


5. Đoản mạch và De Morgan​

A && B không tính B nếu A sai. A || B không tính B nếu A đúng. Dùng tính chất này để bảo vệ:

if (b != 0 && a / b > 2) {
...
}

Nếu đảo thành a / b > 2 && b != 0, chương trình đã chia trước khi kịp kiểm tra.

Phủ định một điều kiện kép:

  • !(A && B) tương đương !A || !B
  • !(A || B) tương đương !A && !B

Sai một dấu là đổi cả đặc tả. Hãy viết lại điều kiện trên giấy bằng lời tiếng Việt trước khi phủ định.


6. Ví dụ​

Ví dụ 4.1 — Xếp loại học lực​

Phân tích. Điểm double trong [0, 10]. Ngoài đoạn: từ chối. Xếp loại theo ngưỡng loại trừ từ cao xuống thấp: xuất sắc >= 9, giỏi >= 8, khá >= 6.5, trung bình >= 5, yếu phần còn lại. So sánh >= theo thứ tự giảm dần để mỗi điểm rơi đúng một nhánh.

Lưu đồ.

Mã nguồn.

#include <stdio.h>

int main(void)
{
double diem;

printf("Diem: ");
if (scanf("%lf", &diem) != 1 || diem < 0.0 || diem > 10.0) {
printf("Diem khong hop le.\n");
return 1;
}

if (diem >= 9.0) {
printf("Xuat sac\n");
} else if (diem >= 8.0) {
printf("Gioi\n");
} else if (diem >= 6.5) {
printf("Kha\n");
} else if (diem >= 5.0) {
printf("Trung binh\n");
} else {
printf("Yeu\n");
}
return 0;
}

Kết quả.

Diem: 8
Gioi
Diem: 10.5
Diem khong hop le.

Phân tích chi tiết. Nếu kiểm tra diem >= 5 đứng trước diem >= 9, điểm 9.5 sẽ bị in “Trung bình”. Thứ tự else if là thuật toán. Ngưỡng 6.5 viết 6.5 kiểu double là chấp nhận được vì ta chỉ so >= một ngưỡng, không đòi diem == 6.5 đúng từng bit. Ca biên cần có: 5, 6.5, 8, 9, 0, 10.


Ví dụ 4.2 — Phương trình bậc hai, đủ trường hợp suy biến​

Phân tích. a x^2 + b x + c = 0, hệ số thực.

  • a == 0 và b == 0 và c == 0: vô số nghiệm.
  • a == 0 và b == 0 và c != 0: vô nghiệm.
  • a == 0 và b != 0: một nghiệm bậc nhất -c / b.
  • a != 0: tính delta = b*b - 4*a*c. Âm: vô nghiệm. Bằng 0: nghiệm kép. Dương: hai nghiệm.

So sánh double với 0 bằng == vẫn là một chọn lựa có chủ đích ở bài toán đại số phổ thông, nơi hệ số được nhập “đẹp”. Ở mức cao thủ, hệ số đến từ đo đạc thì “bằng 0” phải là “gần 0 trong epsilon”. Ví dụ này ghi rõ giả định: hệ số nhập từ bàn phím được coi là đúng các giá trị người dùng gõ, và ta chấp nhận == cho 0 và cho delta.

Lưu đồ.

Mã nguồn.

#include <stdio.h>
#include <math.h>

int main(void)
{
double a;
double b;
double c;
double delta;
double x1;
double x2;

printf("a b c: ");
if (scanf("%lf %lf %lf", &a, &b, &c) != 3) {
return 1;
}

if (a == 0.0) {
if (b == 0.0) {
if (c == 0.0) {
printf("Vo so nghiem\n");
} else {
printf("Vo nghiem\n");
}
} else {
printf("Nghiem bac nhat: x = %.4f\n", -c / b);
}
} else {
delta = b * b - 4.0 * a * c;
if (delta < 0.0) {
printf("Vo nghiem\n");
} else if (delta == 0.0) {
printf("Nghiem kep: x = %.4f\n", -b / (2.0 * a));
} else {
x1 = (-b + sqrt(delta)) / (2.0 * a);
x2 = (-b - sqrt(delta)) / (2.0 * a);
printf("Hai nghiem: x1 = %.4f, x2 = %.4f\n", x1, x2);
}
}
return 0;
}

Biên dịch cần liên kết thư viện toán trên một số hệ: gcc -std=c11 -Wall -Wextra pt.c -lm.

Kết quả.

a b c: 1 -3 2
Hai nghiem: x1 = 2.0000, x2 = 1.0000
a b c: 0 0 5
Vo nghiem
a b c: 1 2 1
Nghiem kep: x = -1.0000

Phân tích chi tiết. sqrt chỉ được gọi khi delta không âm. Gọi sqrt trên số âm cho NaN, rồi in ra một “nghiệm” vô nghĩa. Nhánh a == 0 phải đứng trước công thức bậc hai, vì mẫu 2a bằng 0. Ca 1 -3 2 là (x-1)(x-2). Ca 1 2 1 là (x+1)^2. Hãy thêm ca 0 2 -4 để thấy nghiệm bậc nhất 2, và ca 0 0 0.


Ví dụ 4.3 — Thực đơn bằng switch​

Phân tích. Người dùng chọn một ký tự: c cộng, t trừ, n nhân, h chia hai số thực. Chữ hoa và chữ thường cùng nghĩa. Phép chia chặn mẫu 0. Lựa chọn lạ rơi vào default.

Lưu đồ.

Mã nguồn.

#include <stdio.h>

int main(void)
{
char phep;
double a;
double b;

printf("Phep (c/t/n/h) va hai so: ");
if (scanf(" %c %lf %lf", &phep, &a, &b) != 3) {
return 1;
}

switch (phep) {
case 'c':
case 'C':
printf("Tong = %.4f\n", a + b);
break;
case 't':
case 'T':
printf("Hieu = %.4f\n", a - b);
break;
case 'n':
case 'N':
printf("Tich = %.4f\n", a * b);
break;
case 'h':
case 'H':
if (b == 0.0) {
printf("Khong chia cho 0.\n");
return 1;
}
printf("Thuong = %.4f\n", a / b);
break;
default:
printf("Phep khong ho tro.\n");
return 1;
}
return 0;
}

Kết quả.

Phep (c/t/n/h) va hai so: h 10 4
Thuong = 2.5000

Phân tích chi tiết. Khoảng trắng trước %c bảo scanf bỏ qua newline còn sót. Hai nhãn c và C rơi xuyên có chủ đích, rồi một break chung. Quên break sau nhánh cộng sẽ khiến chương trình in cả tổng lẫn hiệu. switch không thay thế được if bên trong nhánh chia: điều kiện “bằng 0” không phải là một case của ký tự phép tính.


Ví dụ 4.4 — Năm nhuận và ngày hợp lệ​

Phân tích. Năm nhuận nếu chia hết cho 400, hoặc chia hết cho 4 nhưng không chia hết cho 100. Tháng 1, 3, 5, 7, 8, 10, 12 có 31 ngày. Tháng 4, 6, 9, 11 có 30. Tháng 2 có 29 hoặc 28. Năm phải dương. Đây là bài kết hợp if và switch.

Lưu đồ rút gọn.

Mã nguồn.

#include <stdio.h>

int main(void)
{
int ngay;
int thang;
int nam;
int toi_da;
int nhuan;

printf("ngay thang nam: ");
if (scanf("%d %d %d", &ngay, &thang, &nam) != 3 || nam <= 0) {
printf("Khong hop le\n");
return 1;
}

nhuan = (nam % 400 == 0) || (nam % 4 == 0 && nam % 100 != 0);

switch (thang) {
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
toi_da = 31;
break;
case 4: case 6: case 9: case 11:
toi_da = 30;
break;
case 2:
toi_da = nhuan ? 29 : 28;
break;
default:
printf("Khong hop le\n");
return 1;
}

if (ngay < 1 || ngay > toi_da) {
printf("Khong hop le\n");
return 1;
}

printf("Hop le\n");
return 0;
}

Kết quả.

ngay thang nam: 29 2 2000
Hop le
ngay thang nam: 29 2 1900
Khong hop le

Phân tích chi tiết. 2000 chia hết cho 400 nên nhuận. 1900 chia hết cho 100 nhưng không cho 400 nên không nhuận. Viết nam % 4 == 0 một mình sẽ nhận nhầm 1900. Toán tử ?: ở tháng 2 hợp lệ vì chỉ chọn một số. Đoản mạch trong biểu thức nhuận: nếu đã chia hết cho 400 thì vế sau không cần xét; với cách viết || ở trên, điều đó xảy ra tự nhiên.


Ví dụ 4.5 — Thuế lũy tiến (mức cao thủ của nhánh)​

Phân tích. Thu nhập chịu thuế theo năm, đơn vị triệu đồng, không âm. Biểu giả lập cho bài học:

Phần thu nhậpThuế suất
Đến 605%
Trên 60 đến 12010%
Trên 120 đến 21615%
Trên 21620%

Thuế không bằng “cả thu nhập nhân một suất”. Mỗi đồng chỉ chịu suất của bậc nó rơi vào. Với 150: 60 triệu suất 5%, 60 triệu tiếp suất 10%, 30 triệu còn lại suất 15%.

Lưu đồ.

Mã nguồn.

#include <stdio.h>

int main(void)
{
double thu_nhap;
double thue;
double phan;

printf("Thu nhap (trieu): ");
if (scanf("%lf", &thu_nhap) != 1 || thu_nhap < 0.0) {
return 1;
}

thue = 0.0;

phan = thu_nhap < 60.0 ? thu_nhap : 60.0;
thue += phan * 0.05;

if (thu_nhap > 60.0) {
phan = thu_nhap < 120.0 ? thu_nhap - 60.0 : 60.0;
thue += phan * 0.10;
}
if (thu_nhap > 120.0) {
phan = thu_nhap < 216.0 ? thu_nhap - 120.0 : 96.0;
thue += phan * 0.15;
}
if (thu_nhap > 216.0) {
thue += (thu_nhap - 216.0) * 0.20;
}

printf("Thue = %.2f trieu\n", thue);
return 0;
}

Kết quả với 150.

Thu nhap (trieu): 150
Thue = 13.50 trieu

Tính tay: 60*0.05 + 60*0.10 + 30*0.15 = 3 + 6 + 4.5 = 13.5.

Phân tích chi tiết. Bốn mốc là dữ liệu của chính sách, nên nên là hằng có tên trong bài làm của bạn. Chuỗi if độc lập ở đây đúng hơn else if, vì một người có thể chịu nhiều bậc cùng lúc. Nhầm sang else if là đánh thuế toàn bộ thu nhập theo đúng một suất — sai chính sách, vẫn biên dịch sạch. Ca kiểm thử bắt buộc: 0, 60, 61, 120, 216, 217.


7. Bài tập có gợi ý​

Bài 4.G1 — Oẳn tù tì một lượt​

Hai người nhập b, d hoặc k (búa, đấm, kéo). In người thắng hoặc hòa. Quy tắc: búa thắng kéo, kéo thắng đấm, đấm thắng búa.

Gợi ý. Xử lý hòa trước. Rồi một người thắng nếu đúng một trong ba cặp. Nhớ chữ hoa. switch lồng trong switch đọc được; đừng viết 9 if copy-paste mà không có bảng quy tắc trên giấy.

Bài 4.G2 — BMI​

BMI = cân / (cao * cao) với cao tính mét, cân tính kg. Phân loại WHO rút gọn: gầy < 18.5, bình thường < 25, thừa cân < 30, béo phần còn lại. Từ chối số không dương.

Gợi ý. Chuỗi else if từ ngưỡng thấp lên cao. In cả chỉ số với hai chữ số lẫn phân loại.

Bài 4.G3 — Giải bậc nhất rồi gắn vào bậc hai​

Lấy lưu đồ Ví dụ 4.2, thêm ca a = 0, b = 0, c = 0 vào bộ kiểm thử của chính bạn. Nếu chương trình in “vô nghiệm”, chỉ ra nhánh bị sai.

Gợi ý. Hình thoi c == 0 đang bị nuốt hoặc bị đảo.

Bài 4.G4 — Máy ATM một giao dịch​

Số dư ban đầu nhập vào. Chọn r rút, g gửi. Rút không được vượt số dư, không được âm. Gửi không được âm. In số dư mới.

Gợi ý. switch cho loại giao dịch, if cho ràng buộc tiền. Tiền nên là long long đơn vị đồng, không phải float.

Bài 4.G5 — Phủ định điều kiện​

Điều kiện hợp lệ của một điểm thi: 0 <= d && d <= 10. Viết phủ định bằng De Morgan và bằng một câu tiếng Việt. Dùng phủ định đó để in “không hợp lệ”.

Gợi ý. Phủ định của “nằm trong đoạn” là “nhỏ hơn đầu đoạn hoặc lớn hơn cuối đoạn”.

Bài 4.G6 — Fall-through có chủ đích​

Nhập số tháng, in số ngày tối đa của tháng không nhuận bằng switch, mỗi nhóm tháng dùng chung một break.

Gợi ý. Nhìn Ví dụ 4.4. Tháng 2 cố định 28 trong bài này.


8. Bài tập tự làm​

Cơ bản​

  1. Nhập một số nguyên, in “âm”, “không” hoặc “dương”.
  2. Nhập hai số, in số lớn hơn. Nếu bằng nhau, in “bang nhau” chứ không im lặng chọn một số.
  3. Nhập điểm 0..10, in “dat” nếu từ 5 trở lên, ngược lại “chua dat”. Từ chối ngoài đoạn.
  4. Nhập một ký tự. Nếu là chữ cái tiếng Anh, in hoa hay thường. Nếu là chữ số, in “chu so”. Còn lại in “ky tu khac”. Dùng phạm vi 'a'..'z', đừng liệt kê 26 case trừ khi bạn đang luyện switch.

Vững​

  1. Giải phương trình bậc hai đủ suy biến, kèm bảng ít nhất 8 ca như Bài 2 đã yêu cầu ở mức thuật toán. Lần này chấm theo cú pháp nhánh và mã thoát.
  2. Nhập giờ, phút, giây. Kiểm tra hợp lệ (0–23, 0–59, 0–59) rồi in dạng hh:mm:ss.
  3. Tính cước taxi: 11000 đồng cho km đầu, 10000 đồng mỗi km từ km thứ 2 đến km thứ 30, 8000 đồng từ km thứ 31. Quãng đường là số thực dương. Làm tròn km theo quy tắc bạn phải ghi vào đặc tả trước khi code.
  4. Trò chơi một lượt “kéo búa đấm” như G1, nhưng nhập sai ký tự thì báo lỗi riêng, không tính là thua.

Nâng cao​

  1. Hóa đơn điện sinh hoạt theo bậc giả lập: 0–50 kWh giá 1800, 51–100 giá 2000, 101–200 giá 2500, trên 200 giá 3000, đơn vị đồng/kWh, tính lũy tiến. Chỉ số mới không nhỏ hơn chỉ số cũ. In tiền từng bậc và tổng. Ca biên tại đúng 50, 100, 200.
  2. Tam giác. Ba độ dài dương. Kiểm tra bất đẳng thức tam giác. Nếu đúng, phân loại đều, cân, vuông, vuông cân, hoặc thường. Vuông nếu a*a + b*b gần c*c với sai số tương đối nhỏ, các cạnh đã sắp. Đừng dùng == trên double cho trường hợp vuông.
  3. Máy trạng thái của cửa. Trạng thái là ký tự D đóng hoặc M mở. Sự kiện K khóa, U mở khóa, B đẩy. Cửa chỉ mở được nếu đang mở khóa; chỉ khóa được nếu đang đóng. In trạng thái mới hoặc “su kien khong hop le”. Một lần chạy xử lý một sự kiện. Vẽ bảng trạng thái × sự kiện trước khi viết switch.

Siêu khó, có ứng dụng​

  1. Bộ kiểm tra cấu hình mật khẩu cho một hệ thống nội bộ. Mật khẩu nhập bằng chuỗi ngắn dưới 64 ký tự (đọc từng ký tự đến newline, chưa cần thư viện chuỗi nâng cao). Hợp lệ khi đồng thời: dài từ 8 đến 32, có chữ thường, chữ hoa, chữ số, và một ký tự trong tập !@#$%. In từng lý do thất bại, không chỉ một câu “sai”. Thiết kế nhánh sao cho mọi lý do đều được kiểm, đừng return ở lý do đầu tiên.
  2. Định tuyến thiết bị y tế giả lập. Nhịp tim, huyết áp tâm thu, SpO2. Phân loại một trong bốn mức: ổn, theo dõi, cấp cứu, dữ liệu lỗi (ngoài miền sinh lý: nhịp 30–220, tâm thu 70–250, SpO2 50–100). Quy tắc cấp cứu nếu nhịp ngoài 40–150 hoặc tâm thu ngoài 80–180 hoặc SpO2 < 90. Theo dõi nếu không cấp cứu nhưng sát ngưỡng (nhịp ngoài 50–110, tâm thu ngoài 90–140, SpO2 < 95). Viết bảng quyết định. Kiểm thử phải có ca hai chỉ số bình thường và một chỉ số cấp cứu, để chứng minh bạn không dùng else if nuốt mất chỉ số sau.
  3. Trình thông dịch phép tính có ưu tiên đơn giản. Nhập một dòng dạng so phep so với số nguyên và + - * /. Nếu sau này bạn muốn a + b * c, hãy viết trên giấy vì sao một switch đơn không đủ, và thiết kế thuật toán hai bước: đọc token, rồi tính nhân chia trước. Hiện thực bản một phép thật sự chạy được, còn bản nhiều phép nộp bằng lưu đồ và mã giả. Đây là cửa ngõ của trình thông dịch, vẫn chỉ dùng kiến thức rẽ nhánh.

9. Câu hỏi tự kiểm​

  1. else gắn với if nào?
  2. Quên break thì chuyện gì xảy ra, có phải lỗi cú pháp không?
  3. Vì sao không switch trên double?
  4. Viết một điều kiện đoản mạch bảo vệ phép chia.
  5. Thuế lũy tiến khác thuế “một suất cho toàn bộ” ở cấu trúc nhánh nào?
  6. if (a = 1) nghĩa là gì?

10. Tóm tắt và cầu nối​

Rẽ nhánh chọn đúng một việc dựa trên điều kiện đã đặc tả. if / else if là lựa chọn có thứ tự. switch là lựa chọn theo hằng nguyên, với break là một phần của nghĩa. Đoản mạch bảo vệ biểu thức nguy hiểm. Những bài toán thật — lịch, thuế, trạng thái — sai thường vì thứ tự nhánh và vì đặc tả, không vì thiếu dấu ngoặc.

Bài 5 lặp lại một khối đã đúng. Vòng lặp không sửa một nhánh sai; nó chỉ thực hiện nhánh đó nhiều lần.


11. Đào sâu: nhánh là đặc tả được viết thành đường đi​

Mỗi if là một hình thoi. Nếu bạn không vẽ được hình thoi, bạn chưa có đặc tả. Những lỗi sau đây biên dịch sạch:

  • Thứ tự else if sai ưu tiên. Kiểm tra điểm >= 5 trước >= 9 thì điểm 9 thành trung bình.
  • Nhánh không loại trừ nhưng viết else if. Thuế lũy tiến phải cộng nhiều bậc. else if chỉ giữ một bậc.
  • Quên break. switch rơi xuyên. Đó là nghĩa của ngôn ngữ, không phải lỗi cú pháp.
  • else mồ côi. else gắn với if gần nhất chưa có else. Thiếu {} là đủ để lệch.
  • So double bằng == trong điều kiện nghiệp vụ. Ngưỡng >= thường ổn. "Đúng bằng một số đo" thì không ổn.

Đoản mạch là một nhánh ẩn. b != 0 && a / b > 2 có hình thoi thứ nhất bảo vệ hình thoi thứ hai. Viết ngược là chia trước khi hỏi.

Ví dụ 4.6 — else mồ côi, vẽ lại cho đúng​

Phân tích. Muốn in "a am" khi a < 0, và chỉ khi a >= 0 mới xét b. Bản thiếu ngoặc gắn else vào if trong. Lưu đồ đúng phải có hình thoi a ở ngoài.

Lưu đồ đúng.

Mã nguồn.

#include <stdio.h>

int main(void)
{
int a, b;
if (scanf("%d %d", &a, &b) != 2) {
return 1;
}
if (a < 0) {
printf("a am\n");
} else {
if (b < 0) {
printf("b am\n");
} else {
printf("khong am\n");
}
}
return 0;
}

Kết quả. Đầu vào -1 4 in a am. Đầu vào 1 -4 in b am. Đầu vào 1 4 in khong am. Đầu vào -1 -4 vẫn in a am và không xét b.

Phân tích chi tiết. Ca thứ tư là ca phân biệt lưu đồ đúng và lưu đồ else mồ côi. Nếu else dính nhầm vào if (b < 0), ca a âm và b dương có thể in "a am" hoặc không in gì, tùy cách viết hỏng. Hãy cố tình bỏ ngoặc một lần, chạy ca -1 4, và ghi lại câu in sai.

12. Bài tập mở rộng​

Dễ​

  1. Nhập tuổi, in "tre em" nếu dưới 12, "thieu nien" nếu dưới 18, "nguoi lon" phần còn lại. Vẽ lưu đồ trước.
  2. Nhập một chữ số 0..9, switch in chữ tiếng Việt: không, một, ..., chín. Có default.
  3. Dùng ?: để in trị tuyệt đối. Một hình thoi.

Vừa​

  1. Ba cạnh, kiểm tra có lập thành tam giác không. Chỉ một việc đó. Bảng 6 ca gồm 3-4-5, 1-2-3, cạnh âm, cạnh 0.
  2. Máy ATM: số dư, chọn gửi hoặc rút. Vẽ lưu đồ có ít nhất ba hình thoi: loại giao dịch, số tiền âm, rút quá số dư.
  3. Viết lại điều kiện "điểm hợp lệ và khác 0" bằng &&. Viết phủ định bằng De Morgan và bằng một câu tiếng Việt.

Khó​

  1. Hóa đơn nước: 0–10 m3 giá A, 11–20 giá B, trên 20 giá C, tính lũy tiến. In tiền từng bậc. Ca đúng tại 10 và 20.
  2. Xếp loại hạnh kiểm kết hợp học lực: chỉ "gioi" học lực khi hạnh kiểm từ khá trở lên. Vẽ bảng quyết định 4x4 trước khi viết if.
  3. switch tháng, rơi xuyên có chú thích, ra số ngày. Tháng 2 hỏi thêm năm nhuận bằng một if bên trong, không nhét lịch vào case.

Siêu khó​

  1. Bộ phân loại đơn hàng: khối lượng, khoảng cách, hàng dễ vỡ. Phí cơ bản theo khối, phụ phí khoảng cách, nhân 1.5 nếu dễ vỡ. Từ chối khối hoặc khoảng cách không dương. Viết bảng rồi lưu đồ rồi code. Ít nhất 8 ca, trong đó có ca dễ vỡ nhưng khoảng cách bằng 0 phải bị từ chối trước khi nhân hệ số.
  2. Trình đơn lồng: chọn hình (tròn, chữ nhật, vuông), rồi nhập kích thước, in diện tích. switch ngoài, if kiểm tra số đo. Mỗi nhánh có lưu đồ riêng dài không quá một trang.

Dự án thật​

  1. Bảng quyết định xét học bổng. Một khoa xét học bổng dựa trên điểm trung bình, số tín chỉ, và số môn dưới 5. Quy tắc do bạn đặt nhưng phải viết thành bảng trước: chẳng hạn điểm từ 8, ít nhất 15 tín, không môn dưới 5 thì đạt; điểm từ 7 và không môn dưới 5 thì xét bổ sung; còn lại loại. Chương trình nhập ba thông tin và in đúng một kết luận. Nộp bảng, lưu đồ, mã, và 10 ca. Người chấm phải thấy ca "điểm cao nhưng có môn dưới 5" bị loại.

Bộ 25 bài tập​

  1. Nhập một số, in "âm", "không" hoặc "dương". Vẽ một lưu đồ trước. Gợi ý: Một hình thoi cho âm, một hình thoi cho bằng 0.
  2. Nhập hai số. Nếu bằng nhau in bang nhau, ngược lại in số lớn hơn. Gợi ý: Nhánh bằng nhau đứng riêng, đừng chọn bừa một số.
  3. Điểm 0..10: từ 5 là dat, dưới 5 là chua dat. Ngoài đoạn thì từ chối. Gợi ý: if (diem < 0 || diem > 10) từ chối trước khi xếp loại.
  4. Dùng ?: in trị tuyệt đối của một số nguyên. Gợi ý: x >= 0 ? x : -x.
  5. switch một chữ số 0..9, in chữ tiếng Việt. Có default. Gợi ý: Mỗi case một break. default in ngoài phạm vi.
  6. Viết lại điều kiện "điểm nằm trong 0..10" và phủ định của nó bằng ||. Gợi ý: Phủ định của nằm trong đoạn là nhỏ hơn đầu hoặc lớn hơn cuối.
  7. Tháng không nhuận: switch rơi xuyên đúng nhóm 31 ngày, 30 ngày, tháng 2. Gợi ý: Nhiều case chung một break. Tháng 2 cố định 28.
  8. Sửa một if thiếu ngoặc để else hết mồ côi. Ca kiểm thử a âm, b dương. Gợi ý: Bọc cả hai if bằng ngoặc nhọn. Ca -1 4 phải in đúng ý bạn.
  9. Xếp loại điểm: xuất sắc, giỏi, khá, trung bình, yếu. Ngưỡng kiểm từ cao xuống thấp. Ca biên 5, 6.5, 8, 9. Gợi ý: else if từ 9 xuống 5. Đổi thứ tự sẽ xếp sai điểm 9.
  10. Giải phương trình bậc nhất bx + c = 0 đủ ba trường hợp. Gợi ý: b khác 0 thì một nghiệm. b và c cùng 0 thì vô số. Chỉ c khác 0 thì vô nghiệm.
  11. Giải bậc hai đủ suy biến a = 0. Ít nhất 8 ca. Gợi ý: Kiểm tra a trước khi chia cho 2a. Thêm ca 0 0 0 và 0 0 5.
  12. Kiểm tra một ngày tháng năm. Ca 29/2/2000 và 29/2/1900. Gợi ý: 2000 chia hết 400 nên nhuận. 1900 thì không.
  13. BMI: nhập cân và cao hợp lệ, in phân loại. Cao tính mét. Gợi ý: BMI = cân / (cao*cao). else if từ ngưỡng thấp lên cao.
  14. Oẳn tù tì một lượt. Hòa xử lý trước. Ký tự lạ là lỗi, không phải thua. Gợi ý: Nếu hai ký tự bằng nhau thì hòa. Sau đó mới xét ba cặp thắng.
  15. ATM một giao dịch: gửi hoặc rút, chặn tiền âm và rút quá số dư. Tiền là long long. Gợi ý: switch loại giao dịch. if chặn số tiền. Rút xong không được âm.
  16. Ba cạnh dương. Kiểm tra tam giác, rồi phân loại đều, cân, vuông, thường. Vuông không dùng == trên số thực. Gợi ý: Ba bất đẳng thức cạnh. Vuông khi hiệu aa+bb và c*c rất nhỏ sau khi sắp cạnh.
  17. Cước taxi lũy tiến theo km. Viết quy tắc làm tròn trước khi code. Gợi ý: Viết rõ km lẻ làm tròn thế nào rồi mới cộng giá từng chặng.
  18. Thuế thu nhập lũy tiến bốn bậc. Ca đúng tại các mốc bậc. Gợi ý: Mỗi đồng chỉ chịu suất bậc của nó. else if một suất là sai chính sách.
  19. Hóa đơn điện lũy tiến. Chỉ số mới không được nhỏ hơn chỉ số cũ. In tiền từng bậc. Gợi ý: Tách số kWh từng bậc rồi nhân giá bậc đó.
  20. Cửa có trạng thái đóng hoặc mở, sự kiện khóa, mở khóa, đẩy. Bảng trạng thái nhân sự kiện trước khi viết switch. Gợi ý: Vẽ bảng trước. Sự kiện không hợp lệ không đổi trạng thái.
  21. Mật khẩu: dài 8..32, có thường, hoa, số, và một ký tự trong !@#$%. In mọi lý do sai, không dừng ở lý do đầu. Gợi ý: Bốn biến cờ, duyệt hết chuỗi, in mọi cờ còn thiếu.
  22. Học bổng. Bảng quyết định từ điểm, số tín chỉ, số môn dưới 5. Ca điểm cao nhưng có môn dưới 5 phải loại. Mười ca. Gợi ý: Kiểm tra môn dưới 5 trước khi khen điểm cao.
  23. Đơn hàng. Khối lượng, khoảng cách, hàng dễ vỡ. Phí theo khối, phụ phí đường, hệ số vỡ. Từ chối số không dương trước khi nhân hệ số. Gợi ý: if số đo không dương thì return trước dòng nhân 1.5.
  24. Phân loại sinh hiệu. Nhịp tim, huyết áp, SpO2. Bốn mức: lỗi dữ liệu, cấp cứu, theo dõi, ổn. Ca một chỉ số cấp cứu không được nuốt bởi else if. Gợi ý: Một chỉ số xấu phải kéo cả kết luận lên cấp cứu. Đừng else if nuốt chỉ số sau.
  25. Menu hình. Chọn tròn, chữ nhật hoặc vuông, nhập kích thước, in diện tích. switch chọn hình, if chặn số đo âm. Gợi ý: case từng hình. Trong mỗi case, kích thước âm thì không tính diện tích.