This C programming guide is currently only available in Vietnamese. Please toggle the language switch (🇻🇳) in the top navigation to read the full article.

In this third part of the series, we cover conditional statements (if, else if, else, switch-case), looping constructs (for, while, do-while), and explore fundamental programming algorithms like prime number checking and Fibonacci sequence generation in C.

Để chương trình không chỉ chạy tuần tự từ trên xuống dưới một cách đơn điệu, chúng ta cần đưa vào các điều kiện rẽ nhánh và vòng lặp. Đây là những khối xây dựng căn bản để cấu thành bất cứ một giải thuật (algorithm) nào trong thế giới phần mềm. Trong bài học này, chúng ta sẽ làm chủ cấu trúc điều khiển luồng trong C.

1. Cấu trúc rẽ nhánh (Conditionals)

Rẽ nhánh giúp chương trình đưa ra quyết định thực thi các đoạn mã khác nhau dựa trên kết quả kiểm tra điều kiện (đúng hoặc sai).

Sử dụng if-else

Đây là cấu trúc rẽ nhánh phổ biến nhất. Điều kiện bên trong if phải trả về giá trị logic: đúng (khác 0) hoặc sai (bằng 0).

check_score.c
#include <stdio.h>

int main() {
    double score;
    printf("Nhap diem cua ban: ");
    scanf("%lf", &score);

    if (score >= 9.0) {
        printf("Hoc sinh xuat sac\n");
    } else if (score >= 8.0) {
        printf("Hoc sinh gioi\n");
    } else if (score >= 6.5) {
        printf("Hoc sinh kha\n");
    } else if (score >= 5.0) {
        printf("Hoc sinh trung binh\n");
    } else {
        printf("Chua dat yeu cau\n");
    }

    return 0;
}

Sử dụng switch-case

Khi có quá nhiều nhánh rẽ dựa trên giá trị cụ thể của một biến số nguyên hoặc ký tự, switch-case là sự thay thế sạch sẽ hơn so với chuỗi if-else if dài dặc. Đừng quên lệnh break để ngắt luồng rẽ nhánh.

menu.c
#include <stdio.h>

int main() {
    int choice;
    printf("1. Choi game moi\n");
    printf("2. Tai game cu\n");
    printf("3. Thoat\n");
    printf("Nhap lua chon: ");
    scanf("%d", &choice);

    switch(choice) {
        case 1:
            printf("Dang khoi tao game moi...\n");
            break;
        case 2:
            printf("Dang tai du lieu game cu...\n");
            break;
        case 3:
            printf("Dang thoat game...\n");
            break;
        default:
            printf("Lua chon khong hop le.\n");
    }

    return 0;
}

⚠️ Lưu ý quan trọng về switch-case:

  • Lỗi Fall-through (Trôi lệnh): Nếu không có câu lệnh break ở cuối mỗi case, luồng thực thi của chương trình sẽ tiếp tục chạy xuống khối lệnh của các case tiếp theo một cách tuần tự cho đến khi gặp lệnh break hoặc hết cấu trúc switch. Tham khảo thêm về C switch statement trên cppreference.com.
  • Hạn chế kiểu dữ liệu: Khác với các ngôn ngữ lập trình hiện đại như JavaScript, Python hay Java, biểu thức điều kiện trong câu lệnh switch của C chỉ chấp nhận kiểu số nguyên hoặc ký tự (int, char, enum), tuyệt đối không hỗ trợ số thực (float, double) hay chuỗi ký tự (strings).

C. Đánh giá ngắn mạch (Short-Circuit Evaluation)

Khi đánh giá một biểu thức logic phức tạp sử dụng && (AND) hoặc || (OR), trình biên dịch C áp dụng quy tắc tối ưu hóa ngắn mạch:

  • Đối với A && B: Nếu biểu thức A được đánh giá là sai (0), trình biên dịch sẽ không chạy/đánh giá biểu thức B vì chắc chắn cả cụm A && B sẽ sai.
  • Đối với A || B: Nếu biểu thức A được đánh giá là đúng (khác 0), trình biên dịch sẽ không đánh giá biểu thức B vì chắc chắn cả cụm sẽ đúng.

Ứng dụng an toàn: Kỹ thuật này dùng để viết mã nguồn cực kỳ an toàn, ví dụ: if (ptr != NULL && *ptr == 10). Nếu con trỏ ptr là NULL, điều kiện thứ hai (giải tham chiếu *ptr vốn gây lỗi sập app crash) sẽ không bao giờ được thực thi.

Cạm bẫy: Tránh đặt các hàm có tác dụng phụ (side-effect) ở vế phải, ví dụ: if (x == 1 && saveData()). Nếu x != 1, hàm saveData() sẽ không bao giờ chạy!

D. Bản chất biên dịch: Bảng nhảy (Jump Tables)

Tại sao switch-case lại chạy nhanh hơn chuỗi if-else if dài dặc khi có hàng chục lựa chọn?
Khi các giá trị hằng của case nằm sát nhau (ví dụ 1, 2, 3, 4, 5), trình biên dịch C sẽ không tạo ra chuỗi so sánh tuần tự. Thay vào đó, nó tạo ra một Bảng nhảy (Jump Table) — một mảng chứa các con trỏ địa chỉ code trong bộ nhớ.
Khi thực thi, CPU chỉ cần lấy giá trị biến làm chỉ mục index để truy xuất trực tiếp địa chỉ dòng lệnh tương ứng trong mảng và nhảy tới đó. Phép toán này chạy với độ phức tạp thời gian cực hạn \(O(1)\), nhanh hơn nhiều so với việc thực hiện \(n\) lần so sánh của if-else.

