Bài 8 · Vận dụng · 20 phút· Cập nhật 11/06/2026
Chuyển đổi kiểu ngầm định
Biên soạn bởi Nguyễn Anh Tuấn
Chuyển đổi kiểu ngầm định (implicit conversion) trong C: thăng cấp số nguyên, mất mát khi gán thu hẹp, bẫy so sánh signed/unsigned - bắt bằng cờ -Wconversion.
Ở Bài 7 bạn đã biết 7 / 2 ra 3, và "muốn 3.5 thì viết 7 / 2.0". Nhưng khoan - 7 là int, 2.0 là double, hai kiểu khác nhau (Bài 5) mà sao cộng trừ nhân chia được? Vì C tự chuyển đổi kiểu ngầm định (implicit conversion). "Ngầm định" không có nghĩa là tuỳ tiện - tất cả theo ba luật:
- ▸Trộn trong biểu thức: kiểu RỘNG HƠN thắng - int gặp double thì int được nâng lên double.
- ▸Thăng cấp số nguyên (integer promotion): char và short luôn được nâng lên int trước khi tính.
- ▸Phép GÁN đi ngược: ép giá trị về kiểu của ĐÍCH - kể cả khi phải cắt bớt (thu hẹp).
Vì sao đáng học riêng một bài
hien.c - ba phép tính, hai luật
#include <stdio.h>
int main(void) {
printf("%d\n", 7 / 2); /* int / int -> chia nguyen */
printf("%.1f\n", 7 / 2.0); /* int -> double -> chia thuc */
printf("%d\n", 'A' + 1); /* char -> int -> 66 */
return 0;
} Kết quả khi chạy
3 3.5 66
- ▸Dòng 2: chỉ cần MỘT vế là double, cả phép toán thành double - đây là lý do "7 / 2.0 ra 3.5".
- ▸Dòng 3: 'A' là char mang mã 65; trước khi cộng, nó được THĂNG CẤP lên int → 65 + 1 = 66.
- ▸Hệ quả: cộng hai char vẫn ra int - C không tính toán trực tiếp ở cỡ char.
Phép gán ép giá trị về kiểu của đích. Đích hẹp hơn thì mất mát xảy ra im lặng:
thu-hep.c - bộ cờ -Wall -Wextra cảnh báo cả hai dòng gán này
#include <stdio.h>
int main(void) {
int x = 3.9; /* double -> int: CAT phan le */
unsigned char c = 300; /* 300 vuot 255 -> modulo 256 */
printf("%d\n", x);
printf("%d\n", c);
return 0;
} Kết quả khi chạy
3 44
Và đây là bẫy đáng sợ nhất của chuyển đổi ngầm định - số âm gặp số không dấu:
am-vs-khong-dau.c - vẫn bộ cờ đó, cảnh báo -Wsign-compare ở dòng so sánh
#include <stdio.h>
int main(void) {
unsigned int u = -1;
printf("%u\n", u); /* -1 thanh bao nhieu? */
printf("%d\n", -1 < 1u); /* so sanh nguoc doi */
return 0;
} Kết quả khi chạy
4294967295 0
- ▸3.9 → 3: đổi số thực sang int là CẮT về phía 0, không làm tròn.
- ▸300 → 44: kiểu không dấu quấn vòng theo modulo (300 − 256) - được chuẩn định nghĩa rõ.
- ▸-1 < 1u ra 0: int và unsigned int cùng hạng → vế CÓ DẤU bị đổi sang KHÔNG DẤU, -1 thành 4294967295.
Trung thực
Sáu ví dụ - đoán kết quả trước rồi bấm qua từng bước chuyển đổi. Cuối công cụ có máy trộn kiểu: chọn hai kiểu bất kỳ xem kiểu kết quả và lý do:
printf("%d", 7 / 2);- 1 Cả 7 và 2 đều là int - không phải đổi gì.
Máy trộn kiểu: a ⊕ b ra kiểu gì?
4 byte, có dấu · 8 byte, số thực
- - Có một vế là double → vế còn lại được đổi sang double.
Không ai rà tay từng phép gán. Người viết C có nghề bật cảnh báo rồi để trình biên dịch chỉ điểm:
- ▸Bộ cờ của khoá (-std=c17 -Wall -Wextra) đã bắt được: gán làm đổi giá trị (3.9 → 3, 300 → 44) và so sánh signed/unsigned.
- ▸Thêm -Wconversion khi muốn soi gắt hơn: báo MỌI chuyển đổi ngầm định có thể mất mát (trên cùng file demo: 3 cảnh báo thành 5).
- ▸Quy tắc vàng: thấy cảnh báo chuyển đổi thì sửa KIỂU cho khớp, đừng ép kiểu (cast) cho im - cast là tắt radar đúng chỗ nguy hiểm.
Bài tiếp theo
Câu hỏi thường gặp
Triết lý của C: tin lập trình viên và ưu tiên tiện + nhanh. Trộn int với double là việc quá thường gặp, bắt ép kiểu tay từng chỗ thì code rườm rà. Đổi lại, vài ca chuyển đổi âm thầm gây bất ngờ - nên bài này tồn tại, và nên bật cảnh báo để máy canh giùm.
Khi hai vế khác kiểu, C đổi vế "hẹp" sang kiểu "rộng" hơn để không mất thông tin TRONG PHÉP TÍNH: int gặp double thì lên double, int gặp long thì lên long. Tên đầy đủ trong chuẩn là "usual arithmetic conversions". Lưu ý nó chỉ áp dụng trong biểu thức - phép GÁN thì ngược lại: ép về kiểu của đích, kể cả khi phải cắt bớt.
CPU tính toán thuận nhất ở cỡ int, nên C quy định: char và short luôn được nâng lên int trước khi tham gia phép toán. Hệ quả thú vị: cộng hai char ra int, và một biểu thức toàn char vẫn không bao giờ tràn kiểu char giữa chừng.
Chuyển đổi ngầm định luôn CẮT về phía 0 (3.9 → 3, -3.9 → -3), không làm tròn. Muốn tròn thật thì dùng hàm round() trong <math.h> rồi mới gán. Phân biệt hai việc này tránh được cả lớp bug tính tiền, tính điểm.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Biểu thức 'A' + 1 trong C cho kết quả kiểu gì, giá trị bao nhiêu?
- 1
Đoán hết 6 ví dụ
Quay lại công cụ Bước 4: với mỗi ví dụ, tự đoán kết quả TRƯỚC khi bấm qua các bước.
✅ Hoàn thành khi: Đoán đúng 6/6 (hoặc giải thích lại được những ví dụ đoán sai mà không nhìn đáp án).
- 2
Máy trộn kiểu trong đầu
Không nhìn công cụ:
char⊕char,int⊕unsigned int,long⊕doublera kiểu gì? Rồi kiểm bằng máy trộn ở cuối công cụ.✅ Hoàn thành khi:
int(thăng cấp cả hai) ·unsigned int(vế có dấu bị đổi) ·double(rộng hơn thắng). - 3
Tự chạy bẫy số âm
Chép chương trình Bước 3 (
u = -1) về máy, biên dịch bằng bộ cờ của khoá và chạy.✅ Hoàn thành khi: Thấy 4294967295 và 0; trình biên dịch kèm cảnh báo sign-compare ngay dòng so sánh.
- 4
Bật thêm radar
Biên dịch lại chương trình Bước 2 + Bước 3 với
cc -std=c17 -Wall -Wextra -Wconversion. Đếm số cảnh báo so với khi chưa có-Wconversion.✅ Hoàn thành khi: Nhiều cảnh báo hơn hẳn (file demo của bài: 3 → 5) -
-Wconversionbắt thêm các chuyển đổi có thể mất mát. - 5
Cắt hay tròn?
Viết chương trình in
(int)3.9,(int)-3.9, vàround(3.9)(nhớ#include <math.h>, link-lmnếu Linux).✅ Hoàn thành khi: 3, -3 (cắt về phía 0) và 4 (tròn thật) - nói được khi nào dùng cách nào.
- 6
Săn bẫy trong code cũ
Mở lại bài tập của các bài trước, tìm một chỗ trộn
intvớidoublehoặc so sánh hai kiểu khác nhau, và tự giải thích chuyển đổi nào đang xảy ra.✅ Hoàn thành khi: Chỉ ra đúng vế nào bị đổi sang kiểu nào, đối chiếu được bằng máy trộn kiểu.