首页 » C语言解惑 » C语言解惑全文在线阅读

《C语言解惑》19.1 函数变量的作用域

关灯直达底部

变量作用域根据起作用的范围分为对一个函数、一个程序、一个文件及整个程序4个层次。要特别注意分辨各个层次的处理方法。

19.1.1 块结构之间的变量屏蔽规则

C语言规定,任何以花括号“{”和“}”括起来的复合语句都属于块结构,在块内可以对变量进行定义。块结构用在同一个函数内,遵循变量屏蔽原则。

1.块结构定义错误

【例19.1】找出下面程序中的错误,改正后分析它的输出结果。


#include <stdio.h>int max(int,int);int c=108;int main ( ){      {     int a=45,b=98,c=0;     c=max(45,98);     {        int c=0;        for(int i=0; i<11;i++)     c=c+i;        c *= c;        c=max(c,98);        printf (/"max=%dn/",c);           }           printf (/"max=%dn/",c);           c=max(a,-c);           printf (/"max=%dn/",c);       }      printf (/"max=%dn/",c);      return 0;  }  int max(int a, int b)  {       static int c;       if (a<b) c=b;       else c=a;       return c;  }  

首先排除max函数,这个函数的声明和定义均正确。它虽然使用变量c,但与主函数里定义的变量c以及全局变量c均没有关系,所以要在块内寻找错误。顺便说一句,这个函数设计得不好,它的目的只是想混淆视听,以便对c的定义产生错觉。

程序第1次调用max函数求得c=98。然后进入下一个块内。

C语言规定可以在多个块内定义同名的变量,而且遵循变量定义原则,即在执行语句之前定义。for语句中的循环体“()”内不算块结构,而且它是执行语句,所以不能在该语句的循环体内定义变量(C++语言可以,但C语言不行)。将这3行语句改写如下:


int c=0,i=1;for(; i<11;i++)  //等效for(i=1; i<11;i++)    c=c+i;  

这里定义的变量c屏蔽了上层定义的c,计算得出c=55。自乘之后为3025,调用max函数,最大值就是3025,第1个输出语句的输出为:


max=3025  

程序返回上一层块内,丢弃n内层的c值,输出原来的c值,即


max=98  

这时选c的负值作为参数,肯定最大值是正45,输出为


max=45  

再返回一层,只有全局变量起作用,所以输出为


max=108  

结论:在块内定义的变量其作用域仅限于块内。若块内定义与块外或外部定义具有相同的变量名,则它们是没有关系的。变量必须在程序开始时声明或者定义。推而广之,本块使用的变量必须在本块开始时定义。

2.正确理解块结构定义的变量之作用范围

【例19.2】一个源程序的清单如下:


#include <stdio.h>int max(int,int);int c;int main ( ){       {          int a=45,b=98;          static int sum;          sum=max(45,98);          a+=b;          {      int a={1,-2,3,-4,5},i=0;      static int sum;               for(i=0; i<5;i++)      if(a[i]<0) c=c+a[i];      else       sum=sum+a[i];      printf (/"正数和=%d,负数和=%dn/",sum,c);     }     c=max(a,sum);     printf (/"max=%dn/",c);     c=a+b+sum;     }     printf (/"总和=%dn/",c);     return 0;}int max(int a, int b){       if (a<b) return b;       else    return a;}  

有两人对这个程序进行分析,分别给出如下结果。

甲:这个程序是错误的,原因是变量c和sum没有初始化,计算的结果不定。

乙:这个程序是对的。变量c和sum均被初始化为0。第1个输出语句为:


正数和=9,负数和=-6  

因为a+b之和大于sum,所以第2个输出语句为:


max=143  

因为c=a+b+sum=143+98+9=250,所以最后一个输出为:


总和=250  

请分析他们哪位说的正确?

