- C++程序设计基础(上)
- 周霭如 林伟健编著
- 697字
- 2020-08-28 09:48:05
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可以看到,内层块可以说明与外层块同名的变量,函数可以通过非局部量返回运算结果。但将变量说明为全局量可能会发生意想不到的副作用。有时,不需要访问该变量的函数可能会意外地修改了它,产生难以查找的错误。除非有特殊的要求,否则,程序中一般不应该使用全局变量。当函数的数据传输只使用参数而不需要全局变量时,我们称这个函数是低耦合度的。低耦合度的程序模块便于调试、便于重用。