在C++中,整数类型分为有符号整数(signed)和无符号整数(unsigned),它们的主要区别在于是否能表示负数。下面我会详细说明各种整数类型的存储方式、范围,以及溢出时的行为。
1. C++中的整数类型
C++标准定义了几种基本整数类型,每种类型的大小(字节数)可能因编译器和平台而异,但通常遵循以下约定(以常见 32 位或 64 位系统为例):
类型 | 典型大小(字节) | 有符号范围 | 无符号范围 |
---|---|---|---|
char | 1 (8位) | -128 到 127 | 0 到 255 |
short | 2 (16位) | -32,768 到 32,767 | 0 到 65,535 |
int | 4 (32位) | -2,147,483,648 到 2,147,483,647 | 0 到 4,294,967,295 |
long | 4 或 8 | 平台相关,通常同 int 或更大 | 平台相关 |
long long | 8 (64位) | -2^63 到 2^63-1 | 0 到 2^64-1 |
- 有符号类型:默认情况下,
char
、short
、int
、long
、long long
是有符号的。 - 无符号类型:通过添加
unsigned
修饰符,如unsigned int
、unsigned char
等。 - 特殊情况:
char
可以是有符号(signed char
)或无符号(unsigned char
),具体取决于编译器实现。
2. 存储方式
整数在内存中的存储方式与字节序(大端或小端,前面已解释)和符号表示方法有关。
有符号整数的存储
- 表示方法:使用二进制补码(Two’s Complement)。
- 正数:直接用二进制表示。
- 负数:取正数的二进制反码加 1。
- 示例(以 8 位
signed char
为例):127
:01111111
128
:10000000
1
:11111111
(00000001
取反后加 1)
无符号整数的存储
- 表示方法:直接用二进制表示,没有符号位,所有位都用于表示数值。
- 示例(以 8 位
unsigned char
为例):0
:00000000
255
:11111111
范围计算
- 有符号:
n
位能表示2^(n-1)
到2^(n-1)-1
。 - 无符号:
n
位能表示0
到2^n-1
。
3. 溢出问题
溢出指的是整数运算结果超出了类型所能表示的范围。C++中,有符号和无符号整数的溢出行为不同。
有符号整数溢出
-
行为:有符号整数溢出是未定义行为(Undefined Behavior, UB)。
- 编译器可以做任何事(如崩溃、返回错误值、忽略溢出等),不可预测。
-
示例:
#include <iostream> int main() { int a = 2147483647; // int 最大值 std::cout << a + 1 << std::endl; // 溢出,结果未定义 }
- 可能输出
2147483648
(循环到最小值),但不能依赖这种行为。
- 可能输出
-
原因:补码表示下,溢出可能导致符号位翻转,但标准不强制要求这种“环绕”行为。
无符号整数溢出
-
行为:无符号整数溢出是定义明确的行为,遵循模运算(取模 2^n,其中 n 是位数)。
- 超出最大值时,从 0 开始循环;低于 0 时,从最大值开始循环。
-
示例:
#include <iostream> int main() { unsigned int a = 4294967295; // unsigned int 最大值 std::cout << a + 1 << std::endl; // 输出 0 std::cout << a - 4294967296 << std::endl; // 输出 4294967295 }
- 加 1:
4294967295 + 1 = 0
(模 2^32)。 - 减 1:
0 - 1 = 4294967295
(模 2^32)。
- 加 1:
实际影响
- 有符号溢出:可能导致程序错误,需避免(如检查边界或使用更大类型)。
- 无符号溢出:常用于循环计数器等场景,但需注意逻辑错误。
4. 注意事项
- 类型提升:
-
在混合运算中(如
int
和unsigned int
),有符号类型可能被提升为无符号,导致意外结果。 -
示例:
int a = -1; unsigned int b = 1; std::cout << (a < b) << std::endl; // 输出 0(false)
a
被提升为无符号,变为4294967295
,大于1
。
-
- 编译器警告:
- 开启
Woverflow
(如在 GCC/Clang 中)可以检测有符号溢出。
- 开启
- 解决溢出:
-
使用更大的类型(如
long long
)。 -
使用标准库(如
<limits>
)检查范围:#include <limits> std::cout << std::numeric_limits<int>::max() << std::endl; // int 最大值
-
5. 总结与记忆
- 有符号:
- 范围:
2^(n-1)
到2^(n-1)-1
。 - 存储:补码形式。
- 溢出:未定义行为,小心使用。
- 范围:
- 无符号:
- 范围:
0
到2^n-1
。 - 存储:直接二进制。
- 溢出:模运算,循环安全。
- 范围:
- 口诀:
- “有符号带负补码存,溢出乱来莫依赖;无符号全正模运算,溢出循环很可爱。”