C/C++ 的数据类型表示范围取决于编译器和硬件平台,但标准规定了最小范围,实际范围可通过 limits.h 和 float.h 获取。以下以最常见的 32/64 位系统(LP64/ILP32/LLP64) 为准,列出典型范围。

1. 整型(整数类型)

1.1 有符号整型

类型 位数 最小值 最大值
signed char 8 -128 127
short (或 short int) 16 -32,768 32,767
int 32 -2,147,483,648 2,147,483,647
long (Linux 64位: 64位; Windows 64位: 32位) 32/64 -2,147,483,648 / -9.22e18 2,147,483,647 / 9.22e18
long long 64 -9,223,372,036,854,775,808 9,223,372,036,854,775,807

1.2 无符号整型

类型 位数 最小值 最大值
unsigned char 8 0 255
unsigned short 16 0 65,535
unsigned int 32 0 4,294,967,295
unsigned long 32/64 0 4.29e9 / 1.84e19
unsigned long long 64 0 18,446,744,073,709,551,615

⚠️ 注意char 是否有符号由实现定义(通常为 signed char,但有些 ARM 工具链默认为 unsigned char)。


1.3 整数存储

计算机内部只能存储0和1(二进制),所以需要用二进制来表示数字。但数字有正数负数,如何用二进制表示负数?

三种编码方式应运而生:原码反码补码

结论: 计算机中存储和运算都是用补码,原码和反码只是理解补码的中间步骤。

2.1 原码(Sign-Magnitude)

定义: 最高位表示符号(0=正,1=负),其余位表示数值的绝对值。

十进制 原码(8位)
+5 0 0000101
-5 1 0000101
+0 0 0000000
-0 1 0000000

特点:

  • 直观易懂,人一眼能看出大小

  • 有两个0(+0和-0),浪费了一个编码

  • 减法无法直接用加法器实现(需要额外处理符号位)


