第5章程序结构本章要点:●作用域、可见性和生存期●局部变量和全局变量●静态成员●友元●常类型●多文件结构●编译预处理5.1作用域与可见性作用域讨论的是标识符的有效范围,可见性是标识符是否可以引用的问题。我们知道在某个函数中声明的变量就只能在这个函数中起作用,这就是受变量的作用域与可见性的限制。作用域与可见性二者既相互联系,又存在着相当的差异。5.1.1作用域作用域是一个标识符在程序正文中有效的区域。C++的作用域有函数原型作用域、块作用域(局部作用域)和文件作用域。1.函数原型作用域函数原型作用域是C++程序中最小的作用域。第3章中介绍过,在函数原型的说明中一定要包含形参的类型说明。在函数原型声明时的形式参数的作用范围就是函数原型作用域。例如,有如下函数声明:doubleArea(doubleradius)其中标识符radius的作用(或称有效)范围就在函数Area的左({)、右括号(})之间,在程序的其它地方无法引用这个标识符,我们称标识符radius的作用域是函数原型作用域。对于这种情况,标识符radius实际上是可有可无的,省去它,也决不会影响到程序的编译和运行结果,但是,考虑程序的可读性,还是要在函数原型声明时给形参声明一个容易理解、记忆的名称。2.块作用域为了理解块作用域,先来看一个例子:voidfun(inta){intb(a);cinb;if(b0){b的作用域intc;c的作用域∶}}这段程序中,在函数fun()内又声明了整型数b,并把参数a的值赋给它作为初值。接下来,在if语句的分支内,又声明了整型变量c。这里的b和c都具有块作用域,是不同的块作用域。块是一对大括号括起来的一段程序,在这个例子中,函数体是一个块,if语句之后的分支体又是一个较小的块,二者是包含关系。在块中声明的标识符,其作用域从声明处开始,一直到块结束的大括号为止。因此,变量b的作用域从声明处开始,到它所在的块(即整个函数体)结束处为止,变量c的作用域从声明处开始到它所在的块,即分支体结束为止。3.文件作用域不在前述各个作用域中出现的声明就具有文件作用域,这样声明的标识符的作用域开始于声明点结束于文件尾。一般情况下,类似例5-1中所声明的全局变量就具有文件作用域,它们在整个文件中都有效。例5-1作用域实例。#includeiostream.hinti;voidmain(){i=5;//给具有文件作用域的赋初值{//子块1inti=7;//定义局部变量,具有块作用域couti=iendl;//输出7}couti=iendl;//输出5}在这个例子中,在主函数之前声明的变量i具有文件作用域,它的有效作用范围是整个源代码文件。在主函数开始给这个具有文件作用域的变量赋初值5,接下来在子块1中又声明同名变量并赋初值7。第一次输出的结果是7,这是因为具有块作用域的变量把具有文件作用的变量屏蔽掉了,也就是具有文件作用域的变量变得不可见(这是下面要讨论的可见性问题)。当程序运行到子块1结束后,进行第二次输出时,输出的就是具有文件作用域的变量的值5。5.1.2可见性现在,让我们从另一个角度——标识符引用,来看变量的有效范围,即标识符的可见性。程序运行到某一点,能够引用到的标识符,就是该处可见的标识符。文件作用域最大,接下来是类作用域和块作用域。图5-1描述了作用域的一般关系。可见性表示从内层作用域向外层作用域“看”时能看到什么。因此,可见性和作用域有着密切的关系。文件作用域类作用域块作用域图5-1作用域关系图作用域可见性的一般规则:(1)标识符要声明在前,引用在后。(2)在同一作用域中,不能声明通明的标识符。(3)在没有互相包含的不同的作用域中声明同名的标识符互不影响。(4)如果在两个或多个具有包含关系的作用域中声明了同名标识符,则外层标识符在内层不可见。例5-1中的标识符i就是文件作用域与块作用域相互包含的实例。可见性在分析两个同名标识符作用域相互包含的特殊情况时,非常有用。在本节中,举例只涉及到简单数据类型的变量,但上述关于作用域与可见性的规律同样也适用于自定义数据类型和类的对象。5.2生存期无论是简单变量还是类的对象都有诞生和结束的时刻。变量和对象从诞生到结束的这段时间就是它的生存期。在生存期内,对象将保持它的状态(即数据成员的值),变量也将保持它的值不变,直到它们被更新为止。本节,我们使用对象来统一表示类的对象和一般的变量。对象的生存期可以分为静态生存期和动态生存期两种。5.2.1静态生存期如果对象的生存期与程序的运行期相同,我们称它具有静态生存期。在文件作用域中声明的对象都是具有静态生存期的。如果要在函数内部的块作用域中声明具有静态生存期的对象,则要使用关键字static,例如下列语句声明的变量i,便是具有静态生存期的变量也称为静态变量:staticinti;我们在本章5.4节将专门讨论类的静态成员。5.2.2局部生存期在块作用域中声明的变量具有局部生存期。此生存期诞生于声明点,而终止于其作用域结束处。因此,具有局部生存期的变量都具有块作用域。但反之则不然,一般具有块作用域的变量都具有局部生存期,但当在块作用域内将变量说明为静态变量时,该变量则具有静态生存期。例如:voidmain(){staticintk;//…}5.2.3动态生存期除了上述两种情况,其余的对象都具有动态生存期。在块作用域中声明的具有动态生存期的对象,习惯上也被称为局部生存期对象。动态生存期对象诞生于声明点,结束于该标识符作用域结束处。5.3局部变量和全局变量函数之间的数据共享经常通过局部变量和全局变量来实现,下面我们分别来介绍这两种变量。5.3.1局部变量局部变量具有局部作用域。因此,在不同函数体内的局部变量是互相不可见的,这就很好地实现了函数之间的数据隐藏。这也是结构化程序设计中实现数据隐藏的唯一办法。局部变量包括自动(auto)变量、内部静态(static)变量和函数参数。自动变量是在函数体或分程序内声明的变量,具有块作用域。声明时,变量前可以加auto,也可以不加,程序中没有特别说明的变量都是自动变量.自动变量以堆栈方式占用内存空间.因此,当程序运行到此类变量声明处时,会立刻为它分配内存空间,而一旦其生存期结束,系统立即收回这个堆栈,此变量也就立即消失.内部静态变量在前面已介绍过,它具有文件作用域和静态生存期,系统在固定的内存区-------数据区为它分配空间.函数参数实质上就是自动变量.局部变量能够在调用和被调用函数之间通过参数进行数据传递.如果把数据存储在局部变量中,函数在不同的块之间只能通过参数传递来共享数据。5.3.2全局变量全局变量具有文件作用域.在整个程序中,除了在定义有同名局部变量的块中之外,其他地方都可以直接访问全局变量.将数据存放在全局变量中,不同的函数在不同的地方对同一个全局变量进行访问,实现了这些函数之间的数据共享.例5-2局部变量和全局变量例题。#includeiostream.hinti=1;voidmain(){staticinta;intb=-10;intc=0;voidother(void);cout----main----\n;couti=ia=ab=bc=cendl;c=c+8;other();cout----main----\n;couti=ia=ab=bc=cendl;other();}voidother(void){staticinta=1;staticintb;//a,b为静态局部变量,具有全局寿命,局部可见,只在第一次进入函数时被初始化intc=5;//c为局部变量,具有动态生存期,每次进入函数时被初始化a=a+3;i=i+2;c=c+5;cout----other----\n;couti=ia=ab=bc=cendl;b=a;}运行结果:----main----i=1a=0b=-10c=0----other----i=3a=4b=0c=10----main----i=3a=0b=-10c=8----other----i=5a=7b=4c=10例5-3局部对象和全局对象例题。#includeiostream.hclassClock//时钟类声明{public://外部接口Clock();voidSetTime(intNewH,intNewM,intNewS);//三个形参均具有函数原型作用域voidShowTime();~Clock(){}private://私有数据成员intHour,Minute,Second;};Clock::Clock()//构造函数{Hour=0;Minute=0;Second=0;}voidClock::SetTime(intNewH,intNewM,intNewS){Hour=NewH;Minute=NewM;Second=NewS;}voidClock::ShowTime(){coutHour:Minute:Secondendl;}ClockglobClock;//声明对象globClockvoidmain()//具有静态生存期,文件作用域{coutFirsttimeoutput:endl;globClock.ShowTime();//对象的成员函数具有类作用域globClock.SetTime(10,30,45);Clockmyclock(globClock);//声明具有块作用域的对象myclockcoutSecondtimeoutput:endl;myclock.ShowTime();//引用具有块作用域的对象myclock}运行结果:Firsttimeoutput:0:0:0Secondtimeoutput:10:30:45在这个程序中,包含了具有各种作用域类型的变量和对象,其中时钟类声明中函数成员SetTime的三个形参具有函数原型作用域,对象myclock等具有块作用域,时钟类的数据、函数成员具有类作用域,对象globClock具有文件作用域。在主函数中,这些变量、对象及其公有成员都是可见的。就生存期而言,除了具有文件作用域的对象globClock具有静态生存期,与程序的运行期相同之外,其余都具有动态生存期。观察程序的运行过程可以看到,在主函数之外声明的对象globClock具有静态生存期、文件作用域,通过构造函数被初始化。在主程序中首先输出时间,这时为0:0:0;接着将对象globClock的时间设置为10:30:45;然后声明另一个具有块作用域的对象myClock,并用globClock来对它进行初始化,这时调用默认的拷贝构造函数将globClock的数据成员值一一赋给myclock,因此对象myclock和globClock具有完全相同的数据,输出myclock的时间也是10:30:45。5.4静态成员静态成员是解决同一个类的不同对象之间的数据和函数共享问题的。例如,我们可以抽象出某公司全体雇员的共性,设计如下的雇员类:classemployee{private:intEmpNo;intID;char*name;//以字符指针指向字符串首地址,第6章详细介绍…//其他数据成员与函数成员略}如果需要统计雇员总数,这个数据存放在什么地方呢?若以类外的变量来存储总数,不能实现数据的隐藏。若在类中增加一个数据成员用以存放总数,必然在每一个对象中都存储一副本,不仅冗余,而且每个对象分别维护一个“总数”,势必造成数据的不一致性。因此,比较理想的方案是类的所有对象共同拥有一个用于存放总数的数据成员,这就是下面要介绍的静态数据成员。5.4.1静态数据成员我们说“一个类的所有对象具有相同的属性”,是指属性的个数、名称、数据类型相同,各个对象的属性值则可以各不相同,并且随着程序的执行而变化,这样的属性在面向对象方法中称为“实例属性”。面向对象方法中还有“类属性”的概念,类属性是描述类