都不正确。乙对第1个输出语句判断正确。全局变量c和静态变量sum都被初始化为0值。他对第2个输出的判断结果是对的,那只是数据的偶然性。当离开该块时,在该块定义的静态变量sum的值与普通变量的一样,都自动消失。这时起作用的是上一块的同名变量,即这时的sum=98。98<143,所以输出结果与乙给出的一样。但在求c值时仍然用到sum,所以他的计算结果就错了,应该是c=143+98+98=339。正确的输出为:


总和=339  

变量屏蔽原则:编译程序为块内的自动型变量动态分配存储空间。具体地说,是将这些自动型变量使用的堆栈空间在进入块内时就给予分配,一旦退出该块,分配给它的空间就立即消失(即这些自动型变量消失),所以自动型变量既不能被块外的变量或函数所引用,也不能保存其值。当各块具有同名的自动型变量时,屏蔽其他块定义的同名变量,只有本块定义的自动变量起作用;当退出该块后仍为当前所在块的同名变量起作用。当自动型变量与某外部型变量具有相同的名字时,只有块中定义的自动型变量起作用;当退出该块后仍为外部变量起作用。

结论:复合块中,外层不能使用内层定义的变量,内层可以使用外层的非同名变量,各层使用自己的同名变量。非同名外部变量可供各层的程序使用。

19.1.2 程序和文件内的变量

如果程序很小,一个程序可能只有一个主函数。不过一般来说,一个源文件含有多个函数。为此,将它们都作为程序文件看待。

1.正确初始化变量

【例19.3】检查出下面程序中的错误并改正之。


#include <stdio.h>int fac (int);int main ( ){           int i;           for ( i=1; i<=4; i++)                 printf ( /"%d !=%dn/",i, fac(i) );           return 0;}int fac ( int n){           static int f= 1;           int i=1;           for ( i=1; i<=n; i++)                 f=f*i;           return(f);}  

函数错误地定义“static int f=1;”,静态变量只初始化一次,这就使它的初值总保持为上一个阶乘值,而不是1。当计算3!的时候,就得到3!=12的错误结果。计算结果错误为:


1!=12!=23!=124!=288  

这时调用阶乘函数,运行结果应该为:


1! = 12! = 23! = 64! = 24  

应将这条语句改为定义一个自动型变量“int f=1;”。

一定要注意变量的初始化规则。例如对如下的程序块:


{    int x;    static int y;    static int z=5;}  

块中的简单类型的局部变量x没有初始化,x有不确定的初始值,y被说明为静态的,所以为0,z初始化为5。

变量初始化规则:只能对外部和静态变量做一次初始化工作,从概念上看应在编译时进行。自动型和寄存器型变量,每进入函数或复合语句一次,就被初始化一次,而且初值不限于常数,可以包含以前已定义过的值,甚至包含函数调用的合法表达式。

如没有明显地进行初始化,则C编译程序对变量的初始化规则是:

(1)外部型和静态型变量初始值为0;

(2)自动型和寄存器型变量初始值为随机数。

2.同文件内的同名变量作用域

【例19.4】分析下面程序的输出结果。


#include <stdio.h>extern int a;int b=50;void func1(void);void func2(void);void main( ){      int i;      for ( i=1; i<4; i++)      {              ++a;              printf ( /"%dt/",a );              printf ( /"%dt/",b++ );              func1();              func2();      }}int a=10;void func1(){      ++a;      printf ( /"%dt/",a );}void func2(){      int a=100;      int b=15;      ++a;      printf (/"%dt%dn/",a,++b );}  

【分析】首先要分清变量的类型。变量a是外部变量,主函数main及函数func1都使用它,所以两个函数的运行都影响变量a的数值。变量b也是全局变量,主函数main使用printf函数调用它,函数func1不使用它。但函数func2里定义了与全局变量同名的变量a和b,所以它使用自己的变量,对全局变量没有影响。

分析的关键是主程序循环调用func1和func2函数3次。既然函数func2里也定义了一个本函数使用的同名变量a和b,所以它们只执行加1操作,输出总是101和16。

变量b是全局变量,但只有主函数main使用printf函数调用它,而且是“b++”运算,所以第1次输出是b的初始值50,随后两次循环输出51和52。

