C Cơ Bản: Biến, Hằng, Chuỗi, Toán Tử và Biểu Diễn Số Nguyên

Khi khai báo biến trong C, trình biên dịch yêu cầu các khai báo phải xuất hiện ngay từ đầu khối lệnh. Việc đặt khai báo sau các câu lệnh thực thi sẽ gây lỗi biên dịch.

Mọi biến khai báo bên trong hàm main() đều là biến cục bộ — tuy nhiên, biến cục bộ không nhất thiết phải nằm trong main(); chúng có thể tồn tại trong bất kỳ hàm nào khác.

Một biến được khai báo với từ khóa const, ví dụ const int num = 5;, trở thành hằng số tại thời điểm chạy — giá trị của nó không thể thay đổi sau khi khởi tạo. Cố gắng gán lại sẽ dẫn đến lỗi biên dịch.

Tương tự, mảng yêu cầu kích thước khai báo phải là biểu thức hằng (constant expression). Nếu dùng biến const làm chỉ số — như int arr[num]; — trình biên dịch vẫn báo lỗi vì num không được coi là *hằng thời biên dịch* (compile-time constant) trong chuẩn C trước C23, trừ khi nó là enum hoặc macro.

Hằng được định nghĩa bằng #define

Khi viết #define MAX 10, tiền xử lý sẽ thay thế mọi xuất hiện của MAX bằng chữ số 10 trước khi biên dịch. Đây là hằng tiền xử lý — không chiếm bộ nhớ và không có kiểu dữ liệu.

Hằng liệt kê (enum)

Khối enum color { blue, red, yellow }; tạo ra ba hằng số nguyên mang giá trị mặc định lần lượt là 0, 1, 2. Các tên này không thể gán lại giá trị mới; nhưng biến khai báo kiểu enum color a; hoàn toàn có thể thay đổi giá trị (ví dụ: a = red;).

Chuỗi ký tự và ký tự kết thúc null

Chuỗi như "hello bit.\n" là chuỗi ký tự dạng literal — được lưu trong bộ nhớ dưới dạng mảng ký tự kết thúc bằng ký tự null '\0'. Đây là dấu hiệu xác định độ dài chuỗi và điều kiện dừng khi duyệt.

Sự khác biệt giữa hai cách khai báo sau đây rất quan trọng:

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <string.h>

int main() {
    char arr1[] = "abc";           // Tương đương: {'a','b','c','\0'}
    char arr2[] = {'a','b','c'};   // Không chứa '\0' → không phải chuỗi hợp lệ

    printf("arr1: %s\n", arr1);     // In đúng: "abc"
    printf("arr2: %s\n", arr2);     // Hành vi không xác định: in ngẫu nhiên đến khi gặp '\0'

    printf("strlen(arr1): %zu\n", strlen(arr1)); // Kết quả: 3
    printf("strlen(arr2): %zu\n", strlen(arr2)); // Kết quả ngẫu nhiên (do thiếu '\0')

    return 0;
}

Hàm strlen() đếm số ký tự cho đến khi gặp '\0' đầu tiên — nếu mảng không chứa ký tự kết thúc, hàm sẽ tiếp tục đọc bộ nhớ ngoài phạm vi mảng, gây ra kết quả không lường trước.

Các ký tự thoát (escape sequences)

Ký tự thoát Mô tả
\'Ký tự nháy đơn
\"Ký tự nháy kép
\\Dấu gạch chéo ngược
\nXuống dòng
\tTab ngang
\rCon trỏ về đầu dòng
\bLùi một ký tự (backspace)
\fĐổi trang
\aPhát âm thanh cảnh báo
\vTab dọc
\?Dấu hỏi (tránh xung đột với chuỗi tam giác)
\0Ký tự null (ASCII 0)
\xhhKý tự ASCII theo mã hex (ví dụ: \x41 = 'A')
\oooKý tự ASCII theo mã bát phân (ví dụ: \101 = 'A')

Ví dụ in ký tự đặc biệt:

int main() {
    printf("Single quote: '%c'\n", '\'');
    printf("Double quote: \"%c\"\n", '\"');
    return 0;
}

Đối với đường dẫn c:\\test\\32\\test.c, chuỗi \\32 được hiểu là ký tự có mã bát phân 32 (tức 0x1A, ký tự SUB trong ASCII), không phải hai ký tự riêng lẻ '\\''3' rồi '2'.

Câu lệnh rẽ nhánh cơ bản

