Biến, phép gán, và kiểu — dấu bằng không phải 'bằng nhau'
x = x + 1 vô lý trong toán nhưng hợp lệ ở đây: dấu bằng không so sánh mà chép giá trị vào một ô nhớ có tên, sau khi vế phải đã tính xong.
TL;DR: Dấu bằng trong code là phép gán, không phải phép so sánh "bằng nhau" như trong toán học: máy tính xong toàn bộ vế phải trước, rồi mới chép kết quả vào ô nhớ bên trái. Mỗi ô nhớ chỉ giữ đúng một giá trị tại một thời điểm, nên gán lại là ghi đè: giá trị cũ mất hẳn, không có cách nào lấy lại nếu chưa được cất sang một ô khác. Dòng lệnh x = x + 1 vô nghiệm nếu coi là phương trình toán học, nhưng lại hoàn toàn hợp lệ trong code vì thứ tự tính toán khác hẳn: vế phải dùng giá trị CŨ của x, còn vế trái nhận giá trị MỚI, hai thời điểm khác nhau nên không hề mâu thuẫn.
Ở bài trước bạn đã biết máy thi hành đúng từng dòng lệnh theo thứ tự, không suy diễn ý định người viết. Giờ thử đọc một dòng lệnh cụ thể:
x = x + 1
Nếu đây là một phương trình toán học, nó vô nghiệm — không có số nào cộng thêm 1 mà vẫn bằng chính nó. Nhưng dòng này chạy được, không báo lỗi gì cả. Nhiều tài liệu nói "biến lưu giá trị" rồi dừng ở đó, khiến người học đoán mò cơ chế. Bài này giải thích đúng cơ chế phép gán để bạn đọc được bất kỳ dòng gán nào, kể cả dòng "trông vô lý" như trên.
1. Analogy — tủ ký gửi có dán nhãn tên
Cứ hình dung dãy tủ ký gửi ở một siêu thị. Mỗi tủ có một nhãn tên dán cố định ở cửa, chẳng hạn tủ dán nhãn diem. Bên trong tủ chỉ chứa đúng một món đồ tại một thời điểm: cất món mới nghĩa là mở tủ, lấy món cũ ra (nếu có), rồi đặt món mới vào — món cũ không được giữ lại đâu cả, trừ khi bạn chủ động mang nó sang tủ khác trước. Ô nhớ trong chương trình hoạt động y hệt: một chỗ chứa có nhãn tên cố định, nhưng giá trị bên trong đổi được, và mỗi lần đổi là một lần ghi đè hoàn toàn.
| Đời thường | Ô nhớ trong chương trình |
|---|---|
Tủ ký gửi dán nhãn diem | Ô nhớ có tên diem (biến) |
| Đặt món đồ mới vào tủ, món cũ bị lấy ra | Gán giá trị mới, giá trị cũ bị ghi đè và mất |
| Nhãn dán ngoài tủ không đổi dù bên trong đổi món | Tên biến giữ nguyên xuyên suốt chương trình, chỉ giá trị bên trong thay đổi |
| Một tủ chỉ chứa đúng MỘT món tại một thời điểm | Một ô nhớ chỉ giữ đúng MỘT giá trị tại một thời điểm |
Đọc x = 5 là "đem giá trị 5 gán vào ô x", không phải "x bằng 5". Đọc đúng cách, bạn sẽ không bao giờ bị x = x + 1 làm bối rối.
2. Phép gán — vế phải tính xong trước, rồi mới chép vào ô
Dòng gán đơn giản nhất chỉ có một giá trị có sẵn ở vế phải:
diem = 90
Máy làm đúng hai việc theo thứ tự: (1) xác định giá trị của vế phải — ở đây là 90, đã có sẵn, không cần tính gì; (2) chép giá trị đó vào ô có tên diem. Trước dòng này, ô diem có thể chưa tồn tại; sau dòng này, nó tồn tại và giữ giá trị 90.
Giờ đến phần thú vị: vế phải không phải một con số có sẵn, mà là một phép tính dùng lại chính tên ô bên trái.
x = 5
x = x + 1
print(x)
Trước khi đọc tiếp: sau khi chạy xong ba dòng trên, dòng print(x) in ra số nào? Viết ra con số bạn tin chắc.
Output thật:
6
Đọc dòng x = x + 1 theo đúng thứ tự máy làm, không đọc theo thứ tự người: (1) máy nhìn vào ô x, thấy giá trị đang có là 5; (2) máy tính vế phải x + 1 bằng giá trị đó: 5 + 1 = 6 — một giá trị mới, tạm thời chưa gán vào đâu cả; (3) máy chép 6 vào ô x, ghi đè lên 5. Dòng print(x) chỉ đọc và in ra giá trị đang có trong ô x tại đúng thời điểm nó chạy, không tự nó làm thay đổi gì.
Sau dòng gán, ô x chỉ còn giữ 6. Giá trị 5 đã mất — đúng như tủ ký gửi ở phần 1: mở tủ, lấy món cũ ra, đặt món mới vào.
Đây là lý do x = x + 1 không mâu thuẫn với phương trình toán học cùng dạng. Trong toán, = khẳng định hai vế bằng nhau cùng một lúc. Trong code, = là lệnh chạy theo thứ tự: "x" vế phải là giá trị cũ, đọc trước khi gán; "x" vế trái nhận giá trị mới, chỉ tồn tại sau khi gán xong. Hai thời điểm khác nhau, nên không mâu thuẫn.
3. Cơ chế bên dưới — ba bước máy luôn làm
Ba bước ở mục 2 áp dụng cho mọi dòng gán, không riêng x = x + 1:

