3.6 变量存储特性与标识符作用域

一个被说明的变量,除名字、类型和值的基本特性外,还有其他特性,包括存储、作用域、可见性和连接等特性。

标识符存储特性确定了标识符在内存中的生存时间和连接特性。

标识符作用域是指在程序正文中能够引用这个标识符的那部分区域。

如果一个标识符在作用域的某部分程序正文区域中能够被直接引用,则称标识符在这个区域中可见。

C++的一个应用程序称为一个项目。一个项目可以由多个文件组成。标识符的连接特性决定标识符能否被工程中的其他文件引用。

3.6.1 存储特性

C++有两类存储特性:自动存储和静态存储。

1.自动存储

自动存储用关键字auto和register说明。只有变量具有自动存储特性。这种变量在进入说明的块时生成,在结束块时删除。

例如: auto double x, y;

显式地说明变量x、y为自动变量。

函数的参数和局部变量都是自动存储的。C++把变量默认为自动存储,所以关键字auto很少用。

关键字register说明把变量存放在寄存器中。如今,C++的优化编译器能够识别经常使用的变量,决定是否存放在寄存器中,而不需要程序员进行register说明。

由此可见,自动存储是变量的默认状态。

2.静态存储

关键字extern和static说明静态存储变量和函数标识符。全局说明的标识符默认为extern。

如果这两个关键字用于说明变量,程序在开始执行时就分配和初始化存储空间;如果用于说明函数,表示从程序执行开始就存在这个函数名。

尽管标识符被说明为静态时,程序一开始执行就存在,但不等于它们在整个程序中可用。用static 说明的局部变量只能在定义该变量的函数体中使用。与自动变量不同的是,static 在第一次使用时进行初始化(默认初始化值为0)。函数退出时,系统保持其存储空间和数值。下次调用这个函数时,static变量还是上次退出函数时的值。

【例3-29】静态变量与自动变量的测试。

            #include<iostream>
            using namespace std;
            int func();
            int main()
            {  cout<<func()<<endl;
              cout << func() << endl;
            }
            int func()
            {  int a=0;             //自动变量a,再次调用时重新分配存储空间
              static int b=1;       //静态变量b,再次调用时保留原值
              a ++;
              b ++;
              cout << "auto a = " << a << endl;
              cout << "static b = " << b << endl;
              return a + b;
            }

程序运行后,输出结果为:

            auto a = 1
            static b = 2
            3
            auto a = 1
            static b = 3
            4

【例 3-30】测试密码输入。本程序用静态变量记录用户输入密码的次数,若连续 3 次输入错误,则显示错误信息,并结束程序。

            #include<iostream>
            using namespace std;
            int password(const int & key);
            int main()
            {  //调用函数,测试用户输入密码
              if(password(123456))  cout<<"Welcome!"<<endl;
              else  cout<<"Sorry,you are wrong!"<<endl;
            }
            int password(const int & key)
            {  static int n=0;              //静态变量
              int k;
              cout<< "Please input your password: ";
              cin>>k;                       //输入密码
              n++;                          //记录输入次数,即函数调用次数
              if(n<3)                       //输入次数合法
                  if(k==key)  return 1;     //密码正确
                  else  password(key);      //递归,重新输入
              else                          //连续输入3次错误
                  if(k!=key)    return 0;
            }

上述程序中,password函数用递归调用方式接收用户输入,用静态变量n记录输入的次数,即函数被调用的次数。函数使用常引用参数,相当于传值参数,可以适应实际参数的不同形式,例如,可以用变量、常量、表达式等形式的实际参数。在应用中,根据password函数返回值可以有不同的处理方式,这里的主函数仅以输出信息表示。

3.6.2 标识符的作用域与可见性

程序中常用的标识符有变量、常量、函数、类型等命名符。作用域是指一个已说明的标识符在程序正文中有效的那部分区域。若一个标识符在某部分程序正文能够被直接引用,则称这个标识符在这部分程序正文内可见。在一般情况下,一个标识符在作用域内可见,但在嵌套或层次结构程序模块中,如果定义了同名标识符,它们的可见性和作用域就不一定等价。

C++的标识符有5种作用域:函数原型、块、函数、类和文件作用域。类成员的作用域和可见性将在第6章和第8章中讨论。

1.函数原型作用域

只有函数原型形式参数表中使用的标识符才具有函数原型作用域。因为函数原型是一个独立的声明语句,形式参数名不需要使用,所以,函数原型不要求参数表中使用标识符名称,只要求类型。如果函数原型参数表中使用名称,将被编译器忽略。

例如,C++编译器认为以下函数原型是相同的:

            double funPrototype (double, double);
            double funPrototype (double a, double b);
            double funPrototype (double x, double y);

