2.2 让程序记住计算结果——变量

2.2.1 计算机的记忆功能

和人类一样,计算机可以具有记忆的能力。这种记忆能力也可以分为长期记忆的能力和短期记忆能力。而计算机的长期记忆能力是靠外部存储设备实现的,短期记忆能力是靠内部存储器件——即所谓内存实现的。

内存(Memery Unit)这个词,本来就是记忆器件的意思(8)。这个器件有如下特点。

1.加电之后才能够记忆。

2.加电后并不是一片空白,而是记着一些乱七八糟的没有意义的东西。

3.断电后记忆消失。

4.记忆的内容可以被代码读出也可以被代码写入。

5.记忆的内容被读出时,内容不变;被写入新内容时原来记忆的内容消失。

代码有多种方式使用内存帮助记忆,其中最常见的方式是通过变量。

2.2.2 在代码中实现“记忆”

1.定义变量

在C代码中让计算机帮助记忆最常见的方法是通过变量(Variable)。很遗憾,在C标准中竟然找不到关于变量的定义,因此这里只能给出一个关于变量的描述性的定义。

如同在黑板上演算题目一样,程序运行过程中也不可避免地需要存储空间保存记录数据。本质上变量就是一段确定长度的、用于存放数据的存储空间(9)

当然,常量也是一段确定长度的、用于存放数据的内存空间。和变量不同的是,程序编写者在源程序中不能修改常量数据,也无从知道常量数据具体的存放位置(10),因为常量往往被编译到了指令中去,很少有单独占据一块数据空间的情况。

变量与常量还有另外一个显著的区别,那就是变量一定有一个名字,而常量可以有一个名字(符号常量)也可以没有名字。

这样,“变量”就可以大体地被归纳为这样的定义:变量是一段连续的、被命名了的、用于存放数据的、且可以求得其位置的、具有类型含义的存储空间。

我承认,这个定义很拗口也很啰嗦。但是和C标准委员会相比,至少我是诚实认真的。所以,请不要笑话我。

由于变量在本质上是存放在内存中的数据或是指存放这个数据的内存空间,因而必须要确定它的数据类型(11)。这在使用变量之前就得和编译器说清楚。此外为这块内存取名也是编程者的责任。至少基于这两点,在C语言中使用变量之前必须首先进行变量定义(Definition)。

定义变量要通过数据类型的名字,如前面提到的“int”就是一种数据类型的名字,此外还要为变量本身取个名字,比如“i”,这个名字需要编程者根据标识符规则按照自己的编程风格自己命名。

有了这两个名字就可以定义变量了,例如:

2.2 让程序记住计算结果——变量 - 图1

定义变量也是以“;”作为结束标志。“int i;”向编译器表明的是“i”是一个“int”类型的变量。然而所谓“‘i’是一个‘int’类型的变量”这种说法只是代码世界中的一句“行话”,从这句话里我们并不能彻底地了解其真实的含义。这个问题的谜底藏在机器世界中。

对于编译器而言,“int i;”意味着去寻找一块空闲的内存,大小为“int”类型数据所占据空间的大小,也就是4byte。同时意味着代码和计算机共同约定把这块连续的4byte内存空间叫做“i”。更进一步,这块内存中的内容将被计算机解读为一个“int”类型的数据,亦即一个二进制整数,并将按照int类型运算规则参与运算。这就是变量定义的全部含义。

也可以一次定义几个同类型的变量,如:

2.2 让程序记住计算结果——变量 - 图2

2.垃圾值

程序代码2-4

2.2 让程序记住计算结果——变量 - 图3

运行结果如图2-5所示。

2.2 让程序记住计算结果——变量 - 图4

图2-5 拉圾值

这段代码演示了如何调用printf()函数输出i变量的值,然而这个“2009001733”并没有什么意义。原因就在于前面提到的起初内存中总是“记着一些乱七八糟的没有意义的东西”。这种东西通常被叫做“垃圾值”。

3.给变量赋值

由于定义变量之后,变量中的数据其实只是“垃圾值”,所以在使用变量中的数据运算时,一定要事先在其中填充有意义的数据。向变量中填充数据有多种手段,最常用的一种是通过赋值运算。

