5. C 语言的数据类型全家桶

上一篇文章里,我们学会了给数据起名字——变量名,并且知道声明变量时要写类型。你有没有好奇过:为什么 C 语言要提供 intfloatdoublechar 这么多种类型?只用一个“万能数字”类型不行吗?

答案是:不行。因为计算机的内存是有限的,CPU 处理不同数据的效率也不一样。类型系统让你能在“省空间”和“高精度”之间自由权衡——它也是 C 语言高效、贴近硬件的核心原因之一。

今天,我们就来逐一拜访 C 语言的数据类型家族,看看每个“抽屉”有多大,能装什么,该怎么选。到最后你会发现,了解类型,就是了解计算机如何存储和运算数据。


一、整型家族:存整数的各种型号

整型,顾名思义,就是用来存放整数的类型。但整数也有大小之分——你不可能用同一个箱子去装“5”和“20 亿”,C 语言为此准备了一整套大小不同的整型。

1. 基本整型

类型典型占用字节(64位系统)大致取值范围
short2 字节-32,768 ~ 32,767
int4 字节-2,147,483,648 ~ 2,147,483,647
long4 或 8 字节(Windows 64位是4,Linux 64位是8)同 int 或更大
long long8 字节-9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807

其中 short 全称是 short int(简写 short),longlong intlong longlong long int。最常用的就是 int

声明整型变量:

int student_count = 35;
short temperature = -10;
long long world_population = 7800000000L;  // L 后缀表示 long long 常量

需要注意,C 标准只规定了 shortintlonglong long 的最小范围,具体大小由编译器和平台决定。要想写出可移植的程序,得用 sizeof 获取实际大小(后面会讲),或者使用固定宽度整型如 int32_t(在 <stdint.h> 里,高阶阶段再说)。

2. 无符号整型:把负数空间让给正数

有时候,一个整数根本没有负值(比如年龄、编号、计数),这时如果还保留负数范围,就白白浪费了一半的容量。C 语言允许你在整型前面加上 unsigned 关键字,把整个取值范围平移到非负数。

类型典型字节取值范围
unsigned short20 ~ 65,535
unsigned int40 ~ 4,294,967,295
unsigned long4/80 ~ 4,294,967,295 或更大
unsigned long long80 ~ 18,446,744,073,709,551,615

声明示例:

unsigned int age = 25;           // 年龄不为负
unsigned short port = 65535;     // 网络端口号

注意unsignedsigned 之间的赋值、比较要特别小心,混合使用时极易产生“隐形 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 语言提供了三种精度:

类型典型字节有效数字大致范围
float4约 6-7 位十进制有效数字±3.4E+38
double8约 15-16 位有效数字±1.7E+308
long double10/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 charunsigned 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 charchar;在 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。接下来,我们要把这些数据“说”给用户听,也要“听”用户说了什么——下一篇,格式化输入输出(printfscanf),我们终于能真正和程序对话了。


课后小练习

  1. 写一个程序,打印你平台上所有整型(包括 unsigned)的占用字节数和最大值(用 limits.h 里的宏)。看看 long 是 4 还是 8 字节。
  2. 声明一个 unsigned short 变量并赋予最大值 65535,然后加 1,打印结果,观察溢出行为。
  3. 解释:为什么 float 变量赋值 0.1,然后用 printf("%.10f", f) 输出,看到的不是精确的 0.1?
  4. (小挑战)写一个程序,交换两个 int 变量的值,但不要使用第三个变量。提示:可以用加减法或异或运算。

我们下期见!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值