《C++程序设计》课件第8章_第1页
《C++程序设计》课件第8章_第2页
《C++程序设计》课件第8章_第3页
《C++程序设计》课件第8章_第4页
《C++程序设计》课件第8章_第5页
已阅读5页,还剩142页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第8章作用域与名字空间8.1作用域与名字空间概述

8.2static关键字8.3预处理命令本章小结习题

创建名字是编程的一个最为基本的行为,当项目逐步变大,名字的数目增多时,就可能会导致名字冲突。

C++大大加强了对名字的管理,使你可以对名字的创建、名字的可见性、名字在内存中对应的位置以及名字的链接属性进行控制。本章展示了在C++中如何使用两种技术来对名字进行控制。首先,在全局范围内对名字进行控制的一个更有用的技术是C++的名字空间(namespace)特性,它使得你可以将全局的名字空间分成不同的区域。其次可用关键字static来控制名字的可见性和链接属性,同时也讲述了这个关键字在类内部的特殊含义。8.1作用域与名字空间概述8.1.1作用域和生命周期

1.作用域作用域在许多程序设计语言中非常重要。通常来说,一段程序代码中所用到的名字并不总是有效可用的,而限定这个名字的可用性的代码范围就是这个名字的作用域。作用域能够提高程序逻辑的局部性,增强程序的可靠性,减少名字冲突。

C++的作用域范围分为:局部作用域、文件作用域、全局作用域、函数作用域、函数原型作用域和类作用域。1)局部作用域局部作用域是指某标识符的定义出现在一对花括号所括起来的一段程序内,该标识符的作用域范围从声明点开始,直到该花括号结束为止。局部作用域也称块作用域。下面通过程序说明。

【程序8.1】#include<iostream>usingnamespacestd;voidprint(intd) //d的作用域开始

{ inta=10; //a的作用域开始if(a>1&&d>0) { intb=2*a; //b的作用域开始

cout<<"b="<<b<<endl; //输出20 }//b的作用域结束

//cout<<"b="<<b<<endl; intc=5; //c的作用域开始

if(c>a){ cout<<"c="<<c<<endl; }cout<<"a="<<a<<endl; //输出10 cout<<"c="<<c<<endl; //输出5 cout<<"d="<<d<<endl; //输出6 }//a,c,d的作用域结束

intmain(){ print(6); return0;}

上例中如果把“//cout<<"b="<<b<<endl;”前面的注释符号//去掉,编译将出错:

errorC2065:'b':undeclaredidentifier

因为b的作用域仅限于if(a>1){/*…*/}中,在该块之外,变量b是不可见的。如果在switch语句和if语句中进行条件测试的表达式中声明标识符,那么该标识符的作用域将局限于该语句内。程序8.2和程序8.3分别展示了在if语句和switch语句中声明的标识符的作用域。【程序8.2】#include<iostream>usingnamespacestd;voidprint(){ inti=0; cin>>i; if(i==1) //i的作用域开始

cout<<"Thevalueofiis1\n"; elsecout<<"Thevalueofiisnot1\n"; //i的作用域结束

cout<<"i="<<i<<endl; //出错,errorC2065:'i':undeclaredidentifier}intmain(){ print(); return0;}【程序8.3】#include<iostream>usingnamespacestd;intgetValue(intnumber){ return3*number;}voidcheckValue(){ inttotal=0; switch(total=getValue(3)) //total的作用域开始

{ case9: cout<<"total="<<total<<".OK!\n"; } //total的作用域结束

cout<<"total="<<total; //出错:errorC2065:'total':undeclaredidentifier}intmain(){ checkValue(); return0;}

与switch语句和if语句不同,在for语句中声明的标识符,其作用域并不局限于该语句内。

【程序8.4】#include<iostream>usingnamespacestd;voidf(){ for(inti=1;i<4;i++) // i的作用域开始

{ cout<<"i="<<i<<endl; } cout<<"outsidetheforloop:i="<<i<<endl;}//i的作用域结束intmain(){ f(); return0;}

