Bài 60.122 phút đọc
Level 1: Máy tính dòng lệnh
Sau bài này bạn sẽ làm được
- Đọc đầu vào an toàn bằng fgets và strtod
- Xử lý chia không, phần trăm số thực, toán tử sai, rác
- Chia chương trình thành hàm, main dưới ba mươi dòng
- Biên dịch sạch với bộ cờ cảnh báo đầy đủ
Dự án đầu tiên nhỏ nhưng dạy một thói quen quan trọng nhất của lập trình C: không bao giờ tin đầu vào. Một máy tính dòng lệnh đọc "5 + 3" và in 8, nhưng phần lớn công việc là xử lý mọi cách người dùng gõ sai.
#Mục tiêu
terminal
./calc
=== MAY TINH === Nhap bieu thuc (vd: 5 + 3): 5 + 3 Ket qua: 8 Nhap bieu thuc: 10 / 4 Ket qua: 2.5 Nhap bieu thuc: 5 / 0 Loi: chia cho 0 Nhap bieu thuc: 0 Tam biet
| Phần áp dụng | Chương | Dùng cho |
|---|---|---|
| Phần 0 tới 2 | Cú pháp, vào ra, toán tử, điều khiển, vòng lặp | Toàn bộ dự án |
| Bài 56.4 | Xử lý lỗi | Bốn mã lỗi và cách trả |
| Bài 59.1 | Đọc chuỗi an toàn | fgets thay vì gets |
#Lõi phân tích
calc.c, hàm tính an toàn
enum { OK = 0, E_DINHDANG = -1, E_TOANTU = -2, E_CHIA0 = -3, E_MODF = -4 };
static int tinh(const char *dong, double *ra) {
char *p;
double a = strtod(dong, &p);
if (p == dong) return E_DINHDANG; /* khong phai so */
while (*p == ' ') ++p;
char op = *p++;
while (*p == ' ') ++p;
char *q;
double b = strtod(p, &q);
if (q == p) return E_DINHDANG;
switch (op) {
case '+': *ra = a + b; return OK;
case '-': *ra = a - b; return OK;
case '*': *ra = a * b; return OK;
case '/': if (b == 0) return E_CHIA0; *ra = a / b; return OK;
case '%':
if (b == 0) return E_CHIA0;
if (a != floor(a) || b != floor(b)) return E_MODF;
*ra = fmod(a, b); return OK;
default: return E_TOANTU;
}
}terminal
gcc -std=c17 -Wall -Wextra -Wpedantic calc.c -o calc.exe -lm
# Chay bo kiem tra tam cac truong hop bien
./calc_thu
5 + 3 = 8 10 / 4 = 2.5 7 % 3 = 1 5 / 0 -> chia cho 0 5.5 % 2 -> % chi dung voi so nguyen abc -> khong phai bieu thuc hop le 2 ^ 3 -> toan tu khong hop le -4 * 2.5 = -10
#Kiến trúc
Vòng lặp menu trong main
Đọc một dòng, tính, in kết quả hoặc lỗi, lặp lại. Thoát bằng một lựa chọn cụ thể.maindưới ba mươi dòng.Đọc dòng an toàn
Dùngfgetsvào một bộ đệm cố định, không bao giờ dùnggetshayscanf("%s"). Xử lý dòng dài hơn bộ đệm.Hàm tính thuần tuý
Hàmtinhnhận chuỗi, trả mã lỗi và kết quả qua tham số ra. Không in gì, không đọc gì. Nhờ vậy kiểm thử được dễ dàng.Hàm dịch mã lỗi
Một hàm biến mã lỗi thành thông điệp tiếng Việt. Tách phần hiển thị khỏi phần tính.
#Yêu cầu
Tự làm thử
- Menu lặp, thoát bằng lựa chọn 0.
- Nhập an toàn bằng
fgetscộngstrtod. - Xử lý đủ: chia cho không,
%với số thực, toán tử không hợp lệ, đầu vào rác, dòng rỗng. - Chia thành hàm,
maindưới ba mươi dòng. - Biên dịch sạch với
-Wall -Wextra -Wpedantic.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
#Cái bẫy
Cách người mới hay làm
double a, b;
char op;
scanf("%lf %c %lf", &a, &op, &b); /* SAI nhieu cach */
printf("%g\n", a op b);
/* Van de:
1. scanf khong xu ly duoc dau vao rac: go "abc" thi a khong
duoc gan, va chuong trinh dung gia tri rac.
2. Khong phat hien duoc so lan doc that bai.
3. Ky tu con lai ket trong luong, lan sau doc sai.
4. Khong xu ly chia 0. */
Cách đúng
char dong[256];
while (in_menu(), fgets(dong, sizeof dong, stdin)) {
if (la_thoat(dong)) break;
double kq;
int e = tinh(dong, &kq);
if (e == OK) printf("Ket qua: %g\n", kq);
else printf("Loi: %s\n", loi(e));
}
/* Moi dau vao di qua tinh(), tra mot ma loi ro rang,
va khong gi lam sap chuong trinh. */
#Coi là xong khi
Tóm tắt
- Bài học cốt lõi: không bao giờ tin đầu vào; phần lớn công việc là xử lý cách người dùng gõ sai.
strtodvới con trỏ dừng phát hiện được "không phải số";fgetsthay chogetsvàscanf.- Hàm tính thuần tuý trả mã lỗi, tách khỏi phần hiển thị, dễ kiểm thử.
- Năm mã lỗi cho năm cách hỏng; không nhánh nào sập hay trả kết quả sai lặng lẽ.
- Xong khi gõ bất kỳ gì cũng không sập, và biên dịch sạch với bộ cờ đầy đủ.