2.2 反码(One's Complement)

定义:

  • 正数:反码 = 原码
  • 负数:反码 = 原码符号位不变,其他位取反
十进制 原码(8位) 反码
+5 0 0000101 0 0000101
-5 1 0000101 1 1111010
+0 0 0000000 0 0000000
-0 1 0000000 1 1111111

特点:

  • 正数不变,负数按位取反

  • 仍然有两个0(+0和-0)

  • 减法可以通过加法实现(但需要循环进位)


2.3 补码(Two's Complement)★★★★★

定义:

  • 正数:补码 = 原码(=反码)
  • 负数:补码 = 反码 + 1

更快的口诀: "原码取反,再加一"(针对负数)

十进制 原码 反码 补码
+5 0 0000101 0 0000101 0 0000101
-5 1 0000101 1 1111010 1 1111011
+0 0 0000000 0 0000000 0 0000000
-0 1 0000000 1 1111111 1 00000000(溢出,仍为0)

特点(必须背住!):

  • 只有1个0(解决了"两个0"的问题)

  • 减法可以用加法器统一实现

  • 计算机中实际存储和运算用的就是补码!

  • 8位补码的范围:-128 ~ +127

编码方式 正数表示 负数表示 0的个数 范围(8位) 是否用于存储
原码 符号位0+绝对值 符号位1+绝对值 2个(+0和-0) -127 ~ +127
反码 同原码 符号位不变,其余取反 2个(+0和-0) -127 ~ +127
补码 同原码 反码+1 1个(只有0) -128 ~ +127

2. 浮点类型(IEEE 754)

类型 位数 最小值(正) 最大值 精度(有效十进制位数)
float 32 ≈1.175 × 10⁻³⁸ ≈3.402 × 10³⁸ 6~7 位
double 64 ≈2.225 × 10⁻³⁰⁸ ≈1.798 × 10³⁰⁸ 15~16 位
long double 80 / 128 (x86扩展精度) ≈3.36 × 10⁻⁴⁹³² ≈1.18 × 10⁴⁹³² 18~19 位

特殊值float/double 还可表示正负无穷(±INF)、NaN(非数值)、零(有符号零 ±0.0)。

阶码的范围是0-(2^n-1),全1有特殊意义,表示无穷大。

对于规格化单精度:

其中 bibi​ 是尾数位从左到右的比特。

对于双精度 (64位):

  • 指数位 11 位,偏移量 1023
  • 尾数位 52 位
  • 公式同理,只需替换偏移量和位数。

3. 布尔型与字符类型

类型 取值 说明
bool (C++ / C99) true (1) 或 false (0) 占 1 字节(典型)
char -128~127 或 0~255 用于存储 ASCII 或 UTF-8 字节
wchar_t 平台相关(16/32位) 用于宽字符

4. void 类型

  • void:没有值,不能表示范围。用于无返回值函数或通用指针 void*

5. 如何获取本机的精确范围?

IEEE 754 通过将 SME 编码到固定的二进制位域中来实现存储。

例如:E=127 → 实际指数 0,E=128 → 实际指数 1,E=126 → 实际指数 -1。

(2) 非规格化值(Denormalized / Subnormal)

当 E == 0 且 M ≠ 0 时:

(3) 零值

当 E == 0 且 M == 0 时:

(4) 无穷大(Infinity)

当 E == 255 且 M == 0 时:

(5) 非数值(NaN, Not a Number)

当 E == 255 且 M ≠ 0 时:

3.2 双精度(64 位)

包含头文件 <limits.h>(整型)和 <float.h>(浮点型)。

  • 示例:

    #include <stdio.h>
    #include <limits.h>
    #include <float.h>
    
    int main() {
        printf("int max: %d\n", INT_MAX);
        printf("double min positive: %e\n", DBL_MIN);
        return 0;
    }

    也可用 C++ 的 <limits> 模板:

    std::numeric_limits<int>::max();

    6. 标准最低要求(ISO C/C++ 保证)

    类型 最小位数 最小值 最大值
    signed char 8 -127 127
    short 16 -32,767 32,767
    int 16 -32,767 32,767
    long 32 -2,147,483,647 2,147,483,647
    long long (C99/C++11) 64 -9.22e18 9.22e18

    注意:标准允许采用补码原码,现代平台全是补码。

    任意一个二进制浮点数 V 可表示为:

    V = (-1)ˢ × M × 2ᴱ

  • S:符号位(Sign),0 表示正数,1 表示负数。

  • M:尾数(Significand 或 Mantissa),是一个二进制小数,范围通常为 [1, 2) 或 [0, 1),取决于指数部分。

  • E:指数(Exponent),对尾数进行缩放,可以是正数或负数。

  • IEEE 754 定义了多种浮点格式,最常用的是:

    类型 总位数 符号位 指数位 尾数位 指数偏移(Bias)
    单精度(binary32) 32 1 8 23 127
    双精度(binary64) 64 1 11 52 1023
    半精度(binary16) 16 1 5 10 15
    四精度(binary128) 128 1 15 112 16383
     

    3.1 单精度(32 位)

    [31]    符号位 S
    [30:23] 指数位 E (8 位)
    [22:0]  尾数位 M (23 位)

    表示的值分为几种情况(根据指数 E 的值):

    (1) 规格化值(Normalized)

    当 0 < E < 255(即 E 不全为 0 也不全为 1)时:

  • 实际指数 = E - Bias,Bias = 127。

  • 尾数隐含整数部分为 1,即实际尾数 = 1.M(二进制)。

  • 因此值 = (−1)S×1.M(2)×2E−127(−1)S×1.M(2)​×2E−127。

  • 实际指数 = 1 - Bias = -126(固定)。

  • 尾数隐含整数部分为 0,即实际尾数 = 0.M。

  • 值 = (−1)S×0.M×2−126(−1)S×0.M×2−126。

  • 作用:表示非常接近零的数,实现渐进下溢(gradual underflow),避免突然下溢到零。

  • 表示 ±0(符号位决定正负零)。正零和负零在算术上通常视为相等。

  • 表示 ±∞(符号位决定)。用于溢出结果,如 1.0/0.0。

  • 表示未定义或无效的运算结果,如 0.0/0.0,∞ - ∞ 等。

  • 可分类为 SNaN(Signaling NaN,会触发异常)和 QNaN(Quiet NaN,不触发异常),通常由 M 的最高位区分。

  • [63]    符号位 S
    [62:52] 指数位 E (11 位)
    [51:0]  尾数位 M (52 位)

    Bias = 1023。

  • 规格化范围:E 从 1 到 2046,实际指数 = E - 1023。

  • 非规格化:E = 0,M ≠ 0,实际指数 = -1022。

  • 无穷大:E = 2047,M = 0。

  • NaN:E = 2047,M ≠ 0。


  • 7. 常见陷阱

  • 隐式类型转换:有符号与无符号混合计算时,有符号会被转为无符号,可能导致意外结果(如 -1 > 0U 为真)。

  • 溢出:有符号溢出是未定义行为;无符号溢出是模 2^n 运算。

  • 浮点数比较

    不要直接使用 == 比较两个浮点数,因为累积误差会导致本来相等的值微量不同。应使用:

    fabs(a - b) < epsilon

     整数转浮点的精度丢失

    当整数超过 2²⁴(单精度)或 2⁵³(双精度)时,无法精确表示所有整数,会产生舍入。
    例:16777217 在单精度中会变成 16777216。

    运算顺序影响精度

    例如 (a+b)+c 与 a+(b+c) 可能因舍入顺序不同导致结果有微小差异。

    非规格化数性能问题

    有些 CPU 在处理非规格化数时速度较慢(甚至产生异常),可通过设置 FTZ(Flush To Zero)标志强制将非规格化数视为 0 来提高性能(通常在图形或音频处理中)。

  • 例题

  • 一个 32位无符号整数可以表示的最大值,最接近下列哪个选项?
    
    A. 4×10^9    [*]
    B. 4×10^10
    C. 2×10^9
    D. 2×10^10

    解析:两种思路,一是记住32位无符号数的表示范围为0-4,294,967,295,约为4x10^9;

  • 二是使用更通用的方法。计算一个十进制位需要多少个二进制位,虽然10不能使用整个二进制位表示,但可以按下面的思路推导出来。

  • 一个4进制位,可以用2个二进制位表示,因为log2(4)=2

  • 一个8进制位,可以用3个二进制位表示,因为log2(8)=3

  • 一个16进制位,可以用4个二进制位表示,因为log2(16)=4

  • .....

  • 那么一个10进制位,可以用多个少个二进制位表示呢?同样,可以按上面的计算方法,log2(10)≈3.22.

  • 32位无符号数可以表示为4x2^30. 30/3.22≈9.316≈9(舍0.316),因此,如果答案在A/B之间,应该选择A。同样,也可以表示成2x2^31. 31/3.22≈9.627≈10(入0.373),如果答案在c/d之间,应该选择D。显然,前者舍0.316,后者入0.373,前者更接近真实值。

  • 32 位 int 类型的存储范围是( )?
    
     A. -2147483647 ~ +2147483647
     B. -2147483647 ~ +2147483648
     C. -2147483648 ~ +2147483647
     D. -2147483648 ~ +2147483648

    解析:整数分为无符号数和有符号数,无符号数的范围为0-(2^n-1);有符号的范围为-2^(n-1)-(2^(n-1)-1).最小的负数是-2^(n-1),二进制表示为1000.....0.

8. 备考

(1)核心背诵表

上面的内容必须滚瓜烂熟

(2) 典型题型分类与解法

题型1:数据溢出计算(“算”题型)

特征:给一个超出范围的数,问赋值后或运算后结果是多少(通常会变成负数或截断)。

  • 真题变式short a = 32767; a++; printf("%d", a); 输出什么?

    • 解析:32767是short的最大值(二进制 0111 1111 1111 1111),加1变成 1000 0000 0000 0000,在补码中代表 -32768

  • 计算技巧:超出最大值会“循环”到最小值。例如 short 里 32767 + 1 = -3276832767 + 2 = -32767

题型2:类型比较与整型提升(“比”题型)

特征:比较 int 和 unsigned int,或者 char 和 unsigned char 的大小,判断 if 语句真假。

  • 核心规则(整型提升):当 int 和 unsigned int 运算或比较时,int 会被隐式转换为 unsigned int

  • 经典陷阱题

    unsigned int a = 10;
    int b = -20;
    if (a > b) { printf("A"); } else { printf("B"); }
    • 解析:输出结果是 B。因为 b 被转换成很大的无符号数(-20 的补码变成 4294967276),导致 a > b 为假。

题型3:内存大小与范围推断(“识”题型)

特征:给出字节数(如2字节、4字节),问该类型能表示的最大值或最小值。

  • 公式

  • 无符号最大值:2n−12n−1(n为位数,如8位char就是 28−1=25528−1=255)。
  • 有符号最大值:2n−1−12n−1−1(如16位short就是 215−1=32767215−1=32767)。
  • 有符号最小值:−2n−1−2n−1(如16位short就是 −32768−32768)。
  • 易错点:为什么负数绝对值比正数大1?因为补码中 1000...000 代表 −2n−1−2n−1,而正数没有对应的 +2^{n-1}$(会溢出)。


三、 代码阅读题中的混合考查

阅读程序大题中,它不会单独问范围,而是隐藏在循环或数组下标中:

  • 死循环陷阱

    for (char i = 0; i < 128; i++) { ... }
    • 解析:当 char 为 signed 时,i 最大到127,再加1变成 -128,永远满足 i < 128,导致死循环

  • 数组下标越界

    unsigned char a[256]; 
    for (int i = 0; i <= 255; i++) a[i] = i;
    • 解析:当 i=255 赋值正常,但循环条件 i<=255 对于 unsigned char 是恒成立的(255+1溢出变0),会导致数组越界访问。

💎 备考小贴士

  1. 拒绝口算:遇到边界值(如 32767、-2147483648),一定要在草稿纸上画二进制草图,补码的进位关系非常容易看错。
  2. 关键词敏感:看到 unsigned,立刻警惕“无符号与有符号比较”的陷阱。
  3. char特判:看到 char 类型的循环,要立刻判断它是否会因为 127+1=-128 或 255+1=0 导致死循环或溢出。

这里有一道题留给你思考,试试能不能快速判断答案:

题目:在32位系统中,定义 unsigned short x = 65535;,执行 x += 2; 后,x 的值是?

  • A. 65537

  • B. 1

  • C. 65534

  • D. 0

答案是 B(因为无符号short范围0~65535,65535 + 1 = 0,+ 2 = 1)。如果你做对了,说明你已经掌握了最核心的溢出规律!如果有不理解的地方,随时可以再问我。

更多推荐