运行结果如下:2)文件作用域若某变量定义语句是在一个程序文件中的所有函数定义之外说明的,且该语句前带有static保留字时,则称该语句所定义变量都具有文件作用域,即变量在其定义处之后的整个程序文件中有效,但是在其他文件中是无效不可见的,也就是说具有文件作用域的标识符不能使用extern在其他文件中声明它们。具有文件作用域的变量没有初始化时,在编译时将自动初始化为各种类型的“0”(如果是指针,应该初始化为NULL;字符串应该是“”(空串),字符应该是“\0”)。【程序8.5】#include<iostream>usingnamespacestd;//具有文件作用域的变量

staticfloatprice=1.5;//具有文件作用域的函数的原型

staticfloatcomputeMoney(int);intmain(){ //调用具有文件作用域的函数

floattotal=computeMoney(5);cout<<"Totalmoneyis"<<total<<endl; //输出Totalmoneyis7.5 //输出具有文件作用域的变量的值

cout<<"priceis"<<price<<endl; //输出priceis1.5 return0;}staticfloatcomputeMoney(intnumber){ //引用具有文件作用域的变量

returnnumber*price;}3)全局作用域若某变量在一个程序文件中的所有函数文件之外(通常在所有函数定义之前)定义,则该变量具有全局作用域,即变量在其定义处之后的程序文件中是有效可访问的。如果要在某程序文件中使用不在本程序文件中定义的全局变量,则必须在本文件开始进行声明,格式如下:extern<类型名><变量名>,<变量名>,…;

注意:该声明格式类似于变量声明,但是不能对变量进行初始化,且必须在整个语句前加上extern保留字。若全局变量没有初始化,编译时将自动赋值为各种类型的“0”(如果是指针,应该是NULL,字符串应该是“”,字符应该是“\0”)。【程序8.6】#include<iostream>usingnamespacestd;

intxPosition; //全局变量

intyPosition=0; //全局变量

voidincreaseNumber(int,int); //函数原型

intmain(){ cout<<"Beforeincreasing,positionis("<<xPosition<<","<<yPosition<<")"<<endl; increaseNumber(-3,8);cout<<"Afterincreasing,positionis("<<xPosition<<","<<yPosition<<")"<<endl; return0;}voidincreaseNumber(intxOffset,intyOffset){ xPosition+=xOffset; yPosition+=yOffset;}

运行结果如下:

注意作用域的屏蔽效应。C++支持不同作用域中使用同名标识符。当存在两个或多个声明具有包含关系的作用域时,在外层声明了标识符后,如果内层中没有声明与之同名的标识符,则外层标识符在内层可见。但如果在同一函数内使用同名标识符,具有内层作用域的标识符会覆盖外层作用域的标识符,直到离开内层作用域为止。【程序8.7】#include<iostream>usingnamespacestd;intvar=10; //全局变量

intmain(){ cout<<var<<endl; if(var>5) { intvar=20; //这个var屏蔽了全局var变量

cout<<var<<endl; if(var>15){ intvar=30; //这个var屏蔽了if语句外所定义的局部var cout<<var<<endl; } cout<<var<<endl; } cout<<var<<endl; return0;}

运行结果如下:【程序8.8】#include<iostream>usingnamespacestd;inta=0; //全局变量,并且初始化为0voidfunc(){ inta; a=100; cout<<a<<endl; //输出a的值

}intmain(intargc,char*argv[]){ func(); cout<<a<<endl; //输出a的值

return0;}

运行结果如下:4)函数作用域标号是唯一具有函数作用域的标识符,它使得该标识符在一个函数内的任何位置均可被使用。

【程序8.9】#include<iostream>#include<string>usingnamespacestd;voidcategoryThings(){ stringname;Begin: cout<<"Selectfromapple,orange,banana,tomato,eggplant,corn:";cin>>name; if(name=="apple"||name=="orange"||name=="banana") gotoFruit; elseif(name=="tomato"||name=="eggplant"||name=="corn") gotoVegetable; else { cout<<"selectwrongly,tryagain!"<<endl; gotoBegin; }Fruit: cout<<"Belongtofruit\n"<<endl; gotoEnd;Vegetable: cout<<"Belongtovegetable\n"<<endl; gotoEnd;End: cout<<"---------Theend---------"<<endl;}intmain(){ categoryThings(); return0;}

注意,使用标号容易出错。goto语句或switch语句可能会越过某些变量的定义语句,从而使变量不能被初始化。 【程序8.10】#include<iostream>usingnamespacestd;voidfun(){ inta=10; if(a>0)gotoEnd; intb=20;End: cout<<"------------------------------------"<<endl;}intmain(){ fun(); return0;}

编译时出错:

test.cpp:6:error:jumptolabel'End'test.cpp:4:error:fromheretest.cpp:5:error:crossesinitializationof'intb'test.cpp:5:warning:unusedvariable'intb'errorC2362:initializationof'b'isskippedby'gotoEnd'goto语句直接跳到函数最后部分,使得变量b未能初始化5)函数原型作用域函数原型范围指定了函数原型的开始与结束之间的区域,函数原型中出现的标识符仅仅在函数原型内有意义,即函数原型的作用域开始于函数原型声明的左括号,结束于函数原型声明的右括号。例如:

intinformation(intage,char*name);上例中,标识符age和name是可有可无的,即等价于“intinformation(int,char*);”,但是标识符可以增强可读性。上例中带标识符的函数原型使人一看就明白第一个参数是年龄值,第二个参数是姓名。2.生命周期 生命期也叫生存期。变量之间为什么会有不同的作用域?这和生命周期有关。生命周期和作用域之间的关系是:如果一个变量没有了生命周期,那么它也就没有了作用域;但是另一方面,如果一个变量超出了它的作用域,它的生命周期未必就结束了,例如局部静态变量。程序运行时,需要操作系统为它分配内存。存放程序代码的内存空间称为代码区。存放数据的内存区域有三种:数据区、栈区和堆区。为什么要把存放数据的内存空间分成三个部分呢?这是因为程序中要用到的数据具有不同的生存周期要求,为方便实现要求,编译器把它们分别放到不同空间。

对于数据区的数据,它们的生命周期和程序一样长久。只要程序一开始运行,这种生命期的变量就存在,当程序结束时,其生命期就结束。这些数据如何产生和释放,都由程序自动完成,程序员不必关心。正因为程序自动为数据区的数据分配内存,所以这些数据所需要的内存必须已知。数据未被初始化时,将默认为0。堆区的数据由程序员决定它们何时分配内存,何时释放内存。此外,堆里的数据可以是明确已知的,也可以是不确定的。数据的初始化需由程序员来完成,否则未初始化的数据值是不确定的。由于程序员负责内存的分配与释放,因此容易产生错误。比如使用还未分配内存的变量。最常见的是“内存泄漏”:一个已经分配内存的数据,当程序中不再需要它时,却始终没有为它释放内存。这样会白白消耗系统资源,降低内存的使用率。对于栈区的数据,它们如何产生和释放都由程序自动完成,程序员不必关心。同时这些数据所需要的内存必须已知,否则程序就不能自动为它们分配内存了。数据若没有初始化,那么值是不定的。生命期与存储区域密切相关,存储区域主要有代码区、数据区、栈区和堆区,对应的生命期为静态生命期、局部生命期和动态生命期。1)动态生命期放在堆区的数据具有动态生命期。该生命期由程序中特定的函数调用(C语言中的malloc()和free()或操作符(C++语言中的new和delete))来创建和释放。当用函数malloc()或new为变量分配空间时,生命期开始;当用free()或delete释放该变量的空间或程序结束时,生命期结束。

【程序8.11】#include<iostream>usingnamespacestd;classTree{ floatheight;public: Tree(intht); floatgetHeight();};inlineTree::Tree(intht=0):height(ht){};inlinefloatTree::getHeight(){ returnheight;}intmain(){//new为变量t1分配空间

Tree*t1=newTree(50); floatht=t1->getHeight(); cout<<ht<<endl; //delete释放t1的空间,生命期结束

deletet1; return0;}

运行结束输出树的高度如下:2)局部生命期在函数内部声明的变量或者是块中声明的变量具有局部生命期。这种生命期始于其声明点,直到其作用域结束处。具有局部生命期的变量放在内存的栈区。注意,具有局部生命期的变量也具有局部作用域,但是具有局部作用域的变量不一定具有局部生命期,比如静态局部变量具有静态生命期。

【程序8.12】#include<iostream>usingnamespacestd;voidprint(){//具有局部生命期的变量