Sơ đồ dưới đây vẽ đúng trạng thái ô nhớ trước và sau khi chạy x = x + 1, minh hoạ tại sao giá trị cũ và giá trị mới không bao giờ tồn tại cùng lúc trong ô x:

Trước dòng lệnh, ô x giữ 5. Ngay lúc máy tính xong vế phải nhưng chưa gán, ô x vẫn còn là 5: kết quả 6 đang "lơ lửng", chưa nằm trong ô nào. Chỉ sau bước gán, ô x mới đổi thành 6 và 5 bị ghi đè hẳn. Không có bước trung gian nào mà x giữ cả hai giá trị cùng lúc — đây là hệ quả trực tiếp của "một ô chỉ giữ một giá trị tại một thời điểm" đã nêu ở KeyTakeaways.
4. Trace một chuỗi ba lần gán liên tiếp
Cùng một cơ chế đó vẫn đúng khi có nhiều dòng gán nối tiếp, mỗi dòng dùng lại giá trị vừa ghi ở dòng trước:
# 1. Khoi tao diem xuat phat
diem = 10
# 2. Cong diem thuong vi tra loi dung
diem = diem + 5
# 3. Tru diem phat vi tra loi tre
diem = diem - 2
# 4. Xem ket qua cuoi cung
print(diem)
Trước khi đọc tiếp: tự chạy bằng tay bốn dòng trên, dòng nào cũng tính lại từ giá trị ô diem NGAY LÚC ĐÓ. Dòng print(diem) in ra số nào?
Output thật:
13
Đối chiếu từng dòng (cột giữa là giá trị CŨ vế phải dùng để tính, cột cuối là giá trị MỚI vừa ghi vào ô):
| Dòng vừa chạy | Vế phải tính bằng giá trị cũ nào | Giá trị mới ghi vào ô diem |
|---|---|---|
diem = 10 | (chưa có giá trị cũ, đây là lần gán đầu tiên) | 10 |
diem = diem + 5 | diem cũ là 10 → 10 + 5 = 15 | 15 |
diem = diem - 2 | diem cũ là 15 → 15 - 2 = 13 | 13 |
Kết quả cuối cùng khớp output thật: diem = 13. Mỗi dòng chỉ nhìn thấy giá trị ô đang giữ NGAY LÚC dòng đó chạy.
5. Vì sao lập trình đọc dấu bằng khác toán học?
| Toán học | Lập trình |
|---|---|
x = x + 1 vô nghiệm — không có x nào thoả mãn | x = x + 1 hợp lệ — đây là một LỆNH, không phải một MỆNH ĐỀ cần giải |
= khẳng định hai vế bằng nhau cùng một lúc, đúng cả hai chiều | = là phép gán, chỉ chạy theo MỘT chiều: tính vế phải trước, chép sang vế trái sau |
a = b và b = a diễn đạt cùng một khẳng định | a = b và b = a là hai lệnh khác nhau — chép giá trị theo hai chiều ngược nhau |
Khảo sát 97 giáo viên phổ thông (Chiodini và cộng sự, ICER 2025): bản thân giáo viên hiếm khi nhầm dấu bằng là phép so sánh (chỉ khoảng 3%), nhưng 30/97 người báo học sinh của mình mắc lỗi này thường xuyên. Phần này nghe "quá cơ bản" chính là cảm giác khiến người ta lướt qua rồi mắc đúng lỗi đó.
6. Pitfall tổng hợp
❌ Nhầm 1 — sợ hoặc bỏ qua dòng x = x + 1 vì tưởng nó "sai về toán": người mới đọc code thường khựng lại ở đây, nghĩ tác giả viết nhầm.
✅ Đọc lại theo đúng thứ tự máy làm ở mục 2: lấy giá trị cũ, tính vế phải, ghi đè.
❌ Nhầm 2 — tưởng ô nhớ giữ được cả giá trị cũ lẫn giá trị mới sau khi gán lại:
diem = 10
diem = diem + 5
# lam tuong diem "nho" ca 10 lan 15 - SAI
✅ Sau dòng thứ hai, ô diem chỉ còn giữ 15. Giá trị 10 mất hẳn. Nếu cần giữ lại giá trị cũ để dùng sau, phải chủ động chép nó sang một ô khác trước khi ghi đè:
# 1. Cat gia tri cu sang mot o rieng, TRUOC khi ghi de
diem_cu = diem
# 2. Bay gio moi cap nhat gia tri moi cho diem
diem = diem + 5
Giờ diem_cu giữ 10, còn diem giữ 15 — hai ô độc lập, không ô nào tự "nhớ" lịch sử của mình.
7. 📚 Đào sâu — vì sao phép gán khó hơn nó trông thế
Nghiên cứu về mô hình tư duy phép gán:
- Investigating the Viability of Mental Models Held by Novice Programmers (Ma và cộng sự, 2007) — khoảng 1/3 sinh viên năm nhất KHÔNG có mô hình tư duy đúng cho phép gán giá trị; nhóm có mô hình đúng làm bài tốt hơn rõ rệt.
- Surveying Upper-Secondary Teachers on Programming Misconceptions (Chiodini và cộng sự, ICER 2025) — nguồn của con số 30/97 giáo viên ở mục 5.
Ghi chú: cả hai đo trên người ĐÃ qua ít nhất một môn nhập môn — bài tưởng "quá cơ bản" này vẫn là nơi nhiều người kẹt lại.
8. Liên hệ các bài khác
- Bài 1 — Chương trình là dãy lệnh — máy thi hành đúng dòng gán theo thứ tự, không suy diễn ý định.
- Bài 3 — Chuỗi và số — mỗi ô nhớ còn giữ một kiểu dữ liệu, chưa nói ở bài này.
- Bài 4 — Bảng trace — dùng đúng bảng ba cột ở mục 3–4 để trace chương trình dài hơn, nhiều biến hơn.
- Bài 5 — Mini challenge: hoán đổi — áp dụng "một ô chỉ giữ một giá trị" để giải thích vì sao hoán đổi hai biến cần một ô tạm.
9. Tóm tắt
- Trace mọi dòng gán bằng đúng 3 bước ở mục 3, kể cả khi biến xuất hiện ở cả hai vế như trong
x = x + 1; đừng cố giải nó như phương trình. - Debug một chuỗi gán dài bằng cách viết ra giá trị hiện tại của từng ô TRƯỚC khi đọc dòng kế tiếp — giá trị cũ có thể đã bị ghi đè từ dòng trước, trí nhớ dễ đánh lừa hơn bạn tưởng.
- Trước khi ghi đè một ô, tự hỏi có cần giữ giá trị cũ không; nếu có, chép nó sang một ô khác trước khi gán — bản thân ô gốc không tự lưu lịch sử.
10. Tự kiểm tra
- Q1Vì sao dòng lệnh x = x + 1 không hề mâu thuẫn với việc phương trình toán học cùng dạng vô nghiệm?
- Q2Đoạn sau ghi giá trị nào vào ô
diemsau dòng cuối cùng?diem = 20 diem = diem + 10 diem = diem - 5 - Q3Sau khi chạy hai dòng y = 3 rồi y = y + y, ô y giữ giá trị bao nhiêu? Giải thích cơ chế, không chỉ nêu kết quả.
- Q4Bạn đọc đoạn code: a = 5 rồi b = a. Sau đó bạn đổi a = 9. Ô b có tự đổi theo thành 9 không? Vì sao?
- Q5Vì sao nói 'một ô chỉ giữ một giá trị tại một thời điểm' lại là lý do khiến bạn không thể hoán đổi giá trị của hai biến chỉ bằng hai dòng gán trực tiếp (a = b rồi b = a)?
- Q6Mô tả quy trình ba bước máy luôn thực hiện khi chạy MỘT dòng lệnh gán bất kỳ, không riêng gì x = x + 1.
Bài tiếp theo: Chuỗi và số
Bài này đáng gửi cho bạn học cùng?
Copy link đã gắn nguồn — dán group, chat, hoặc LinkedIn.
Bài này có giúp bạn hiểu bản chất không?
Hỏi đáp về bài này
Chưa có câu hỏi
Có gì chưa rõ trong bài? Đặt câu hỏi đầu tiên — câu trả lời từ cộng đồng giúp bạn (và người sau).
Đặt câu hỏi đầu tiên