再上一篇:5.2 函数的形参、实参、返回值及函数的原型说明
上一篇:5.3 函数的嵌套与递归调用
主页
下一篇:5.5 内联函数
再下一篇:5.6 具有缺省参数值和参数个数可变的函数
文章列表

5.4 作用域和存储类

《VC++程序设计基础》,讲述C++语言的语法和标准库,以及Visual C++ 函数库和MFC类库的使用,并附相关代码示例。

作用域(Scope)是指程序中所说明的标识符在哪一个区间内有效,即在哪一个区间内可以使用或引用该标识符。在C++中,作用域共分为五类:块作用域、文件作用域、函数原型作用域、函数作用域和类的作用域。类的作用域在介绍类与对象之后,再作说明,本节介绍前四种作用域。Scope.doc

存储类(Storage class)决定了何时为变量分配存储空间,及该存储空间所具有的特征。在变量说明时,指定变量的存储类。

5.4.1作用域

5.4.1.1 块作用域

我们把用花括号括起来的一部分程序称为一个块(block)。在块内说明的标识符,只能在该块内引用,即其作用域在该块内,开始于标识符的说明处,结束于块的结尾处(Local Scope)。例如:

void ex(float x,float y)

{

cout << "Input i,j:";

int i,j; //A

cin >> i >> j;

{

int a,b; //B

a=6;

j=a;

} //C

......

} //D

在一个函数内部定义的变量或在一个块中定义的变量称为局部(local)变量。如上例中的所有变量。换言之,块作用域的变量都是局部变量。在一个函数内定义的局部变量, 当退出函数时,局部变量也就不存在了;在块内定义的变量,在退出该块时,块作用域的局部变量也就不存在了。如上例中,变量i,j的作用域从行A开始到行D结束;变量a,b的作用域从行B开始到行C结束。

引入块作用域的目的,是为了解决标识符的同名问题。当标识符具有不同的作用域时,允许标识符同名,当标识符的作用域完全相同时,不允许标识符同名。同一块作用域内的标识符当然也不允许同名。

例如:

int ab(void)

{ //块A

int i,j;

....

{ // 块B

int i,j;

...

}

}

在块A内说明了变量i和j,而在块B内也说明了变量i和j,这种情况是允许的。在块B内使用变量i时,到底是使用块A内的i还是使用块B内的i呢?当然在程序中是不允许有二义性的。为此规定如下:

(1)具有块作用域的标识符在其作用域内,将屏蔽其作用块包含本块的同名标识符,即局部更优先。

根据以上规则,在块A和块B内均说明了变量i,在块B内将屏蔽块A内的变量i,使其不起作用。因此,在块B内,使用变量i时,当然是使用块B内定义的变量i,而不能使用块A内定义的变量i。一旦退出块B,块B内定义的变量i就不存在了。下面的程序说明了局部变量的同名问题。

#include

void main(void)

{

int i=100,j=200,k=300;

cout<

{

int i=500,j=600;

k=i+j;

cout<

}

cout<

}

执行以上程序后,输出为:

100 200 300

500 600

100 200 1100

(2)在for语句中说明的循环控制变量具有块作用域,其作用域为包含for语句的那个内层块,而不是仅作用于for语句。例如:

{

......

for( int i=0;i<10;i++) {

cout<

}

cout << "i=" << i; //输出i是允许的,输出值为10

}

这段程序等同于:

{

......

int i ;

for( i=0;i<10;i++) {

cout<

}

cout << "i=" << i;

}

即这种变量的说明不同于在循环体内说明的变量,如程序:

#include

void main( void )

{

for(int i=0;i<5;i++){ //A

int j=0;

cout<<++j<<'\t';

} //B

}

的输出为:

1 1 1 1 1

因变量j从行A开始,到行B结束,每一次循环开始时,为变量j分配存储空间,而到行B时,结束变量j的作用域,变量j就不存在了。所以尽管在循环体内每一次都对j加1,但输出的值都为1。

5.4.1.2 文件作用域

在函数外定义的变量(标识符)或用extern 说明的变量(标识符)称为全局变量(标识符)。全局变量的作用域称为文件作用域(File Scope),即在整个文件中都是可以访问的。其缺省的作用范围是:从定义全局变量的位置开始到该源程序文件结束,即符合标识符说明在前使用在后的原则。当全局变量出现引用在前,而说明在后时,要先对全局标识符作外部说明。其方法在后面介绍。当在块作用域内的变量与全局变量同名时,局部变量优先。但与块作用域不同的是,在块作用域内可通过作用域运算符“::”来引用与局部变量同名的全局变量。

例 5.7 在块作用域内引用文件作用域的同名变量。

#include

int i= 100;

void main(void)

{

int i,j=50;

i=18; //访问局部变量i

::i= ::i+4; //访问全部变量i

j= ::i+i; //访问全部变量i和局部变量j

cout<<"::i="<<::i<<'\n';

cout<<"i="<

cout<<"j="<

}