2.块作用域

块是指在函数定义中由一对花括号相括的一段程序单元。一个程序块内允许嵌套另外一个块。在块中说明的标识符具有块作用域,其作用域从说明点开始,直到结束块的右花括号处为止。

【例3-31】一个有错误的程序。

            #include<iostream>
            using namespace std;
            int main()
            {  double a;
              cin >> a;
              while(a > 0)
              {  double sum=0;       //sum的作用域从这里开始
                  sum += a;
                  cin >> a;
              }                      //sum的作用域从这里结束
              //cout<<sum<<endl;     //错误,sum无定义
            }

上述程序把变量sum放在while语句块内说明,循环结束后,sum的作用域也结束了。变量sum不能正确地累加和输出数据。正确的程序应该是:

            #include<iostream>
            using namespace std;
            int main()
            {  double a;             //a的作用域开始
              double sum=0;          //sum的作用域开始
              cin >> a;
              while(a > 0)
              {  sum+=a;
                  cin >> a;
              }
              cout << sum << endl;
            }                       //a和sum的作用域结束

如果嵌套的内层块与外层块有同名的变量,则内层块的内部变量将覆盖外层的同名变量。作用域不同的变量,系统分配不同的存储空间,它们的生存期也不相同。

【例3-32】同名变量的演示。

            #include<iostream>
            using namespace std;
            int main()
            {  int a=1;              //外层的a
              {  int a=1;            //内层的a
                  a ++;
                  cout << "inside a = " << a << endl;
              }                      //内层的a作用域结束
              cout << "outside a = " << a << endl;
            }                        //外层的a作用域结束

程序运行结果:

            inside a = 2
            outside a = 1

在例程的内层程序块中,外层定义的变量a被内层的a所覆盖,即不可见,也失去了作用,直到内层程序块结束,外层的a作用重新恢复,可以访问。

3.函数作用域

语句标号(后面带冒号的标识符)是唯一具有函数作用域的标识符。语句标号一般用于switch结构中的 case 标号,以及 goto 语句转向入口的语句标号。标号可以在函数体中任何地方使用,但不能在函数体外引用。

实际上,函数体是一个特殊的语句块。

4.文件作用域

任何在函数之外说明的标识符都具有文件作用域。这种标识符从说明处起至文件尾的任何函数都可见。

【例3-33】使用文件作用域变量的例程。

            #include<iostream>
            using namespace std;
            int a=1,b=1;                 //a,b的作用域从这里开始
            void f1(int x)               //f1函数可以访问a,b
            {  a=x*x;
              b = a * x;
              return;
            }
            int c;                       //c的作用域从这里开始,默认初始值为0
            void f2(int x,int y)         //f2函数可以访问a,b,c
            {  a=x>y?x:y;
              b = x < y ? x : y;
              c = x + y;
              return;
            }
            int main()
            {  f1(4);                   //main函数可以访问a,b,c
              cout << "call function f1 :\n";
              cout << "a = " << a << ", b = " << b << endl;
              f2 (10, 23);
              cout << "call function f2 :\n";
              cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
            }

程序运行结果:

call function f1 :a = 16, b = 64

call function f2 :

a = 23, b = 10, c = 33

5.全局变量和局部变量

具有文件作用域的变量称为全局变量,具有块作用域的变量称为局部变量。全局变量说明时默认初始值为 0。当局部量与全局量同名时,在块内,全局量被屏蔽。要在块内访问全局量,可以用作用域运算符“::”。

【例3-34】在函数体内访问全局变量。

            #include<iostream>
            using namespace std;
            int x;
            int main()
            {  int x=256;
              cout<<"global variable x="<<::x<<endl;      //输出全局变量x的值
              cout<<"local variable x="<<x<<endl;         //输出局部变量x的值
            }

程序运行结果:

            global variable x = 0
            local variable x = 256

在主函数模块内,局部变量x屏蔽了全局变量x,但不是覆盖,虽然全局变量x不可见(不能直接引用),但依然有作用,所以可以用作用域符指定访问。全局量的作用域不会因为同名局部量而被覆盖。而外层块与内层块有同名变量时,在内层块不能通过作用域符访问外层块的同名变量。这是块作用域变量与全局变量不同的地方。

从例3-32、例3-33和例3-34可以看到,内层块可以说明与外层块同名的变量,函数可以通过非局部量返回运算结果。但将变量说明为全局量可能会发生意想不到的副作用。有时,不需要访问该变量的函数可能会意外地修改了它,产生难以查找的错误。除非有特殊的要求,否则,程序中一般不应该使用全局变量。当函数的数据传输只使用参数而不需要全局变量时,我们称这个函数是低耦合度的。低耦合度的程序模块便于调试、便于重用。