2.10 变量
一些动态变化的量(比如车速、温度、股票价格等)称为变量,这些量在源程序中用常量无法表示。
用C语言进行编程,要使用数据区(数据值可变)而不是代码区(数据值不允许变)的内存单元来存放数据,都必须向编译程序提出申请。
在C语言源程序中,向编译程序申请一个(或几个)存放某种类型数据的、值的大小可以变化的内存单元称为定义变量。
2.10.1 变量的定义
定义一个简单变量的格式为:
类型名 变量名;
类型名要使用关键字(比如int、float、char等,参见2.7节),变量名必须使用标识符。
通过这种方式,编译程序为我们在内存中分配一个合适大小的内存单元,用来存放一个此类型的数据。
例如,在定义变量 int num; 之后, C编译程序通常会为变量num在内存中分配地址连续的2个(或4个)字节作为一个内存单元,用来存放变量num的数据,编译程序通过这种方式,把一个变量num与一个可以存放int型数据的内存单元联系了起来,参见图2-1。
延伸与拓展:编译程序为变量分配内存单元的技术内幕
编译程序通过扫描每个函数中的所有变量定义,建立起一张表,即变量名–内存单元地址对照表。接下来在扫描所有的语句时,编译程序就可以通过查找这张对照表,将源程序语句中出现的变量名转变为机器语言指令中的内存单元的地址,即指令中的操作数。
编译程序通过把变量名映射为内存地址的方式,达到了为变量分配相应内存单元的目的(类似于导游通过将游客名对应为房间号,达到了为每个游客分配一个房间的目的)。
定义多个简单变量的格式为:
类型名 变量名列表;
例如:
int age , num ,sum ;
在变量名列表中的多个变量名之间,要用逗号隔开,最后要以分号结束。编译程序会为每一个同类型的变量分配同样字节数的内存单元,用来存放各变量的值。
最好使用意义明确的标识符来定义变量,比如对于兔子的只数,使用标识符rabbit_count作为变量名就比用标识符n作变量名要好得多。
2.10.2 变量名和变量的值
由于变量的内存单元由编译程序分配,因此,在源程序的语句中,可以通过书写变量名来表示要“访问”(即存或取)变量所对应的内存单元中的数据了,这个数据称为变量的值。
比如,语句 num + 2 表示把变量名num所对应的内存单元中的值取出来(简称为把变量num的值取出来)再加上2( num + 2 其实是表达式,细节参见2.14节)。而语句“num = 21 ;”表示要把数值21存入变量名num所对应的内存单元中(简称为把数值21存入变量num中,“num = 21;”其实是赋值语句,细节参见2.16节)。
变量的值是“取之不尽”的。从内存单元取得一个变量的值,其实只是从一个内存单元中复制了这个值而已,该变量的值(没有发生任何变化)仍然可以再次取用;但是,变量的值又是“一存就变”的,只要运行了一条与存数操作有关的语句,(在内存单元中的)变量的“旧值”就被变量的新值覆盖掉了,变量的“旧值”将不复存在。
延伸与拓展:机器语言程序与高级语言源程序的最大不同点
机器语言程序中的指令经常使用内存地址(作为操作数)来指明要存取哪个内存单元中的数据;而高级语言源程序中的语句使用变量名来指明要存取哪个内存单元中的数据。这是机器语言程序与高级语言源程序最重要的不同点之一。内存地址是长长的、难以记忆的二进制位串;而变量名则是程序员自己所起的好记和易懂的(但要符合标识符规定的)名字。
通过这种方式,首先,使得源程序更为简明而可读性又好。其次,高级语言源程序所要加工的数据获得了极为宝贵的内存绝对位置无关性。这使得源程序具有了良好的移植性,也为多道程序同时放入内存中创造了先决条件。
但使用高级语言编程也存在一些缺点,在源程序的语句(或表达式)中,究竟何处是取变量的值(变量值不变),何处是将一个值存到变量之中(变量值将改变),变得有些模糊不清。这给初学者阅读和理解程序的运行带来一定的困难。这个问题读者要给予充分的注意,否则你就很可能读不懂很多程序。
2.10.3 各种基本类型的变量定义
1)整型变量(int):在程序运行时,需要内存单元存放(数值可以变化的)可正可负的整数时,要用此类型来定义。C编译器一般分配2个(16位系统)或4个字节(32位系统)的内存空间给一个int型变量。
占用两个字节的int型变量的取值范围在–32768~32767之间,这个范围比较小。
2)单精度浮点型变量(float):在程序运行时,需要内存单元存放以实数形式(即有小数分量)出现的量(比如34.1、–678.34、0.368等),要用此类型来定义。
比如,“float x,y;”就定义了两个单精度浮点型变量x和y。
一般C编译器分配4个字节的内存空间给一个float型变量。
单精度浮点型变量的精度是十进制的7位。也就是说,只有数值中的高7位数字肯定是正确无误的。
float型变量的优点是:取值范围远比int型变量大(大约正数是在1.17×10–38~3.4×1038之间,负数取值范围与正数是关于原点对称的)。float型变量的缺点是:运算速度不如int型变量快,所存入的数据通常也只是一个近似值。
3)字符型变量:用类型关键字char来定义字符型变量。
在程序运行时,需要用内存单元存储单个字符(通常是ASCII字符时),要用此类型来定义。C编译器分配1个字节的内存空间给一个char类型的变量。比如,“char ch1,ch2;”定义了两个字符变量ch1和ch2。
字符型变量属于一种从表面上看来是非数值型的量—字符。但其实在计算机的内存中,它通常就是一个以ASCII码形式存储的,占用一个字节内存的二进制码。在C语言中,把这个ASCII码当作一个小整数来对待。比如字符'a',实际上被编译程序看成是占用一个字节的整数97(但在有些高级语言比如Pascal语言中,不能把字符量当成整型量来对待)。
在高级编程语言中,任何类型变量的值都有一定的限定范围(这是由于存储变量值的字节数有限而造成的),程序运行时超出了变量允许的取值范围称为发生了溢出(关于溢出的讨论,请参见本书后面的讨论)。这是在编程时要注意避免和进行处理的。
变量允许的取值范围与所分配到的字节数有关,不同编译器为同一种类型变量分配的字节数很可能不一样,C语言标准只是规定了各种类型变量所占用内存的最少字节数(参见后续内容)。
延伸与拓展:“变量类型”的技术内幕
对于不同类型的变量,编译程序分配给变量的内存单元字节数很可能不一样,数据的外部形式、机内形式不一样,运算时选用的运算指令类型不一样(比如对于实型量加法,编译程序选用浮点数加法指令;而对于整型量加法,则选择整数加法指令),输入输出变量值的转换工作不一样,变量取值的允许范围可能不一样,允许进行的运算也不一样;但上述所有这些不一样,除了最后两项需要编程者注意外,大多都不需要编程者来具体操心。这些原本极为琐碎的基础性的编程工作,只要我们恰当地定义了变量的类型,并在程序语句中合理地使用变量,编译程序(包括标准输入输出库函数)基本上就可以为我们代劳了。
2.10.4 变量的初始化
变量的初始化就是存入一个初始数据到变量对应的内存单元中。
在C语言中,变量定义后,通常还要进行初始化,也就是事先要将一个数据存放在变量对应的内存单元中。有了一个有效的确定值,该变量才能在语句和表达式中用来参与运算或进行输出。否则,变量所对应的内存单元中(以前运行别的程序遗留下)的无效数据(我们将其称为垃圾数据),就会在程序语句的执行运算或输出时被误用,造成程序运行错误。
最简单的初始化变量的方法是在定义变量时,在变量后面用一个等号“=”给予它一个初始值。初始值必须是常量(或常量表达式)。比如以下变量定义:
int number , sum = 100;
char ch1 = 'a' , ch2;
float area = 65.432 ;
这三条变量定义的“变量内存取值示意图”如图2-2所示。
变量名 number sum ch1 ch2 area
变量的值 垃圾数据 100 'a' 垃圾数据 65.432
可见,除了变量 number和ch2中的值是垃圾数据外,int型变量sum、char型变量ch1和float型变量area都得以初始化了。
初始化变量还有其他两种方法,即使用scanf()输入库函数和赋值语句(请参见2.12节和2.16节)。