复杂一点的是变量a,它在主函数后面定义是一样的,不影响main函数使用它。主函数里对它执行加1操作,输出11。func2使用这个a值,做加1操作,输出12。后面就是重复执行两次,主函数输出13和15,func1函数输出14和16。

由此分析,可得到如下运行结果:


11      50      12      101     1613      51      14      101     1615      52      16      101     16  

由此可见,外部变量在整个程序中都可存取,它提供了在函数间进行数据通信的另一种方法。只要将用作函数间通信的参数说明为外部变量,而在函数定义的形式参数表中和调用函数的实参表中不需要给出,在函数中只要直接对这些外部变量进行操作即可。使用的屏蔽原则与块变量的相同。

结论:外部型变量可以被程序中的所有函数引用。外部型变量实质上具有“全局型”定义,它的作用域是整个程序。如果有同名变量,则只有内部变量起作用。

如果要在定义一个外部型变量之前使用它,就必须使用关键字extern进行声明。程序中的变量a,在没赋值时主程序就引用它,所以使用关键字extern进行声明。

3.同文件内不允许有同名函数

【例19.5】分析下面程序的输出结果。


#include <stdio.h>#include <stdlib.h>int p(int,int);int main( ){    int a=50,b=2;    printf(/"%dn/",p(a,b));    return 0;}int p(int a, int b){      return a/b;}  

可能有人会马上回答“输出25”。其实,因为stdlib.h中有个与p同名的函数,所以这个程序通不过编译。解决的方法有两种:因为这个程序用不到这个头文件,所以可以删除。另一种是为函数p改名。

19.1.3 多文件变量作用域

【例19.6】这个源程序包括两个文件。在VC中的工程项目如图19-1所示。

各个文件的内容如下:


//c19_6.c#include <stdio.h>int main( ){    int a=50,b=2;    extern char *str;    printf(/"%s%dn/",str,pe(a,b));    return 0;}//c19_61.cchar str=/"a/b=/";int pe(int a, int b){      return a/b;}  

图19-1 双文件示意图

程序编译出错,请分析程序存在的错误。

【解答】主程序所在文件没有声明引用c19_61.c文件中的函数pe。在多文件编程中,也不允许有同名函数,如果引用别的文件中的函数时,则需要声明被引用函数的原型。

文件中的各个函数不能使用其他文件函数中的变量(所以各个函数内的变量可以同名),如果使用另一个文件的全局变量,则必须声明。c19_6.c中虽然声明外部变量,但声明的格式不对。c19_61.c将str定义为字符数组,c19_6.c也应该使用


extern char str;  

方式,不能声明为字符指针。main函数在运行到这个位置时,应该读该地址的前4位。前4位是字符,不是地址,所以出错。

可能有人说,字符数组和字符指针不是可以互换吗?确实可以,但有特例,这就是少有的特例之一。

修改后的文件如下:


//c19_6.c#include <stdio.h>int pe(int,int);int main( ){    int a=50,b=2;    extern char str;    printf(/"%s%dn/",str,pe(a,b));    return 0;}//c19_61.cchar str=/"a/b=/";int pe(int a, int b){return a/b;}  

不过并不推荐这种文件组织方式,而是推荐将公共变量定义在头文件中,使用它的源文件用包含语句将其包含即可。注意使用双引号,不要使用尖括号。

注意:在多文件编程中,一定使用双引号包含自定义的头文件。

现在将字符串直接定义在main函数中,三个文件的内容组织如下:


//c19_6.h#include <stdio.h>int pe(int,int);extern char str;//c19_6.c#include /"c18_6.h/"int main( ){    int a=50,b=2;    char str=/"a/b=/";    printf(/"%s%dn/",str,pe(a,b));    return 0;}//c19_61.cint pe(int a, int b){return a/b;}  

图19-2是其示意图。

图19-2 包含头文件的示意图

运行结果如下:


a/b=25  

