ITEEDU

3.10 存储类

第1章到第3章用标识符作为变量名。变量属性包括名称、类型、长度和值。本章用标识符作为用户自定义的函数名。实际上,程序中的每个标识符还有其他属性,包括存锗类(storageclass)、作用域(scope)和连接(linkage)。

C++提供了4个存储类说明符(storage class specifier):auto、register、extern和static。标识符的存储类说明符可以确定其存储类、范围和连接。

标识符的存储类确定了标识符在内存中存在的时间。有些标识符的存在时间很短,有些则重复生成和删除,有些存在于整个程序的执行期间。

标识符的作用域是程序中能引用这个标识符的区域。有些标识符可以在整个程序中引用,而有些标识符只能在程序中的有限部分引用。

标识符的连接确定多源文件程序(第6章将会讨论)中,只有当前源文件或是在任何正确声明的源文件中识别标识符。

本节介绍4个存储类说明符和两个存储类。3.11节介绍标识符的作用域。

存储类说明符可以分为两个存储类:自动存储类(autmatic storage class)和静态存储类(static storage class)。关键字auto和regtster用来声明自动存储类变量。这种变量在进入声明的块时生成,在块活动期间存在,在退出这个块时删除。

只有变量能作为自动存储类。函数的局部变量和参数通常是自动存储类。存储类说明符auto显式声明变量为自动存储类。例如,下列声明表示float变量x和y是自动存储类的局部变量,即只在定义该变量的函数体中存在:

auto  float    x,y;

局部变量默认为自动存储类,因此关键字auto很少使用。本书余下部分将自动存储类变量简称为自动变量。

性能提示3.3

自动存储可以节省内存,因为自动存储类变量在进入声明的块时生成并在退出这个块时删除。

软件工程视点3.11

自动存储是最低权限原则的例子。变量不用时没有必要放在内存中。

机器语言版本中的数据通常装入寄存器(register)中进行计算和其他处理。

性能提示3.4

存储类说明符register,可以放在自动变量声明之前,让编译器在计算机的高速硬件寄存嚣中而不是内存中保存这个变量。如果能在硬件寄存器中保存计数器、总和等大量使用的变量.则可以消除从内存向寄存器装入变量和将结果返回内存中的重复开销。

常见编程错误3.19

一个标识符使用多个存储类说明符是个语法错误,一个标识符只能使用一个存储类说明符。例如,如果一个标识符设为register,就不能再设为auto。

编译器可以忽略register声明。例如,编译器可用的寄存器个数可能不足。下列声明建议将counter变量放在计算机的寄存器中,不管编译器是否这么做,counter都初始化为1:

register int countcr = 1;

register关键字只能用于局部变量和函数参数。

性能提示3. 5

register声明通常是不需要的。如今的优化编译器通常能识别经常使用的变量,并决定将其教在寄存器中而不需要程序员进行register声明。

关键字extern和static是用来声明静态存储类变量和函数的标识符。这种变量从程序开始执行时就存在。对于变量,程序开始执行时就分配和初始化存储空间;对于函数,从程序开始执行时就存在函数名。但是,尽管变量和函数名从程序开始执行时起就存在,但这并不是说这些标识符可以在整个程序中使用。3.11节将会介绍存储类和作用域是两个不同的概念。

静态存储类有两种标识符:外部标识符(如全局变量和函数名)与存储类说明符Static中声明的局部变量。全局变量和函数名默认为存储类说明符extern。全局变量生成时将变量声明放在任何函数定义之外.在整个程序执行期间保存该全局变量的值。全局变量和函数可以由文件中已声明或定义的任何函数引用。

软件工程视点1.12

将变量声明为全局变量而不是局部变量可能发生意料不到的副作用,不需要访问该变量的函数可能有意或意外修改这个变量,一般来说,除了有独特性能要求,否则应避免使用全局变量。

软件工程视点3.13

只在某个函数中使用的变量应声明为该函数中的局都变量,而不是声明为全局变量。

用关键字static声明的局部变量仍然只在定义该变量的函数中使用,但与自动变量不同的是,static局部变量在函数退出时保持其数值。下次调用这个函数时,static局部变量包含上次函数退出时的值。下列语句将局部变量count声明为static,并将其初始化为1。

static int count = 1

所有静态存储类的数字变量默认初始化为0,但也可以由程序员显式初始化(第5章介绍的静态指针变量也是初始化为0)。

存储类说明符extern和static在显式作用于外部标识符时具有特殊意义。第18章将介绍说明符extern和static作用于外部标识符和多源文件程序。

3.11 作用域规则

程序中一个标识符有意义的部分称为其作用域。例如,块中声明局部变量时,其只能在这个块或这个块嵌套的块中引用。一个标识符的4个作用域是函数范围(function scope)、文件范围(filescope)、块范围(block scope)和函数原型范围(function-prototype scope)。后面还要介绍第五个——类范围(class scope)。

任何函数之外声明的标识符取文件范围。这种标识符可以从声明处起到文件末尾的任何函数中访问。全局变量、任何函数之外声明的函数定义和函数原型都取文件范围。

