4.整数类型
文章目录
- 四、整数类型
- 1.简介
- 2.signed,unsigned
- 3.整数的子类型
- 4.整数类型的极限值
- 5.整数的进制
- 6.浮点数类型
- 7.布尔类型
- 8.字面量的类型
- 9.字面量后缀
- 10.溢出
- 11.sizeof 运算符
- 12.类型的自动转换
- 赋值运算
- 13.混合类型的运算
- 14.整数类型的运算
- 15.函数
- 16.类型的显式转换
- 17.可移植类型
提示:以下是本篇文章正文内容,下面案例可供参考
四、整数类型
1.简介
整数类型用来表示较大的整数,类型声明使用 int 关键字。
int a;
上面示例声明了一个整数变量 a 。
不同计算机的 int 类型的大小是不一样的。比较常见的是使用4个字节(32位)存储一个 int 类型的值,但是2个字节(16位)或8个字节(64位)也有可能使用。它们可以表示的整数范围如下。
- 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。
2.signed,unsigned
C 语言使用 signed 关键字,表示一个类型带有正负号,包含负值;使用 unsigned 关键字,表示该类型不带有正负号,只能表示零和正整数。对于 int 类型,默认是带有正负号的,也就是说 int 等同于 signed int 。由于这是默认情况,关键字signed 一般都省略不写,但是写了也不算错。
signed int a;
// 等同于
int a;
int 类型也可以不带正负号,只表示非负整数。这时就必须使用关键字 unsigned 声明变量。
unsigned int a;
整数变量声明为 unsigned 的好处是,同样长度的内存能够表示的最大整数值,增大了一倍。比如,16位的 signed int 最大值为32,767,而 unsigned int 的最大值增大到了65,535。unsigned int 里面的 int 可以省略,所以上面的变量声明也可以写成下面这样。
unsigned a;
字符类型 char 也可以设置 signed 和 unsigned 。
signed char c; // 范围为 -128 到 127
unsigned char c; // 范围为 0 到 255
注意,C 语言规定 char 类型默认是否带有正负号,由当前系统决定。这就是说, char 不等同于signed char ,它有可能是 signed char ,也有可能是 unsigned char 。这一点与 int 不同, int 就是等同于 signed int 。
3.整数的子类型
如果 int 类型使用4个或8个字节表示一个整数,对于小整数,这样做很浪费空间。另一方面,某些场合需要更大的整数,8个字节还不够。为了解决这些问题,C 语言在 int 类型之外,又提供了三个整数的子类型。这样有利于更精细地限定整数变量的范围,也有利于更好地表达代码的意图。
- short int (简写为 short ):占用空间不多于 int ,一般占用2个字节(整数范围为-32768~
32767)。 - long int (简写为 long ):占用空间不少于 int ,至少为4个字节。
- long long int (简写为 long long ):占用空间多于 long ,至少为8个字节。
short int a;
long int b;
long long int c;
上面代码分别声明了三种整数子类型的变量。
默认情况下, short 、 long 、 long long 都是带符号的(signed),即 signed 关键字省略了。它们也可以声明为不带符号(unsigned),使得能够表示的最大值扩大一倍。
unsigned short int a;
unsigned long int b;
unsigned long long int c;
C 语言允许省略 int ,所以变量声明语句也可以写成下面这样。
short a;
unsigned short a;
long b;
unsigned long b;
long long c;
unsigned long long c;
不同的计算机,数据类型的字节长度是不一样的。确实需要32位整数时,应使用 long 类型而不是 int类型,可以确保不少于4个字节;确实需要64位的整数时,应该使用 long long 类型,可以确保不少于8个字节。另一方面,为了节省空间,只需要16位整数时,应使用 short 类型;需要8位整数时,应该使用 char 类型。
4.整数类型的极限值
有时候需要查看,当前系统不同整数类型的最大值和最小值,C 语言的头文件 limits.h 提供了相应的常量,比如 SCHAR_MIN 代表 signed char 类型的最小值 -128 , SCHAR_MAX 代表 signed char 类型的最大值 127 。
为了代码的可移植性,需要知道某种整数类型的极限值时,应该尽量使用这些常量。
SCHAR_MIN , SCHAR_MAX :signed char 的最小值和最大值。
SHRT_MIN , SHRT_MAX :short 的最小值和最大值。
INT_MIN , INT_MAX :int 的最小值和最大值。
LONG_MIN , LONG_MAX :long 的最小值和最大值。
LLONG_MIN , LLONG_MAX :long long 的最小值和最大值。
UCHAR_MAX :unsigned char 的最大值。
USHRT_MAX :unsigned short 的最大值。
UINT_MAX :unsigned int 的最大值。
ULONG_MAX :unsigned long 的最大值。
ULLONG_MAX :unsigned long long 的最大值。
5.整数的进制
C 语言的整数默认都是十进制数,如果要表示八进制数和十六进制数,必须使用专门的表示法。八进制使用 0 作为前缀,比如 017 、 0377 。
int a = 012; // 八进制,相当于十进制的10
十六进制使用 0x 或 0X 作为前缀,比如 0xf 、 0X10 。
int a = 0x1A2B; // 十六进制,相当于十进制的6699
有些编译器使用 0b 前缀,表示二进制数,但不是标准。
int x = 0b101010;
注意,不同的进制只是整数的书写方法,不会对整数的实际存储方式产生影响。所有整数都是二进制形式存储,跟书写方式无关。不同进制可以混合使用,比如 10 + 015 + 0x20 是一个合法的表达式。
printf() 的进制相关占位符如下。
%d :十进制整数。
%o :八进制整数。
%x :十六进制整数。
%#o :显示前缀 0 的八进制整数。
%#x :显示前缀 0x 的十六进制整数。
%#X :显示前缀 0X 的十六进制整数。
int x = 100;
printf("dec = %d\n", x); // 100
printf("octal = %o\n", x); // 144
printf("hex = %x\n", x); // 64
printf("octal = %#o\n", x); // 0144
printf("hex = %#x\n", x); // 0x64
printf("hex = %#X\n", x); // 0X64
6.浮点数类型
任何有小数点的数值,都会被编译器解释为浮点数。所谓“浮点数”就是使用 m * be 的形式,存储一个数值, m 是小数部分, b 是基数(通常是 2 ), e 是指数部分。这种形式是精度和数值范围的一种结合,可以表示非常大或者非常小的数。浮点数的类型声明使用 float 关键字,可以用来声明浮点数变量。
float c = 10.5;
上面示例中,变量 c 的就是浮点数类型。
float 类型占用4个字节(32位),其中8位存放指数的值和符号,剩下24位存放小数的值和符号。float 类型至少能够提供(十进制的)6位有效数字,指数部分的范围为(十进制的) -37 到 37 ,即数值范围为10-37到1037。有时候,32位浮点数提供的精度或者数值范围还不够,C 语言又提供了另外两种更大的浮点数类型。double :占用8个字节(64位),至少提供13位有效数字。long double :通常占用16个字节。注意,由于存在精度限制,浮点数只是一个近似值,它的计算是不精确的,比如 C 语言里面 0.1 + 0.2并不等于 0.3 ,而是有一个很小的误差。
if (0.1 + 0.2 == 0.3) // false
C 语言允许使用科学计数法表示浮点数,使用字母 e 来分隔小数部分和指数部分。
double x = 123.456e+3; // 123.456 x 10^3
// 等同于
double x = 123.456e3;
上面示例中, e 后面如果是加号 + ,加号可以省略。注意,科学计数法里面 e 的前后,不能存在空格。另外,科学计数法的小数部分如果是 0.x 或 x.0 的形式,那么 0 可以省略。
0.3E6
// 等同于
.3E6
3.0E6
// 等同于
3.E6
7.布尔类型
C 语言原来并没有为布尔值单独设置一个类型,而是使用整数 0 表示伪,所有非零值表示真。
int x = 1;
if (x) {
printf("x is true!\n");
}
上面示例中,变量 x 等于 1 ,C 语言就认为这个值代表真,从而会执行判断体内部的代码。C99 标准添加了类型 _Bool ,表示布尔值。但是,这个类型其实只是整数类型的别名,还是使用 0 表示伪, 1 表示真,下面是一个示例。
_Bool isNormal;
isNormal = 1;
if (isNormal)
printf("Everything is OK.\n");
头文件 stdbool.h 定义了另一个类型别名 bool ,并且定义了 true 代表 1 、 false 代表 0 。只要加载这个头文件,就可以使用这几个关键字。
#include <stdbool.h>
bool flag = false;
上面示例中,加载头文件 stdbool.h 以后,就可以使用 bool 定义布尔值类型,以及 false 和 true 表示真伪。
8.字面量的类型
字面量(literal)指的是代码里面直接出现的值。
int x = 123;
上面代码中, x 是变量, 123 就是字面量。编译时,字面量也会写入内存,因此编译器必须为字面量指定数据类型,就像必须为变量指定数据类型一样。一般情况下,十进制整数字面量(比如 123 )会被编译器指定为 int 类型。如果一个数值比较大,超出了 int 能够表示的范围,编译器会将其指定为 long int 。如果数值超过了 long int ,会被指定为unsigned long 。如果还不够大,就指定为 long long 或 unsigned long long 。小数(比如 3.14 )会被指定为 double 类型。
9.字面量后缀
有时候,程序员希望为字面量指定一个不同的类型。比如,编译器将一个整数字面量指定为 int 类型,但是程序员希望将其指定为 long 类型,这时可以为该字面量加上后缀 l 或 L ,编译器就知道要把这个字面量的类型指定为 long 。
int x = 123L;
上面代码中,字面量 123 有后缀 L ,编译器就会将其指定为 long 类型。这里 123L 写成 123l ,效果也是一样的,但是建议优先使用 L ,因为小写的 l 容易跟数字 1 混淆。八进制和十六进制的值,也可以使用后缀 l 和 L 指定为 Long 类型,比如 020L 和 0x20L 。
int y = 0377L;
int z = 0x7fffL;
如果希望指定为无符号整数 unsigned int ,可以使用后缀 u 或 U 。
int x = 123U;
L 和 U 可以结合使用,表示 unsigned long 类型。 L 和 U 的大小写和组合顺序无所谓。
int x = 123LU;
对于浮点数,编译器默认指定为 double 类型,如果希望指定为其他类型,需要在小数后面添加后缀f (float)或 l (long double)。
科学计数法也可以使用后缀。
1.2345e+10F
1.2345e+10L
总结一下,常用的字面量后缀有下面这些。
f 和 F : float 类型。
l 和 L :对于整数是 long int 类型,对于小数是 long double 类型。
ll 和 LL :Long Long 类型,比如 3LL 。
u 和 U :表示 unsigned int ,比如 15U 、 0377U 。
u 还可以与其他整数后缀结合,放在前面或后面都可以,比如 10UL 、 10ULL 和 10LLU 都是合法的。
下面是一些示例。
int x = 1234;
long int x = 1234L;
long long int x = 1234LL
unsigned int x = 1234U;
unsigned long int x = 1234UL;
unsigned long long int x = 1234ULL;
float x = 3.14f;
double x = 3.14;
long double x = 3.14L;
10.溢出
每一种数据类型都有数值范围,如果存放的数值超出了这个范围(小于最小值或大于最大值),需要更多的二进制位存储,就会发生溢出。大于最大值,叫做向上溢出(overflow);小于最小值,叫做向下溢出(underflow)。一般来说,编译器不会对溢出报错,会正常执行代码,但是会忽略多出来的二进制位,只保留剩下的位,这样往往会得到意想不到的结果。所以,应该避免溢出。
unsigned char x = 255;
x = x + 1;
printf("%d\n", x); // 0
上面示例中,变量 x 加 1 ,得到的结果不是 256 ,而是 0 。因为 x 是 unsign char 类型,最大值是255 (二进制 11111111 ),加 1 后就发生了溢出, 256 (二进制 100000000 )的最高位 1 被丢弃,剩下的值就是 0 。
再看下面的例子。
unsigned int ui = UINT_MAX; // 4,294,967,295
ui++;
printf("ui = %u\n", ui); // 0
ui--;
printf("ui = %u\n", ui); // 4,294,967,295
上面示例中,常量 UINT_MAX 是 unsigned int 类型的最大值。如果加 1 ,对于该类型就会溢出,从而得到 0 ;而 0 是该类型的最小值,再减 1 ,又会得到 UINT_MAX 。溢出很容易被忽视,编译器又不会报错,所以必须非常小心。
for (unsigned int i = n; i >= 0; --i) // 错误
上面代码表面看似乎没有问题,但是循环变量 i 的类型是 unsigned int,这个类型的最小值是 0 ,不可能得到小于0的结果。当 i 等于0,再减去 1 的时候,并不会返回 -1 ,而是返回 unsigned int 的类型最大值,这个值总是大于等于 0 ,导致无限循环。为了避免溢出,最好方法就是将运算结果与类型的极限值进行比较。
unsigned int ui;
unsigned int sum;
// 错误
if (sum + ui > UINT_MAX) too_big();
else sum = sum + ui;
// 正确
if (ui > UINT_MAX - sum) too_big();
else sum = sum + ui;
上面示例中,变量 sum 和 ui 都是 unsigned int 类型,它们相加的和还是 unsigned int 类型,这就有可能发生溢出。但是,不能通过相加的和是否超出了最大值 UINT_MAX ,来判断是否发生了溢出,因为sum + ui 总是返回溢出后的结果,不可能大于 UINT_MAX 。正确的比较方法是,判断 UINT_MAX - sum与 ui 之间的大小关系。
下面是另一种错误的写法。
unsigned int i = 5;
unsigned int j = 7;
if (i - j < 0) // 错误
printf("negative\n");
else
printf("positive\n");
上面示例的运算结果,会输出 positive 。原因是变量 i 和 j 都是 unsigned int 类型, i - j 的结果也是这个类型,最小值为 0 ,不可能得到小于 0 的结果。正确的写法是写成下面这样。
if (j > i) // ....
11.sizeof 运算符
sizeof 是 C 语言提供的一个运算符,返回某种数据类型或某个值占用的字节数量。它的参数可以是数据类型的关键字,也可以是变量名或某个具体的值。
// 参数为数据类型
int x = sizeof(int);
// 参数为变量
int i;
sizeof(i);
// 参数为数值
sizeof(3.14);
上面的第一个示例,返回得到 int 类型占用的字节数量(通常是 4 或 8 )。第二个示例返回整数变量占用字节数量,结果与前一个示例完全一样。第三个示例返回浮点数 3.14 占用的字节数量,由于浮点数的字面量一律存储为 double 类型,所以会返回 8 ,因为 double 类型占用的8个字节。sizeof 运算符的返回值,C 语言只规定是无符号整数,并没有规定具体的类型,而是留给系统自己去决定, sizeof 到底返回什么类型。不同的系统中,返回值的类型有可能是 unsigned int ,也有可能是unsigned long ,甚至是 unsigned long long ,对应的 printf() 占位符分别是 %u 、 %lu和 %llu 。这样不利于程序的可移植性。C 语言提供了一个解决方法,创造了一个类型别名 size_t ,用来统一表示 sizeof 的返回值类型。该别名定义在 stddef.h 头文件(引入 stdio.h 时会自动引入)里面,对应当前系统的 sizeof 的返回值类型,可能是 unsigned int ,也可能是 unsigned long 。C 语言还提供了一个常量 SIZE_MAX ,表示 size_t 可以表示的最大整数。所以, size_t 能够表示的整数范围为 [0, SIZE_MAX] 。
printf() 有专门的占位符 %zd 或 %zu ,用来处理 size_t 类型的值。
printf("%zd\n", sizeof(int));
上面代码中,不管 sizeof 返回值的类型是什么, %zd 占位符(或 %zu )都可以正确输出。
如果当前系统不支持 %zd 或 %zu ,可使用 %u (unsigned int)或 %lu (unsigned long int)代替。
12.类型的自动转换
某些情况下,C 语言会自动转换某个值的类型。
赋值运算
赋值运算符会自动将右边的值,转成左边变量的类型。
(1)浮点数赋值给整数变量
浮点数赋予整数变量时,C 语言直接丢弃小数部分,而不是四舍五入。
int x = 3.14;
上面示例中,变量 x 是整数类型,赋给它的值是一个浮点数。编译器会自动把 3.14 先转为 int 类型,丢弃小数部分,再赋值给 x ,因此 x 的值是 3 。这种自动转换会导致部分数据的丢失( 3.14 丢失了小数部分),所以最好不要跨类型赋值,尽量保证变量与所要赋予的值是同一个类型。注意,舍弃小数部分时,不是四舍五入,而是整个舍弃。
int x = 12.99;
上面示例中, x 等于 12 ,而不是四舍五入的 13 。
(2)整数赋值给浮点数变量
整数赋值给浮点数变量时,会自动转为浮点数。
float y = 12 * 2;
上面示例中,变量 y 的值不是 24 ,而是 24.0 ,因为等号右边的整数自动转为了浮点数。
(3)窄类型赋值给宽类型
字节宽度较小的整数类型,赋值给字节宽度较大的整数变量时,会发生类型提升,即窄类型自动转为宽类型。
比如, char 或 short 类型赋值给 int 类型,会自动提升为 int 。
char x = 10;
int i = x + y;
上面示例中,变量 x 的类型是 char ,由于赋值给 int 类型,所以会自动提升为 int 。
(4)宽类型赋值给窄类型
字节宽度较大的类型,赋值给字节宽度较小的变量时,会发生类型降级,自动转为后者的类型。这时可能会发生截值(truncation),系统会自动截去多余的二进制位,导致难以预料的结果。
int i = 321;
char ch = i; // ch 的值是 65 (321 - 256)
上面例子中,变量 ch 是 char 类型,宽度是8个二进制位。变量 i 是 int 类型,将 i 赋值给 ch ,后者只能容纳 i (二进制形式为 101000001 ,共9位)的后八位,前面多出来的二进制位被丢弃,保留后八位就变成了 01000001 (十进制的65,相当于字符 A )。浮点数赋值给整数类型的值,也会发生截值,浮点数的小数部分会被截去。
double pi = 3.14159;
int i = pi; // i 的值为 3
上面示例中, i 等于 3 , pi 的小数部分被截去了。
13.混合类型的运算
不同类型的值进行混合计算时,必须先转成同一个类型,才能进行计算。转换规则如下:
(1)整数与浮点数混合运算时,整数转为浮点数类型,与另一个运算数类型相同。
3 + 1.2 // 4.2
上面示例是 int 类型与 float 类型的混合计算, int 类型的 3 会先转成 float 的 3.0 ,再进行计算,得到 4.2 。
(2)不同的浮点数类型混合运算时,宽度较小的类型转为宽度较大的类型,比如 float 转为
double , double 转为 long double 。
(3)不同的整数类型混合运算时,宽度较小的类型会提升为宽度较大的类型。比如 short 转为 int ,
int 转为 long 等,有时还会将带符号的类型 signed 转为无符号 unsigned 。
下面例子的执行结果,可能会出人意料。
int a = -5;
if (a < sizeof(int))
do_something();
上面示例中,变量 a 是带符号整数, sizeof(int) 是 size_t 类型,这是一个无符号整数。按照规则,signed int 自动转为 unsigned int,所以 a 会自动转成无符号整数 4294967291 (转换规则是 -5 加上无符号整数的最大值,再加1),导致比较失败, do_something() 不会执行。
所以,最好避免无符号整数与有符号整数的混合运算。因为这时 C 语言会自动将 signed int 转为unsigned int ,可能不会得到预期的结果。
14.整数类型的运算
两个相同类型的整数运算时,或者单个整数的运算,一般来说,运算结果也属于同一类型。但是有一个例外,宽度小于 int 的类型,运算结果会自动提升为 int 。
unsigned char a = 66;
if ((-a) < 0) printf("negative\n");
else printf("positive\n");
上面示例中,变量 a 是 unsigned char 类型,这个类型不可能小于0,但是 -a 不是 unsigned char 类型,会自动转为 int 类型,导致上面的代码输出 negative。
再看下面的例子。
unsigned char a = 1;
unsigned char b = 255;
unsigned char c = 255;
if ((a - 5) < 0) do_something();
if ((b + c) > 300) do_something();
上面示例中,表达式 a - 5 和 b + c 都会自动转为 int 类型,所以函数 do_something() 会执行两次。
15.函数
函数的参数和返回值,会自动转成函数定义里指定的类型。
int dostuff(int, unsigned char);
char m = 42;
unsigned short n = 43;
long long int c = dostuff(m, n);
上面示例中,参数变量 m 和 n 不管原来的类型是什么,都会转成函数 dostuff() 定义的参数类型。下面是返回值自动转换类型的例子。
char func(void) {
int a = 42;
return a;
}
上面示例中,函数内部的变量 a 是 int 类型,但是返回的值是 char 类型,因为函数定义中返回的是这个类型。
16.类型的显式转换
原则上,应该避免类型的自动转换,防止出现意料之外的结果。C 语言提供了类型的显式转换,允许手动转换类型。
只要在一个值或变量的前面,使用圆括号指定类型 (type) ,就可以将这个值或变量转为指定的类型,这叫做“类型指定”(casting)。
(unsigned char) ch
上面示例将变量 ch 转成无符号的字符类型。
long int y = (long int) 10 + 12;
上面示例中, (long int) 将 10 显式转为 long int 类型。这里的显示转换其实是不必要的,因为赋值运算符会自动将右边的值,转为左边变量的类型。
17.可移植类型
C 语言的整数类型(short、int、long)在不同计算机上,占用的字节宽度可能是不一样的,无法提前知道它们到底占用多少个字节。
程序员有时控制准确的字节宽度,这样的话,代码可以有更好的可移植性,头文件 stdint.h 创造了一些新的类型别名。
(1)精确宽度类型(exact-width integer type),保证某个整数类型的宽度是确定的。
int8_t :8位有符号整数。
int16_t :16位有符号整数。
int32_t :32位有符号整数。
int64_t :64位有符号整数。
uint8_t :8位无符号整数。
uint16_t :16位无符号整数。
uint32_t :32位无符号整数。
uint64_t :64位无符号整数。
上面这些都是类型别名,编译器会指定它们指向的底层类型。比如,某个系统中,如果 int 类型为32位, int32_t 就会指向 int ;如果 long 类型为32位, int32_t 则会指向 long 。下面是一个使用示例。
#include <stdio.h>
#include <stdint.h>
int main(void) {
int32_t x32 = 45933945;
printf("x32 = %d\n", x32);
return 0;
}
上面示例中,变量 x32 声明为 int32_t 类型,可以保证是32位的宽度。
(2)最小宽度类型(minimum width type),保证某个整数类型的最小长度。
int_least8_t
int_least16_t
int_least32_t
int_least64_t
uint_least8_t
uint_least16_t
uint_least32_t
uint_least64_t
上面这些类型,可以保证占据的字节不少于指定宽度。比如, int_least8_t 表示可以容纳8位有符号整数的最小宽度的类型。
(3)最快的最小宽度类型(fast minimum width type),可以使整数计算达到最快的类型。
int_fast8_t
int_fast16_t
int_fast32_t
int_fast64_t
uint_fast8_t
uint_fast16_t
uint_fast32_t
uint_fast64_t
上面这些类型是保证字节宽度的同时,追求最快的运算速度,比如 int_fast8_t 表示对于8位有符号整数,运算速度最快的类型。这是因为某些机器对于特定宽度的数据,运算速度最快,举例来说,32位计算机对于32位数据的运算速度,会快于16位数据。
(4)可以保存指针的整数类型。
intptr_t :可以存储指针(内存地址)的有符号整数类型。
uintptr_t :可以存储指针的无符号整数类型。
(5)最大宽度整数类型,用于存放最大的整数。
intmax_t :可以存储任何有效的有符号整数的类型。
uintmax_t :可以存放任何有效的无符号整数的类型。
上面的这两个类型的宽度比 long long 和 unsigned long 更大。