重温 C 语言的第一天。作为 Java 出身的开发者,我习惯从 Java 的视角切入:先回顾 Java 的数据类型体系,再对照 C 中的有符号 / 无符号、进制表示、浮点数存储与表达式求值规则,把那些容易踩的坑一次性梳理清楚。

一、Java 数据类型

1.1 基本类型(8 种)

类型 字节 范围 默认值
byte1-128 ~ 1270
short2-32768 ~ 327670
int4-2³¹ ~ 2³¹-10
long8-2⁶³ ~ 2⁶³-10L
float4±3.4×10³⁸0.0f
double8±1.8×10³⁰⁸0.0d
char20 ~ 65535'\u0000'
boolean未明确true / falsefalse

1.2 引用类型

类、接口、数组、枚举、注解。默认值都是 null。

1.3 包装类

基本类型 包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

1.4 关键坑

二、有符号与无符号

同一串二进制,两种解释完全不同(以 8 位为例):

二进制 无符号解释 有符号解释
0000 000000
0111 1111127127
1000 0000128-128
1111 1111255-1

注意 1000 0000:有符号时不是"负 0",而是 -128——补码表示里没有负 0。

三、进制

进制 前缀 合法数字
二进制0b0-1
八进制00-7
十进制无0-9
十六进制0x0-9, a-f

3.1 示例

3.2 十六进制、八进制、二进制互转

例:0xab → a = 1010,b = 1011 → 1010 1011 → 从右往左每 3 位一组:10 101 011 → 253(八进制)。

四、浮点数与 IEEE 754

4.1 float 组成(32 位)

┌─┬─────────┬───────────────────────┐
│S│   E(8)  │        M(23)          │
└─┴─────────┴───────────────────────┘
 S:符号位(0 正 1 负)
 E:指数,按"移码"存储,真实指数 = E - 127
 M:尾数,隐含最高位 1,即 1.M

规格化数值 = (-1)^S × 1.M × 2^(E-127)

4.2 double 组成(64 位)

4.3 特殊值

指数 E 尾数 M 含义
全 0全 0±0
全 0非 0非规格化数(极小数)
1 ~ 254任意规格化数(正常数值)
全 1全 0±∞
全 1非 0NaN

4.4 精度坑

0.1 的二进制是 0.0001100110011... 无限循环,永远存不精确:

#include <stdio.h>
#include <math.h>

int main(void) {
    double a = 0.1 + 0.2;
    printf("%.17f\n", a);              /* 0.30000000000000004 */
    printf("%d\n", a == 0.3);          /* 0 —— 不相等! */

    /* 正确姿势:用误差范围比较 */
    double eps = 1e-9;
    printf("%d\n", fabs(a - 0.3) < eps);   /* 1 */
    return 0;
}

五、表达式求值

5.1 整型提升与常用算术转换

int i = -1;
unsigned int u = 1;

if (i < u)
    printf("i < u\n");      /* 不会走到这里 */
else
    printf("i >= u\n");     /* 实际输出:i >= u */

i 被转换成 unsigned int,-1 变成 4294967295,自然大于 1。Java 里没有这个问题,因为整数全部有符号。

5.2 运算符优先级与结合性

优先级 运算符 结合性
1(最高)() [] -> .—
2! ~ ++ -- + -(单目) * & sizeof (类型)右 → 左
3* / %左 → 右
4+ -左 → 右
5<< >>左 → 右
6< <= > >=左 → 右
7== !=左 → 右
8&左 → 右
9^左 → 右
10|左 → 右
11&&左 → 右
12||左 → 右
13?:右 → 左
14= += -= *= /= %= 等右 → 左
15(最低),左 → 右

口诀:单目 > 算术 > 移位 > 关系 > 相等 > 位运算(& ^ |)> 逻辑 > 条件 > 赋值 > 逗号。位运算符的优先级低于比较运算符,是高频错误点:x & 1 == 0 实际是 x & (1 == 0)。

5.3 求值顺序与未定义行为(UB)

int i = 1;
printf("%d %d\n", i++, i++);   /* 求值顺序未指定,结果不可依赖 */
i = i++;                       /* 未定义行为 */
i++ + ++i;                     /* 未定义行为 */

5.4 整数溢出

int x = 2147483647;    /* INT_MAX */
x = x + 1;             /* 有符号溢出:未定义行为(UB) */

unsigned int y = 0u;
y = y - 1;             /* 无符号回绕:合法,y 变成 4294967295 */

这也是"无符号数做减法要小心"的原因:结果永远不会是负数,而是回绕成一个巨大的数。

5.5 经典例题

表达式 结果 原因
-1 < 1U(C)假-1 被转为无符号数 4294967295
-1 < 1(Java)真Java 整数全是有符号
0.1 + 0.2 == 0.3假二进制无法精确表示 0.1
sizeof(i++)i 不变sizeof 编译期求大小,不求值表达式
char c = 200; 后输出 %d通常 -56超出 signed char 范围,补码机器上回绕

六、今日小结

下一篇:指针与数组——C 语言的灵魂,也是从 Java 转过来最需要重建心智模型的部分。