Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

Gray-Ice

个人博客兼个人网站

最近学的很杂,PyQt5,数据分析,操作系统……都在学习。可能我就是这样吧,对于任何事物都只有三分钟热情。这样就没有办法了,只能先学一点,等一段时间后再学一点。之前学过C语言,不过学的不深,而且经过了一年多差不多忘的干净了。本篇的主题是关于C语言中的整数类型,参考书为C primer Plus第六版(说是参考,实际上大部分都是抄出我认为有必要记的地方啦)。

那么下面开讲。

C语言中的整数类型可以表示不同的取值范围和正负值,一般情况下使用int即可,但是为了满足特定任务和机器的需求,还可以选择其他类型。

int类型

int类型是有符号整形,即int类型的值必须是整数,可以是正整数,负整数或零。其取值范围因计算机系统而异。一般而言,储存一个int要占用一个机器字长。因此,早期的16位IBM PC兼容使用16位来储存一个int值。其取值范围(即int值的取值范围)是-3276832767。目前的个人计算机一般是32位,因此用32位储存一个值(其实现在大部分都是64位了,这本书可能有点老了)。ISO C规定int的取值范围最小为-3276832767。一般而言,系统用一个位表示有符号整数的正负号。

声明int类型

先写上int,然后写变量名,如:

1
2
3
int fire;
// 也可以一次声明多个变量
int gray, ice;

编译器在编译的时候会为变量赋予名称并分配int大小的内存空间。

给变量赋值:

1
2
3
4
5
6
7
8
int gray = 4;
// 或者一次给多个变量赋值
int gray = 4, ice = 5;
//也可以选择只赋值给指定的变量
int fire, gray = 4;
// 也可以通过函数(如scanf())获得值
int fire;
scanf("%d", &fire);

int fire 和int fire = 4的区别是,int fire创建了一个内存空间,而int fire = 4创建了一个内存空间并为其赋值。

C语言把不含小数和指数的数称为整形,因此22和-22都是整形常量,但是22.0和2.2E1则不是。C语言把大多数整形常量视为int类型,但是非常大的整数除外。

打印int值

%d指定了在一行中打印整数的位置,%d称为转换说明,它指定了printf()应该用什么格式来显示一个值。格式化字符串中的每个%d都与待打印变量列表中相应的int值匹配。这个值可以是int类型的变量,int类型的常量或其他任何值为int类型的表达式。切记一定要细心,不然可能会出现奇妙的错误。如:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

void meaningless();
int main(){
int fire;
fire = 10;
printf("%d, and %d", fire);
return 0;
};

输出结果为:

1
10, and -1013542408

这个奇怪的负数就令人感到离谱。书上说,因为没有给后面那个%d提供任何值,所以打印出的值是内存中的任意值。

八进制和十六进制

在C语言中,用特定的前缀表示使用哪种进制。0x或0X前缀表示十六进制,与此类似,0前缀表示是八进制。不过,使用不同的进制是为了方便,不会影响数被储存的方式,也就是说,无论把数字写成16, 020, 或者0x10,储存该数的方式都相同,因为计算机内部都以二进制进行编码。

下面我写几条C语言中给变量赋值8进制,16进制的语句:

1
2
3
4
int hex, octal;
hex = 0X10; // 16进制,以0x或0X为前缀
octal = 010; // 8进制,以0为前缀
printf("%d, %d\n", hex, octal);

下面是输出结果:

1
2
3
gcc test.c  # 编译C文件
./a.out # 执行文件。似乎是因为我使用的Linux系统,使用gcc编译完成后的文件后缀名默认为.out
16, 8 # 输出结果。十六进制中的10转换成十进制就是16, 同理八进制中的10转换成十进制就是8

打印时显示八进制和十六进制

打印时使用%o显示八进制,使用%x显示十六进制。如果要显示数的前缀,八进制需要使用%#o,十六进制需要使用%#x或%#X。

那么下面有代码演示:

1
2
3
4
5
int hex, octal;
hex = 0X10;
octal = 010;
printf("%x, %X, %o\n", hex, hex, octal);
printf("%#x, %#X, %#o\n", hex, hex, octal);

下面是输出结果:

1
2
3
4
gcc test.c  # 编译C文件
./a.out # 执行文件
10, 10, 10 # 输出结果。这里没有加代表进制的前缀
0x10, 0X10, 010 # 输出结果。这里加上了代表进制的前缀

通过这一段我们可以明白,在C语言中整数(这里先不提浮点数等,我还没有学到)都是以相同的形式的储存的,printf()只是将其转换成了不同的形式打印出来。想来也是,如果我们储存整数的时候将进制类型也一并储存进去,那么需要增加程序所占用的内存不说,整体看起来也不够优雅,而等到打印的时候再决定数据显示的进制确实为一种机智的方式,我从这里又得到了新的收获。

其他的整数类型

