在C语言里表达负数最直接的方式就是在整数字面量前面加一个负号,例如写 int a = -10; 就表示变量 a 里存了一个负十。但计算机内存并不会真的放一个减号进去,而是把数值按照补码形式编码成二进制。要搞懂C语言怎么表达负数,就得先明白原码、反码和补码这三个概念,以及它们和有符号类型的关系。

一、字面量层面的负数表达
从语法上看,C语言用一元负号运算符来表示负数。下面这段代码演示了如何定义负数变量:
#include <stdio.h>
int main(void) {
int x = -5; // 十进制负数
int y = -0x0A; // 十六进制负数,等价于 -10
int z = -010; // 八进制负数,等价于 -8
printf("%d %d %dn", x, y, z);
return 0;
}
这里的负号其实是运算符,编译器会先算出正数的补码,再对其取负得到最终补码。对程序员来说,直接写负号就是最自然的负数表达方式。
二、计算机底层的补码表示
对于有符号整型(如 int、short),C语言标准规定使用补码来表示负数(自C99起明确要求)。以8位 char 为例:
- 原码:最高位为符号位,0正1负,其余为数值。-5 的原码是 10000101。
- 反码:负数原码符号位不变,其他位取反,-5 反码是 11111010。
- 补码:反码加1,-5 补码是 11111011,这也是内存中真正存放的内容。
补码的好处是加法和减法可以统一用同一套电路,而且 0 只有一种表示(00000000),没有负零。
查看负数补码的小例子
#include <stdio.h>
int main(void) {
signed char c = -5;
// 以无符号方式打印每个字节的二进制效果
unsigned char u = (unsigned char)c;
printf("value=%d, hex=%02Xn", c, u); // 输出 value=-5, hex=FB
return 0;
}
0xFB 就是 11111011,正好对应上面说的补码。
三、负号和补码的区别
很多人把“写负号”和“补码”混为一谈,其实它们不在同一层:
| 对比项 | 负号 | 补码 |
|---|---|---|
| 所在层面 | 语言语法 | 数据在内存的编码 |
| 表现形式 | 源码里的 - 号 | 二进制位模式 |
| 是否可见 | 人能直接看到 | 需打印或调试才看到 |
简单说,负号是你告诉编译器“这是个负数”的写法,补码是编译器最终安排进内存的办法。
四、写代码时的注意点
1. 无符号类型不要赋负数
如果你写 unsigned int u = -1;,负号仍然生效,但结果会按模运算回绕,得到该类型最大值。这常常引发隐蔽 bug。
2. 右移负数
有符号负数右移在C里是算术右移(补符号位),但标准允许实现定义行为,可移植代码尽量少依赖它。
3. 类型转换
把负数赋给更宽的类型会做符号扩展,例如 char 的 -1 转 int 后仍是 -1,底层补码从全1字节变成全1四字节。
五、小结
在C语言里表达负数,语法上就是加负号,底层则统一用补码存储。明白原码反码补码的转换,以及负号只是语法糖、补码才是真相,就能看懂溢出、类型提升和位运算里的很多奇怪现象。写代码时留意无符号混用和移位规则,负数就不会再成为你的盲区。