5.5 变量的作用域和存储类别
5.5.1 变量的作用域
C语言中变量必须先定义后使用,定义过的变量在程序中是否可以随处使用呢?这一节来介绍变量的作用域。作用域是指变量是有效的或者说能够被编译系统和连接程序所识别的程序段范围。简单地说即定义过变量之后有些代码段可以使用这个变量而有些代码段不能使用该变量。根据具体使用情况,作用域又可划分为局部作用域和全局作用域。与之相对应的一组概念为局部变量和全局变量。局部变量具有局部作用域,而全局变量具有全局作用域。
(1)局部变量
在函数内部或者复合语句内部定义的变量称为局部变量。函数内部的变量只在本函数范围内有效,复合语句中定义的变量只能用于本复合语句。
注意:main函数内定义的变量也是局部变量,只能在main函数内部使用,并不因为是主函数中的变量就随处可用。函数定义中的形参也属于局部变量。
【例5.7】使用局部变量。
运行结果如图5-11所示。
图5-11
分析:
程序中共有3个函数,分别为main函数、fun1和fun2。main函数中共定义了3个变量t、p、q,均为局部变量。其中t的作用域从定义开始(即第6行)至main函数结束(即第13行)。第7至9行为一个复合语句,其中定义了p和q,因此其作用域范围为8、9行。在第9行之后main函数结束之前的这个范围内使用p或者q都是非法的。
函数fun1中有2个局部变量a和b,作用域范围从第16行至第18行。函数fun2中有3个局部变量包括形参x、函数内定义的m和n。
(2)全局变量
在函数外部定义的变量称为全局变量。它的作用域范围从其定义开始,位于其后的程序中的所有函数均可使用该全局变量。换句话说,从其定义开始直至本源程序文件结束都是有效的。
【例5.8】使用全局变量。
运行结果如图5-12所示。
图5-12
分析:
程序中分别在第2行和第20行定义了两个全局变量var和g。根据全局变量的特点,var的作用域范围从第2行开始一直到32行结束,因此后面的main函数、fun1函数、fun2以及fun3都可以访问这个变量。而g是在fun1函数之后定义的全局变量,故只有fun2和fun3可以访问,如果在main函数中访问g就是非法的。
5.5.2 变量的存储类别
变量的作用域规定了能够使用该变量的范围,或者说作用域是从空间上对变量进行衡量的。是否也能从时间尺度上对变量进行衡量呢?这就是本节介绍的内容———变量的存储类别。时间尺度指变量在存储区中保留的时间长短,这个时间尺度即变量的生存期也称生命期。例如,有的变量等到程序结束它的空间才被释放,而有的变量是函数调用一结束即消亡等等。
变量的生存期和变量的存储类别是密切相关的,C语言中主要有4种存储类别:auto、register、extern和static。现在有个问题,这个存储类别用在哪里呢?其实变量定义中是有存储类别的,前面的程序没有出现这几个关键字也是有原因的,学过本节后你就明白了。
结合上一节内容,局部变量只能用auto、static和register存储类修饰,全局变量只能用static和extern来修饰。
(1)auto存储类
auto是单词automatic的简称,auto变量称为自动变量,而且auto是默认的存储类别。在函数中经常定义变量,显然属于自动局部变量。换言之,auto只能用于局部变量。它的生存期如何衡量呢?从定义时开始创建,只要这个函数没有将控制权交回调用它的函数,这个函数中的自动局部变量都是具有生命的,即存储区中一直保留变量的值。一旦函数将控制权返还给它的调用函数,变量的存储区就释放了,或者说变量的值就不存在了。
例如:
为了让读者理解自动局部变量的生存期,再看一个完整的例题。
【例5.9】auto变量的使用。
运行结果如图5-13所示。
图5-13
分析:
主函数中用一个while循环调用fun函数5次。第一次调用开始,var变量创建开始具有生存期,调用结束后var的空间就释放了。第二次调用开始,var变量又“复活”了,调用结束后再次消亡。后面的几次调用都是这样,因此输出的结果都是3。
(2)register存储类
register的含义为寄存器,这个关键字使用不是很多。声明寄存器变量的方法为:
register double date;
register int count;
注意:register变量不允许用取地址运算符&来对变量取地址,这是因为寄存器没有如同内存一样的标准的存储区地址。
寄存器变量的生存期规则与auto无异,唯一的区别是变量存储的位置。我们知道,计算机的核心部件CPU中也包含存储区而且是高速存储区,称之为寄存器,只有声明为register存储类的变量才会存储到CPU的寄存器中。寄存器的资源是十分宝贵的,一般情况下,不必专门声明为register变量,除非某个变量被访问的频率非常高才会使用这个关键字。
这里还要避免一个认识误区,并不是把变量声明为register后就能提高程序效率,毕竟寄存器的个数有限,如果资源不够,编译系统仍会将该变量当做普通变量对待。另外,普通变量也有可能被编译系统识别出来当做register变量,原因就是现在的编译程序都做了优化。在这个问题上读者不必特别关心,或者说写程序时不需要使用这个关键字。
(3)extern存储类
上一节学习了全局变量,它的作用域范围从定义开始直至源程序文件结束。有时还需要配合extern关键字来扩展其作用域范围。
【例5.10】同一个文件内使用extern。
运行结果如图5-14所示。
图5-14
分析:
注意第5行,试想如果删除这一行代码,程序还正确吗?显然,a和b是在第9行处定义的,其后才能使用,但是第5行的extern关键字将其作用域扩展至第9行之前也能够使用。
再看一个多文件结构的程序,如图5-15所示。
图5-15 多文件结构
源程序文件1中有3个全局变量a、b、c,main函数、fun1函数都能够使用它们,源程序文件2中也有3个全局变量d、e和f,可以在fun2和fun3函数中使用。现在我们希望在源程序文件2中使用a,这时就需要extern帮忙了。只需要在源文件2的顶部加上extern int a;就将a的作用域扩展到了源文件2中,这样fun2和fun3都可以使用。如图5-16所示。
图5-16 使用extern
源文件2中的语句extern int a;扩展了a的作用域范围,当然fun3函数中的语句extern char b;也将b的作用域扩展至fun3函数中。但是源文件2中的d、e等变量对于main函数来说是不可见的。
(4)static存储类
static可以用于局部变量和全局变量,因此有静态局部变量和静态全局变量。先看静态局部变量,如果一个函数中声明了一个静态变量(静态局部变量),第一次调用该函数时变量创建并具有生存期,至调用结束这个变量并没有消亡,下次被调用时这个值仍然被保留,这不同于前面的auto变量。
【例5.11】静态局部变量的使用。
运行结果如图5-17所示。
图5-17
分析:
这个程序和例5.9的唯一区别即在第15行多了static关键字。从运行结果不难发现,每次调用结束后var的值都一直存在,下一次调用可以获得上次的值。如果在编程过程中需要达到这个效果,可以选用静态局部变量。
再来看一下静态全局变量。不管是静态局部变量还是静态全局变量,如果没有明确的初始化(以数值类变量为例),则系统会在编译时初始化为0。这里仍以图5-6为例进行说明,源文件1中有一个静态全局变量c,这里的static保证了它的作用域局限于源文件1而不能被扩展至源文件2,当然就不能够使用前面的extern关键字来扩展了,也就是static和extern不能够同时使用。静态全局变量为某个源文件提供了一定程度的私有性,防止其他文件来“破坏”它。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。