Bỏ qua điều hướng, tới nội dung chính
Học C
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ụngChươngDùng cho
Phần 0 tới 2Cú pháp, vào ra, toán tử, điều khiển, vòng lặpToàn bộ dự án
Bài 56.4Xử lý lỗiBốn mã lỗi và cách trả
Bài 59.1Đọc chuỗi an toànfgets 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

  1. 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ể. main dưới ba mươi dòng.
  2. Đọc dòng an toàn

    Dùng fgets vào một bộ đệm cố định, không bao giờ dùng gets hay scanf("%s"). Xử lý dòng dài hơn bộ đệm.
  3. Hàm tính thuần tuý

    Hàm tinh nhậ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.
  4. 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ử

  1. Menu lặp, thoát bằng lựa chọn 0.
  2. Nhập an toàn bằng fgets cộng strtod.
  3. 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.
  4. Chia thành hàm, main dưới ba mươi dòng.
  5. 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ố"; fgets thay cho gets và 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 đủ.