← Lập trình C cơ bản

Bài 5 · Cơ bản · 22 phút· Cập nhật 11/06/2026

Biến & kiểu dữ liệu

Biên soạn bởi Nguyễn Anh Tuấn

Khai báo biến với char, short, int, long long, float, double; xem sizeof, signed/unsigned, overflow và cách chọn kiểu dữ liệu phù hợp cho tiền tệ

Một biến (variable) là một vùng lưu trữ được đặt tên để gọi và giữ một giá trị.
Khai báo gồm ba phần: [kiểu dữ liệu] + [tên biến] + (tuỳ chọn) [giá trị khởi tạo].

Quy ước đặt tên biến:

  • phải bắt đầu bằng chữ hoặc ký tự gạch dưới “_”
  • có thể chứa chữ, số và ký tự gạch dưới “_”
  • không được chứa các từ khoá của C như: int, char, if, for v.v...
  • phân biệt chữ IN HOA và in thường: fullname và fullName là 2 biến khác nhau

bien.c - ví dụ

#include <stdio.h>

int main(void) {
    int tuoi = 15;             // khai báo + gán giá trị khởi tạo
    printf("Tuoi: %d\n", tuoi);
    tuoi = 16;                 // gán lại giá trị mới
    printf("Sang nam: %d\n", tuoi);
    return 0;
}

Kết quả khi chạy

Tuoi: 15
Sang nam: 16
  • Khai báo biến: <kiểu dữ liệu> <tên biến> = <giá trị khởi tạo>; vd int tuoi = 15;
  • %d trong printf là “chỗ để điền một số int”.
  • Gán lại (tuoi = 16;) ghi đè giá trị cũ trong hộp.

C có vài nhóm kiểu cơ bản như: số nguyên (int), số thực (float) và ký tự (char).
Với số nguyên, int là kiểu mặc định, còn short, longlong long là những cách thu hẹp hoặc mở rộng miền giá trị.
Kích thước cụ thể phụ thuộc vào compiler và platform, nên C cung cấp sizeof để kiểm tra kích thước thực tế trên máy tính.

KiểuDùng khi nào?Tối thiểu theo chuẩn CĐiển hình macOS/Linux 64-bit
charmột byte, ký tự hoặc dữ liệu nhỏít nhất 8 bit1 byte
shortsố nguyên nhỏ, ít dùng hơn `int`ít nhất 16 bit2 byte
intsố nguyên mặc định, dùng nhiều nhấtít nhất 16 bit4 byte
longsố nguyên lớn hơn `int` trên nhiều hệ Unixít nhất 32 bit8 byte trên macOS/Linux, 4 byte trên Windows 64-bit
long longsố nguyên rất lớn, ví dụ tổng tiền theo xuít nhất 64 bit8 byte
floatsố có phần thập phân, độ chính xác thấp hơntheo chuẩn floating của implementation4 byte, khoảng 6-7 chữ số có nghĩa
doublesố có phần thập phân, lựa chọn mặc định hơn `float`ít nhất rộng bằng `float`8 byte, khoảng 15-16 chữ số có nghĩa
Thử một số nguyên: → kiểu nào chứa được?
signed char 1 byte · -128 … 127
✗ tràn
unsigned char 1 byte · 0 … 255
✗ tràn
short 2 byte · -32,768 … 32,767
✓ vừa
unsigned short 2 byte · 0 … 65,535
✓ vừa
int 4 byte · -2,147,483,648 … 2,147,483,647
✓ vừa
unsigned int 4 byte · 0 … 4,294,967,295
✓ vừa
long 8 byte · -9,223,372,036,854,775,808 … 9,223,372,036,854,775,807
✓ vừa
unsigned long 8 byte · 0 … 18,446,744,073,709,551,615
✓ vừa
long long 8 byte · -9,223,372,036,854,775,808 … 9,223,372,036,854,775,807
✓ vừa
unsigned long long 8 byte · 0 … 18,446,744,073,709,551,615
✓ vừa
float 4 byte · ~7 chữ số · tới ~10^38
double 8 byte · ~16 chữ số · tới ~10^308

