第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 知识体系总览

下面这张图帮你理清本章的核心脉络:

C语言基本数据类型 整型 short (2字节) -32768~32767 int (4字节) -21亿~21亿 long (4/8字节) 更大范围 字符型 char (1字节) -128~127 unsigned char (0~255) 浮点型 float (4字节) 6-7位精度 double (8字节) 15-16位精度 核心:类型决定内存大小和二进制解释方式 浮点数比较用差值法,别用==

3.6 类型选择实战建议

场景 推荐类型 原因
循环计数器 int 效率最高,范围够用
传感器原始数据 short 或 unsigned short 省内存,范围匹配
文件大小 long 或 long long 可能超过2GB
单字符/字节数据 char / unsigned char 刚好1字节
科学计算 double 精度要求高
嵌入式实时控制 float 省内存,速度可接受

我的建议:不确定的时候,先用int和double。等代码跑通了,再根据实际内存和性能需求去优化类型。别一开始就追求极致的内存节省——代码正确比省几个字节重要得多。

好了,这一章的内容就到这里。数据类型是C语言的基石,理解它们的表示和存储方式,能帮你避免很多隐蔽的bug。下一章我们会聊运算符和表达式——到时候你会发现,类型转换又是一个大坑。


公众号:蓝海资料掘金营,微信deep3321