2.4 整型数据

2.4.1 符号常量

定义一个整型变量时要使用关键字int。下面来看图2.4.1中的例子。

由于整型变量i有自己的内存空间,因此既可以存储整型常量123,又可以存储PI*2,也就是3*2(即结果6)。由define定义的PI为什么是常量呢?接下来我们通过改变编译设置来了解什么是预处理,了解预处理后你就会明白define定义的内容是常量的原因。右键单击对应的项目,选择“属性”选项,将“预处理到文件”设置为“是”,如图2.4.2所示。

设置完毕后,选择重新编译会显示编译失败,但此时不用担心,因为开启预编译后VS只进行预处理,而不再进行其他编译动作(VS针对没有编译出最终可执行程序的操作会提示编译失败)。然后,如图2.4.3所示,右键单击“1.整型常量与变量”项目,在右键菜单中选择“在文件资源管理器中打开文件夹”选项,在打开的窗口中双击打开Debug文件夹,如图2.4.4所示。

img

图2.4.1 定义整型变量

img

图2.4.2 将“预处理到文件”设置为“是”的界面

查看项目文件,在Debug文件夹中找到main.i文件,如图2.4.4所示。使用写字板或Notepad++打开main.i,并直接滚动到文件的结尾部分,会出现如图2.4.5所示的内容,可以发现define已经不存在,这是因为预处理时会消除所有的define。同时我们用define将PI定义为3,编译器在预处理后会把代码中出现的所有常量PI都替换为3,所以PI是常量,不可修改。

以上操作的作用是,如果我们在程序中多次用到某个常量,那么可以使用define定义的方法来简化操作。这样,在编写程序时,如果需要改变程序中多处的同一个常量,那么只需改变define后面的符号常量PI的值即可。

img

图2.4.3 选择“在文件资源管理器中打开文件夹”选项

img

图2.4.4 打开后的Debug文件夹

img

图2.4.5 预处理后文件的内容

2.4.2 整型常量的不同进制表示

计算机中只能存储二进制数,即0和1,而在对应的物理硬件上则是高、低电平。为了更方便地观察内存中的二进制数情况,除我们正常使用的十进制数外,计算机还提供了十六进制数和八进制数。

下面介绍不同进制数的对应关系。

首先,在计算机中,1字节为8位,1位即二进制的1位,它存储0或1。int型常量的大小为4字节,即32位。

设有二进制数0100 1100 0011 0001 0101 0110 1111 1110,其最低位是2的零次方,最高位是2的30次方,最高位为符号位,具体情况将在补码部分讲解。

上面的二进制数对应的八进制数是011414253376,它以0开头标示,数位的变化范围是0~7。二进数转换为八进制数的方式是,对应的二进制数每3位转换为1位八进制数。首先将上面的二进制数按每3位隔开,得到01 001 100 001 100 010 101 011 011 111 110,然后每3位对应0~7范围内的数进行对应转换,得到八进制数011414253376。由于实际编程时,识别八进制数时前面需要加0,所以在前面加了一个0。

上面的二进制数对应的十进制数是1278301950,直接赋值即可。

上面的二进制数对应的十六进制数是0x4C3156FE,它以0x开头标示,数位的变化范围是0~9和A~F,其中A代表10,F代表15,对应的二进制数每4位转换为1位十六进制数,具体对应方式请读者自行处理。

下面新建项目“2.整型的进制转换”。如图2.4.6所示,要在已有的解决方案内新建项目,可在解决方案位置右键单击,在右键菜单中选择“添加”→“新建项目”,在打开的对话框中填写名称“2.整型的进制转换”。同时,如图2.4.7所示,在右键菜单中选择“设为启动项目”,将“2.整型的进制转换”设为启动项目。

img

图2.4.6 在已有的解决方案内新建项目界面

img

图2.4.7 将“2.整型的进制转换”设为启动项目界面

接着在代码第7行处设置断点,然后单击“执行”按钮,得到如图2.4.8所示的结果。在监视窗口中输入&i(取地址i),得到i的地址,左键长按将其拖入右边的内存区域,就可以看到i的内存。我们的32位控制台应用程序的地址范围是从0到4G,即从0x00000000到0xFFFFFFFF,如图2.4.9所示,这称为进程(程序运行起来后称为进程)地址空间。程序编译完毕,开始执行时,会被放入进程地址空间的代码段区域。执行到哪条语句,PC指针就指向该条语句对应的地址。例如,目前我们执行到语句int i=0x7b,变量i会在栈空间上被分配空间,大小为4字节,起始地址为0x0013FAF8。按F10键,会看到如图2.4.10所示的结果,其中i的值变为7b(我们以十六进制方式查看内存),其十进制值为7×16+11=123。i的值是0x0000007b。为什么显示结果为7b 00 00 00呢?原因是英特尔的CPU采用了小端方式进行数据存储,因此低位在前、高位在后。