Số nguyên dùng signed/unsigned char, short, int, long, long long; số có phần thập phân dùng float / double. Kích thước trên là điển hình trên macOS/Linux 64-bit; chuẩn C chỉ bảo đảm mức tối thiểu.

kich-thuoc.c - tự hỏi máy của mèo con

#include <limits.h>
#include <stdio.h>

int main(void) {
    printf("CHAR_BIT = %d\n", CHAR_BIT);
    printf("char      : %zu byte\n", sizeof(char));
    printf("short     : %zu byte\n", sizeof(short));
    printf("int       : %zu byte\n", sizeof(int));
    printf("long      : %zu byte\n", sizeof(long));
    printf("long long : %zu byte\n", sizeof(long long));
    printf("float     : %zu byte\n", sizeof(float));
    printf("double    : %zu byte\n", sizeof(double));
    return 0;
}
  • `sizeof` trả về số byte, in bằng `%zu`.
  • Chuẩn C quy định mức tối thiểu, không cam kết mọi máy tính cấp phát biến có cùng kích thước.
  • Khi cần đúng số bit, về sau mèo con sẽ gặp các kiểu trong `<stdint.h>` như `int32_t`, `uint64_t`.

Với số nguyên, C cho phép ghép modifier vào int. Viết short cũng là short int, viết long long cũng là long long int. Modifier không làm số "tốt hơn"; nó chỉ đổi miền giá trị và lượng bộ nhớ cần dùng.

ModifierÝ nghĩaVí dụ
signedcó dấu âm/dương; mặc định với hầu hết kiểu số nguyênsigned int x;
unsignedkhông âm, dùng toàn bộ bit cho phía 0 trở lênunsigned int view_count;
shortít nhất 16 bit, thường nhỏ hơn hoặc bằng `int`short days;
longít nhất 32 bit, có thể rộng hơn `int`long population;
long longít nhất 64 bit, dùng khi miền giá trị lớnlong long cents;

modifier.c

#include <stdio.h>

int main(void) {
    short days = 365;
    int score = 10;
    long population = 98000000L;
    long long cents = 19999LL;
    unsigned int views = 4000000000U;

    printf("%hd %d %ld %lld %u\n", days, score, population, cents, views);
    return 0;
}

Kết quả khi chạy

365 10 98000000 19999 4000000000
  • `short`, `long`, `long long` có thể bỏ chữ `int` vì compiler tự hiểu.
  • `unsigned` không có số âm; phù hợp cho bit mask, byte thô, hoặc bộ đếm không bao giờ âm.
  • Format specifier phải khớp kiểu: `%hd` cho `short`, `%ld` cho `long`, `%lld` cho `long long`, `%u` cho `unsigned int`.

Điều thú vị: char chính là một số nhỏ. Trên môi trường ASCII/UTF-8 compatible mà mèo con thường dùng, mỗi ký tự ứng một số: 'A' = 65, 'B' = 66, 'a' = 97. Vì vậy bạn có thể làm toán trên ký tự:

ascii.c

#include <stdio.h>

int main(void) {
    char c = 'A';
    printf("%c la ma so %d\n", c, c);   // in cả chữ và số
    c = c + 1;                          // sang ký tự kế tiếp
    printf("%c la ma so %d\n", c, c);
    return 0;
}

Kết quả khi chạy

A la ma so 65
B la ma so 66

Cùng một byte, hai cách đọc

Đúng như bài “Hệ nhị phân” ở khoá Máy tính: cùng một dãy bit, in bằng %d ra số, in bằng %c ra ký tự. char chỉ là “byte có ý nghĩa chữ”.

Vì mỗi kiểu chỉ có một số byte cố định trên từng máy, nó chỉ chứa được một khoảng giá trị. Vượt khoảng đó là tràn số (overflow). Với kiểu unsigned, giá trị “quay vòng” theo modulo; với kiểu có dấu, tràn là hành vi không xác định (undefined behavior). Ví dụ dưới dùng giả định phổ biến: unsigned char có 8 bit.