赋值运算的运算符是“=”,其基本用法是在“=”左边写上一块可以被赋值的内存的名字或者一个表示一块可以被赋值的内存的表达式,最简单的是写一个变量名(后面将会看到还有其他写法),而在“=”右面写要赋给这个变量的值。例如:

2.2 让程序记住计算结果——变量 - 图5

这表示的含义是把789的机器数(0000 0000 0000 0000 0000 0011 0001 0101)复制到i所表示的那块32bits的内存中去并替换掉了i中原来的内容。如图2-6所示,表示的就“=”是这个运算的实际含义。

2.2 让程序记住计算结果——变量 - 图6

图2-6 赋值运算的含义

特别要注意的是,不要把“=”运算读成“等于”而应该读成“赋值为”。因为这个运算的含义和数学中的“等于”有着天壤之别。在日常生活中读错别字,别人可能还会理解你的意思,但对于计算机来说,你的错别字一定意味着一个错误的命令,计算机会按照你错误的命令不折不扣地执行。所以一定要把原来数学中的习惯改过来,这会在你日后的程序写作中减少至少三分之一的错误(12)

初学者很容易把代码中的赋值运算错误地与列方程混淆起来,他们有时很难理解如:

2.2 让程序记住计算结果——变量 - 图7

这样的运算式。在这个表达式中,“=”左右的x的含义有细微的差异:左边表示以x命名的那块内存;右边表示x所代表的内存中所表示的数据的值。总的含义是取出x所代表的内存中的值与7相加,再将所得的值放到x所命名的那块内存中去。也就是把x值加上7得到的值赋值给x内存。

此外在写代码时初学者会写出类似下面似是而非的错误表达:

2.2 让程序记住计算结果——变量 - 图8

这个表达的错误在于x+2并不表示一块内存,代码中的赋值运算与代数中的列方程是两个完全不同的概念。

也可以在定义变量的时候就给变量赋值,在变量名后面加上=初值。如:

2.2 让程序记住计算结果——变量 - 图9

其中的i和k分别被赋值为3和4。

在C语言中,所有的运算都会求得一个值(13),作为一种运算,“=”如同+、-等运算一样能求得一个值,i=789的值就是789。也就是说赋的值是什么,“=”运算得到的值就是什么。这和2+3的值是5没什么本质的不一样。下面试一下这个语句:

2.2 让程序记住计算结果——变量 - 图10

输出如下:

2.2 让程序记住计算结果——变量 - 图11

这里输出的第一个789并不是“=”右面的789的值,而是“i=789”运算式得到的运算结果,正如第二个789是788+1的值一样。

4.代码的进一步改进

给变量赋值可以使代码实现记忆运算结果的功能,这样就可以写出完全与人类思维计算过程相同的代码。

首先求2的1次幂,这通常是根据定义得到的,然后记住这个2。并报出答案。

对于代码来说,由于需要记住计算结果,所以需要首先定义一个用于记忆的变量。由于计算的结果是一个不大的整数,所以可以把这个变量定义成“int”类型。可以为这个变量取个有意义的名字,如mi。由于2的1次幂是根据定义得到的,所以可以直接赋值。报出答案可以用调用printf()函数模拟实现:

2.2 让程序记住计算结果——变量 - 图12

然后根据前面记住的2的1次幂的结果2计算2的2次幂,为2*2=4,记住并报出答案。

对于代码来说就是:

2.2 让程序记住计算结果——变量 - 图13

然后根据前面记住的2的2次幂的结果4计算2的3次幂,为4*2=8,记住并报出答案。对应的代码依然可以写做:

2.2 让程序记住计算结果——变量 - 图14

后面的过程与前面的一样,是个重复的过程。对于代码来说,“复制”、“粘贴”然后稍加编辑就可以了。

顺便说一句,在编辑代码时应该努力避免一个字符一个字符地键入,编写代码最重要的工作是思考。代码中的大部分都应该通过“复制”、“粘贴”完成,这样做不但编辑效率高而且不容易出错。

完成后的代码如下。为节约篇幅,代码只计算了2的1次幂到2的5次幂。

程序代码2-5

2.2 让程序记住计算结果——变量 - 图15

这个代码包含了一系列重复的语句,这种语句的重复可以更简洁地用循环语句表示。循环语句将在后面的章节专门介绍。

练习

使用变量编程计算2的11到20次幂并输出。