C语言提供3个附属关键字修饰基本整数类型: short, long, unsigned。

shot int类型(或者简写为short)占用的储存空间可能比int类型少,常用于较小数值的场合以节省空间。与int类似,short是有符号类型。

long int或long占用的储存空间可能比int多,适用于较大数值的场合。与int类似,long是有符号类型。

long long int 或long long(C99标准加入)占用的储存空间可能比long多,适用于更大数值的场合。该类型至少占64位。与int类似,long long是有符号类型。

unsigned int或unsigned只用于非负值的场合。这种类型与有符号类型表示的范围不同。例如,16位的unsigned int允许的取值范围是065535,而不是-3276832767。用于表示正负号的位现在用于表示另一个二进制位,所以无符号整形可以表示更大的数。

在C90标准中,添加了unsigned long int 或 unsigned long 和unsigned short int 或 unsigned short类型。C99标准又添加了unsigned long long int 或 unsigned long long。

在任何有符号类型前面添加关键字signed,可强调使用符号类型的意图。例如:short, short int, signed short, signed short int都表示同一种类型。

声明其他整数类型

其他整数类型的声明方式与int类型相同。

如:

1
2
3
short fire;
unsigned good;
signed short goo1;

使用多种整数类型的原因

为什么说short类型”可能”比int类型占用的空间少,long类型”可能”比int类型占用的空间多?因为C语言只规定了short占用的储存空间不能多于int,long占用的储存空间不能少于int。这样规定是为了适应不同的机器。例如,过去的一台Windows 3.x的机器上,int类型和short类型都占16位,long类型占32位。后来,Windows和苹果系统都使用16位储存short类型,32位存储int类型和long类型(使用32位可以表示的整数数值超过20亿)。现在,计算机普遍使用64位处理器,为了存储64位的整数,才引入了long long 类型。

现在,个人机上最常见的设置是,long long占64位,long占32位,short占16位,int占16位或32位(依计算机的自然字长而定)。原则上,这四种类型代表四种不同的大小,但在实际使用中,有些类型之间通常有重叠。

如果一个数超出了int类型的取值范围,且在long类型的取值范围内时,使用long类型。然而,对于那些long占用的空间比int大的系统,使用long类型会减慢运算速度。因此,如非必要,不要使用long类型。另外要注意一点,如果在long类型和int类型占用空间相同的机器上编写代码,当确实需要32位的整数时,应使用long类型而不是int类型,以便把程序移植到16位机后仍然可以正常工作。类似的,如果确实需要64位的整数,应使用long long类型。

如果在int设置为32位的系统中要使用16位的值,应使用short类型以节省存储空间。通常,只有当程序使用相对于系统可用内存较大的整形数组时,才需要重点考虑节省空间的问题。使用short类型的另一个原因是,计算机中某些组件使用的硬件寄存器是16位。

long常量和long long常量

通常,程序代码中使用的数字(如: 2345)都被储存为int类型。如果使用1000000这样的大数字,超出了int类型所能表示的范围,编译器会将其视为long int类型(假设这种类型可以表示该数字)。如果数字超出了long可表示的最大值,编译器则将其视为unsigned long类型。如果还不够大,编译器则将其视为long long 或unsigned long long类型(前提是编译器能识别这些类型)。

八进制和十六进制常量被视为int类型,如果值太大,编译器会尝试使用unsigned int。如果还不够大,编译器会依次使用long, unsigned long, long long 和 unsigned long long 类型。

整数溢出

这里要讲的是整数溢出,那么什么是整数溢出呢?
我们先看一段代码:

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>

int main(){
int i = 2147483647;
unsigned int j = 4294967295;
printf("%d, %d, %d\n", i, i+1, i+2);
printf("%u, %u, %u", j, j+1, j+2);
return 0;
}

编译并执行它:

1
2
3
4
gcc test.c
./a.out
2147483647, -2147483648, -2147483647
4294967295, 0, 1

注意一下输出结果,也许你会有疑问,为什么值为2147483647的int类型的变量i加上1就会变成负数呢?同样为什么值为4294967295的unsigned int类型的j加上1就会从零开始呢?这就是整数溢出了。因为int类型是有个取值的区间的,如果大过了这个区间就会从头开始。同理unsigned int也是如此,不过因为unsigned int没有符号,所以是从0开始。接下来我们来看一看变量i转换成二进制后的数,因为printf不支持直接打印出二进制,所以我使用了linux下一个叫做bc的计算器工具。这里变量i转换成二进制后为1111111111111111111111111111111,一共有31位,而变量i实际上是占了32位的,因为还有一个位给了符号。同样,变量j转换成二进制为11111111111111111111111111111111,一共有32位,因为不用腾出一个位给符号了。关于位数的正确与否,不用怀疑我是不是数错了,因为我C语言很差劲,所以拿python算了算,嘿嘿。

评论



愿火焰指引你