inta=10;cout<<a++<<endl;}intmain(){ //具有局部生命期的变量i for(inti=0;i<3;i++) { print(); } return0;}

运行结果如下:

虽然print()函数中的变量a在输出其值的同时,自身值也增加1,但是任何一次调用print()函数都是从头开始,变量a将重新定义,而不保存上次调用结束时的值。3)静态生命期在固定的数据区中分配空间的变量,具有静态生命期。全局变量、静态全局变量和静态局部变量都具有静态生命期。函数驻在代码区也具有静态生命期,这种生命期与程序的运行相同,只要程序一开始运行,这种生命期的变量就存在;当程序结束时,其生命期就结束。

【程序8.13】#include<iostream>usingnamespacestd;//首先声明三个函数

voidstaticLocal();voidglobal();voidstaticGlobal();//静态全局变量staticintb=9;//全局变量intc=9;//主函数intmain(){ cout<<"staticlocalvarible:\n"; staticLocal();staticLocal(); cout<<"staticglobalvarible:\n"; staticGlobal(); staticGlobal(); cout<<"globalvarible:\n"; global(); global(); return0;}//对三个已声明函数的定义

voidstaticLocal(){ staticinta=9; cout<<a<<endl; a++;}voidstaticGlobal(){ cout<<b<<endl; b++;}voidglobal(){ cout<<c<<endl; c++;}

运行结果如下:

从上面的例子可以看出,只要程序没有结束,具有静态生命期的变量仍然存在着,并且保留着最近一次运行时得到的值。局部静态变量a更是特殊,尽管超出了函数staticLocal()的作用域,它已经失去作用不可见了,但仍然存在,第二次调用时保留的是第一次运行结束后的值。

3.作用解析符当一局部变量和一全局变量同名时,所有对该变量名的引用都会指向局部变量,此时,如果使用全局变量,就要利用全局作用域解析符 :: 来通知编译器。全局作用域解析符作为前缀用在变量名前,如:: varname。【程序8.14】#include<iostream>usingnamespacestd;intnumber=1;intmain(){intnumber=4;cout<<::number<<endl;//显示1,因为用了全局解析符::

cout<<number<<endl;//显示4,引用的是局部变量

return0;}8.1.2名字空间

1.名字空间的作用缺省情况下,在全局名字空间域中声明的每个对象、函数、模板或类型都引入了一个全局实体。在全局名字空间引入的全局实体必须具有唯一的名字。考虑这样的问题,如果我们在程序中使用一个库,该库的全局实体名字和程序中的全局实体名字相同,也即产生了名字冲突。一种解决办法是使用冗长、难懂的名字,以使冲突尽可能减少。但这种方案不是很理想。试想在一个大项目中,用C++写的程序中可能有相当数目的全局类、函数以及模板在整个程序中都是可见的,名字冲突的问题更趋严重,用冗长难懂的名字写程序效率低下。而且,编写不会和其他符号冲突的对象名、函数名、模板名或类型名是一个挑战。名字空间是解决名字冲突的更好方法。简而言之,名字空间能把一个全局名字空间分成多个可管理的小空间。不同名字空间可以有同名的标识符,使用时不会产生冲突。

2.名字空间的创建名字空间定义以关键字namespace开头,后面是名字空间的名字。该名字在它被定义的域中必须是唯一的。在名字空间之后是由花括号({})括起来的声明块。一个名字空间可以包含多种类型的标识符:变量名、常量名、函数名、结构名、类名和名字空间(嵌套名字空间)。

创建一个名字空间和创建一个类非常相似。但有两点需要注意:

(1)名字空间定义的结尾,右花括号后面没有分号。

(2)名字空间只能在全局范围内定义,它们之间可以互相嵌套。

【程序8.15】namespaceSpace1{ constintOFFSET=32; floatvar1=1.6; floatvar2=2.4; charch='A';floatfloatSum() { floatsum=var1+var2; returnsum; } charcharSum() { charsum=ch+OFFSET; returnsum; }}【程序8.16】namespaceOuterSpace{ intv1=1; intv2=2;

namespaceInnerSpace { intv3=3; intv4=4; }}

名字空间的定义不一定是连续的。一个namespace可以在多个头文件中使用一个标识符来定义。

【程序8.17】namespaceSpace{ constdoublepi=3.1416; classmatrix{/*...*/}; floatgetArea(matrix&); matrixoperator==(constmatrix&m1,constmatrix&m2){/*...*/} }

它与下面的形式是等价的:

namespaceSpace{ constdoublepi=3.1416; classmatrix{/*...*/};}namespaceSpace{ floatgetArea(matrix&); matrixoperator==(constmatrix&m1,constmatrix&m2){/*...*/}}【程序8.18】//header1.h#ifndefHEADER1_H#defineHEADER1_HnamespaceMySpace{ externinta; voidf1();}#endif//header2.h#ifndefHEADER2_H#defineHEADER2_H#include"header1.h"namespaceMySpace{ externintb; voidf2();}#endif//example.cpp#include"header2.h"intmain(){}

可以给名字空间创建别名。特别地,当一个名字空间的名字冗长繁复时,一个简短的别名有助于提高工作效率。

【程序8.19】namespaceInternational_Business_Machines{/*...*/}namespaceIBM=International_Business_Machines;

也可以创建未命名的名字空间——不用标识符而只用namespace增加一个名字空间。使用未命名的名字空间不会和已有的名字空间名重复,防止由于名字冲突造成的错误,并且使用未命名的名字空间可以命名较易记的名称,方便记忆等。【程序8.20】namespace{ classApple{/*...*/}; classOrange{/*...*/}; classBanana{/*...*/}; classshuiguo{ Appleapple[2]; Orangeorange[3]; Bananabanana[4]; }fruit; inti,j,k;}intmain(){}3.名字空间的使用引用名字空间成员可以采取三种方法:第一种方法是用作用域运算符,第二种方法是用using指令把所有名字引入到名字空间中,第三种方法是用using声明一次性引用名字。

(1)用作用域运算符 :: 可以明确地指定名字空间的任何名字,就像引用一个类中的名字一样。如下列所示:

【程序8.21】#include<iostream>namespaceOuterSpace{ intv1=1; intv2=2;namespaceInnerSpace { intv3=3; intv4=4; }}intmain(){ std::cout<<"OuterSpacevalues:"<<std::endl; std::cout<<OuterSpace::v1<<std::endl;std::cout<<OuterSpace::v2<<std::endl; std::cout<<"InnerSpacevalues:"<<std::endl; std::cout<<OuterSpace::InnerSpace::v3<<std::endl; std::cout<<OuterSpace::InnerSpace::v4<<std::endl; return0;}【程序8.22】#include<iostream>usingnamespacestd;namespaceSpace1{ constintOFFSET=32; floatvar1=1.6; floatvar2=2.4; charch='A';floatfloatSum() { floatsum=var1+var2; returnsum; } charcharSum() { charsum=ch+OFFSET; returnsum; }}intmain(){ cout<<"Namespacemembervalues:"<<endl; cout<<Space1::ch<<endl; cout<<Space1::var1<<endl; cout<<Space1::var2<<endl; cout<<"Returnvalueofnamespacememberfuncton:"<<endl; cout<<Space1::charSum()<<endl; cout<<Space1::floatSum()<<endl; return0;}【程序8.23】#include<iostream>namespaceMySpace{ classCar { staticintwheelNumber; floattime; floatspeed; public: Car(floatt=0,floats=0):time(t),speed(s){} floatgetMeters(); };classBus; voidanExample();}intMySpace::Car::wheelNumber=4;floatMySpace::Car::getMeters(){returntime*speed;}classMySpace::Bus{ floattime; floatspeed;public: Bus(floatt=0,floats=0):time(t),speed(s){}floatgetMeters();};floatMySpace::Bus::getMeters(){returntime*speed;}voidMySpace::anExample(){ MySpace::Busbus(1.2,33); bus.getMeters();}intmain(){}

总是使用作用域运算符修饰的名字形式来引用名字空间成员是非常麻烦的,尤其是当名字空间名很长的时候。如果不得不一直使用作用域运算符,我们可能会希望创建一些短名字的名字空间,因为它们不但易读而且易于键入,但是使用短的名字空间名会增加与程序中的其他全局名冲突的可能性。所以用长的名字空间名来发行我们的库更为合适一些。

(2)关键字using和namespace的搭配使用称为使用指令。关键字using表明一个名字空间中的所有名字都在当前范围内。using指令一次引入名字空间的全部成员名,把这些名字当成当前范围的全局名来看待。其形式为:

usingnamespace名字空间名字;【程序8.24】#include<iostream>usingnamespacestd;namespaceSpace1{ constintOFFSET=32; floatvar1=1.6; floatvar2=2.4; charch='A';floatfloatSum() { floatsum=var1+var2; returnsum; }charcharSum() { charsum=ch+OFFSET; returnsum; }}usingnamespaceSpace1; //使用指令

intmain(){ cout<<"Namespacemembervalues:"<<endl; cout<<ch<<endl;cout<<var1<<endl; cout<<var2<<endl; cout<<"Returnvalueofnamespacememberfuncton:"<<endl; cout<<charSum()<<endl; cout<<floatSum()<<endl; return0;}

上一节讲过,名字空间可以包含名字空间,即名字空间是可以嵌套的。可以作用using指令实现名字空间的嵌套。

(1) using指令能把名字空间甲的所有名字引入到名字空间乙中,让甲中的名字嵌套在乙中。【程序8.25】#include<iostream>usingnamespacestd; //使用指令,引入名字空间stdnamespaceOuterSpace{ intv1=1; intv2=2; namespaceInnerSpace { intv3=3; intv4=4; }}usingnamespaceOuterSpace; //使用指令,引入名字空间OuterSpaceusingnamespaceInnerSpace; //使用指令,引入名字空间InnerSpaceintmain(){ cout<<"OuterSpacevalues:"<<endl; cout<<v1<<endl; cout<<v2<<endl; cout<<"InnerSpacevalues:"<<endl; cout<<v3<<endl; cout<<v4<<endl;}(2) using指令也可以将名字空间包含在函数中,这样名字空间中的所有名字都嵌套在这个函数中。

【程序8.26】#include<iostream>usingnamespacestd;namespaceSpace{ inta=1; intb=5; intsum();}intSpace::sum(){ returna+b;}voiddoubleSum(){ usingnamespaceSpace; cout<<"a="<<a<<endl; cout<<"b="<<b<<endl; cout<<"Doubletheirsum,theresultis"<<2*sum()<<endl;}intmain(){ doubleSum(); return0;}

将usingnamespace语句包含于程序中时可能会遇到如程序8.27所示的问题。

【程序8.27】#include<iostream>namespaceOneSpace{ intv1=1; intv2=2;}namespaceAnotherSpace{ intv1=3; intv2=4;}usingnamespacestd;usingnamespaceOneSpace;usingnamespaceAnotherSpace;intmain(){ cout<<"v1="<<v1<<endl;return0;}

程序不能通过编译,编译器提示的信息为errorC2872:'v1':ambiguoussymbol。编译器不能识别主函数中所指的v1是哪种版本:是在OneSpace中定义的v1,还是在AnotherSpace中定义的v1呢?最好不要在头文件里使用usingnamespace,以免头文件被其他文件引入,造成冲突。为使上面的程序正确编译和运行,我们可以使用作用域解析符,修改如程序8.28所示。 【程序8.28】#include<iostream>namespaceOneSpace{ intv1=1; intv2=2;}namespaceAnotherSpace{ intv1=3; intv2=4;}usingnamespacestd;usingnamespaceOneSpace;usingnamespaceAnotherSpace;intmain(){ cout<<"OneSpace::v1="<<OneSpace::v1<<endl; cout<<"AnotherSpace::v1="<<AnotherSpace::v1<<endl; return0;}(3) using声明使名字空间成员易于使用,它允许程序员指定在程序中要使用的名字。using声明以关键字using开头,后面是名字空间成员名,注意声明中的成员名必须是限定修饰名。

(4) using声明可以放在任何一般的声明可以出现的地方,它可以出现在全局域中、任意名字空间中,也可以出现在局部域中。

(5) using声明允许在不同的名字空间中声明同样的函数,不会引起二义性。【程序8.29】#include<iostream>usingnamespacestd;namespaceU{ voidf(){cout<<"U::f()\n";} voidg(){cout<<"U::g()\n";}}namespaceV{ voidf(){cout<<"V::f()\n";} voidg(){cout<<"V::g()\n";}}voidinvokeFunction(){ usingV::f; f(); usingU::g; g();}intmain(){ invokeFunction(); return0;}

运行结果如下:using声明有一个域,它引入的名字从该声明开始,直到其所在的域都是可见的(声明的是从离声明最近的左括号开始,到对应的最近右括号处结束)。由using声明引入的名字在该域中必须唯一,否则编译不能通过。例如,如果将程序8.29中的函数voidinvokeFunction()改写如下:voidinvokeFunction(){ usingV::f; f(); usingV::g; g(); usingU::g; g();}

