C 类型转换
更新时间:2026-08-26。本文是
languages/c/主题入门层第 12 篇。C 的类型转换分两种:编译器偷偷做的隐式转换,和你亲手写的强制转换。坑在于:隐式转换经常不按你想的来,强制转换又可能静默地丢数据。
本文要回答的问题
char参与运算时发生了什么"整型提升"?- 为什么
(double)a / b和(double)(a / b)结果不同? - 大类型转小类型会怎样?
一、整型提升:小类型先"长大"再运算
C 的一条基本规则:char、short 等小于 int 的类型,参与运算时先提升成 int:
c
char c = 200; // 如果 char 是有符号的,200 超出范围(-128~127)
int n = c + 1; // 先提升成 int 再 +1,n = 201(提升救了一命)整型提升让 c + 1 在 int 里运算,避免了 char 溢出。但注意:提升成的是 int(有符号),所以一个"值很大的 unsigned char"提升后可能变成负数:
c
unsigned char uc = 200;
printf("%d\n", uc + 0); // 200?看平台:uc 提升成 int(能装下 200),输出 200大多数情况提升是"帮忙",只需知道它的存在。
二、隐式算术转换:谁大听谁的
不同类型混在一起运算(或比较)时,按"小 → 大"方向转换:
text
int → long → long long → unsigned long long
int → float → double关键是有符号与无符号那篇讲过的:有符号和无符号同级时,无符号胜出。-1 < 1U 是假,因为 -1 被转成无符号变成 4294967295。
c
int a = -1;
unsigned int b = 1;
printf("%d\n", a < b); // 0:a 被转成无符号规则一句话:混用前想清楚类型,必要时显式转换。
三、强制转换:(类型)表达式
c
int a = 5, b = 2;
double r = (double)a / b; // 2.5:先把 a 转成 double,再除位置很讲究:
c
double r1 = (double)(a / b); // 1.0(错误示范):先整数除法得 2,再转 double
double r2 = (double)a / b; // 2.5(正确):先转 a,除法在浮点里做整数除法截断是运算符入门提过的坑,这里给出解法:只要把其中一个操作数转成浮点,除法就在浮点域进行。(double)a / b 是标准写法。
四、窄化转换:会丢数据的那种
"大类型 → 小类型"叫窄化,可能静默丢数据,编译器在 -Wall 下会警告:
c
double d = 3.99;
int i = (int)d; // i = 3:小数部分被砍掉(向零截断,不是四舍五入)
long big = 4000000000L;
int x = (int)big; // 溢出 int,x 的值不可靠!| 窄化场景 | 结果 |
|---|---|
double → int | 小数部分截断(向零) |
long → int | 超出范围则溢出 |
float → char | 高位被丢弃 |
int → unsigned | 负数变成巨大正数 |
什么时候用强制转换:明确知道不会丢数据、或就是要截断时(如取整)。别为了消除编译器警告就随手加转换——那可能是在掩盖 bug。
五、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
(double)(a/b) | 得到 1.0 而非 2.5 | 转其中一个操作数:(double)a / b |
| 有符号无符号混用 | 比较结果反直觉 | 显式转换 |
double 截断成 int | 小数丢失 | 确认意图,用 round 则需 <math.h> |
| 为消警告乱加转换 | 掩盖 bug | 先想清楚再转 |
| 窄化溢出 | 值不可靠 | 用足够大的类型 |
六、与本站主线衔接
- 隐式转换规则在 C 标准里的完整定义(usual arithmetic conversions),见内存布局与 ABI(专家层)的配套解读;
- 转换在汇编里是
mov+ 符号扩展(movsxd等)指令,见与汇编、性能剖析衔接; - 有符号无符号混用是编译警告重点,见预处理、宏与调试。
一句话总结
隐式转换遵循"小向大、无符号胜出";强制转换要放对位置——(double)a / b 是 2.5、(double)(a / b) 是 1.0;窄化会丢数据,-Wall 的警告别用乱加转换去消。