有符号整数:C++中的基本数值类型

整数类型概述

整数是一种可以表示正数、负数和零的整型(例如-2、-1、0、1、2)。C++提供了4种主要的基本整数类型供使用:

类型最小大小注意事项
short int16位
int16位在现代架构上通常是32位
long int32位
long long int64位

这些不同整数类型之间的主要区别在于它们的大小不同 —— 较大的整数可以存储更大的数字。

提醒

C++只保证整数具有一定的最小大小,而不是具有特定大小。有关如何确定每种类型在你的机器上有多大,请参阅第4.3课 —— 对象大小和sizeof运算符。

顺便说一句……

从技术上讲,bool和char类型被认为是整型(因为这些类型以整数值存储其值)。为了方便接下来几课的讨论,我们将排除这些类型。

有符号整数详解

在日常生活中书写负数时,我们使用负号。例如,-3表示"负三"。我们通常也会将+3识别为"正三"(尽管约定俗成的做法是省略正号前缀)。

数字的正、负或零属性称为其符号。

默认情况下,C++中的整数是有符号的,这意味着数字的符号作为值的一部分存储。因此,有符号整数可以存储正数和负数(以及0)。

定义有符号整数

以下是定义四种有符号整数类型的首选方式:

short s;      // 优先使用"short"而不是"short int"
int i;
long l;       // 优先使用"long"而不是"long int"
long long ll; // 优先使用"long long"而不是"long long int"

尽管short int、long int或long long int也可以使用,但我们优先选择不使用int后缀的简短名称。除了需要更多输入外,添加int后缀会使类型更难与int类型的变量区分开来。如果意外遗漏了short或long修饰符,这可能会导致错误。

整数类型还可以使用可选的signed关键字,按照惯例,该关键字通常放在类型名称之前:

signed short ss;
signed int si;
signed long sl;
signed long long sll;

然而,不应使用这个关键字,因为它是多余的,因为整数默认是有符号的。

最佳实践

优先使用不使用int后缀或signed前缀的简短类型。

有符号整数范围

正如你在上一节中学到的那样,一个有n位的变量可以存储(2^n)个可能的值。但具体是哪些值呢?我们称数据类型可以存储的一组特定值为其范围。整数变量的范围由两个因素决定:其大小(以位为单位)以及它是否有符号。

例如,一个8位有符号整数的范围是-128到127。这意味着一个8位有符号整数可以安全地存储任何介于-128到127(包括两端)之间的整数值。

以下是一个包含不同大小的有符号整数范围的表格:

大小/类型范围
8位有符号-128到127
16位有符号-32,768到32,767
32位有符号-2,147,483,648到2,147,483,647
64位有符号-9,223,372,036,854,775,808到9,223,372,036,854,775,807

对于数学爱好者来说,一个有n位的有符号变量的范围是(-2^{n-1})到(2^{n-1}-1)。

对于非数学爱好者……使用表格吧。 :)

对于高级读者

上述范围假设使用"二进制补码"表示法。这种表示法是现代架构的事实标准(因为它更容易在硬件中实现),并且现在是C++20标准所要求的。我们在第O.4课 —— 整数的二进制和十进制表示转换中讨论了二进制补码。

在之前的版本中,出于历史原因允许使用符号-幅度和一的补码表示法。这些表示法产生的值范围是(-2^{n-1}-1)到(+2^{n-1}-1)。

整数运算注意事项

溢出

如果我们试图将值140赋给一个8位有符号整数,会发生什么?这个数字超出了8位有符号整数可以存储的范围。数字140需要9位来表示(8位数值位和1位符号位),但我们在一个8位有符号整数中只有8位(7位数值位和1位符号位)可用。

C++20标准做出了如下一般性声明:“如果在表达式的求值过程中,结果在数学上未定义,或者不在其类型的可表示值范围内,则行为是未定义的”。通俗地说,这被称为溢出。

因此,将值140赋给一个8位有符号整数将导致未定义行为。

如果算术运算(如加法或乘法)试图创建一个超出可表示范围的值,这称为整数溢出(或算术溢出)。对于有符号整数,整数溢出将导致未定义行为。

#include <iostream>

int main()
{
    // 假设是4字节整数
    int x { 2'147'483'647 }; // 4字节有符号整数的最大值
    std::cout << x << '\n';

    x = x + 1; // 整数溢出,未定义行为
    std::cout << x << '\n';

    return 0;
}

在作者的机器上,上述代码打印出:

2147483647
-2147483648

然而,由于第二个输出是未定义行为的结果,因此在你的机器上输出的值可能会有所不同。

对于高级读者

我们在第4.5课 —— 无符号整数以及为何要避免它们中讨论了无符号整数溢出时会发生什么。

一般来说,溢出会导致信息丢失,这几乎总是不希望发生的。如果怀疑某个对象可能需要存储超出其范围的值,请使用范围更大的类型!

整数除法

当用两个整数进行除法运算时,如果商是整数,C++会按你期望的那样工作:

#include <iostream>

int main()
{
    std::cout << 20 / 4 << '\n';
    return 0;
}

这产生了预期的结果:

5

但让我们看看当整数除法导致分数结果时会发生什么:

#include <iostream>

int main()
{
    std::cout << 8 / 5 << '\n';
    return 0;
}

这产生了一个可能出乎意料的结果:

1

当用两个整数进行除法运算(称为整数除法)时,C++总是产生一个整数结果。由于整数不能存储分数值,任何分数部分都会被简单地丢弃(而不是四舍五入)。

仔细看看上面的例子,8 / 5产生值1.6。分数部分(0.6)被丢弃,结果为1。或者,我们可以说8 / 5等于1余3。余数被丢弃,剩下1。

类似地,-8 / 5的结果是-1。

警告

使用整数除法时要小心,因为你会丢失商的任何分数部分。然而,如果这是你想要的,整数除法是安全的,因为结果是可以预测的。

如果需要分数结果,我们在第6.2课 —— 算术运算符中展示了一种方法。

测验时间

问题1

5位有符号整数的范围是多少?

问题2

a) 13 / 5的结果是什么?

b) -13 / 5的结果是什么?

关注公众号,回复"cpp-tutorial"

可领取价值199元的C++学习资料

公众号二维码

扫描上方二维码或搜索"cpp-tutorial"