【例19.7】这个源程序包括5个文件,有4个c文件和1个头文件。编译对这个文件第1次扫描时,没有警告信息,但第2次扫描时出错。请找出错误并改正之。

这个程序使用独立的c文件编写求两个实数的最大值、最小值和平均值的函数,这些函数除了返回值之外,还要将自己被调用的次数加到总计数器上。

头文件声明它们的函数原型,主函数接收2个输入值,输出最大值、最小值、平均值、平均值的2倍和调用函数的总次数。具体要求源程序如下:


//主文件find.c//作用:调用各个函数。#include <stdio.h>#include /"find.h/"int count=0;     //初始化计数变量countvoid main( ){     double   a,b;     printf(/"Input a and b:n/");     scanf(/"%lf%lf/",&a,&b);     printf ( /"max =%lfn/",max( a,b ));     printf ( /"min=%lfn/",min( a,b ));     printf ( /"mean=%lfn/",mean( a,b ));     printf ( /"2*mean=%lfn/", K*mean( a,b ));     //使用常数K     printf ( /"count=%dn/",count);}  

为了观察count,两次调用mean函数。定义常数K为double型,便于计算。


//头文件find.h的作用:声明函数原型及外部变量double max(double,double);double min(double,double);double mean(double, double);extern const double K=2;     //声明全局常变量Kextern int count;     //声明全局计数变量count//文件max.c的内容:求最大值函数max#include /"find.h/"double max(double m1, double m2){     count=count+1;     if(m1 > m2) return m1;     else return m2;}//文件min.c的内容:求最小值函数min#include /"find.h/"double min(double m1, double m2){     count=count+1;     if(m1 > m2) return m2;     else return m1;}//文件mean.c的内容:求平均值函数mean#include /"find.h/"double mean(double m1, double m2){       count=count+1;       return (m2+m1)/K;     //使用常数K} 

【解答】常数K不能那样定义。如果使用


#define K 2  

定义一个常量K是可以的。但这个K没有数据类型的概念,所以不推荐使用这种方法。这里使用const时,应该参考普通变量的使用方法。不应该在头文件中定义,应该声明为外部常量,即


extern const double K;     //声明全局常变量K  

然后在find.c中定义如下:


const double K=2;     //定义常数K为double型  

图19-3的右边是主文件find.c的示意图,左边是文件结构图。

图19-3 修改后的find.c示意图


//修改后的头文件find.hdouble max(double,double);double min(double,double);double mean(double, double);extern const double K;     //声明全局常变量Kextern int count;     //声明全局计数变量count  

运行示范如下:


Input a and b:50 90max =90.000000min=50.000000mean=70.0000002*mean=140.000000count=4  

推荐:将函数原型全部声明在头文件中,公共变量也声明在头文件中,然后在需要的地方加以定义。当然,只能在一个地方定义,一定不能重复定义。

【例19.8】工程文件c19_8包含c19_8.c和c19_81.c两个文件,请找出错误并改正之。


//c19_8.c#include <stdio.h>int number=25;extern void display(void);int main( ){    display();    return 0;}//c19_81.c#include <stdio.h>int number=25;void display( ){    printf(/"%dn/",number);    return;}  

【解答】两个文件的函数里可以有同名变量,并且单独使用互不影响。但同名的全局变量只能在一个文件里声明,在另一个文件里定义,不能同时定义。将c19_81.c里的变量改为外部变量的声明即可,即


extern int number;  

其实最好的设计方法是使用头文件。下面是按此要求设计的源程序。


//c19_8.h#include <stdio.h>void display(void);extern int number;//c19_8.c#include /"c19_8.h/"int number;int main( ){    printf(/"输入:/");    scanf(/"%d/",&number);    display();    return 0;}//c19_81.c#include /"c19_8.h/"void display( ){   printf(/"%dn/",number);   return;}  

两个文件的公共变量number,在头文件里声明,在另一个文件里定义。这里有意改为在主函数里通过键盘赋值,所以在文件开始处还需要声明一次,否则通不过第2次扫描。而在文件c19_81.c中,则不需要再声明。