上一篇文章里,我们学会了给数据起名字——变量名,并且知道声明变量时要写类型。你有没有好奇过:为什么 C 语言要提供 int、float、double、char 这么多种类型?只用一个“万能数字”类型不行吗?
答案是:不行。因为计算机的内存是有限的,CPU 处理不同数据的效率也不一样。类型系统让你能在“省空间”和“高精度”之间自由权衡——它也是 C 语言高效、贴近硬件的核心原因之一。
今天,我们就来逐一拜访 C 语言的数据类型家族,看看每个“抽屉”有多大,能装什么,该怎么选。到最后你会发现,了解类型,就是了解计算机如何存储和运算数据。
一、整型家族:存整数的各种型号
整型,顾名思义,就是用来存放整数的类型。但整数也有大小之分——你不可能用同一个箱子去装“5”和“20 亿”,C 语言为此准备了一整套大小不同的整型。
1. 基本整型
| 类型 | 典型占用字节(64位系统) | 大致取值范围 |
|---|---|---|
short | 2 字节 | -32,768 ~ 32,767 |
int | 4 字节 | -2,147,483,648 ~ 2,147,483,647 |
long | 4 或 8 字节(Windows 64位是4,Linux 64位是8) | 同 int 或更大 |
long long | 8 字节 | -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 |
其中 short 全称是 short int(简写 short),long 是 long int,long long 是 long long int。最常用的就是 int。
声明整型变量:
int student_count = 35;
short temperature = -10;
long long world_population = 7800000000L; // L 后缀表示 long long 常量
需要注意,C 标准只规定了 short ≤ int ≤ long ≤ long long 的最小范围,具体大小由编译器和平台决定。要想写出可移植的程序,得用 sizeof 获取实际大小(后面会讲),或者使用固定宽度整型如 int32_t(在 <stdint.h> 里,高阶阶段再说)。
2. 无符号整型:把负数空间让给正数
有时候,一个整数根本没有负值(比如年龄、编号、计数),这时如果还保留负数范围,就白白浪费了一半的容量。C 语言允许你在整型前面加上 unsigned 关键字,把整个取值范围平移到非负数。
| 类型 | 典型字节 | 取值范围 |
|---|---|---|
unsigned short | 2 | 0 ~ 65,535 |
unsigned int | 4 | 0 ~ 4,294,967,295 |
unsigned long | 4/8 | 0 ~ 4,294,967,295 或更大 |
unsigned long long | 8 | 0 ~ 18,446,744,073,709,551,615 |
声明示例:
unsigned int age = 25; // 年龄不为负
unsigned short port = 65535; // 网络端口号
注意:unsigned 和 signed 之间的赋值、比较要特别小心,混合使用时极易产生“隐形 bug”。比如:
unsigned int a = 10;
int b = -5;
if (a > b) printf("a > b\n"); // 你可能觉得正确,但实际 a > b 为假!
为什么?因为 b 会被隐式转换成 unsigned int,-5 变成一个巨大的正数(比如 4294967291),自然大于 10。这种陷阱非常常见,后面讲到类型转换时会专门再谈。
3. 如何确定类型的大小?引入 sizeof
sizeof 是 C 语言的一个运算符,用于返回某个类型或变量占用的字节数。它是一个编译时操作,不会在运行时消耗额外时间。
用法:
printf("int 占用 %zu 字节\n", sizeof(int));
printf("long long 占用 %zu 字节\n", sizeof(long long));
int x;
printf("变量 x 占用 %zu 字节\n", sizeof x); // 变量可以不加括号
printf("表达式 3.14 的类型占用 %zu 字节\n", sizeof 3.14);
%zu 是专门用来输出 sizeof 返回值(类型为 size_t)的格式化符。
我们用一个小程序来看看当前平台上各整型的大小:
#include <stdio.h>
int main(void) {
printf("short: %zu 字节\n", sizeof(short));
printf("int: %zu 字节\n", sizeof(int));
printf("long: %zu 字节\n", sizeof(long));
printf("long long: %zu 字节\n", sizeof(long long));
printf("unsigned int: %zu 字节\n", sizeof(unsigned int));
return 0;
}
在 64 位 Windows/MinGW 和 64 位 Linux 上运行,long 的结果可能不一样,亲自试试看。
二、浮点型:带小数点的世界
处理小数,比如计算圆周率、价格、温度,就需要浮点型。C 语言提供了三种精度:
| 类型 | 典型字节 | 有效数字 | 大致范围 |
|---|---|---|---|
float | 4 | 约 6-7 位十进制有效数字 | ±3.4E+38 |
double | 8 | 约 15-16 位有效数字 | ±1.7E+308 |
long double | 10/12/16(因平台而异) | 至少和 double 一样 | 更大 |
直接看例子:
float pi = 3.14159f; // 常量尾部加 f 表示 float 类型
double e = 2.718281828; // 不加 f 的浮点常量默认是 double
long double huge = 1.23e400L; // L 后缀表示 long double
精度陷阱:浮点数在计算机内是以二进制科学计数法存储的,很多十进制小数无法精确表示(就像十进制的 1/3 是 0.33333…)。最常见的结果是 0.1 + 0.2 != 0.3。所以,判断两个浮点数相等通常不直接用 ==,而是看它们的差是否在一个很小的误差范围内。
三、字符型:char 不只是一个字母
char 用来存放一个字符,但它在物理上是一个 1 字节(8 位)的整数。字符和整数在 C 语言里可以无缝转换。
char ch = 'A'; // ch 里面存的是 65(A 的 ASCII 码)
printf("%c\n", ch); // 输出 A
printf("%d\n", ch); // 输出 65
常见 ASCII 码速记:'A' 是 65,'a' 是 97,'0' 是 48。
char 也可以有无符号之分:signed char(范围 -128 ~ 127)和 unsigned char(0 ~ 255)。但单独的 char 是有符号还是无符号,由编译器决定(x86 上通常是有符号的)。所以,如果你要把 char 当纯数字用,最好明确写出 signed char 或 unsigned char。
字符常量必须用单引号括起来,比如 'A'、'6'、'\n'。'\n' 这种是转义字符,我们在第三篇里见过几个,这里再补充几个常用的:
'\0':空字符(字符串结束标志),ASCII 码 0'\t':制表符'\\':反斜杠自身'\'':单引号
四、查看类型的极值:<limits.h> 和 <float.h>
想知道你当前平台上 int 最大能存多少,不用靠背表,C 标准库已经给你准备好了。
#include <stdio.h>
#include <limits.h>
#include <float.h>
int main(void) {
printf("int 最小值: %d, 最大值: %d\n", INT_MIN, INT_MAX);
printf("unsigned int 最大值: %u\n", UINT_MAX);
printf("long long 最大值: %lld\n", LLONG_MAX);
printf("float 有效数字位数: %d\n", FLT_DIG);
printf("double 有效数字位数: %d\n", DBL_DIG);
return 0;
}
运行一下,你就能看到自己平台上的真实边界值。当你需要控制数据不越界时,这些宏非常有用。
五、类型选择的经验法则
什么时候用什么类型?以下是一些简单建议:
- 一般整数:就用
int,默认选择。 - 需要保证非负的整数(长度、索引、数量):用
unsigned int,但要小心混合运算。 - 节省大数组内存:如果数值在 -128~127 内,用
signed char或char;在 0~65535 内,用unsigned short。 - 需要极大的整数:
long long。 - 普通小数:
double(别用float,因为精度较低,且现代计算机速度差异不大)。 - 单个字符:
char,但注意符号问题。
这种选择不是绝对的,随着你写的程序越来越多,你会越来越有感觉。
六、常见错误与陷阱
1. 整型溢出(Overflow)
unsigned int a = UINT_MAX; // 4294967295
a = a + 1; // 变成 0,环绕了
printf("%u\n", a); // 输出 0
无符号溢出会安静地“环绕”到 0;有符号溢出是未定义行为,可能崩溃,可能得到奇怪的值,这是 C 里最危险的坑之一。
2. 浮点数精度丢失
float f = 0.1f;
if (f * 10 == 1.0) { // 条件可能不成立!
printf("相等\n");
}
解决办法是用 fabs(f*10 - 1.0) < 1e-6 之类的比较。
3. 错误地将 char 用于算术运算
char a = 100;
char b = 50;
char c = a + b; // 150 可能超出 char 范围(如果 char 是 signed,150 就溢出了)
printf("%d\n", c); // 输出可能是 -106?
最好将 char 提升为 int 后再进行算术。
4. 格式化符与类型不匹配
int x = 10;
printf("%lld\n", x); // 用 %lld 打 int,未定义行为
float y = 3.14f;
printf("%d\n", y); // 用 %d 打 float,同样错误
使用正确的格式化符(后面有专门一篇详讲),现阶段记住:
%d/%i:int%u:unsigned int%ld:long int,%lld:long long int%f:float/double(%lf也是 double 但 printf 里%f就够了)%c:char%zu:size_t(sizeof 返回值)
七、小结
今天你认识了 C 语言的整个数据类型家族:整型的大小差异与有无符号,浮点型的精度取舍,以及字符型的整型本质。你还学会了用 sizeof 探查平台上的真实大小,用 <limits.h> 获取边界值。
这些类型就是你与世界交流的词汇量。选对类型,不仅能让你的程序更省内存,还能避免大量诡异的 bug。接下来,我们要把这些数据“说”给用户听,也要“听”用户说了什么——下一篇,格式化输入输出(printf 和 scanf),我们终于能真正和程序对话了。
课后小练习
- 写一个程序,打印你平台上所有整型(包括 unsigned)的占用字节数和最大值(用
limits.h里的宏)。看看long是 4 还是 8 字节。 - 声明一个
unsigned short变量并赋予最大值 65535,然后加 1,打印结果,观察溢出行为。 - 解释:为什么
float变量赋值0.1,然后用printf("%.10f", f)输出,看到的不是精确的 0.1? - (小挑战)写一个程序,交换两个
int变量的值,但不要使用第三个变量。提示:可以用加减法或异或运算。
我们下期见!

615

被折叠的 条评论
为什么被折叠?