程序中其他部分保持不变,则编译器将报错:

errorC2884:'g':introducedbyusing-declarationconflictswithlocalfunction'g'

下面再举一个例子。

【程序8.30】#include<iostream>usingnamespacestd;namespaceIntSpace{ inti=10; intj=20; intk=30;}inti=1;intj=2;intmain(){ cout<<i<<endl; //输出1 cout<<IntSpace::i<<endl; //输出10 cout<<j<<endl; //输出2 usingIntSpace::j; cout<<j<<endl; //输出20 ++j; cout<<j<<endl; //输出21intk=3; cout<<k<<endl; //输出3 return0;}8.2static关键字

static为静态变量声明符。它所声明的变量在声明它的程序块、子程序块或函数内部有效,其值保持在整个程序期间分配存储器空间,编译器默认值为0。static是C++中很常用的修饰符,常被用来控制变量的存储方式和可见性。为什么要引入static呢? 函数内部定义的变量,在程序执行到它的定义处时,编译器为它在栈上分配空间。大家知道,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题:如果想将函数中此变量的值保存至下一次调用时,如何实现?最简单的方法是定义一个全局的变量,但定义为一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量不仅仅受此函数控制)。题引入了static静态变量。此外,当需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见时,可引入类的static成员变量。8.2.1内存分配方式我们知道,C++将内存划分为三个逻辑区域:堆、栈和静态存储区。既然如此,我们可以将位于它们之中的对象分别称为堆对象、栈对象和静态对象。一个完整的程序在内存中的分布情况如图8.1所示。栈一般用于存放局部变量或对象,如我们在函数定义中用类似下面语句声明的对象:

Typestack_object;图8.1内存分布stack_object便是一个栈对象,它的生命期是从定义点开始的当所在函数返回,作用域结束时,生命期结束。另外,几乎所有的临时对象都是栈对象。堆又叫自由存储区,它是在程序执行的过程中动态分配的,所以它最大的特性就是动态性。在C++中,所有堆对象的创建和销毁都要由程序员负责,因此,如果处理不好,就会发生内存问题。如果分配了堆对象,却忘记了释放,就会产生内存泄漏;而如果已释放了对象,却没有将相应的指针置为NULL,该指针就是所谓的“悬挂指针”,再度使用此指针时,就会出现非法访问,严重时甚至会导致程序崩溃。

那么,C++中是怎样分配堆对象的?唯一的方法是用new(当然,用类malloc指令也可获得C式堆内存),只要使用new,就会在堆中分配一块内存,并且返回指向该堆对象的指针。所有的静态对象和全局对象都在静态存储区分配。全局对象是在main()函数执行前就分配好了的。局部静态对象通常也是在函数中定义的,就像栈对象一样,只不过其前面多了个static关键字。局部静态对象的生命期是从其所在函数第一次被调用开始的,更确切地说,是当第一次执行到该静态对象的声明代码时,产生该静态局部对象,直到整个程序结束时才销毁该对象。8.2.2static用于限制存储

1.函数内部的static变量当函数中的局部变量为static时,该变量只在函数第一次被调用时初始化,函数调用之间保持变量的值不变。那为什么不使用全局变量呢?static变量的优点就是在函数的范围之外它是不可用的。

【程序8.31】#include<iostream>usingnamespacestd;voidf(){staticints=0; //此句只会被执行一次

cout<<"s="<<++s<<endl;}intmain(){for(inti=0;i<10;i++)f();return0;}

运行结果如下:static的另一层含义是“在某个作用域外不可访问”。当应用static于函数名和所有函数外部的变量时,它的意思是“在文件的外部不可以使用这个名字”。函数名或变量是局部于文件的;我们说它具有文件作用域(filescope)。例如,编译和连接下面两个文件会引起连接器错误:

//file1staticintfs;//filescopemeansonlyavailableinthisfile:intmain(){fs=1;}//file2externintfs; //tryingtoreferencefsvoidfunc(){fs=1000;}2.static成员变量

(1)内存中的位置:静态存储区。

(2)初始化和定义:

a.静态数据成员定义时要分配空间,所以不能在类声明中定义。