tran.c

#include <stdio.h>

int main(void) {
    unsigned char b = 255;   // 1 byte: chứa 0..255
    printf("%u\n", (unsigned)b); // 255
    b = b + 1;               // vượt 255 -> quay vòng
    printf("%u\n", (unsigned)b); // 0
    return 0;
}

Kết quả khi chạy

255
0
  • Chọn kiểu đủ lớn cho dữ liệu: số dân thì dùng int/long, không dùng char.
  • unsigned = số không âm; với 8 bit, unsigned char thường là 0..255, còn signed char thường là -128..127.
  • Đây đúng là “tràn số 8-bit” bạn đã gặp ở khoá Máy tính hoạt động thế nào.

Trong nhóm số thực, float/double lưu giá trị bằng nhị phân hữu hạn bit, nên nhiều số thập phân chỉ là giá trị gần đúng. Hệ quả nguy hiểm nhất là khi xử lý tiền. Thử cộng 0.10 nhiều lần - để ý nó trông đúng nhưng không đúng:

Cộng 0.10 (10 xu) lặp lại:
Lưu bằng float / double
99.9999999999986
In ra (làm tròn 2 số): 100.00 - trông đúng!
✗ float == 100.00 ? → 0
Lưu bằng số nguyên (xu)
10.000 xu
= 100.00 - đúng theo số xu đã cộng.
✓ 10000 == 1000 × 10
⚠️ Float trông đúng khi in ra (100.00), nhưng giá trị trong máy là 99.9999999999986 - KHÁC 100.00. Nên if tong == 100.00 sẽ SAI, và mỗi phép cộng/làm tròn lại trôi thêm. Với tiền, hãy lưu bằng số nguyên đơn vị nhỏ nhất (xu/đồng) hoặc kiểu thập phân chuẩn.
  • float/double là số GẦN đúng - đừng so hai số thực bằng ==.
  • ĐỪNG lưu tiền bằng float/double; lưu số nguyên ở đơn vị nhỏ nhất: long long so_xu, chỉ /100 khi hiển thị.
  • Đổi tiền tệ: nhân tỉ giá rồi làm tròn một lần theo quy ước nhất quán.

Trung thực: kết quả float còn tuỳ máy

float/double theo chuẩn IEEE 754 nên có cùng định dạng bit ở mọi máy, nhưng kết quả tính có thể lệch nhẹ giữa kiến trúc CPU và trình biên dịch (x87 80-bit vs SSE2, lệnh FMA, cờ tối ưu như -ffast-math). Thêm một lý do để không bao giờ tin float ra "y hệt" ở mọi nơi.

Khi một giá trị không bao giờ đổi (số Pi, số lớn nhất…), hãy đặt tên cho nó như một hằng số - vừa dễ đọc vừa tránh gõ sai. Hai cách hay dùng:

hang.c

#include <stdio.h>

#define PI 3.14159        // thay thế văn bản (không có kiểu)
int main(void) {
    const int MAX = 100;  // biến chỉ-đọc, có kiểu int
    printf("%.2f %d\n", PI, MAX);
    return 0;
}

Kết quả khi chạy

3.14 100
  • const: một biến có kiểu nhưng KHÔNG cho sửa giá trị.
  • #define: bộ tiền xử lý dán thẳng giá trị vào trước khi biên dịch.
  • Quy ước: tên hằng số viết HOA cho dễ nhận ra.

Câu hỏi thường gặp

C là ngôn ngữ “định kiểu tĩnh” (statically typed): phải nói trước biến thuộc kiểu gì để compiler kiểm tra cách dùng và sinh mã truy cập đúng số byte. Python gắn kiểu với giá trị lúc chạy nên tiện hơn khi thử nhanh, nhưng chậm hơn.

