C语言变量的作用域


所谓的作用域就是指变量起作用的范围,也是变量的有效范围。
变量按他的作用域可以分为局部变量和全局变量。
局部变量
在一个函数内部声明的变量是内部变量,它只在本函数内有效,在本函数以外是不能使用的,这样的变量就是局部变量。此外,函数的形参也是局部变量,形参我们会在后面再详细解释。

比如上节课程序中定义的 unsigned long sec 这个变量,它是定义在 main 函数内部的,所以只能由 main 函数使用,中断函数就不能使用这个变量。同理,我们如果在中断函数内部定义的变量,在 main 函数中也是不能使用的。
全局变量
在函数外声明的变量就是全局变量。一个源程序文件可以包含一个或者多个函数,全局变量的作用范围是从它开始声明的位置一直到程序结束。

比如上节课程序中定义的 unsigned char LedBuff[6]这个数组,它的作用域就是从开始定义的位置一直到程序结束,不管是 main 函数,还是中断函数 InterruptTimer0,都可以直接使用这个数组。

局部变量只有在声明它的函数范围内可以使用,而全局变量可以被作用域内的所有的函数直接使用。所以在一个函数内既可以使用本函数内声明的局部变量,也可以使用全局变量。


从编程规范上讲,一个程序文件内所有的全局变量都应定义在文件的开头部分,在文件中所有函数之前。


由于 C 语言函数只有一个返回值,但是我们却经常会希望一个函数可以提供或影响多个结果值,这时我们就可以利用全局变量来实现。但是考虑到全局变量的一些特征,应该限制全局变量的使用,过多使用全局变量也会带来一些问题。

1) 全局变量可以被作用域内所有的函数直接引用,可以增加函数间数据联系的途径,但同时加强了函数模块之间的数据联系,使这些函数的独立性降低,对其中任何一个函数的修改都可能会影响到其它函数的执行结果,函数之间过于紧密的联系不利于程序的维护的。

2) 全局变量的应用会降低函数的通用性,函数在执行的时候过多依赖于全局变量,不利于函数的重复利用。目前我们编写的程序还都比较简单,就一个.c 文件,但以后我们要学到一个程序中有多个.c 文件,当一个函数被另外一个.c 文件调用的时候,必须将这个全局变量的变量值一起移植,而全局变量不只被一个函数调用,这样会引起一些不可预见的后果。

3) 过多使用全局变量会降低程序的清晰度,使程序的可读性下降。在各个函数执行的时候都可能改变全局变量值,往往难以清楚的判断出每个时刻各个全局变量的值。

4) 定义全局变量会永久占用单片机的内存单元,而局部变量只有进入定义局部变量的函数时才会占用内存单元,函数退出后会自动释放所占用的内存。所以大量的全局变量会额外增加内存消耗。


综上所述之原因,在编程规范上有一条原则,就是尽量减少全局变量的使用,能用局部变量代替的就不用全局变量。


还有一种特殊情况,大家在看别人程序的时候请注意,C 语言是允许局部变量和全局变量同名的,他们定义后在内存中占有不同的内存单元。如果在同一源文件中,全局变量和局部变量同名,在局部变量作用域范围内,只有局部变量有效,全局变量不起作用,也就是说局部变量具有更高优先级。但是从编程规范上讲,是要避免全局变量与局部变量重名的,从而避免不必要的误解和误操作。


C语言变量的存储类别


变量的存储类别分为自动、静态、寄存器和外部这四种。其中后两种我们暂不介绍,主要是自动变量和静态变量这两种。


函数中的局部变量,如果不加 static 这个关键字来修饰,都属于自动变量,也叫做动态存储变量。这种存储类别的变量,在调用该函数的时候系统会给他们分配存储空间,在函数调用结束后会自动释放这些存储空间。动态存储变量的关键字是 auto,但是这个关键字是可以省略的,所以我们平时都不用。

