整型
整型这个词是我们自造的。它不同于整数类型;整数类型还包括布尔类型和字符类型。
分类
一般地,我们称“有符号整数类型”和“无符号整数类型”叫做整型。 为什么叫它们整型?因为这些类型的变量一般用于存放整数。在C++里,整型又被具体地分为许多种类。
类型 | 类型说明符 |
---|---|
基本型 | int |
短整型 | short short int |
长整型 | long long int |
扩展长整型 | long long long long int |
可以看到,整型一般分为基本型、短整型、长整型和扩展长整型这四类;上表中同时给出了它们的类型说明符。你会发现,对于同一个类型可能有多个等价的类型说明符——意思就是任意一个都可以使用,效果完全相同。比如对于扩展长整型,你既可以写:
也可以简单写成:
那么,这四种类型的区别是什么呢?区别就是不同类型的整型变量在内存空间中所占的“存储单元的多少”(简单说叫做存储空间的大小)是不同的。
类型 | 存储单元的多少 |
---|---|
基本型 int | 不少于 16 位(2 字节) |
短整型 short | 不少于 16 位(2 字节) |
长整型 long | 不少于 32 位(4 字节) |
扩展长整型 long long | 不少于 64 位(8 字节) |
其中,字节(Byte)是计算机存储空间的基本单位。(类似长度的基本单位叫做“米”,存储空间的基本单位就是字节。)在目前通用的计算机中,一个字节有 8 位。(位(Bit,又译比特)就是指二进制中的一位,是存储空间的最小单位。)
什么意思呢?就是说比如当我们定义一个 int
型的变量的时候,那么计算机就要在它所拥有的存储空间里头开辟一个不少于 16 位长度的存储空间,用来存储这个数据。 显而易见地,正因为它们所占用的字节数不同,所能存储的数据范围也会不同。因为一个 32 位的二进制数能够表达 个数,而一个 16 位的二进制数所表达的数据的范围只有 个。(如果你不熟悉这件事情,你可以去问问你的数学老师。这里我不再解释。)
一般地,你要根据自己的需要去决定你要定义整数的类型。比方说你要定义的整数是一个非常小的数——这个数最大都不会超过几千,那你可以把它定义成 short
型,一定程度上节省空间。
值得注意的一点是,C++ 的标准里,关于某种特定的数据类型到底应该占用多大的内存空间,定义得比较宽泛。正如你们所见,C++ 最新的标准只规定了它们至少占用多大空间,却没有具体地定义它们的大小。
但是就目前通用的操作系统来说,整型的大小是这样规定的:
类型 | > 16 位 Windows | 32 位 UNIX※ | 64 位 UNIX※ |
---|---|---|---|
int | 4 字节 | 4 字节 | 4 字节 |
short | 2 字节 | 2 字节 | 2 字节 |
long | 4 字节 | 4 字节 | 8 字节 |
long long | 8 字节 | 8 字节 | 8 字节 |
※ UNIX 指 UNIX 系统(含 macOS, FreeBSD)和类 UNIX 系统(GNU/Linux 等)
除了数据长度的分类,正如之前所说,整型也分为无符号整数类型和有符号整数类型。也就是:
类型 | 有符号 | 无符号 |
---|---|---|
基本型 | int signed signed int | unsigned int unsigned |
短整型 | short short int signed short signed short int | unsigned short unsigned short int |
长整型 | long long int signed long signed long int | unsigned long unsigned long int |
扩展长整型 | long long long long int signed long long signed long long int | unsigned long long unsigned long long int |
一个整型数据有无符号这个特性把刚才 4 种基本的子类型再分成不同的两类。比方说 int
类型可以再划分成 signed int
型和 unsigned int
型。那么这里“有无符号”的意思是什么?就是数学上的“正负号”是否存在。换句话说,有符号类型可以存正数,0和负数;而无符号类型只能存储非负数。
你也从上面的表格中可以观察到,signed这个词是可以省略的。所以当你写下:
int num{0};
时,就表明你定义了一个有符号基本型整型
那么到此为止我们就把那么关于整数数据类型的所有的子类型都介绍完毕了。尽管听上去非常复杂,但其实在大部分情况下,使用 int
型就足够了。
存储细节
无符号整数类型
无符号类型整型的存储较为简单。将所存储数的二进制形式逐位存储到内存中即可。例如:
其中 123 的二进制形式为 。 因此,32 位的 unsigned int
型能存储的最小值为 ,最大值为 ,即 。对于其它大小的类型也是这样的道理。
表示 现在的写法是一个二进制写法。有的书上也会用 来表示二进制数。
有符号整数类型
首先,很自然地,有符号类型整型的存储分为“非负数”和“负数”两个部分,每个部分各占一半。举例来讲,32 位的 signed int
型变量一共能存储 种数值:其中非负数有 个;同样地,负数也有 个。那么它们具体的存储方式是这样的:
二进制存储 | 实际表示的数 | 二进制存储 | 实际表示的数 |
---|---|---|---|
0000...0000 | 1000...0000 | ||
0000...0001 | 1000...0001 | ||
0000...0010 | 1000...0010 | ||
0000...0011 | 1000...0011 | ||
0000...0100 | 1000...0100 | ||
0000...0101 | 1000...0101 | ||
0000...0110 | 1000...0110 | ||
........... | ... | ........... | ... |
0111...1011 | 1111...1011 | ||
0111...1100 | 1111...1100 | ||
0111...1101 | 1111...1101 | ||
0111...1110 | 1111...1110 | ||
0111...1111 | 1111...1111 |
可以看到,从 0000....0000
到 0111....1111
这些首位为 0
的数都是非负数,而从 1000....0000
到 1111....1111
这些首位为 1
的数都是负数。所以,有符号类型的存储空间首位也被称作符号位,它指明了一个数是否为负数。
另外还可以注意到,不论符号正负,两部分的数字排列都是从小到大。比如非负数部分从最小的 一直排到最大的 ,而负数部分从最小的 一直排列到最大的 。这样的排列有助于计算机进行基本的算术运算。这种存储方式被称作补码(Two's complement)存储。之所以叫做补码,是因为任何一个数与它的相反数的补码形式恰好“互补”。一个数和它所对应的补码可以根据这个公式进行计算:
举例来说,我们有
int i{-123};
首先 的绝对值 的二进制形式为 ,我们称其为原码。然后对于 32 位的长度进行取反:取反(NOT)的含义就是对应位反转, 0
变为 1
, 1
变为 0
。故取反的结果为: ,称其为反码(Ones' complement)。最后再加1,便得到了补码 。这就是有符号整数类型在计算机中的存储方式。
总结
整型按存储空间的大小分为 4 类;按有无符号分为 2 类,一共 8 小类。每一类的区别主要体现在所能表示的数的范围上。对于大多数的情况,下表整理了它们能表示数的范围:※
类型说明符 | 类型 | 字节数 | signed 表示范围 | unsigned 表示范围 |
---|---|---|---|---|
int | 标准型 | 4 | ~ | ~ |
short | 短整型 | 2 | ~ | ~ |
long | 长整型 | 4※ | ~ | ~ |
long long | 扩展长整型 | 8 | ~ | ~ |
※ 在 64 位的 UNIX 或类 UNIX 系统下, long
占用 8 字节,表示范围与 long long
相同。
尽管如此复杂,但在一般情况下 int
足以应付大部分场景。
练习
- 尝试编写程序:输入 0 到 5 之间的整数 x,计算
3446744073709550592
的 x 倍。注意使用正确的类型变量来存储。