2. Cấu trúc Vòng lặp (Loops)

Vòng lặp giúp lặp đi lặp lại một khối mã lệnh nhiều lần mà bạn không cần phải copy đi viết lại.

Vòng lặp for

Dùng khi bạn biết rõ số lần lặp cụ thể. Cấu trúc gồm: Khởi tạo; Điều kiện lặp; Biểu thức thay đổi bước nhảy.

loop_for.c
#include <stdio.h>

int main() {
    // In cac so tu 1 den 5
    for (int i = 1; i <= 5; i++) {
        printf("i = %d\n", i);
    }
    return 0;
}

Vòng lặp whiledo-while

  • while: Kiểm tra điều kiện trước, nếu thỏa mãn mới chạy thân vòng lặp.
  • do-while: Chạy thân vòng lặp trước ít nhất một lần, sau đó mới kiểm tra điều kiện lặp.
loop_while.c
#include <stdio.h>

int main() {
    int count = 5;
    while (count > 0) {
        printf("count = %d\n", count);
        count--;
    }

    int input;
    // do-while phu hop de kiem tra tinh hop le cua du lieu nhap vao
    do {
        printf("Nhap mot so lon hon 10: ");
        scanf("%d", &input);
    } while (input <= 10);

    printf("Thanh cong! So ban nhap la: %d\n", input);
    return 0;
}

Lệnh goto & Mô hình Dọn Dẹp Tài Nguyên (Cleanup Pattern)

Mặc dù lệnh goto thường bị coi là cạm bẫy vì dễ tạo ra mã nguồn "mì ăn liền" (spaghetti code) khó kiểm soát luồng, nhưng trong ngôn ngữ C hệ thống (như trong mã nguồn của hệ điều hành Linux kernel), goto được sử dụng rất rộng rãi cho dọn dẹp tài nguyên tại một điểm duy nhất khi xảy ra lỗi giữa chừng, tránh lặp lại các dòng code giải phóng bộ nhớ hay đóng file nhiều lần.

cleanup_goto.c
#include <stdio.h>
#include <stdlib.h>

int process_data() {
    int *buffer1 = (int*) malloc(100 * sizeof(int));
    if (buffer1 == NULL) goto cleanup_none;

    int *buffer2 = (int*) malloc(200 * sizeof(int));
    if (buffer2 == NULL) goto cleanup_buf1;

    // Giả lập lỗi xảy ra trong quá trình xử lý dữ liệu
    int error_occurred = 1;
    if (error_occurred) goto cleanup_all;

    // Chạy thành công
    free(buffer2);
    free(buffer1);
    return 0;

    // Điểm dọn dẹp tập trung (Single-point of cleanup)
cleanup_all:
    printf("Lỗi xảy ra, dọn dẹp cả hai bộ đệm...\n");
    free(buffer2);
cleanup_buf1:
    printf("Dọn dẹp bộ đệm 1...\n");
    free(buffer1);
cleanup_none:
    return -1;
}

3. Thuật toán thực tế: Kiểm tra Số nguyên tố

Hãy cùng kết hợp if-else và vòng lặp for để giải quyết một bài toán kinh điển: kiểm tra xem số n nhập vào có phải là số nguyên tố hay không.

Số nguyên tố là số lớn hơn 1 và chỉ chia hết cho 1 và chính nó.

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

int main() {
    int n;
    printf("Nhap vao mot so nguyen: ");
    scanf("%d", &n);

    if (n < 2) {
        printf("%d khong phai la so nguyen to\n", n);
        return 0;
    }

    int isPrime = 1; // 1 nghia la dung, 0 nghia la sai
    
    // Lap tu 2 den can bac hai cua n de kiem tra chia het
    // Su dung sqrt() trong math.h giup thuat toan chay nhanh hon rat nhieu
    for (int i = 2; i <= sqrt(n); i++) {
        if (n % i == 0) {
            isPrime = 0; // Co uoc so khac -> khong phai so nguyen to
            break;       // Ngat luong lap som vi khong can kiem tra them nua
        }
    }

    if (isPrime == 1) {
        printf("%d la so nguyen to\n", n);
    } else {
        printf("%d khong phai la so nguyen to\n", n);
    }

    return 0;
}

📥 Tải về mã nguồn mẫu: prime_check.c

Thử thách nhỏ dành cho bạn:

Bạn hãy viết chương trình in ra tất cả các số nguyên tố nhỏ hơn 100 bằng cách sử dụng vòng lặp lồng nhau (vòng lặp ngoài duyệt từ 2 đến 99, vòng lặp trong kiểm tra số nguyên tố).

📝 Kiểm tra kiến thức bài 3
Sự khác biệt cốt lõi giữa vòng lặp whiledo-while trong C là gì?

Related Articles

Bài viết liên quan trong series

Lesson 5: Functions, Recursion & Variable Scope in C Bài 5: Hàm, Đệ quy & Phạm vi biến trong C Lesson 3: Operators, Precedence & Bitwise Operations in C Bài 3: Toán tử, Thứ tự ưu tiên & Phép toán Bitwise trong C Back to C Series Overview Quay lại Lộ trình C Series