标号(后面带冒号的标识符,如start:)是惟一具有函数范围的标识符。标号可以在所在函数中任何地方使用,但不能在函数体之外引用。标号用于switch结构中(如case标号)和goto语句中(见第18章)。标号是函数内部的实现细节,这种信息隐藏(infomation hiding)是良好软件工程的基本原则

之一。

块中声明的标识符的作用域为块范围。块范围从标识符声明开始,到右花括号(})处结束。函数开头声明的局部变量的作用域为块范围,函数参数也是,它们也是函数的局部变量。任何块都可以包含变量声明。块嵌套时,如果外层块中的标识符与内层块中的标识符同名,则外层块中的标识符“隐藏”,直到内层块终止。在内层块中执行时,内层块中的标识符值是本块中定义的,而不是同名的外层标识符值。声明为static的局部变量尽管在函数执行时就已经存在.但该变量的作用域仍为块范围。存储时间不影响标识符的作用域。

只有函数原型参数表中使用的标识符才具有函数原型范围。前面曾介绍过,函数原型不要求参数表中使用的标识符名称,只要求类型。如果函数原型参数表中使用名称,则编译器忽略这些名称。函数原型中使用的标识符可以在程序中的其他地方复用,不会产生歧义。

常见编程错误3.20

如果在内层块和外层块中使用同名标识符,而程序员又希望在内层块中引用外层块中的标识符,这通常会产生逻辑错误,因为实际上使用的还是内层块中标识符的值。

编程技巧3.10

避免隐藏外层块范围名称的变量名,因此要在程序中避免重复使用标识符。

图3.12的程序演示了全局变量、自动局部变量和static局部变量的作用域问题。

// Fig. 3.12:fig03 12.cpp
 // A scoping example
 #include < iostream.h>
 void a( void );  // function prototype
 void b( void );  // function prototype
 void c void );  // function prototype
 int x = 1;     // global variable
 int main( )
 {
   int x - 5;  // local variable to main
   cout << "local x in outer scope of main is "<< x << endl;
   {          // start new scope
     int x = 7;
     cout << "local x in inner scope of main is "<< x <<  endl;
   }            // end new scope
   cout << "local x in outer scope of main is" << x << endl;
   a( );       // a has automatic local x
   b( );       // b has static local x
   c( );       // c uses global x
   a( );       // a reinitializes automatic local x
   b( );       // static local x retains its previous value
   c( );       // global x also retains its value
   cout << "local x in main is "<< x << endl;
   return 0;
 }
 void a( void )
 {
   int x=25;        //  initiallzed each time a is called
   cout << endl << "local x in a is "<< x
       <<" after entering a" << endl;
   ++x;
   cout << "local x in a is "<< x
       << "before exiting a" << endl;
 }
 void b( void )
    static int x = 50;  // Static initialization only
                    // first time b is called.
    cout << endl << "local static x is "<< x
        << - on entering b" << endl;
    ++x;
    cout << "local static x is" << x
        << "on exiting b" << endl;
 }
 void c( void )
   cout << endl << "global x is "<< x
       << "on entering c" << endl;
   x *= 10;
   cout << "global x is "<< x << "on exiting c" << endl;
 }

输出结果:

local x in outer scope Of main is 5

local x in inner scope Of main iS 7

local x in outer scope Of main is 5

local x in a is 25 after entering a

local x in a ls 26 before exiting a

local static x is 50 On entering b

local static x is 51 On exiting b

global x is 1 on entering c

global x is 10 on exiting c

local x in a is 25 after entering a

local x in a is 26 before exiting a
local static x is 51 on entering b

local statzc x is 52 On exiting b

global x is lO On entering c

global x is 100 On exiting c

local x in main is 5
图3.12说明变量作用域的例子

全局变量x声明并初始化为1。这个全局变量在任何声明x变量的块和函数中隐藏。在main函数中,局部变量x声明并初始化为5。打印这个变量,结果表示全局变量x在main函数中隐藏。然后在main函数中定义一个新块,将另一个局部变量x声明并初始化为7,打印这个变量,结果表示其隐藏main函数外层块中的x。数值为7的变量x在退出这个块时自动删除,并打印main函数外层块中的局部变量x,表示其不再隐藏。

程序定义三个函数,都设有参数和返回值。函数a定义自动变量x并将其初始化为25。调用a时,打印该变量,递增其值,并在退出函数之前再次打印该值。每次调用该函数时,自动变量x重新初值化为25。函数b声明static变量x并将其初始化为10。声明为static的局部变量在离开作用域时仍然保持其数值。调用b时,打印x,递增其值,并在退出函数之前再次打印该值。下次调用这个函数时,static局部变量x包含数值51。

函数c不声明任何变量,因此,函数c引用变量x时,使用全局变量x。调用函数c时,打印全局变量,将其乘以10,并在退出函数之前再次打印该值。下次调用函数c时,全局变量已变为10。最后,程序再次打印main函数中的局部变量x,结果表示所有函数调用都没有修改x的值,因为函数引用的都是其他范围中的变量。