执行程序后的输出为:

::i=104

i=18

j=122

请读者自行分析以上结果。

5.4.1.3 函数原型作用域

在函数原型的参数表中说明的标识符所具有的作用域称为函数原型作用域(Prototype Scope),它从其说明处开始,到函数原型说明的结束处结束。正因为如此,所以在函数原型中说明的标识符可以与函数定义中说明的标识符不同。由于所说明的标识符与该函数的定义及调用无关,所以,可以在函数原型说明中只作参数的类型说明,而省略参量名。如:

float tt(int x,float y); //函数tt的原型说明

......

float tt(int a,float b) //函数tt的定义

{

......

}

由于可以省略函数原型说明中的参数名,因此,函数tt()的原型说明也可以写成:

float tt(int,float);

5.4.1.4 函数作用域

函数作用域(Function Scope)是指在函数内定义的标识符,在其定义的函数内均有效,即不论在函数内的某一地方定义,均可以引用这种标识符。在C++语言中,只有标号具有函数作用域,即在一个函数中定义的标号,在其整个函数内均可以引用。所以在同一个函数内不允许标号同名,而在不同的函数内,允许标号同名。正是由于标号具有函数作用域,所以不允许在一个函数内用goto语句转移到另一个函数内的某一个语句去执行。如下的函数定义是错误的:void f(float x)

{ float y;

label: cout<<"输入y的值:";

cin>>y;

{

label:y+=256; //A

if( y < 1000 ) goto label;

if( x > 2000) goto label3; //B

}

}

void f2(void )

{

label3: ......

}

void main(void ) { ......}

当编译到程序中的A行时,指出标号同名错误,同时指出B行的标号没有定义。

5.4.2存储类

存储类(Storage Class)是针对变量而言的,它规定了变量的生存期,即何时为变量分配内存空间及何时收回为变量分配的内存空间。变量的存储类反映了变量占用内存空间的期限。

一个C++源程序经编译和连接后,产生了可执行程序文件。为了执行这个程序,系统要为程序分配内存空间,并将程序装入所分配的内存空间内,才能执行这个程序。一个程序在内存中占用的存储空间可以分为三个部分:程序区、静态存储区和动态存储区。

程序区是用来存放可执行程序的程序代码的。

为变量分配静态存储区,还是分配动态存储区是由变量的存储类型所确定的,而变量的存储类型是由程序设计者根据程序设计的需要来指定的。5.4.2.1 变量的存储类型

根据为变量分配存储空间的时间,变量的存储类型可分为动态存储变量和静态存储变量。

在程序的执行过程中,为其分配存储空间的变量称为动态存储变量。当进入动态存储变量的作用域的开始处时,才为这种变量分配内存空间,而一旦执行到这种变量的作用域的结束处时,系统立即收回为这种变量分配的内存空间。这种变量的生命期仅在变量的作用域内。为程序分配的动态存储区是用来存放动态存储变量的。

在程序开始执行时,就为变量分配存储空间,直到程序执行结束时,才收回为变量分配的存储空间。换言之,在程序执行的整个过程中,这种变量一直占用为其分配的内存区,而不管是否处在这种变量的作用域内。这种变量称为静态存储变量。它们的生命期为整个程序的执行期间。为程序分配的静态存储区是用来存放静态存储变量的。

在C++语言中,变量的存储类型分为四种:自动 (auto) 类型、寄存器(register) 类型、静态(static) 类型和外部(extern) 类型。Storage.doc5.4.2.2 自动类型变量

在说明局部变量时,用关键字auto修饰的变量称为自动(automatic)类型变量。换言之,全局变量不可能是自动类型变量。由于C++编译器默认局部变量为自动类型变量,所以在实际应用中,在说明局部变量时,基本上不使用关键字auto来修饰变量。如下面函数中定义的变量x和y都是自动类型变量。void f( void )

{

int x;

auto int y;

......

}

自动类型变量属于动态存储变量。对于这种局部变量而言,在函数执行期间,当执行到变量作用域开始处时,动态地为变量分配存储空间,而当执行到结束变量的作用域处时,系统收回这种变量所占用的存储空间。

注意,对于自动类型变量,若没有明确地赋初值时,其初值是不确定的。如上面的变量x和y都没有确定的初值。

5.4.2.3 静态类型变量

用关键字static 修饰的变量称为静态(static)类型变量。静态类型变量属于静态存储变量。如:

static int y=5;

static char s;

void f(void)

{ static float x;

......

}

变量y、s和x都是静态类型变量。静态类型变量均有确定的初值,当说明变量时没有指定其初值时,则编译器将其初值置为0。因此,变量y的初值为5,而变量s和x的初值均为0。用static修饰的局部变量和全局变量具有不同的含义,我们分别讨论之。

当用static修饰局部变量时,则要求系统对这种变量采用静态存储分配方式。说明这种存储类型的变量通常有二种目的之一:第一种是,要保存函数运行的结果,以便下次调用函数时,能继续使用上次计算的结果;第二种是,不在这种变量的作用域内,通过函数返回静态变量的地址来使用这种变量的值,这种情况,我们在第八章指针中介绍。

