目前阅读的这本书感觉真的非常棒,它真的在一点一点教会我怎样由浅入深的学习和理解计算机这门学科。在学习小数时大家可能会认为“万能的计算机是不会出现计算错的”。但实际上,依然存在程序运行后无法得到正确数值的情况。其中,小数运算就是一个典型的例子。在第2章中,我们对整数的二进制数表现方法做了说明。由于计算机内部所有的信息都是以二进制数的形式来处理的,因此在这一点上,整数和小数并无差别。不过,使用二进制数来表示整数和小数的备份方法却有很大的不同。
小数点后4位用二进制数表示时的数值范围为0.0000~0.1111。因此,这里只能表示0.5、0.25、0.125、0.0625这四个二进制数小数点后面的位权组合而成(相加总和)的小数。将这些数值组合后能够表示的数值,即为无序的十进制数。十进制数0的下一位是0.0625。因此,这中间的小数,就无法用小数点后4位数的二进制数来表示。同样,0.0625的下一位数一下子变成了0.125。这时,如果增加二进制数小数点后面的位数,与其相对应的十进制数的个数也会增加,但不管增加多少位,2的-00次幂怎么相加都无法得到0.1这个结果。实际上,十进制数0.1转换成二进制后,会变成0.00011001100.(1100循环)这样的循环小数。这和无法用十进制数来表示1/3是一样的道理。1/3就是0.3333…,同样是循环小数。至此应该明白了为什么用代码清单的程序无法得到正确结果了吧。因为无法正确表示的数值,最后都变成了近似值。
那什么是浮点数呢?很多编程语言中都提供了两种表示小数的数据类型,分别是双精度浮点数和单精度浮点数。双精度浮点数类型用64位、单精度浮点数类型用32位来表示全体小数。在C语言中,双精度浮点数类型和单精度浮点数类型分别用double 和 float来表示。不过,这些数据类型都采用浮点数来表示小数。浮点数是指用符号、尾数、基数和指数这四部分来表示的小数。
第四章主要是熟练使用有棱有角的内存。在之前的文章中提到内存,其实内存的物理机制是很简单的。接下来就来说说它为什么简单吧。内存实际上是一种名为内存IC的电子元件。虽然内存IC包括 DRAM、SRAM、ROM等多种形式,但从外部来看,其基本机制都是一样的。内存IC中有电源、地址信号、数据信号、控制信号等用于输入输出的大量引脚(IC的引脚),通过为其指定地址(address),来进行数据的读写。下面我们来看一下指针,指针是C语言的重要特征,但很多人都说它难以理解,甚至还有人因无法理解指针而对C语言的学习产生了很强的挫败感。指针也是一种变量,它所表示的不是数据的值,而是存储着数据的内存的地址。通过使用指针,就可以对任意指定地址的数据进行读写。虽然前面所提到的假想内存IC中仅有10位地址信号,但大家在 Windows 计算机上使用的程序通常都是32位(4字节)的内存地址。这种情况下,指针变量的长度也是32位。
下面让我们回到主题,解释一下本章标题中出现的“熟练使用有棱有角的内存”。首先,我们先来看一下内存最直接的使用方法。我们要用到数组。数组是指多个同样数据类型的数据在内存中连续排列的形式。作为数组元素的各个数据会通过连续的编号被区分开来,这个编号称为索引(index)。指定索引后,就可以对该索引所对应地址的内存进行读写操作”。而索引和内存地址的变换工作则是由编译器自动实现的。栈和队列,都可以不通过指定地址和索引来对数组的元素进行读写。需要临时保存计算过程中的数据、连接在计算机上的设备或者输入输出的数据时,都可以通过这些方法来使用内存。如果每次保存临时数据都需指定地址和索引,程序就会变得比较麻烦,因此要加以改进。栈和队列的区别在于数据出人的顺序是不同的。在对内存数据进行读写时,栈用的是LIFO(Last Input First Out,后人先出)方式,而队列用的则是FIFO(First Input First Out,先人先出)方式。如果我们在内存中预留出栈和队列所需要的空间,并确定好写人和读出的顺序,就不用再指定地址和索引了。