2.4-整型变量-溢出与整数相除

整型变量即仅能保存整数的变量,C++中有5种基本的整数类型可供使用:

类别 数据类型 最小占用内存
字符型 char 1 byte
整型 short 2 bytes
int 2 bytes 现在通常占 4 bytes
long 4 bytes
long long 8 bytes C99/C++11

char是一种特殊情况,因为它既属于字符型又属于整型,稍后我们会讨论char的特殊性,在这节中,我们可以把它当作整型处理。

各种整数类型的主要区别是其大小——占用内存越多可保存的数就越大,C++中仅指定了各种数据类型所占用的最小内存,而不是规定了其大小,这在《2.3-变量大小与sizeof操作符》中已经说过。

定义整型变量

例如:

char c;

short int si; // valid
short s;      // preferred

int i;

long int li; // valid
long l;      // preferred

long long int lli; // valid
long long ll;      // preferred

尽管short int、long int、long long int都是合法的,但我们通常使用short、long、long long这样简短的写法。除了方便书写以外,添加前缀int也使其不易与int型变量区分。

整型变量的区分

因为char、short、int、long的大小取决于编译器与计算机架构,所以通过大小而非名称来指代整数更有指导意义。我们通常用一个整数占用的bit数来指代它,例如“32位整数”而不是“long型整数”。

整数范围与符号

前面说过,一个n比特的变量可以存储2^n个不同的数,我们把这些数所构成的集合称为其范围,整型变量的范围取决于两个因素:大小(bit数)与其符号(有符号与无符号数)。

有符号数既可以保存正数也可以保存负数,我们可以使用signed关键字来显式地声明一个有符号数:

signed char c;

signed short s;

signed int i;

signed long l;

signed long long ll;

按照惯例,我们将signed放在前面。

1字节有符号(8bit)整型变量的范围为-128到127,-128到127之间的每个整数都可以安全地保存在1字节有符号整型变量中。

如果我们事先知道不需要负数(例如这个变量是表示身高的),就可以使用无符号数,无符号数仅能保存正数,我们可以使用unsigned来显式地声明无符号数:

unsigned char c;

unsigned short s;

unsigned int i;

unsigned long l;

unsigned long long ll;

1字节的无符号整型变量可表示的范围为0-255。

注意到将一个变量声明为无符号数意味着其不能表示负数,但能表示的正数范围却扩大了一倍。见如下表格:

Size/Type Range
1 byte signed -128 to 127
1 byte unsigned 0 to 255
2 byte signed -32,768 to 32,767
2 byte unsigned 0 to 65,535
4 byte signed -2,147,483,648 to 2,147,483,647
4 byte unsigned 0 to 4,294,967,295
8 byte signed -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807
8 byte unsigned 0 to 18,446,744,073,709,551,615

从数学角度来讲,一个n位的有符号变量所能表示的范围是 -(2n-1) 到2n-1-1,n位无符号数所能表示的范围是 0 到 (2n)-1。

新人常会将有符号数与无符号数弄混,我们可以这样来想:为了区分正负数,我们用“-”这个符号来表示负数,如果没有这个符号,就默认为正数,同样的,有符号数可以表示正数、负数,而无符号数只能表示正数。

默认符号与最佳做法

所以,如果我们声明变量时没有指出有无符号会发生什么?

类别 数据类型 有无符号
字符型 char Signed/Unsigned 通常为有符号
整型 short Signed
int Signed
long Signed
long long Signed

除了char以外,所有的整型变量都默认为有符号数,char两种都有可能。所以通常都无需使用signed关键字,因为它是冗余的。

最佳的做法是:除非有特定需求,我们都使用有符号数,因为无符号数相对来说更有可能导致意外的bug。

规则:最好使用有符号数。

溢出

如果一个数超出了该变量所能保存的范围,我们却强行将其保存在这个变量中,此时就会发生“溢出”。结果是部比特位会丢失,因为该变量未被分配足够的内存。在《2.1-详解变量的定义、初始化与赋值》中我们提过,数据是以二进制形式保存的。例如十进制0-15的二进制形式如下:

十进制 二进制
0 0
1 1
2 10
3 11
4 100
5 101
6 110
7 111
8 1000
9 1001
10 1010
11 1011
12 1100
13 1101
14 1110
15 1111

可以看到,存储越大的数所需的比特位越多,因为一个变量的比特位数是固定的,所以能够保存的数也是有限的。

溢出实例

假设一个变量有4位大小,那么上表中的所有数都可以保存在这个变量中,因为它们都不超过4位。

如果我们把一个超过4位的数(例如21,二进制为10101)保存在这个变量中,就会发生溢出,这个变量只会保存最不重要的4位(最右边的4位:0101),其余的位数(最高位1)将会丢失,那么我们最终得到的这个变量的值实际上是5(0101)而不是21(10101)。

现在我们来看一个实例,假设short为16位:

#include <iostream>

int main()
{
    unsigned short x = 65535; // 16位无符号最大值
    std::cout << "x was: " << x << std::endl;
    x = x + 1; // 65536 超出范围,需17位表示,发生溢出
    std::cout << "x is now: " << x << std::endl;

    return 0;
}

结果为:

x was: 65535

x is now: 0

为什么结果是这样的呢?因为65536的二进制形式为1111 1111 1111 1111(16位),65536+1=65537的二进制形式为1 0000 0000 0000 0000,共17位,同上,16位的short型变量只能保存低16位:0000 0000 0000 0000,即0。

同理有:

#include <iostream>

int main()
{
    unsigned short x = 0;
    std::cout << "x was: " << x << std::endl;
    x = x - 1; // 溢出
    std::cout << "x is now: " << x << std::endl;

    return 0;
}
x was: 0

x is now: 65535

溢出会导致信息丢失,这是我们肯定不希望看到的,所以只要有任何疑虑,都要使用更长的数据类型。

并且,只有无符号整数的溢出结果是可预测的,有符号整数和非整型数的溢出结果在不同的操作系统上是不同的。

整数的除法

如果两个整数相除的结果是个整数,那么结果就如你所得一样:

#include <iostream>

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

结果为:

5

如果结果为非整数会怎么样呢?

#include <iostream>

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

结果为:

1

在C++中,两个整型数相除的结果为整型数,所以当结果为小数时,小数部分会被直接舍弃(而不是四舍五入)。就如上示例中,8/5结果为1.6,小数部分“.6”被丢弃,结果为1。

规则:进行整数相除时应当小心,因为小数部分会被舍弃。

关于 “2.4-整型变量-溢出与整数相除” 的 1 个意见

评论关闭。