对于静态类型的局部变量,由于在程序开始执行时,为这种变量分配存储空间,当调用函数而执行函数体后,系统并不收回这些变量所占用的存储空间,当再次执行函数时,变量仍使用相同的存储空间,因此,这些变量仍保留原来的值。

例5.8 使用静态类型的局部变量。

#include

int t( )

{

static int i =100;

i+=5;

return i;

}

void main(void)

{

cout << "i="<< t() << '\n';

cout << "i="<< t() << '\n';

}

执行这程序时,第一行输出的是105,而第二行输出的是110。

应当说明的是,静态类型变量的初始化仅在程序开始执行时处理一次,其后,当执行函数时,由于这种变量已经存在,系统就不再为其初始化了。另外,在程序的执行期间,不管是否处在这种变量的作用域内,变量始终占用着存储空间,但在变量的作用域外,是不能通过变量名来使用这种变量的。如上例,在函数main( )中,是不能使用变量i的,即在main( )内变量i是不可见的。

在程序中说明的全局变量,总是静态存储类型,其缺省的初值总为0。在说明全局变量时,加上修饰词 static,则表示所说明的变量仅限于这个源程序文件内使用(internal linkage 内连接)。当一个程序仅有一个文件组成时,在说明全局变量时,加与不加修饰词static并没有区别。但若有多个文件组成一个程序时,加与不加修饰词 static,其作用就不同了。我们用一个简单的例子来说明之。

例 5.9 限定全局变量的作用域。

设文件F1.CPP的内容为:

#include

static int i=200;

int j=400;

extern void f1(void);

void main(void)

{

cout<

f1( );

}

设文件F2.CPP的内容为:

#include

extern int i;

extern int j;

void f1(void)

{

i+=100;

cout<

j+=100;

cout<

}

这里文件F1.cpp和F2.cpp共同构成一个程序,在文件F1.cpp中定义了全局变量i和j,而在文件F2.cpp中希望使用全局变量i和j。编译时,产生错误,即文件F2.cpp中可以使用全局变量j,而不能使用全局变量i。

当一个程序由多个文件构成,而多个文件由多人分别编写时,难免出现在不同的文件中使用了同名但表示不同含义的全局变量。若将全局变量仅局限于一个文件中使用时,则加修饰词static限制后,就能很好地解决在程序的多文件组织中全局变量的重名问题。

5.4.2.4 寄存器类型变量

用关键字register修饰的局部变量称为寄存器类型变量。这类变量也采用动态存储的分配方式。修饰词register指示编译器不要为这类变量分配内存空间, 尽可能直接分配使用CPU中的寄存器, 以便提高对这类变量的存取速度。这种变量主要用于控制循环次数的临时变量等。其说明方式为:

register int i, j;

对寄存器类型变量的具体处理方式, 随不同的计算机系统而变化。有的机器把寄存器变量作为自动变量来处理,而有的机器限制了定义寄存器变量的个数等等。由于没有为寄存器类型变量分配内存空间,只能用于存放临时值,不能用来长期保存变量的值。显然,静态变量和全局变量不能定义为寄存器类型变量。

例5.10 利用寄存器类型变量求1到15的阶乘。

#include

void main(void)

{

register float fact=1;

register int i;

for(i=1 ; i<=15; i++){

fact*=i;

cout<

}

}

5.4.2.5 外部类型变量

在说明变量时,用关键字extern修饰的变量,称为外部(external)类型变量。外部类型变量一定是全局变量。在C++中,只有二种情况要使用外部型变量:

(1)在同一个源程序文件中定义的全局变量,属于定义性说明在后,而使用在前时,在使用前要说明为外部类型变量。如:

//file.cpp

void f( int i )

{

extern int x,y ; //A 说明x,y为外部类型变量

x+=i; y+=x; //B 使用全局变量x和y

}

int x=100 , y; //说明x,y为全局变量

void main(void)

{ f(10);

cout<<"x="<

f(20);

cout<<" x="<

}

在程序中若删除行A时,则在编译时,指出行B中用到的变量x和y没有进行变量说明。行A向编译器指明,在函数f()中用到的全局变量x和y在本文件的后面加以说明。

(2)当由多个文件组成一个完整的程序时,在一个源程序文件中定义的全局量要被其它若干个源程序文件引用时,引用的文件中要使用外部语句说明外部变量(external linkage外部连接)。如:

// c1.cpp

float x;

extern float f( );

void main(void)

{

cout<

}

//c2.cpp

extern float x; //B

......

在文件c1.cpp中定义了全局变量x,经B行说明后,在文件c2.cpp中可使用c1.cpp中定义的变量x。当希望在一个源文件中定义的全局变量不被其它源程序文件中引用时,只要在变量前加static 。这在前面已作了介绍。连接.doc作用域存储类连接.doc