第3章:基本数据类型——整型、字符型、浮点型的表示与存储
数据类型这东西,说白了就是告诉编译器:「嘿,这块内存我要怎么用」。你给它一个类型,它就知道该分配多少空间、怎么存数据、怎么算结果。我刚开始学C语言时,总觉得这玩意儿太基础,结果后来在项目中吃过亏——一个int用错了范围,数据溢出,查了两天才找到原因。嗯,从那以后我再也不敢小看数据类型了。
3.1 整型家族:int、short、long
整型就是存整数的。但同样是整数,为什么分这么多种?因为内存宝贵,你得按需分配。
3.1.1 int——最常用的整型
int是默认的整型,在32位系统上占4个字节(32位),取值范围是-2^31 ~ 2^31-1,也就是大约-21亿到21亿。我个人习惯是:能用int就用int,除非你明确知道数据范围很小或很大。
核心要点:int的字节数取决于平台。16位系统上是2字节,32/64位系统上是4字节。别写死代码依赖int的长度。
#include <stdio.h>
int main() {
int a = 100;
int b = -200;
int c = 0; // 零也是合法的
printf("a = %d, b = %d, c = %d\n", a, b, c);
return 0;
}
3.1.2 short——省着点用
short一般占2个字节,范围是-32768 ~ 32767。什么时候用?比如你存一个年龄、一个班级人数,这些值肯定不会超过3万。我在做嵌入式项目时,经常用short来存传感器数据——省一半内存呢。
注意:short做运算时会被自动提升为int。如果你不小心把一个大数赋给short,它会截断,结果就错了。我曾经在通信协议解析中犯过这个错——一个16位长度字段用short存,结果数据包超过32767就乱套了。
3.1.3 long——要存大数就用它
long在32位系统上是4字节,在64位系统上是8字节。如果你要存一个文件大小、时间戳这种可能很大的数,用long更安全。C99之后还有long long,至少8字节,范围更大。
#include <stdio.h>
int main() {
short s = 30000;
int i = 2000000;
long l = 3000000000L; // 注意L后缀
long long ll = 9000000000000000000LL; // LL后缀
printf("short: %d, int: %d, long: %ld, long long: %lld\n", s, i, l, ll);
return 0;
}
3.2 字符型:char——其实是小整数
很多初学者以为char就是存字符的。没错,但本质上char是一个8位的整数。它存的是字符的ASCII码值。比如'A'对应65,'0'对应48。
小技巧:char可以分signed和unsigned。默认是signed(-128~127),但如果你只存ASCII字符(0~127),用哪个都一样。如果你要存字节数据(比如0x80~0xFF),记得用unsigned char。
#include <stdio.h>
int main() {
char ch = 'A';
printf("字符: %c, ASCII码: %d\n", ch, ch);
// char本质上就是整数,可以做运算
char next = ch + 1;
printf("下一个字符: %c\n", next); // 输出 'B'
// 小心:char做循环变量时可能溢出
char c = 127;
c = c + 1; // 溢出!变成-128
printf("溢出后的值: %d\n", c);
return 0;
}
为什么会这样?因为char只有8位,最高位是符号位。127的二进制是0111 1111,加1变成1000 0000,在补码表示中就是-128。嗯,这里要注意,做循环时别用char当计数器,除非你确定范围不超过127。
3.3 浮点型:float和double——不精确的实数
浮点数用来存小数。但有个坑:浮点数在计算机里是近似表示的,不是精确值。你想想看,0.1在十进制里很简单,但在二进制里是无限循环小数。所以浮点数比较时千万别用==。
3.3.1 float——单精度浮点
float占4字节,有效数字约6-7位。范围很大(约±3.4×10^38),但精度有限。我在做PID控制算法时,用float存系数,结果发现积分项累积误差越来越大——后来换成double才解决。
3.3.2 double——双精度浮点
double占8字节,有效数字约15-16位。精度高,但占内存也多。我个人建议:除非你明确要省内存,否则直接用double。现在的计算机算力足够,没必要为了省那4个字节给自己挖坑。
#include <stdio.h>
int main() {
float f = 0.1f; // 注意f后缀
double d = 0.1;
// 看看浮点数的真面目
printf("float: %.20f\n", f);
printf("double: %.20lf\n", d);
// 浮点数比较的坑
if (f == 0.1f) {
printf("相等\n");
} else {
printf("不相等?\n"); // 可能不会执行到这里
}
// 正确的比较方式
double epsilon = 0.000001;
if (d - 0.1 < epsilon && d - 0.1 > -epsilon) {
printf("近似相等\n");
}
return 0;
}
避坑指南:我曾经在金融计算中用float存金额,结果累计到一定数量后对不上账了。后来发现是精度问题。记住:涉及金钱、精确计算时,别用浮点数,用整数(比如以分为单位)或者专门的十进制库。
3.4 数据类型的存储本质
不管什么类型,在内存里存的都是二进制。区别在于:
- 整型:直接存二进制补码。正数原码=补码,负数补码=反码+1。
- 字符型:存ASCII码的二进制值,本质就是小整数。
- 浮点型:按IEEE 754标准存储,分为符号位、指数位、尾数位。
举个例子,int类型的-5在32位系统上存的是:11111111 11111111 11111111 11111011。而float类型的-5.0存的是:1 10000001 01000000000000000000000(符号位1,指数129,尾数1.01)。完全不同的二进制模式!
记住:类型决定了编译器怎么解释这块内存里的二进制数据。同样的二进制位,用int解释和用float解释,结果天差地别。
3.5 知识体系总览
下面这张图帮你理清本章的核心脉络:
3.6 类型选择实战建议
| 场景 | 推荐类型 | 原因 |
|---|---|---|
| 循环计数器 | int | 效率最高,范围够用 |
| 传感器原始数据 | short 或 unsigned short | 省内存,范围匹配 |
| 文件大小 | long 或 long long | 可能超过2GB |
| 单字符/字节数据 | char / unsigned char | 刚好1字节 |
| 科学计算 | double | 精度要求高 |
| 嵌入式实时控制 | float | 省内存,速度可接受 |
我的建议:不确定的时候,先用int和double。等代码跑通了,再根据实际内存和性能需求去优化类型。别一开始就追求极致的内存节省——代码正确比省几个字节重要得多。
好了,这一章的内容就到这里。数据类型是C语言的基石,理解它们的表示和存储方式,能帮你避免很多隐蔽的bug。下一章我们会聊运算符和表达式——到时候你会发现,类型转换又是一个大坑。
公众号:蓝海资料掘金营,微信deep3321