4-4 带符号整数
整数integer是一种整数的类型integral type,能够表示正负整数,包括0(例如-2、-1、0、1、2)。C++提供四种主要的基础整数类型供使用:
| Type | Minimum Size | Note |
|---|---|---|
| short int | 16 bits | |
| int | 16 bits | Typically 32 bits on modern architectures |
| long int | 32 bits | |
| long long int | 64 bits |
各种整数类型之间的关键区别在于它们具有不同的大小——更大的整数类型能够容纳更大的数值。
提醒
C++ 仅保证整数具有特定的最小尺寸,而非固定尺寸。有关如何确定每种类型在您机器上的具体大小,请参阅第 4.3 课——对象尺寸与 sizeof 运算符。
顺带一提……
严格来说,bool 和 char 类型被视为整数的类型integral types(因为这些类型将值存储为整数值)。在接下来的几节课中,我们将不讨论这些类型。
有符号整数
在日常书写负数时,我们使用负号。例如,-3表示“负3”。通常我们也会将+3理解为“正3”(尽管惯例要求省略正号前缀)。
这种正数、负数或零的属性称为数的符号sign。
C++中的整数默认带符号signed,即数值的符号作为数值本身的一部分存储。因此有符号整数可同时存储正数、负数及零。
本节课我们将重点探讨有符号整数,无符号整数(仅能存储非负数)将在下节课讲解。
有符号整数的定义
以下是定义四种有符号整数类型的推荐方式:
short s; // prefer "short" instead of "short int"
int i;
long l; // prefer "long" instead of "long int"
long long ll; // prefer "long long" instead of "long long int"
虽然 short int、long int 或 long long int 都能使用,但我们更倾向于使用这些类型的简短名称(不带 int 后缀)。除了增加输入量外,添加 int 后缀还会使该类型更难与 int 类型的变量区分开来。若不慎遗漏 short 或 long 修饰符,便可能导致错误。
整数类型还可选地添加带符号关键字,按惯例通常置于类型名称之前:
signed short ss;
signed int si;
signed long sl;
signed long long sll;
然而,该关键字不应使用,因为它属于冗余——整数默认即为带符号类型。
最佳实践
优先使用不带 int 后缀或带符号前缀的简写类型。
有符号整数的取值范围
正如上一节所述,n位变量可存储2^n种可能值。但具体哪些值?我们称数据类型能存储的特定值集合为其取值范围range。整型变量的取值范围由两方面决定:其位数(以位为单位)以及是否带符号。
例如,8位有符号整数的取值范围为-128至127。这意味着8位有符号整数可安全存储-128至127(含)之间的任意整数值。
下表列出了不同位宽有符号整数的取值范围:
| Size / Type | Range |
|---|---|
| 8-bit signed | -128 to 127 |
| 16-bit signed | -32,768 to 32,767 |
| 32-bit signed | -2,147,483,648 to 2,147,483,647 |
| 64-bit signed | -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 |
对于擅长数学的人来说,n位有符号变量的取值范围是-(2n-1)到(2n-1)-1。
对于不擅长数学的人……请参考表格。 😃
面向高级读者
上述范围基于“补码”二进制表示法。该表示法是现代架构的事实标准(因其更易于硬件实现),现已被C++20标准强制要求。我们在第O.4课——整数在二进制与十进制表示间的转换 中讨论了补码。
在早期标准中,出于历史原因允许使用正负数表示法和一补码表示法。此类表示法产生的数值范围为-(2n-1-1)至+(2n-1-1)。
溢出
若尝试将值140赋给8位有符号整数会发生什么?该数值超出了8位有符号整数的表示范围。数字140需要9位进行表示(8位数值位和1位符号位),而8位有符号整数仅提供8位存储空间(7位数值位和1位符号位)。
C++20标准对此给出概括性说明:“若表达式求值结果在数学上未定义或超出其类型可表示值范围,则行为未定义”。通俗而言,这称为溢出overflow。
因此,将值140赋给8位有符号整数将导致行为未定义。
当算术运算(如加法或乘法)试图生成超出表示范围的数值时,称为整数溢出integer overflow(或算术溢出arithmetic overflow)。对于有符号整数,整数溢出将导致行为未定义。
#include <iostream>
int main()
{
// assume 4 byte integers
int x { 2'147'483'647 }; // the maximum value of a 4-byte signed integer
std::cout << x << '\n';
x = x + 1; // integer overflow, undefined behavior
std::cout << x << '\n';
return 0;
}
在作者的机器上,上述内容打印为:

然而,由于第二个输出属于未定义行为的结果,其数值在您的机器上可能存在差异。
对于进阶读者
我们在第4.5课---无符号整数 中探讨了无符号整数溢出时的表现及其规避原因。
通常情况下,溢出会导致信息丢失,这几乎总是不可取的。若怀疑对象可能需要存储超出其范围的值,请使用更大范围的类型!
整数除法
当两个整数相除时,C++ 的行为符合预期——商为整数时:
#include <iostream>
int main()
{
std::cout << 20 / 4 << '\n';
return 0;
}
这产生了预期的结果:

但让我们看看当整数除法产生小数结果时会发生什么:
#include <iostream>
int main()
{
std::cout << 8 / 5 << '\n';
return 0;
}
这可能产生一个出乎意料的结果:

在进行两个整数的除法运算(称为整数除法)时,C++ 始终返回整数结果。由于整数无法存储小数部分,任何小数部分都会被直接舍弃(而非四舍五入!)。
仔细观察上述示例:8 / 5 的结果为 1.6。小数部分(0.6)被舍弃,最终结果为 1。换言之,8 / 5 等于 1 余 3,其中余数被舍弃后得到 1。
同理,-8 / 5 的结果为 -1。
警告
使用整数除法时需谨慎,商的任何小数部分都将丢失。但若此为预期结果,整数除法是安全的,因其结果具有可预测性。
若需保留小数结果,我们将在第6.2节——算术运算符中展示实现方法。
测验时间
问题 #1
5-bit有符号整数的取值范围是多少?
显示解答
一个5位数可以表示2⁵=32个不同值。对于带符号整数,这些值几乎均等分配给正数和负数,其中负数额外获得一个值。因此其取值范围为-16到15。
另一种理解方式:5-bit中有1位用于表示符号位,剩余4位表示绝对值。4位可表示16个独立数值:在负数范围内,这4位表示-1至-16;在非负数范围内,则表示0至15。非负数范围少1个数值,因为它包含0。
问题 #2
a) 13 除以 5 的结果是多少?
显示解答
13 ÷ 5 = 2 余 3。余数舍去,得出商为 2。
b) -13 除以 5 的结果是多少?
显示解答
-13 ÷ 5 = -2 余数 -3。余数舍去,结果为 -2。

浙公网安备 33010602011771号