Chúng là modifier của nhóm số nguyên. short là cách viết ngắn của short int, longlong int, và long longlong long int. Chúng đổi kích thước tối thiểu và miền giá trị mà biến chứa được.

char lưu một số nhỏ, thường là 1 byte. “Chữ” là cách hiển thị số đó qua bảng mã của máy; trên môi trường ASCII/UTF-8 compatible, số 65 hiện ra là “A”. Vì vậy bạn có thể làm phép tính trên char như trên số.

Là toán tử cho biết một kiểu hay biến chiếm bao nhiêu byte, vd sizeof(int) thường là 4 trên máy hiện đại. Hữu ích để kiểm tra kích thước trên máy của bạn, vì chuẩn C chỉ đặt mức tối thiểu.

Vì máy lưu số thực bằng một số hữu hạn bit (nhị phân). Nhiều số thập phân như 0.1 không biểu diễn chính xác được bằng nhị phân, nên bị làm tròn nhẹ. Đó là lý do đừng so sánh hai số float bằng dấu “==”.

Bằng số nguyên ở đơn vị nhỏ nhất: long long so_xu (hoặc đồng, nếu tiền không có hào) - chỉ chia 100 khi hiển thị. Không dùng float/double: sai số làm tròn tích luỹ qua nhiều giao dịch thành lệch số tiền, và so sánh == dễ thất bại. Khi đổi tiền tệ: nhân tỉ giá rồi làm tròn một lần theo quy ước nhất quán.

const tạo một BIẾN chỉ-đọc, có kiểu rõ ràng (const int MAX = 100;). #define là “thay thế văn bản” do bộ tiền xử lý (preprocessor) làm TRƯỚC khi biên dịch (#define PI 3.14159) - không có kiểu, chỉ là dán chữ.

Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.

Tick những điều em tự tin làm được sau khi học bài này. 0/6

Trả lời vài câu để chắc rằng em đã nắm bài.

Câu 1/3 Điểm: 0

Trong C, một biến char thực chất lưu cái gì?

  1. 1

    Khai báo một biến

    Viết một câu lệnh khai báo biến số nguyên tên “diem” mang giá trị 8.

    Hoàn thành khi: int diem = 8;

  2. 2

    In kích thước kiểu

    Viết chương trình in sizeof(char), sizeof(short), sizeof(int), sizeof(long), sizeof(long long), sizeof(float), sizeof(double) trên máy của mèo con.

    Hoàn thành khi: Dùng %zu cho kết quả sizeof, chạy được với bộ cờ của khoá, và nói được kích thước nào đúng như bảng trong bài, kích thước nào khác.

  3. 3

    char là số nào?

    Chữ “a” (thường) có mã ASCII là bao nhiêu? Dùng công cụ ở Bước 2 (gõ số) hoặc tự tra.

    Hoàn thành khi: 97 - gõ 97 vào công cụ, kiểu signed charunsigned char đều báo vừa và hiện a.

  4. 4

    Bẫy float với tiền

    Dùng công cụ Bước 6: cộng 0.10 một nghìn lần. Giá trị in ra sau khi làm tròn là bao nhiêu, còn giá trị trong máy là bao nhiêu?

    Hoàn thành khi: In ra “100.00” trông đúng, nhưng giá trị trong máy là 99.9999999999986 - so sánh == 100.00 là sai.

  5. 5

    Tràn số (nối với khoá Máy tính)

    Một biến unsigned char đang là 255 (1 byte). Cộng thêm 1 thì nó thành mấy?

    Hoàn thành khi: 0 - quay vòng, đúng như “tràn số” của một byte 8-bit ở khoá “Máy tính hoạt động thế nào?”.

  6. 6

    Chọn kiểu số nguyên

    Cần lưu số xu của một đơn hàng 199.99. Mèo con chọn kiểu nào: int, long long, hay float? Vì sao?

    Hoàn thành khi: Chọn long long so_xu = 19999; hoặc int nếu biết chắc miền giá trị nhỏ; không chọn float vì tiền cần lưu bằng số nguyên đơn vị nhỏ nhất.