整型 | 占用的内存空间 | 所能表达的最少数据范围 |
short int | >= 16位 | -32767 到 32767 |
int | >= 16位 | -32767 到 32767 |
long int | >= 32位 | -2147483647 到 2147483647 |
long long int | >= 64位 | -9223372036854775807 到 9223372036854775807 |
如上面的表格所示,标准规定short int在计算机中最少需要用16位(16 bits)来表示。我们知道16位包含了最多2^{16}=65536种可能性,考虑到正数,负数,和0,所以short int的最少表示范围就是-32767到32767 (实际上是-32768到32767,但是C++标准中沿袭了C的标准,只要求编译器支持-32767到32767就可以了)。其他的也以此类推。除此之外,标准还做了下面的规定:short int占用内存空间的大小一定要小于等于int的,int的大小一定要小于等于long int的,long int的大小一定要小于等于long long int的。
C++标准并没有硬性规定不同整型所占用的内存空间,而是规定了这些整型的最少占用空间。具体每个整型占用多少空间是由编译器决定的。同一个整型在不同的编译器所占用空间的大小可能完全不同。这其实是C++的一个特色:实现定义的行为(imeplementation-defined behavior)。即是说由编译器自己决定应该怎么做。C++标准还有很多地方没有做硬性规定,而是取决于实现定义的行为,我们遇到了会逐一指出。取决于实现定义的行为的好处是可以对不同的系统和不同的硬件构架作出优化。弱点也很明显,程序在不同的系统间的移植会更困难一些。随着计算机技术的发展,系统间的移植也越来越频繁。对于程序员来说,也需要多了解一些不同的系统和编译器的自定义行为,写可移植的C++程序才能更加得心应手。
由于编程中有时候只会用到正整数,C++同时也定义了和上述几种整型所对应的正整型,也叫无符号整型(上述几种整型也叫做有符号整型):unsigned short int,unsigned int,unsigned long int,和unsigned long long int。如下表所示,
整型 | 占用的内存空间 | 所能表达的最少数据范围 |
unsigned short int | >= 16位 | 0 到 65535 |
unsigned int | >= 16位 | 0 到 65535 |
unsigned long int | >= 32位 | 0 到 4294967295 |
unsigned long long int | >= 64位 | 0 到 18446744073709551615 |
C++标准规定无符号整型的底层存储形式和有符号整型一样。也就是说对于同一种硬件构架的处理器和同一个编译器来说,有符号整型和对应的无符号整型所占用的内存空间以及内存对齐的要求是完全一致的。
小知识: 内存对齐(Memory Alignment)是一个底层的概念。从软件来讲,内存的读写的单位是字节(byte),但是从硬件来讲,处理器对内存的读写单位可能大于一个字节。举例来说,对于现在的32位x86构架的处理器,处理器对内存的读写单位是4个字节,即处理器对只能读写为4的整数倍的内存地址。假设你要从内存读入一个int(32位,4个字节),如果这个数据存储的开始地址是4的整数倍(内存对齐),那么处理器只需要进行一次读内存的操作。但是如果他并不是内存对齐的,处理器就需要进行两次读内存的操作才能把这个整数取出来,影响运行效率。
练习:
请解释short int,int,long int,long long int,unsigned short int,unsigned int,unsigned long int, unsigned long long int的联系和区别。
如果整数字面值常量的第一个数字是0,则这个数字表示一个八进制整数。如果整数字面值常量的开头字母是0x或者0X,则这个字面值常量是一个十六进制的整数。我们以2012这个整数字面值常量为例,用下表归纳以上规则,
数据类型 | 十进制 | 八进制 | 十六进制 |
int | 2012 | 03734 | 0x7DC |
unsigned int | 2012u | 03734u | 0x7DCu |
long int | 2012l | 03734l | 0x7DCl |
unsigned long int | 2012ul | 03734ul | 0x7DCul |
long long int | 2012ll | 03734ll | 0x7DCll |
unsigned long long int | 2012ull | 03734ull | 0x7DCull |
上表中的表示十六进制的A,B,C,D,E,F的大小写都可以互换,例如0x7DC可以写成0x7dC或者0x7Dc或者0x7dc。
小知识:你也许注意到了,C++中并没有short int和unsigned short int的字面值常量。从历史角度来讲,在C中(C++也沿袭C的传统),int和unsigned int占用的内存空间大小是处理器的自然字长(natural word size)。因此在C和C++中,任何占用内存存储空间小于int和unsigned int的整数类型,在进行任何运算操作前,都会被隐性转化为int或者unsigned int。这个转化过程叫做整数提升(integral promotion)。因此short int和unsigned short int的字面值常量绝大多数情况下是无意义的,也是无必要的。
如上所述,整数字面值常量的类型和后缀是对应的,但是有一个例外。那就是对于整数字面值常量来说,如果整数字面值常量所表达的数值超出后缀的类型所能表达的最大范围,编译器会自动将这个整数字面常量``升级''。例如如果int有16位,表示范围为-32767到32767,但是当你写32768,编译器会自动为你升级,即自动帮你变成32768L。当然如果你超出了编译器支持的最大整数范围,编译器会报错或者给你一个警告。
下面是一个简单的例子,在32位系统中编译运行都是正常的。可以看到即使超出int的范围(对32位系统,int代表的值应该小于等于2147483647),也没有必要一定要加后缀。
#include <iostream>
int main()
{
std::cout << 32768 << std::endl;
std::cout << 2147483648 << std::endl;
std::cout << 0xFFFFFFFF << std::endl;
return 0;
}
整数字面值常量的升级规则如下表,后缀表示整数字面值常量的后缀,编译器会依表查找最第一个能够足够存储你写下的数的整数类型。例如如果一个系统中int是32位,long int是32位,long long int是64位。你在程序中用了2147483648,编译器会先看int的最大数只有2147483647,long int的最大数只有2147483647,于是编译器认定这是一个long long int。再以0xFFFFFFFF为例,依照表格,编译器会依照int,unsigned int,long int,unsigned long int等的次序查找。这里0xFFFFFFFF编译器会认定是一个unsigned int。
编译器这样做的好处当然就是当你写下整数字面值常量的时候,你不需要考虑后缀究竟要加什么,如果不够存储,编译器会自动升级到更大的整数类型。当然,对于好的编程习惯来讲,不要太过依赖于编译器的自动处理。
后缀 | 十进制字面值常量 | 八进制/十六进制字面值常量 |
无 | int long int long long int | int unsigned int long int unsigned long int long long int unsigned long long int |
u/U | unsigned int unsigned long int unsigned long long int | unsigned int unsigned long int unsigned long long int |
l/L | long int long long int | long int unsigned long int long long int unsigned long long int |
ul/uL/Ul/UL | unsigned long int unsigned long long int | unsigned long int unsigned long long int |
ll/LL | long long int | long long int unsigned long long int |
ull/uLL/Ull/ULL | unsigned long long int | unsigned long long int |
练习:
如果int是16位,long int是32位,long long int是64位,那么32768,2147483648,0xFFFFFFFF,32768L,2147483648L,和0xFFFFFFFFL各会被编译器转换成什么整数类型的字面值常量?
练习:
以下程序的输出结果是什么?注意std::hex和std::oct并不会输出任何东西,而是和std::endl一样代表特别意义。std::hex表示后面的整数用十六进制输出(hex是十六进制的英文hexadecimal的简写),std::oct表示整数用八进制输出(oct是八进制的英文octal的简写),std::endl表述换行。
#include <iostream>
int main()
{
std::cout << 2012 << " "
<< std::hex << 2012 << " "
<< std::oct << 2012 << std::endl;
return 0;
}