Đoạn mã sau minh họa cấu trúc điều kiện và vòng lặp — tuy nhiên cần sửa lỗi cú pháp và logic:

int main() {
    printf("Tham gia Bit!\n");
    int line_count = 0;
    
    while (line_count < 20000) {
        printf("Dòng mã thứ %d\n", line_count + 1);
        line_count++;
    }
    
    if (line_count >= 20000) {
        printf("Nhận được offer hấp dẫn!\n");
    }
    
    return 0;
}

Hàm do người dùng định nghĩa

Hàm cộng hai số nguyên:

int compute_sum(int a, int b) {
    return a + b;
}

int main() {
    int val1 = 200;
    int val2 = 300;
    int result = compute_sum(val1, val2);
    printf("Tổng = %d\n", result);
    return 0;
}

Tính tổng tất cả phần tử trong mảng:

int main() {
    int numbers[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    int idx = 0;
    int total = 0;

    while (idx < 10) {
        total += numbers[idx];
        idx++;
    }

    printf("Tổng các phần tử: %d\n", total);
    return 0;
}

Toán tử dịch bit và sizeof

  • <<: Dịch trái — nhân số nguyên với luỹ thừa của 2.
  • >>: Dịch phải — chia số nguyên có dấu theo luỹ thừa của 2 (làm tròn hướng âm).
  • sizeof: Trả về số byte chiếm dụng bởi kiểu dữ liệu hoặc biểu thức. Với mảng, sizeof(arr) trả về tổng kích thước toàn bộ mảng (không phải con trỏ).

Hàm tìm giá trị lớn hơn

int find_max(int p, int q) {
    return (p > q) ? p : q;
}

int main() {
    int x = 10;
    int y = 20;
    int largest = find_max(x, y);
    printf("Giá trị lớn hơn: %d\n", largest);
    return 0;
}

Biểu diễn số nguyên trong máy tính

Các số nguyên được lưu trữ dưới dạng mã nhị phân, gồm ba dạng chính:

1. Số máy (machine number)

Là biểu diễn nhị phân có bit dấu ở vị trí cao nhất: 0 cho số dương, 1 cho số âm. Ví dụ với 8-bit:

  • +3 → 00000011
  • −3 → 10000011

2. Giá trị thật (true value)

Là giá trị số học tương ứng với số máy. Ví dụ: 10000011 có bit dấu là 1, nên giá trị thật là −3, không phải 131.

3. Các dạng mã

  • Nguyên mã (sign-magnitude): Bit cao nhất là dấu, phần còn lại là trị tuyệt đối.
    Ví dụ: +1 → 00000001, −1 → 10000001. Phạm vi với 8-bit: [−127, +127].
  • Phản mã (one's complement): Với số âm, đảo tất cả bit ngoại trừ bit dấu.
    Ví dụ: −1 → 11111110. Có hai biểu diễn cho 0 (0000000011111111).
  • Bù hai (two's complement): Với số âm, lấy phản mã rồi cộng thêm 1.
    Ví dụ: −1 → 11111111. Đây là dạng được sử dụng phổ biến nhất vì loại bỏ được vấn đề hai số 0 và hỗ trợ phép cộng trực tiếp.

Toán tử phủ định bit (~)

int main() {
    unsigned int a = 0;           // 32-bit: 0x00000000
    unsigned int b = ~a;          // 32-bit: 0xFFFFFFFF

    printf("Giá trị b (hex): 0x%08X\n", b); // In: 0xFFFFFFFF
    printf("Giá trị b (dec): %u\n", b);      // In: 4294967295

    return 0;
}

Lưu ý: Khi áp dụng ~ lên kiểu có dấu, kết quả phụ thuộc vào cách biểu diễn (thường là bù hai), nên nên dùng kiểu unsigned để rõ ràng.

Toán tử tăng/giảm hậu tố và tiền tố

// Hậu tố: giá trị cũ được sử dụng, sau đó mới tăng
int main() {
    int m = 10;
    int n = m++;  // n = 10, sau đó m = 11
    printf("m = %d, n = %d\n", m, n); // In: m = 11, n = 10
    return 0;
}
// Tiền tố: tăng trước, rồi mới sử dụng giá trị mới
int main() {
    int u = 10;
    int v = ++u;  // u = 11, sau đó v = 11
    printf("u = %d, v = %d\n", u, v); // In: u = 11, v = 11
    return 0;
}

Thẻ: C strings sizeof bitwise-operators twos-complement

Đăng vào ngày 8 tháng 6 lúc 00:44