b.静态数据成员在程序中只能提供一个定义,所以静态数据成员的初始化不能在类的头文件中进行。

(3)访问:

a.<类对象名>.<静态数据成员>。

b.<类类型名>::<静态数据成员>。

(4)说明:

a. static数据成员和普通数据成员一样遵守public、protected、private访问规则。b.对于非静态数据成员,每个类对象都有自己的拷贝。静态数据成员被当作类的全局对象,无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类类型的所有对象共享访问。

(5)同全局对象相比,使用静态数据成员有两个优势:

a.静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其他全局名字冲突的可能性。

b.可以实现信息隐藏。静态成员可以是private成员,而全局对象不能。

(6)应用:

classAccount{Account(doubleamount,conststring&owner);stringgetOwner(){returnowner;}private:staticdoubleinterestRate;doubleamount;stringowner;};

为什么把interestRate声明为static,而amount和owner不呢?这是因为每个Account对应不同的主人,有不同数目的钱,而所有Account的利率却是相同的。

因为在整个程序中只有一个interestRate数据成员,它被所有Account对象共享,所以把interestRate声明为静态数据成员减少了每个Account所需的存储空间。

interestRate值可能变化,所以不能声明为const。因为interestRate是静态的,所以它只需要更新一次就可以保证每个Account对象都能访问到更新后的值。要是每个类对象都维持自己的一个拷贝,那么每个拷贝都必须更新,这将导致效率低下和发生更大的错误。

(7)静态数据成员的“唯一性”本质(独立于类的任何对象而存在的唯一实例),使它能够以独特的方式被使用,这些方式对于非static数据成员来说是非法的。a.静态数据成员的类型可以是其所属类,而非static数据成员只能被声明为该类对象的指针或引用。例如:

classBar{public://...private:staticBarmember1; //正确

Bar*member2; //正确

Barmember3; //错误

};b.静态数据成员可以被作为类成员函数的缺省实参,而非static数据成员不可以。例如:

externintvar;classDou{private:intvar;staticintsvar;public://错误:被解析为非static的Dou:var//没有相关的类对象intmember1(int=var);//正确:解析为static的Dou:svar//无需相关的类对象

intmember2(int=svar);//正确:intvar的全局实例

intmember3(int=::var);};8.2.3static成员函数●声明:在类的成员函数返回类型之前加上关键字static,它就被声明为一个静态成员函数。静态成员函数不能声明为const或volatile,这与非静态成员函数不同。●定义:出现在类体外的函数定义不能指定关键字static。●作用:主要用于对静态数据成员的操作。●静态成员函数与类相联系,不与类的对象相联系。●静态成员函数不能访问非静态数据成员。因为非静态数据成员属于特定的类实例。●静态成员函数没有this指针,因此在静态成员函数中隐式或显式地引用这个指针都将导致编译错误。试图访问隐式引用this指针的非静态数据成员也会导致编译时错误。●访问:可以用成员访问操作符( . )和箭头(->)为一个类对象或指向类对象的指针调用静态成员函数,也可以用限定修饰符名直接访问或调用静态成员函数,而无需声明类对象。

当一个源程序由多个源文件组成时,C++语言根据函数能否被其他源文件中的函数调用,将函数分为内部函数和外部函数。

1.内部函数如果在一个源文件中定义的函数,只能被本文件中的函数调用,而不能被同一程序其他文件中的函数调用,则这种函数称为内部函数。定义一个内部函数,只需在函数类型前再加一个“static”关键字即可,如下所示:

static函数类型函数名(函数参数表){…}

关键字“static”译成中文就是“静态的”,所以内部函数又称静态函数。但此处“static”的含义不是指存储方式,而是指对函数的作用域仅局限于本文件。使用内部函数的好处是:不同的人编写不同的函数时,不用担心自己定义的函数是否会与其他文件中的函数同名,因为同名也没有关系。

2.外部函数在定义函数时,如果没有加关键字“static”,或冠以关键字“extern”,则称此函数为外部函数。其格式如下:

[extern]函数类型函数名(函数参数表){…}

调用外部函数时,需要对其进行说明:

[extern]函数类型函数名(参数类型表)[,函数名2(参数类型表2)…];静态成员函数为类的全部对象服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。下面举个静态成员函数的例子。【程序8.32】#include<iostream>usingnamespacestd;classMyclass{public:

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论