隐式类型转换
C 语言在表达式求值和赋值时,会自动进行类型转换。这些转换遵循明确的规则(C99 §6.3),理解它们能帮助你预测表达式的结果类型,也能避免精度丢失和意外行为。隐式转换分为两类:整型提升(Integer Promotion)和寻常算术转换(Usual Arithmetic Conversions)。
整型提升
在表达式中,所有比 int 小的整数类型(char、short、bool、位域)首先被提升为 int 或 unsigned int:
char c = 'A';
short s = 100;
/* 表达式中自动提升为 int */
printf("%zu\n", sizeof(c)); /* 1 */
printf("%zu\n", sizeof(c + 1)); /* sizeof(int),通常是 4 */
printf("%zu\n", sizeof(s * 2)); /* sizeof(int) */
整型提升的原因:CPU 的寄存器通常是 int 大小或更大,操作小类型时先提升为寄存器大小效率更高。
有符号 vs 无符号提升:
如果 int 能表示原类型的所有值,提升为 int;否则提升为 unsigned int:
unsigned short us = 65535;
/* us 提升为 int(如果 int 是 32 位)或 unsigned int(如果 int 是 16 位) */
在 32 位 int 平台上,unsigned short 提升为 int,因为 65535 在 int 范围内。
寻常算术转换
当两个不同类型的操作数进行二元运算时,编译器将它们转换为共同类型。转换方向是"向较大类型靠拢":
- 如果任一操作数是
long double,另一个转为long double - 否则如果任一操作数是
double,另一个转为double - 否则如果任一操作数是
float,另一个转为float - 否则(都是整数):先整型提升,然后:
- 如果两个类型相同,完成
- 如果都是有符号或都是无符号,较小的转为较大的
- 如果一个有符号一个无符号:
- 如果无符号类型的等级 >= 有符号类型,有符号转为无符号
- 如果有符号类型能表示无符号类型的所有值,无符号转为有符号
- 否则,两者都转为有符号类型的无符号版本
int a = 5;
double d = 2.5;
/* int 转为 double */
double result = a + d; /* 5.0 + 2.5 = 7.5 */
unsigned int u = 10;
int s = -5;
/* int 转为 unsigned int */
unsigned int result = u + s; /* s = -5 转为很大的无符号数 */
printf("%u\n", result); /* 4294967301(32 位平台) */
这是最常见的隐式转换陷阱——有符号与无符号混合运算时,有符号数被转为无符号。
赋值转换
赋值时,右侧值转换为左侧变量的类型:
double d = 3.14;
int i = d; /* double 转为 int,小数截断:i = 3 */
int x = 300;
char c = x; /* int 转为 char,高位截断 */
printf("%d\n", c); /* 值取决于 char 是否有符号和平台 */
unsigned int u = -5; /* -5 转为无符号:很大的正数 */
窄化转换(大范围类型赋给小范围类型)可能丢失信息,编译器通常会警告。
函数参数转换
没有原型声明的函数(旧式 C)或可变参数函数(如 printf),参数会进行默认提升:
/* 旧式声明(不推荐) */
int func(); /* 不指定参数类型 */
func(3.14); /* double 转为 int(默认提升) */
/* 可变参数 */
printf("%d\n", 3.14); /* 错误:%d 期望 int,但 3.14 是 double */
/* 没有原型匹配,double 不会转为 int */
可变参数函数的浮点参数提升为 double,char 和 short 提升为 int。所以 printf("%f", 3.14f) 是合法的(float 提升为 double,%f 对应 double)。
常见陷阱
有符号与无符号混合:
int s = -5;
unsigned int u = 10;
if (s < u) /* s 转为 unsigned int */
printf("-5 < 10\n"); /* 不会输出! */
/* 正确做法 */
if (s < 0 || (unsigned int)s < u)
/* ... */
整型提升导致意外:
unsigned char a = 255;
unsigned char b = 1;
unsigned char c = a + b; /* a 和 b 提升为 int,255 + 1 = 256 */
/* 256 转为 unsigned char:0 */
printf("%u\n", c); /* 0 */
/* 中间结果不会溢出,因为提升为 int 了 */
混合运算的精度丢失:
int a = 5;
int b = 2;
double wrong = a / b; /* 整数除法:5/2 = 2,再转为 2.0 */
double right = (double)a / b; /* a 转为 double,5.0/2 = 2.5 */
安全做法
- 避免有符号和无整数的混合运算
- 需要精确控制类型时,显式强制转换
- 开启编译器警告(
-Wall -Wextra -Wconversion) - 使用
<stdint.h>的定宽类型减少平台差异
/* 显式转换避免歧义 */
size_t len = strlen(str);
int i = -1;
/* 危险 */
if (i < len) { } /* i 转为 size_t */
/* 安全 */
if (i < 0 || (size_t)i < len) { }