Skip to content · ⁨Bỏ qua nội dung⁩
Subjects · ⁨Môn học⁩
  • 1 C basics · ⁨Nền tảng C⁩
    1.1

    main, printf & functions

    English

    Every C program starts in main. #include <stdio.h> pulls in a header 头文件 so you can use printf to print. A function 函数 is a named block you can call; \n starts a new line. main returns 0 to mean "success".

    Tiếng Việt

    Mọi chương trình C đều bắt đầu tại main. #include <stdio.h> đưa vào một header để bạn có thể sử dụng printf để in. Một hàm là một khối có tên mà bạn có thể gọi; \n bắt đầu một dòng mới. main trả về 0 để biểu thị "thành công".

    #include <stdio.h>
    
    void greet(void) {
        printf("Hello, world!\n");
    }
    
    int main(void) {
        greet();
        printf("I am learning C.\n");
        return 0;
    }
    
    Mọi chương trình C đều chạy từ main sau khi bao gồm headers
    Mọi chương trình C đều chạy từ main sau khi bao gồm headers
    1.2

    Biến, kiểu & toán học

    English

    C needs a type for every variable: int (integer 整数), double (floating-point 浮点数), char (one character). printf uses a format specifier 格式说明符 — %d, %f, %c — for each value. / between two ints is integer division; % is the remainder 余数.

    The format string drives both printing and reading:

    Specifier Type Example output
    %d int 17
    %f double (%.2f = 2 decimal places) 3.14
    %c char A
    %s a string Mei
    %zu a size (from sizeof / strlen) 3
    %x int, printed in base 16 ff

    scanf("%d", &n) reads keyboard input with the same specifiers (note the &). Its sibling sscanf parses values out of a string, so it runs anywhere:

    Tiếng Việt

    C yêu cầu một kiểu cho mỗi biến: int (số nguyên), double (số thực), char (một ký tự). printf sử dụng một định dạng specifier — %d, %f, %c — cho mỗi giá trị. Phép chia / giữa hai int là phép chia nguyên; phép chia % là phần dư.

    #include <stdio.h>
    
    int main(void) {
        int n = 17;
        double pi = 3.14;
        char grade = 'A';
        printf("%d %.2f %c\n", n, pi, grade);   // 17 3.14 A
        printf("%d %d\n", 7 / 2, 7 % 2);        // 3 1
        return 0;
    }
    

    Chuỗi định dạng điều khiển cả việc in và đọc:

    Specifier Type Ví dụ output
    %d int 17
    %f double (%.2f = 2 chữ số thập phân) 3.14
    %c char A
    %s một chuỗi Mei
    %zu một kích thước (từ sizeof / strlen) 3
    %x int, in theo cơ số 16 ff

    scanf("%d", &n) đọc dữ liệu nhập từ bàn phím với cùng các specifiers (lưu ý dấu &). Anh em sinh đôi của nó là sscanf parse các giá trị ra khỏi một chuỗi, vì vậy nó có thể chạy ở bất cứ đâu:

    #include <stdio.h>
    
    int main(void) {
        char line[] = "Mei 88";
        char name[20];
        int score;
        sscanf(line, "%19s %d", name, &score);   // parse text -> values
        printf("%s scored %d\n", name, score);   // Mei scored 88
        return 0;
    }
    
    1.3

    Bình luận & phong cách

    English

    A comment 注释 is a note for humans; the compiler ignores it. Use // for one line and /* ... */ for a block. Good indentation 缩进 and clear names make code easy to read.

    Common mistakes

    • Every statement ends with ;, and main should return int (return 0;).
    • printf needs a format string: %d for int, %f for double, and \n for a new line.
    • A wrong format like %d for a double prints garbage — match the type.
    Tiếng Việt

    Một bình luận là ghi chú dành cho con người; trình biên dịch bỏ qua nó. Sử dụng // cho một dòng và /* ... */ cho một khối. Indentation tốt và tên rõ ràng giúp code dễ đọc.

    #include <stdio.h>
    
    int main(void) {
        // a single-line comment
        /* a block
           comment */
        int total = 3 + 4;       // clear names help
        printf("%d\n", total);   // 7
        return 0;
    }
    

    Lỗi thường gặp

    • Mỗi câu lệnh đều kết thúc bằng ;, và main nên trả về int (return 0;).
    • printf cần một chuỗi định dạng: %d cho int, %f cho double, và \n cho một dòng mới.
    • Một định dạng sai như %d cho một double sẽ in ra dữ liệu rác — hãy khớp đúng loại.
  • 2 Selection · ⁨Sự lựa chọn⁩
    2.1

    if / else

    English

    An if runs a block when a condition 条件 is true. C has no real boolean 布尔值 type by default: 0 is false and any non-zero value is true. Chain choices with else if and else. Compare with ==, !=, <, >, <=, >=.

    Tiếng Việt

    Một if chạy một khối khi điều kiện là true. C không có kiểu boolean thực sự mặc định: 0 là false và bất kỳ giá trị nào khác 0 đều là true. Xếp chồng lựa chọn với else if và else. So sánh với ==, !=, <, >, <=, >=.

    #include <stdio.h>
    
    int main(void) {
        int score = 72;
        if (score >= 90) {
            printf("A\n");
        } else if (score >= 60) {
            printf("Pass\n");
        } else {
            printf("Fail\n");
        }
        return 0;
    }
    
    if chọn nhánh true; else chọn nhánh false
    if chọn nhánh true; else chọn nhánh false
    2.2

    switch

    English

    A switch picks one case by an integer value. Each case needs a break to jump out 跳出 — without it, C "falls through" into the next case. default runs when nothing matches.

    Common mistakes

    • if (x = 5) assigns and is always true; use == to compare.
    • Each switch case needs a break;, or it falls through to the next.
    • 0 is false and any non-zero value is true in a condition.
    Tiếng Việt

    Một switch chọn một case dựa trên một giá trị số nguyên. Mỗi case cần một break để thoát ra — nếu không, C sẽ "rơi xuống" case tiếp theo. default chạy khi không có gì khớp.

    #include <stdio.h>
    
    int main(void) {
        int day = 3;
        switch (day) {
            case 1: printf("Mon\n"); break;
            case 2: printf("Tue\n"); break;
            case 3: printf("Wed\n"); break;
            default: printf("Other\n");
        }
        return 0;
    }
    

    Lỗi thường gặp

    • if (x = 5) gán và luôn là true; hãy dùng == để so sánh.
    • Mỗi trường hợp switch cần một break;, nếu không sẽ rơi vào case tiếp theo.
    • 0 là false và bất kỳ giá trị nào khác 0 đều là true trong một điều kiện.
  • 3 Loops · ⁨Vòng lặp⁩
    3.1

    Vòng lặp while & for

    English

    A loop 循环 repeats a block. A while loop runs as long as a condition is true. A for loop packs the start, the test, and the increment 自增 (i++) into one line — best when you know how many times to repeat.

    Tiếng Việt

    Vòng lặp lặp lại một khối lệnh. Một vòng lặp while chạy chừng nào điều kiện còn đúng. Một vòng lặp for gộp phần khởi tạo, kiểm tra và tăng (i++) thành một dòng — tốt nhất khi bạn biết số lần lặp.

    #include <stdio.h>
    
    int main(void) {
        int i = 1;
        while (i <= 3) {
            printf("%d ", i);
            i++;
        }
        printf("\n");                    // 1 2 3
        for (int j = 0; j < 5; j++) {
            printf("%d ", j);
        }
        printf("\n");                    // 0 1 2 3 4
        return 0;
    }
    
    while kiểm tra điều kiện; for đếm với bước nhảy
    while kiểm tra điều kiện; for đếm với bước nhảy
    3.2

    Tích lũy

    English

    A common pattern: start an accumulator 累加器 at 0, then add to it inside a loop. The same idea counts items or finds a running total.

    Tiếng Việt

    Một mẫu phổ biến: bắt đầu bộ tích lũy ở 0, sau đó cộng thêm vào bên trong vòng lặp. Ý tưởng tương tự dùng để đếm số lượng hoặc tìm tổng tích lũy.

    #include <stdio.h>
    
    int main(void) {
        int total = 0;
        for (int i = 1; i <= 5; i++) {
            total += i;            // 1 + 2 + 3 + 4 + 5
        }
        printf("%d\n", total);     // 15
        return 0;
    }
    
    3.3

    Vòng lặp lồng nhau & mô hình

    English

    A loop inside another loop is a nested 嵌套 loop. The inner loop finishes fully for each step of the outer loop — perfect for grids and patterns.

    Common mistakes

    • for (i = 0; i < n; i++) runs n times; a semicolon right after for (...) makes an empty loop.
    • A while whose condition never becomes false loops forever.
    • Declare the counter (int i) before or inside the for header.
    Tiếng Việt

    Vòng lặp nằm trong một vòng lặp khác là vòng lặp lồng nhau. Vòng lặp trong chạy hoàn toàn cho mỗi bước của vòng lặp ngoài — lý tưởng cho lưới và mô hình.

    #include <stdio.h>
    
    int main(void) {
        for (int row = 0; row < 3; row++) {
            for (int col = 0; col < 3; col++) {
                printf("*");
            }
            printf("\n");
        }
        return 0;
    }
    

    Lỗi thường gặp

    • for (i = 0; i < n; i++) chạy n lần; dấu chấm phẩy ngay sau for (...) tạo ra một vòng lặp rỗng.
    • Một while mà điều kiện chưa bao giờ trở thành false sẽ lặp vô hạn.
    • Khai báo biến đếm (int i) trước hoặc trong header for.
  • 4 Functions · ⁨Hàm số⁩
    4.1

    Tham số & giá trị trả về

    English

    A function takes parameters 参数 (inputs) and gives back a return value 返回值. The return type comes first (int, double, …); void means it returns nothing.

    Tiếng Việt

    Hàm nhận tham số (đầu vào) và trả về một giá trị. Kiểu trả về xuất hiện đầu tiên (int, double, …); void có nghĩa là hàm không trả về gì.

    #include <stdio.h>
    
    int square(int x) {        // x is a parameter
        return x * x;          // hand back a value
    }
    
    int main(void) {
        int r = square(5);
        printf("%d\n", r);     // 25
        return 0;
    }
    
    Tham số vào; giá trị trả về ra
    Tham số vào; giá trị trả về ra
    4.2

    Mẫu khai báo & phạm vi

    English

    C reads top to bottom, so a function must be known before it is called. A prototype 函数原型 — the header line plus ; — declares it early so you can keep main first. A variable's scope 作用域 is the block it lives in: it is local 局部 and disappears when the block ends.

    Common mistakes

    • A function used before it is defined needs a prototype above main.
    • Arguments are passed BY VALUE — changes inside a function do not affect the caller unless you pass a pointer.
    • A variable declared inside a function is local to it.
    Tiếng Việt

    C đọc từ trên xuống dưới, nên một hàm phải được biết đến trước khi nó được gọi. Một mẫu khai báo — gồm dòng header plus ; — khai báo sớm để bạn có thể đặt main lên đầu. Phạm vi của một biến là khối lệnh chứa nó: nó chỉ tồn tại cục bộ và biến mất khi khối kết thúc.

    #include <stdio.h>
    
    int add(int a, int b);     // prototype: declared before use
    
    int main(void) {
        printf("%d\n", add(3, 4));   // 7
        return 0;
    }
    
    int add(int a, int b) {    // definition comes later
        int sum = a + b;       // sum is local to add
        return sum;
    }
    

    Lỗi thường gặp

    • Một hàm được sử dụng trước khi định nghĩa cần có bản mẫu ở trên main.
    • Đối số được truyền THEO GIÁ TRỊ — thay đổi bên trong hàm không ảnh hưởng đến nơi gọi trừ khi bạn truyền con trỏ.
    • Biến được khai báo bên trong hàm thì thuộc tính cục bộ của hàm đó.
  • 5 Arrays · ⁨Mảng⁩
    5.1

    Mảng 1 chiều

    English

    An array 数组 holds several values of one type. Index 索引 from 0. C does not store an array's length, so a common trick computes the element 元素 count: sizeof(a) / sizeof(a[0]).

    Tiếng Việt

    Mảng chứa nhiều giá trị cùng một kiểu. Chỉ mục bắt đầu từ 0. C không lưu độ dài mảng, vì vậy một mẹo phổ biến là tính số phần tử: sizeof(a) / sizeof(a[0]).

    #include <stdio.h>
    
    int main(void) {
        int scores[3] = {88, 71, 95};
        printf("%d\n", scores[0]);     // 88
        scores[1] = 100;
        printf("%d\n", scores[1]);     // 100
        int n = sizeof(scores) / sizeof(scores[0]);
        printf("%d\n", n);             // 3
        return 0;
    }
    
    Các ô trong mảng được chỉ mục từ 0
    Các ô trong mảng được chỉ mục từ 0
    5.2

    Thuật toán mảng

    English

    Traverse 遍历 an array with a for loop to find a max, a total, or a count. Always loop from 0 up to n - 1.

    Tiếng Việt

    Duyệt qua mảng bằng vòng lặp for để tìm giá trị lớn nhất, tổng, hoặc số lượng. Luôn lặp từ 0 đến n - 1.

    #include <stdio.h>
    
    int main(void) {
        int a[] = {3, 9, 2, 7};
        int n = sizeof(a) / sizeof(a[0]);
        int max = a[0], total = 0;
        for (int i = 0; i < n; i++) {
            if (a[i] > max) max = a[i];
            total += a[i];
        }
        printf("%d %d\n", max, total);   // 9 21
        return 0;
    }
    
    5.3

    Mảng 2 chiều

    English

    A 2-D array is a grid of rows 行 and columns 列: grid[row][col]. Use two nested loops to visit every cell.

    Common mistakes

    • C does NOT check array bounds: writing a[n] in a size-n array is undefined behaviour.
    • Valid indexes run from 0 to n - 1.
    • The array name is a pointer to its first element; sizeof only gives the size in the array's own scope.
    Tiếng Việt

    Mảng 2 chiều là lưới các hàng và cột: grid[row][col]. Sử dụng hai vòng lặp lồng nhau để truy cập mọi ô.

    #include <stdio.h>
    
    int main(void) {
        int grid[2][3] = {{1, 2, 3}, {4, 5, 6}};
        printf("%d\n", grid[1][2]);    // 6
        for (int r = 0; r < 2; r++) {
            for (int c = 0; c < 3; c++) {
                printf("%d ", grid[r][c]);
            }
        }
        printf("\n");                  // 1 2 3 4 5 6
        return 0;
    }
    

    Lỗi thường gặp

    • C KHÔNG kiểm tra giới hạn mảng: việc ghi ⟨a[n]⟩ vào mảng kích thước ⟨n⟩ là hành vi không xác định.
    • Các chỉ mục hợp lệ chạy từ 0 đến n - 1.
    • Tên mảng là con trỏ tới phần tử đầu tiên; sizeof chỉ cung cấp kích thước trong phạm vi riêng của mảng.
  • 6 Pointers · ⁨Con trỏ⁩
    6.1

    &, * và truyền theo con trỏ

    English

    A pointer 指针 stores the address 地址 of a variable. &x gives the address of x; *p dereferences 解引用 the pointer — it reads or writes the value stored there. Passing a pointer lets a function change the caller's variable (pass-by-pointer).

    The classic use is a swap function. Passing values only copies them — the swap is lost. Passing pointers lets the function reach the caller's variables:

    An array name acts as a pointer to its first element, and pointer arithmetic 指针运算 steps by whole elements:

    • NULL is the pointer that points at nothing — check for it before you dereference.

    Common mistakes

    • &x is the address of x; *p is the value stored at p.
    • Never use *p on an uninitialised or NULL pointer — it crashes or corrupts memory.
    • To change a caller's variable, pass its address and write through the pointer.
    Tiếng Việt

    Con trỏ lưu địa chỉ của một biến. &x trả về địa chỉ của x; *p giải tham chiếu con trỏ — nó đọc hoặc ghi giá trị được lưu tại đó. Truyền con trỏ cho phép hàm thay đổi biến của nơi gọi (truyền theo con trỏ).

    Con trỏ lưu một địa chỉ; giải tham chiếu đi theo mũi tên đến giá trị
    Con trỏ lưu một địa chỉ; giải tham chiếu đi theo mũi tên đến giá trị
    #include <stdio.h>
    
    void addOne(int *p) {     // p holds an address
        *p = *p + 1;          // change the value at that address
    }
    
    int main(void) {
        int x = 10;
        int *ptr = &x;        // & takes the address of x
        printf("%d\n", *ptr); // * reads the value: 10
        addOne(&x);
        printf("%d\n", x);    // 11 — changed through the pointer
        return 0;
    }
    

    Ứng dụng kinh điển là hàm hoán đổi. Truyền chỉ giá trị thì chỉ sao chép chúng — sự hoán đổi bị mất. Truyền con trỏ cho phép hàm truy cập biến của nơi gọi:

    #include <stdio.h>
    
    void swap_values(int a, int b) {      // copies: the caller sees nothing
        int t = a; a = b; b = t;
    }
    
    void swap_pointers(int *a, int *b) {  // addresses: the swap is real
        int t = *a; *a = *b; *b = t;
    }
    
    int main(void) {
        int x = 1, y = 2;
        swap_values(x, y);
        printf("%d %d\n", x, y);   // 1 2  (unchanged!)
        swap_pointers(&x, &y);
        printf("%d %d\n", x, y);   // 2 1  (swapped)
        return 0;
    }
    

    Tên mảng hoạt động như con trỏ tới phần tử đầu tiên, và toán học con trỏ bước nhảy theo từng phần tử nguyên vẹn:

    #include <stdio.h>
    
    int main(void) {
        int a[] = {10, 20, 30};
        int *p = a;                 // same as &a[0]
        printf("%d\n", *p);         // 10
        printf("%d\n", *(p + 2));   // 30 — same as a[2]
        return 0;
    }
    
    • NULL là con trỏ không trỏ vào đâu — hãy kiểm tra nó trước khi giải tham chiếu.

    Lỗi thường gặp

    • &x là địa chỉ của x; *p là giá trị được lưu tại p.
    • Tuyệt đối không sử dụng *p trên con trỏ chưa khởi tạo hoặc NULL — nó gây crash hoặc làm hỏng bộ nhớ.
    • Để thay đổi biến của nơi gọi, truyền địa chỉ của nó và ghi thông qua con trỏ.
  • 7 Strings · ⁨Chuỗi ký tự⁩
    7.1

    Mảng ký tự & ký kết thúc null

    English

    A C string 字符串 is an array of char. Every string ends with a hidden null terminator 空终止符 '\0', which marks where it stops. strlen (from <string.h>) counts characters up to that '\0'. Print a whole string with %s and one character 字符 with %c.

    You can traverse a string by looping until you reach '\0'.

    Tiếng Việt

    Chuỗi C là mảng char. Mọi chuỗi đều kết thúc bằng ký kết thúc null ẩn '\0', đánh dấu nơi nó dừng lại. strlen (từ <string.h>) đếm số ký tự cho đến '\0' đó. In cả chuỗi với %s và một ký tự với %c.

    #include <stdio.h>
    #include <string.h>
    
    int main(void) {
        char name[] = "Mei";            // 'M', 'e', 'i', '\0'
        printf("%s\n", name);           // Mei
        printf("%zu\n", strlen(name));  // 3 (stops at '\0')
        printf("%c\n", name[0]);        // M
        return 0;
    }
    
    Chuỗi C là mảng char kết thúc bằng ký kết thúc null ẩn
    Chuỗi C là mảng char kết thúc bằng ký kết thúc null ẩn

    Bạn có thể duyệt chuỗi bằng cách lặp cho đến khi gặp '\0'.

    #include <stdio.h>
    
    int main(void) {
        char word[] = "banana";
        int count = 0;
        for (int i = 0; word[i] != '\0'; i++) {
            if (word[i] == 'a') count++;
        }
        printf("%d\n", count);   // 3
        return 0;
    }
    
    7.2

    Thư viện string.h

    English

    #include <string.h> gives you the everyday string tools:

    Function Does
    strlen(s) the length, not counting the terminator
    strcpy(dst, src) copy — dst must be big enough
    strcat(dst, src) append src onto the end of dst
    strcmp(a, b) compare: 0 when equal, else negative / positive
    • strcmp returns 0 for equal, so the test is strcmp(a, b) == 0 — never a == b.
    • The destination array must have room for the result plus the terminator.

    Common mistakes

    • A C string needs one extra byte for the \0 terminator: a 5-letter word needs char[6].
    • Copy and compare strings with strcpy / strcmp, not = / ==.
    • Forgetting the \0 makes printf("%s", ...) run past the end of the text.
    Tiếng Việt

    #include <string.h> cung cấp cho bạn các công cụ xử lý chuỗi cơ bản:

    Hàm Tác dụng
    strlen(s) độ dài, không tính ký kết thúc
    strcpy(dst, src) sao chép — dst phải đủ rộng
    strcat(dst, src) nối src vào cuối dst
    strcmp(a, b) so sánh: 0 khi bằng nhau, ngược lại âm / dương
    #include <stdio.h>
    #include <string.h>
    
    int main(void) {
        char full[40];
        strcpy(full, "Mei");           // full is now "Mei"
        strcat(full, " Chen");         // append -> "Mei Chen"
        printf("%s (%zu)\n", full, strlen(full));       // Mei Chen (8)
        printf("%d\n", strcmp("apple", "apple") == 0);  // 1 (equal)
        return 0;
    }
    
    • strcmp trả về 0 khi bằng nhau, vì thế phép kiểm tra là strcmp(a, b) == 0 — tuyệt đối không dùng a == b.
    • Mảng đích phải có chỗ cho kết quả cùng với ký kết thúc.

    Lỗi thường gặp

    • Một chuỗi C cần một byte dư cho ký kết thúc \0: một từ 5 chữ cái cần char[6].
    • sao chép và so sánh chuỗi với strcpy / strcmp, không phải = / ==.
    • Quên \0 khiến printf("%s", ...) vượt quá cuối văn bản.
  • 8 Structs · ⁨Cấu trúc (struct)⁩
    8.1

    struct, typedef, . vs ->

    English

    A struct 结构体 groups related values into one type. Each value is a member 成员. typedef gives the struct a short name so you can write Dog instead of struct Dog. Use . on a struct value, but -> on a pointer to a struct.

    Common mistakes

    • Use . on a struct value and -> on a struct pointer.
    • typedef lets you drop the word struct when declaring; without it you write struct Point p;.
    • Assigning one struct to another copies all its fields.
    Tiếng Việt

    struct nhóm các giá trị liên quan thành một kiểu. Mỗi giá trị là một thành viên. typedef đặt tên ngắn cho struct để bạn có thể viết Dog thay vì struct Dog. Dùng . trên giá trị struct, nhưng -> trên con trỏ tới struct.

    #include <stdio.h>
    
    typedef struct {
        char name[20];
        int age;
    } Dog;
    
    int main(void) {
        Dog d = {"Rex", 3};
        printf("%s is %d\n", d.name, d.age);   // Rex is 3   (. on a value)
    
        Dog *p = &d;
        p->age = 4;                            // -> on a pointer
        printf("%s is %d\n", d.name, d.age);   // Rex is 4
        return 0;
    }
    

    Lỗi thường gặp

    • Sử dụng . trên giá trị struct và -> trên con trỏ struct.
    • typedef cho phép bạn bỏ qua từ struct khi khai báo; nếu không có nó, bạn phải viết struct Point p;.
    • Gán một struct cho cấu trúc khác sẽ sao chép tất cả các trường của nó.
    . cho giá trị struct; -> cho con trỏ tới struct
    . cho giá trị struct; -> cho con trỏ tới struct
  • 9 Dynamic memory · ⁨Dinh động bộ nhớ⁩
    9.1

    malloc, free & realloc

    English

    malloc allocates 分配 memory on the heap 堆 at run time and returns a pointer to it. realloc resizes that block; free gives it back. Every malloc needs a matching free, or you get a memory leak 内存泄漏. These live in <stdlib.h>.

    • calloc(n, size) allocates an array like malloc AND fills it with zeros.

    Common mistakes

    • Every malloc needs a matching free; forgetting to free leaks memory.
    • Using memory after free (a "use-after-free") is a serious bug.
    • Check that malloc did not return NULL before you use the memory.
    Tiếng Việt

    malloc cấp phát bộ nhớ trên heap tại thời điểm chạy và trả về con trỏ trỏ đến đó. realloc thay đổi kích thước khối bộ nhớ đó; free giải phóng nó lại. Mỗi lần gọi malloc đều cần một call tương ứng của free, nếu không sẽ gây rò rỉ bộ nhớ. Những hàm này nằm trong <stdlib.h>.

    Locals live on the stack; malloc memory lives on the heap until you free it
    Locals live on the stack; malloc memory lives on the heap until you free it
    #include <stdio.h>
    #include <stdlib.h>
    
    int main(void) {
        int n = 3;
        int *a = malloc(n * sizeof(int));    // room for 3 ints
        for (int i = 0; i < n; i++) a[i] = i * 10;
    
        a = realloc(a, 4 * sizeof(int));     // grow to 4 ints
        a[3] = 30;
    
        for (int i = 0; i < 4; i++) printf("%d ", a[i]);
        printf("\n");                        // 0 10 20 30
    
        free(a);                             // hand the memory back
        return 0;
    }
    
    • calloc(n, size) allocates an array like malloc AND fills it with zeros.

    Lỗi thường gặp

    • Every malloc needs a matching free; forgetting to free leaks memory.
    • Using memory after free (a "use-after-free") is a serious bug.
    • Check that malloc did not return NULL before you use the memory.
  • 10 Data structures · ⁨Cấu trúc dữ liệu⁩
    10.1

    Linked lists

    English

    A linked list 链表 is a chain of nodes 节点. Each node holds a value and a pointer to the next node; the last one points to NULL. Unlike an array it grows one node at a time with malloc. Always free every node when done.

    Tiếng Việt

    A linked list is a chain of nodes. Each node holds a value and a pointer to the next node; the last one points to NULL. Unlike an array it grows one node at a time with malloc. Always free every node when done.

    A linked list: each node holds a value and a pointer to the next, ending at NULL
    A linked list: each node holds a value and a pointer to the next, ending at NULL
    #include <stdio.h>
    #include <stdlib.h>
    
    typedef struct Node {
        int value;
        struct Node *next;
    } Node;
    
    int main(void) {
        Node *head = NULL;
        for (int v = 10; v <= 30; v += 10) {   // prepend 10, 20, 30
            Node *n = malloc(sizeof(Node));
            n->value = v;
            n->next = head;
            head = n;
        }
        for (Node *p = head; p != NULL; p = p->next) {
            printf("%d ", p->value);           // 30 20 10
        }
        printf("\n");
    
        while (head != NULL) {                 // free the whole list
            Node *t = head;
            head = head->next;
            free(t);
        }
        return 0;
    }
    
    10.2

    Stacks & queues

    English

    A stack 栈 is LIFO 后进先出 (last in, first out): push and pop at the same end. A queue 队列 is FIFO 先进先出 (first in, first out): add at the back, remove from the front. Both are easy to build on an array with index variables.

    Common mistakes

    • In a linked list, never lose the head pointer, or the whole list is unreachable.
    • Free every node when you are done, walking the list before you unlink.
    • Check for an empty list (a NULL head) before you pop.
    Tiếng Việt

    A stack is LIFO (last in, first out): push and pop at the same end. A queue is FIFO (first in, first out): add at the back, remove from the front. Both are easy to build on an array with index variables.

    #include <stdio.h>
    
    int main(void) {
        int stack[10];
        int top = 0;                       // next free slot
        stack[top++] = 1;                  // push
        stack[top++] = 2;
        stack[top++] = 3;
        while (top > 0) {
            printf("%d ", stack[--top]);   // pop: 3 2 1
        }
        printf("\n");
        return 0;
    }
    
    #include <stdio.h>
    
    int main(void) {
        int queue[10];
        int front = 0, back = 0;
        queue[back++] = 1;                       // enqueue
        queue[back++] = 2;
        queue[back++] = 3;
        while (front < back) {
            printf("%d ", queue[front++]);       // dequeue: 1 2 3
        }
        printf("\n");
        return 0;
    }
    

    Lỗi thường gặp

    • In a linked list, never lose the head pointer, or the whole list is unreachable.
    • Free every node when you are done, walking the list before you unlink.
    • Check for an empty list (a NULL head) before you pop.
  • 11 Searching, sorting & recursion · ⁨Tìm kiếm, sắp xếp & đệ quy⁩
    11.1

    Linear & binary search

    English

    Linear search 线性查找 checks each element in turn — it works on any array. Binary search 二分查找 is far faster but needs a sorted 已排序 array: it checks the middle and discards half each step. Both return the index, or -1 if missing.

    Tiếng Việt

    Tìm kiếm tuyến tính (linear search) kiểm tra từng phần tử một — nó hoạt động trên mọi loại mảng. Tìm kiếm nhị phân (binary search) nhanh hơn nhiều nhưng cần mảng đã sắp xếp: nó kiểm tra phần tử ở giữa và loại bỏ một nửa mỗi bước. Cả hai đều trả về chỉ số, hoặc -1 nếu không tìm thấy.

    #include <stdio.h>
    
    int linear(int a[], int n, int target) {
        for (int i = 0; i < n; i++)
            if (a[i] == target) return i;
        return -1;
    }
    
    int binary(int a[], int n, int target) {
        int lo = 0, hi = n - 1;
        while (lo <= hi) {
            int mid = (lo + hi) / 2;
            if (a[mid] == target) return mid;
            else if (a[mid] < target) lo = mid + 1;
            else hi = mid - 1;
        }
        return -1;
    }
    
    int main(void) {
        int a[] = {2, 5, 8, 12, 16, 23};
        int n = sizeof(a) / sizeof(a[0]);
        printf("%d %d %d\n", linear(a, n, 12), binary(a, n, 12), binary(a, n, 9));
        return 0;   // 3 3 -1
    }
    
    Linear scans all; binary halves a sorted range
    Linear scans all; binary halves a sorted range
    11.2

    Sorting

    English

    Bubble sort 冒泡排序 repeatedly compares neighbours and swaps 交换 any pair that is out of order. After each pass the largest value "bubbles" to the end.

    Tiếng Việt

    Sắp xếp bong bóng (bubble sort) liên tục so sánh các phần tử kề nhau và hoán đổi bất kỳ cặp nào không đúng thứ tự. Sau mỗi lượt duyệt, giá trị lớn nhất sẽ "nổi" lên cuối mảng.

    #include <stdio.h>
    
    int main(void) {
        int a[] = {5, 2, 9, 1, 7};
        int n = sizeof(a) / sizeof(a[0]);
        for (int i = 0; i < n - 1; i++) {
            for (int j = 0; j < n - 1 - i; j++) {
                if (a[j] > a[j + 1]) {
                    int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t;
                }
            }
        }
        for (int i = 0; i < n; i++) printf("%d ", a[i]);
        printf("\n");   // 1 2 5 7 9
        return 0;
    }
    
    11.3

    Đệ quy

    English

    Recursion 递归 is a function that calls itself. It needs a base case 基准情形 to stop, and a recursive call that steps toward it. Without a base case it never ends.

    Common mistakes

    • Binary search needs a sorted array; recursion needs a base case.
    • Each recursive call must move closer to the base case, or the stack overflows.
    • Bubble and insertion sort are O(n²) — fine to learn, slow at scale.
    Tiếng Việt

    Đệ quy (recursion) là một hàm gọi chính nó. Nó cần một trường hợp cơ sở (base case) để dừng lại, và một lời gọi đệ quy tiến gần về phía trường hợp đó. Không có base case thì nó sẽ không bao giờ kết thúc.

    #include <stdio.h>
    
    int factorial(int n) {
        if (n <= 1) return 1;          // base case
        return n * factorial(n - 1);   // recursive call
    }
    
    int main(void) {
        printf("%d\n", factorial(5));  // 120
        return 0;
    }
    

    Lỗi thường gặp

    • Tìm kiếm nhị phân cần một mảng đã sắp xếp; đệ quy cần một base case.
    • Mỗi lần gọi đệ quy phải tiến gần hơn đến trường hợp cơ sở, nếu không thì stack sẽ bị tràn.
    • Sắp xếp nổi và sắp xếp chèn có độ phức tạp O(n²) — dễ học nhưng chậm khi xử lý dữ liệu lớn.
  • 12 Files & errors · ⁨Tệp & lỗi⁩
    12.1

    Text files

    English

    fopen returns a file pointer 文件指针; the mode 模式 string says what to do — "w" write, "r" read, "a" append. Write with fprintf, read with fscanf, and always fclose when done.

    Tiếng Việt

    fopen returns a file pointer; the mode string says what to do — "w" write, "r" read, "a" append. Write with fprintf, read with fscanf, and always fclose when done.

    #include <stdio.h>
    
    int main(void) {
        FILE *out = fopen("scores.txt", "w");   // open for writing
        fprintf(out, "Alice 80\nBob 95\n");
        fclose(out);
    
        FILE *in = fopen("scores.txt", "r");    // open for reading
        char name[20];
        int score;
        while (fscanf(in, "%s %d", name, &score) == 2) {
            printf("%s -> %d\n", name, score);
        }
        fclose(in);
        return 0;
    }
    
    fopen → read/write → fclose
    fopen → read/write → fclose
    12.2

    Error handling with return codes

    English

    C has no exceptions. Instead a function signals failure with a return code 返回码 — by convention 0 means success and non-zero means an error. The caller checks the code before trusting the result. (fopen follows the same idea: it returns NULL when it fails.)

    Common mistakes

    • Check that fopen did not return NULL before reading or writing.
    • Always fclose a file when you are done.
    • Return a non-zero code from main to signal an error to the caller.
    Tiếng Việt

    C has no exceptions. Instead a function signals failure with a return code — by convention 0 means success and non-zero means an error. The caller checks the code before trusting the result. (fopen follows the same idea: it returns NULL when it fails.)

    #include <stdio.h>
    
    // returns 0 on success, -1 if the divisor is 0
    int safe_divide(int a, int b, int *result) {
        if (b == 0) return -1;          // error code
        *result = a / b;
        return 0;                       // success
    }
    
    int main(void) {
        int r;
        if (safe_divide(10, 2, &r) == 0)
            printf("10 / 2 = %d\n", r);          // 10 / 2 = 5
        if (safe_divide(10, 0, &r) != 0)
            printf("cannot divide by zero\n");   // error reported
        return 0;
    }
    

    Lỗi thường gặp

    • Kiểm tra xem fopen có trả về NULL hay không trước khi đọc hoặc ghi.
    • Always fclose a file when you are done.
    • Return a non-zero code from main to signal an error to the caller.
  • 13 Bits & data · ⁨Bit & dữ liệu⁩
    13.1

    Bits, binary & bitwise operators

    English

    A bit 位 is a single 0 or 1; numbers are stored in binary 二进制. Bitwise 按位 operators work on the bits directly: & AND, | OR, ^ XOR, << shift left (×2 each step), >> shift right (÷2).

    Hexadecimal 十六进制 (base 16) writes binary compactly: one hex digit is exactly four bits. Write hex literals with 0x; print with %x:

    Tiếng Việt

    A bit is a single 0 or 1; numbers are stored in binary. Bitwise operators work on the bits directly: & AND, | OR, ^ XOR, << shift left (×2 each step), >> shift right (÷2).

    #include <stdio.h>
    
    int main(void) {
        int a = 12;              // 1100
        int b = 10;              // 1010
        printf("%d\n", a & b);   // 8   AND  -> 1000
        printf("%d\n", a | b);   // 14  OR   -> 1110
        printf("%d\n", a ^ b);   // 6   XOR  -> 0110
        printf("%d\n", a << 1);  // 24  left shift
        printf("%d\n", a >> 1);  // 6   right shift
        return 0;
    }
    

    Hexadecimal (base 16) writes binary compactly: one hex digit is exactly four bits. Write hex literals with 0x; print with %x:

    #include <stdio.h>
    
    int main(void) {
        printf("%d\n", 0xFF);   // 255  (0x marks a hex literal)
        printf("%x\n", 255);    // ff
        printf("%x\n", 12);     // c
        return 0;
    }
    
    Bitwise operators work on the bits of integers
    Bitwise operators work on the bits of integers
    13.2

    Mã hóa độ dài chuỗi (Run-length encoding)

    English

    Run-length encoding (RLE) is a simple compression 压缩 method: replace each run 游程 of repeated characters with a count and the character. It is lossless 无损 — the original is fully recoverable.

    Common mistakes

    • & is bitwise AND and && is logical AND — do not confuse them.
    • A left shift << 1 doubles a value; a right shift >> 1 halves it.
    • Bit n has value 1 << n (C has no ** power operator); check a bit with (x >> n) & 1.
    Tiếng Việt

    Run-length encoding (RLE) is a simple compression method: replace each run of repeated characters with a count and the character. It is lossless — the original is fully recoverable.

    #include <stdio.h>
    #include <string.h>
    
    int main(void) {
        char *s = "aaabbc";
        int n = strlen(s);
        for (int i = 0; i < n; ) {
            char c = s[i];
            int run = 0;
            while (i < n && s[i] == c) { run++; i++; }
            printf("%d%c", run, c);     // 3a2b1c
        }
        printf("\n");
        return 0;
    }
    

    Lỗi thường gặp

    • & là AND bit và && là AND logic — đừng nhầm lẫn.
    • A left shift << 1 doubles a value; a right shift >> 1 halves it.
    • Bit n has value 1 << n (C has no ** power operator); check a bit with (x >> n) & 1.
  • 14 Preprocessor & qualifiers · ⁨Tiền xử lý & bộ修饰符⁩
    14.1

    Preprocessor, const, static, enum

    English

    The preprocessor 预处理器 runs before compiling. #define makes a macro 宏 — plain text replaced everywhere. const makes a constant 常量 that cannot change. static inside a function keeps a variable's value between calls. An enum (enumeration 枚举) names a set of integers starting at 0.

    Bigger programs split across files: declarations go in a header 头文件 file (mine.h), included with #include "mine.h" — quotes mean your own file, < > means the standard library. An include guard (#ifndef MINE_H / #define MINE_H / #endif) stops a header being included twice.

    Common mistakes

    • #define does plain text replacement with no type checking; wrap macro bodies and arguments in parentheses.
    • A const value cannot be changed after it is set.
    • A static local variable keeps its value between calls to the function.
    Tiếng Việt

    Tiền trình viên chạy trước khi biên dịch. #define tạo macro — văn bản thường được thay thế ở mọi nơi. const tạo hằng số không thể thay đổi. static bên trong hàm giữ nguyên giá trị biến giữa các lần gọi. Một ENUM (enum) đặt tên cho một tập hợp các số nguyên bắt đầu từ 0.

    #include <stdio.h>
    
    #define MAX 100                     // macro: text replaced before compiling
    
    enum Color { RED, GREEN, BLUE };    // named integers 0, 1, 2
    
    int next_id(void) {
        static int count = 0;           // keeps its value between calls
        count++;
        return count;
    }
    
    int main(void) {
        const double PI = 3.14;         // cannot be changed
        printf("%d %.2f\n", MAX, PI);            // 100 3.14
        printf("%d %d %d\n", RED, GREEN, BLUE);  // 0 1 2
        int a = next_id();
        int b = next_id();
        printf("%d %d\n", a, b);                 // 1 2
        return 0;
    }
    

    Các chương trình lớn hơn chia sẻ nhiều file: phần khai báo nằm trong file header (mine.h), được bao gồm bằng #include "mine.h" — dấu ngoặc kép có nghĩa là file của bạn, < > có nghĩa là thư viện tiêu chuẩn. Một include guard (#ifndef MINE_H / #define MINE_H / #endif) ngăn file header được bao gồm hai lần.

    Lỗi thường gặp

    • #define thực hiện thay thế văn bản thường mà không có kiểm tra kiểu; hãy bọc thân macro và đối số trong dấu ngoặc.
    • Giá trị const không thể thay đổi sau khi đã được gán.
    • Biến cục bộ static giữ nguyên giá trị giữa các lần gọi đến hàm.
    #define, const, enum, và static giúp code rõ ràng
    #define, const, enum, và static giúp code rõ ràng

Log in or create account · ⁨Đăng nhập hoặc tạo tài khoản⁩

IGCSE, A-Level & AP