img

图2.4.8 单击“执行”按钮后得到的结果

img

图2.4.9 32位控制台应用程序的地址范围

img

图2.4.10 按F10键后的结果

单击“继续执行”按钮运行到最后,得到如图2.4.11所示的结果。八进制数0173转换为十进制数的方式是1×82+7×81+3×80 =123。那么十进制数123如何转换为二进制数呢?方法是让123不断地除以2,并把余数写在右边,把商写在下方,直到商为0,然后逆序写出所有余数,即可得到转换后的二进制数1111011,详细过程如图2.4.12所示。对应的十六进制数为7b,十进制数转换为十六进制数的方式是除以16,十进制数转换为八进制数的方式是除以8,读者可以自行练习。

小技巧:手动转换一个数的进制后,若不知道转换后的进制数是否正确,则可在Windows操作系统下选择“开始”→“附件”→“计算器”,打开“计算器”,然后选择菜单项“查看”→“程序员”,得到如图2.4.13所示的计算器。输入一个十进制数后,单击“十六进制”“八进制”或“二进制”,即可得到对应进制的转换结果。

img

图2.4.11 按“继续执行”按钮运行到最后的结果

img

图2.4.12 十进制数123转换为二进制数的详细过程

img

图2.4.13 “计算器”的程序员界面

2.4.3 补码的作用

计算机的CPU无法做减法操作,只能做加法操作。CPU中有一个逻辑单元叫加法器。计算机所做的减法、乘法和除法,都是通过加法器将其变化为加法实现的。那么减法具体是如何通过加法实现的呢?实现 2−5 的方法是 2+(−5) 。由于计算机只能存储0和1,因此我们编写程序来查看计算机是如何存储−5的,如图2.4.14所示,5的二进制数为101,称为原码。计算机用补码表示−5,补码是对原码取反后加1的结果,即计算机表示−5时会对5的二进制数(101)取反后加1,如图2.4.15所示。−5在内存中存储为0xfffffffb,因为对5取反后得0xfffffffa,加1后得0xfffffffb,对其加2后得0xfffffffd,见图2.4.16,它就是k的值。当最高位为1(代表负数)时,要得到原码才能知道0xfffffffd的值,即对其取反后加1(当然,也可以减1后取反,结果是一样的)得到3,所以其值为−3。

img

图2.4.14 查看计算机如何存储−5的程序

img

图2.4.15 −5在内存中的存储方式

img

图2.4.16 5取反、加1并加2后的存储方式

2.4.4 整型变量

整型变量包括6种类型,如图2.4.17所示,其中有符号基本整型与无符号基本整型的最高位所代表的意义不同,如图2.4.18所示。不同整型变量表示的整型数的范围如表2.4.1所示,超出范围会发生溢出现象,导致计算出错。

img

图2.4.17 整型变量的6种类型

img

图2.4.18 有符号基本整型与无符号基本整型的最高位所代表的意义

表2.4.1 不同整型变量表示的整型数的范围

img

由图2.4.19可以看出不同类型的整型变量的定义方法。下面介绍溢出。

img

图2.4.19 不同类型的整型变量的定义方法

有符号短整型数可以表示的最大值为32767,当我们对其加1时,b的值会变为多少呢?实际运行打印得到的是−32768。为什么会这样?因为32767对应的十六进制数为0x7fff,加1后变为0x8000,其首位为1,因此变成了一个负数。取这个负数的原码后,就是其本身,值为32768,所以0x8000是最小的负数,即−32768。这时就发生了溢出,我们对32767加1,希望得到的值是32768,但结果却是−32768,因此导致计算结果错误。在使用整型变量时,一定要注意数值的大小,数值不能超过对应整型数的表示范围。有的读者可能会问在编写的程序中数值大于264−1时怎么办?答案是可以自行实现大整数加法,详见后面介绍数组时的说明。

图2.4.19中代码的执行结果如图2.4.20所示。

img

图2.4.20 图2.4.19中代码的执行结果

思考题:针对上例中的溢出问题,如何修改程序才能让打印的b值是32768而不是−32768?