2. 基本数据类型(上):整型
大家好,我是老李。今天咱们聊聊C语言里最基础、也最常用的东西——整型。说实话,我见过不少工作两三年的工程师,写起代码来整型用得挺溜,但一问到补码、溢出这些细节,就开始含糊了。嗯,这章咱们就把这些底层的门道彻底捋清楚。
2.1 整型家族:int、short、long、long long
C语言里的整型,说白了就是用来存整数的。但为什么要有这么多种?你想想看,不同的场景对数值范围和内存占用要求不一样。我早期做单片机开发时,RAM只有几百字节,用错类型可是要出大事的。
先看一张表,把它们的“家底”摸清楚:
| 类型 | 典型字节数 | 取值范围(有符号) | 格式化占位符 |
|---|---|---|---|
| short | 2 | -32768 ~ 32767 | %hd |
| int | 4 | -2147483648 ~ 2147483647 | %d |
| long | 4 或 8 | 取决于平台 | %ld |
| long long | 8 | -2^63 ~ 2^63-1 | %lld |
举个例子,看看它们怎么用:
#include <stdio.h>
#include <stdint.h>
int main() {
short s = 100; // 2字节,省空间
int i = 100000; // 4字节,最常用
long l = 100000L; // 注意L后缀
long long ll = 100000LL; // 注意LL后缀
printf("short: %hd\n", s);
printf("int: %d\n", i);
printf("long: %ld\n", l);
printf("long long: %lld\n", ll);
return 0;
}
这里有个细节:long在32位系统上是4字节,在64位Linux上是8字节,但在64位Windows上还是4字节。是不是很坑?所以我个人建议,除非你明确知道目标平台,否则尽量用int32_t或int64_t。
2.2 无符号整型
无符号整型,就是只能存非负数的整型。用unsigned关键字修饰。为什么要用它?
我在项目中遇到过这样一个场景:需要用一个变量表示数据包的长度。长度不可能为负数吧?用无符号整型就非常合适。而且,无符号整型的正数范围比有符号大一倍。
| 类型 | 字节数 | 取值范围 |
|---|---|---|
| unsigned short | 2 | 0 ~ 65535 |
| unsigned int | 4 | 0 ~ 4294967295 |
| unsigned long long | 8 | 0 ~ 2^64-1 |
for (unsigned int i = 10; i >= 0; i--)。结果程序直接死循环了!为什么?因为无符号数永远不会小于0,i减到0后再减1就变成了4294967295。嗯,这个教训让我记住了:无符号数做循环递减时一定要小心。
2.3 整型数据的存储方式:原码、反码、补码
这部分是重点,也是很多人的盲区。计算机里到底怎么存负数?我刚开始学的时候也迷糊过一阵子。
先看一张图,把三种编码的关系理清楚:
为什么计算机要用补码?说白了,就是为了把减法变成加法。你想想看,CPU里做加法器比做减法器简单多了。用补码,5 - 3就变成了5 + (-3的补码),硬件实现起来非常方便。
看个具体例子,加深理解:
#include <stdio.h>
int main() {
// 以8位有符号整型为例
signed char a = 5; // 补码:0000 0101
signed char b = -5; // 补码:1111 1011
printf("a = %d\n", a);
printf("b = %d\n", b);
// 看看-5的补码在内存里到底是什么
unsigned char *p = (unsigned char *)&b;
printf("b的内存值(十六进制):0x%02x\n", *p);
// 输出:0xfb,也就是1111 1011
// 验证:5 + (-5) = 0
signed char c = a + b;
printf("a + b = %d\n", c); // 输出0
return 0;
}
2.4 整型溢出:一个容易被忽视的问题
说到整型存储,就不得不提溢出。我在做通信协议解析时,就遇到过因为整型溢出导致的bug,排查了好几天才找到原因。
#include <stdio.h>
#include <limits.h>
int main() {
int max = INT_MAX; // 2147483647
printf("INT_MAX = %d\n", max);
printf("INT_MAX + 1 = %d\n", max + 1); // 溢出!变成负数
unsigned int umax = UINT_MAX; // 4294967295
printf("UINT_MAX = %u\n", umax);
printf("UINT_MAX + 1 = %u\n", umax + 1); // 溢出!变成0
return 0;
}
2.5 小结
这一章咱们把整型家族的基本成员都过了一遍。从int、short、long、long long的选用,到无符号整型的应用场景,再到原码、反码、补码的存储原理。这些东西看似基础,但确实是C语言编程的根基。
我个人觉得,理解补码是理解计算机运算的关键。你想想看,为什么-1 + 1 = 0在计算机里能完美实现?就是因为补码的设计。搞懂了这些,后面学位运算、类型转换就会轻松很多。
最后送大家一句话:基础不牢,地动山摇。整型这块看似简单,但真正用好的工程师并不多。希望你能把这些细节吃透,写出来的代码既高效又可靠。
公众号:蓝海资料掘金营,微信deep3321