那么与动态变量对应的就是静态变量。首先,全局变量均是静态变量,此外,还有一种特殊的局部变量也是静态变量。即我们在定义局部变量时前边加上 static 这个关键字,加上这个关键字的变量就称之为静态局部变量,它的特点是,在整个生存期中只赋一次初值,在第一次执行该函数时,它的值就是给定的那个初值,而之后在该函数所有的执行次数中,它的值都是上一次函数执行结束后的值,即它可以保持前次的执行结果。

有这样一种情况,某个变量只在一个函数中使用,但是我们却想在函数多次调用期间保持住这个变量的值而不丢失,也就是说在该函数的本次调用中该变量值的改变要依赖与上一次调用函数时的值,而不能每次都从初值开始。如果我们使用局部动态变量的话,每次进入函数后上一次的值就丢失了,它每次都从初值开始,如果定义成全局变量的话,又违背了我们上面提到的尽量减少全局变量的使用这条原则,那么此时,局部静态变量就是最好的解决方案了。

比如第六章最后的例程中有一个控制数码管动态扫描显示用的索引变量 i,我们当时就是定义成了全局变量,现在我们就可以改成局部静态变量来试试。

  • #include <reg52.h>


  • sbit ADDR0 = P1^0;

  • sbit ADDR1 = P1^1;

  • sbit ADDR2 = P1^2;

  • sbit ADDR3 = P1^3;

  • sbit ENLED = P1^4;


  • unsigned char code LedChar[] = {  //数码管显示字符转换表

  •     0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,

  •     0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E

  • };

  • unsigned char LedBuff[6] = {  //数码管显示缓冲区,初值 0xFF 确保启动时都不亮

  •     0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF

  • };

  • unsigned int cnt = 0;//记录 T0 中断次数


  • void main(){

  •     unsigned long sec = 0; //记录经过的秒数



  •     EA = 1;  //使能总中断

  •     ENLED = 0;  //使能 U3,选择控制数码管

  •     ADDR3 = 1;  //因为需要动态改变 ADDR0-2 的值,所以不需要再初始化了

  •     TMOD = 0x01;  //设置 T0 为模式 1

  •     TH0 = 0xFC;  //为 T0 赋初值 0xFC67,定时 1ms

  •     TL0 = 0x67;

  •     ET0 = 1;  //使能 T0 中断

  •     TR0 = 1;  //启动 T0


  •     while (1){

  •         if (cnt >= 1000){  //判断 T0 溢出是否达到 1000 次

  •             cnt = 0;  //达到 1000 次后计数值清零

  •             sec++;  //秒计数自加 1


  •             //以下代码将 sec 按十进制位从低到高依次提取并转为数码管显示字符

  •             LedBuff[0] = LedChar[sec%10];

  •             LedBuff[1] = LedChar[sec/10%10];

  •             LedBuff[2] = LedChar[sec/100%10];

  •             LedBuff[3] = LedChar[sec/1000%10];

  •             LedBuff[4] = LedChar[sec/10000%10];

  •             LedBuff[5] = LedChar[sec/100000%10];

  •         }

  •     }

  • }

  • /* 定时器 0 中断服务函数 */

  • void InterruptTimer0() interrupt 1{

  •     static unsigned char i = 0; //动态扫描的索引,定义为局部静态变量

  •     TH0 = 0xFC; //重新加载初值

  •     TL0 = 0x67;

  •     cnt++;  //中断次数计数值加 1


  •     //以下代码完成数码管动态扫描刷新

  •     P0 = 0xFF;  //显示消隐

  •     switch (i){

  •         case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;

  •         case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;

  •         case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;

  •         case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;

  •         case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;

  •         case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;

  •         default: break;

  •     }

  • }




大家注意看程序中中断函数里的局部变量 i,我们为其加上了 static 关键字来修饰,就成为了静态局部变量。它的初始化 i = 0 操作只进行一次,程序执行代码中会进行 i++等操作,那么下次再进入中断函数的时候,i 会保持上次中断函数执行完毕后的值。如果去掉 static 这个关键字,那么每次进入中断函数后,i 都会被初始化成 0,大家可以自己修改程序看一下实际效果是否和理论相符。 0.png