版權說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權,請進行舉報或認領
文檔簡介
第8章作用域與名字空間8.1作用域與名字空間概述
8.2static關鍵字8.3預處理命令本章小結習題
創(chuàng)建名字是編程的一個最為基本的行為,當項目逐步變大,名字的數(shù)目增多時,就可能會導致名字沖突。
C++大大加強了對名字的管理,使你可以對名字的創(chuàng)建、名字的可見性、名字在內(nèi)存中對應的位置以及名字的鏈接屬性進行控制。本章展示了在C++中如何使用兩種技術來對名字進行控制。首先,在全局范圍內(nèi)對名字進行控制的一個更有用的技術是C++的名字空間(namespace)特性,它使得你可以將全局的名字空間分成不同的區(qū)域。其次可用關鍵字static來控制名字的可見性和鏈接屬性,同時也講述了這個關鍵字在類內(nèi)部的特殊含義。8.1作用域與名字空間概述8.1.1作用域和生命周期
1.作用域作用域在許多程序設計語言中非常重要。通常來說,一段程序代碼中所用到的名字并不總是有效可用的,而限定這個名字的可用性的代碼范圍就是這個名字的作用域。作用域能夠提高程序邏輯的局部性,增強程序的可靠性,減少名字沖突。
C++的作用域范圍分為:局部作用域、文件作用域、全局作用域、函數(shù)作用域、函數(shù)原型作用域和類作用域。1)局部作用域局部作用域是指某標識符的定義出現(xiàn)在一對花括號所括起來的一段程序內(nèi),該標識符的作用域范圍從聲明點開始,直到該花括號結束為止。局部作用域也稱塊作用域。下面通過程序說明。
【程序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語句中進行條件測試的表達式中聲明標識符,那么該標識符的作用域?qū)⒕窒抻谠撜Z句內(nèi)。程序8.2和程序8.3分別展示了在if語句和switch語句中聲明的標識符的作用域?!境绦?.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語句中聲明的標識符,其作用域并不局限于該語句內(nèi)。
【程序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)文件作用域若某變量定義語句是在一個程序文件中的所有函數(shù)定義之外說明的,且該語句前帶有static保留字時,則稱該語句所定義變量都具有文件作用域,即變量在其定義處之后的整個程序文件中有效,但是在其他文件中是無效不可見的,也就是說具有文件作用域的標識符不能使用extern在其他文件中聲明它們。具有文件作用域的變量沒有初始化時,在編譯時將自動初始化為各種類型的“0”(如果是指針,應該初始化為NULL;字符串應該是“”(空串),字符應該是“\0”)。【程序8.5】#include<iostream>usingnamespacestd;//具有文件作用域的變量
staticfloatprice=1.5;//具有文件作用域的函數(shù)的原型
staticfloatcomputeMoney(int);intmain(){ //調(diào)用具有文件作用域的函數(shù)
floattotal=computeMoney(5);cout<<"Totalmoneyis"<<total<<endl; //輸出Totalmoneyis7.5 //輸出具有文件作用域的變量的值
cout<<"priceis"<<price<<endl; //輸出priceis1.5 return0;}staticfloatcomputeMoney(intnumber){ //引用具有文件作用域的變量
returnnumber*price;}3)全局作用域若某變量在一個程序文件中的所有函數(shù)文件之外(通常在所有函數(shù)定義之前)定義,則該變量具有全局作用域,即變量在其定義處之后的程序文件中是有效可訪問的。如果要在某程序文件中使用不在本程序文件中定義的全局變量,則必須在本文件開始進行聲明,格式如下:extern<類型名><變量名>,<變量名>,…;
注意:該聲明格式類似于變量聲明,但是不能對變量進行初始化,且必須在整個語句前加上extern保留字。若全局變量沒有初始化,編譯時將自動賦值為各種類型的“0”(如果是指針,應該是NULL,字符串應該是“”,字符應該是“\0”)?!境绦?.6】#include<iostream>usingnamespacestd;
intxPosition; //全局變量
intyPosition=0; //全局變量
voidincreaseNumber(int,int); //函數(shù)原型
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++支持不同作用域中使用同名標識符。當存在兩個或多個聲明具有包含關系的作用域時,在外層聲明了標識符后,如果內(nèi)層中沒有聲明與之同名的標識符,則外層標識符在內(nèi)層可見。但如果在同一函數(shù)內(nèi)使用同名標識符,具有內(nèi)層作用域的標識符會覆蓋外層作用域的標識符,直到離開內(nèi)層作用域為止。【程序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)函數(shù)作用域標號是唯一具有函數(shù)作用域的標識符,它使得該標識符在一個函數(shù)內(nèi)的任何位置均可被使用。
【程序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語句直接跳到函數(shù)最后部分,使得變量b未能初始化5)函數(shù)原型作用域函數(shù)原型范圍指定了函數(shù)原型的開始與結束之間的區(qū)域,函數(shù)原型中出現(xiàn)的標識符僅僅在函數(shù)原型內(nèi)有意義,即函數(shù)原型的作用域開始于函數(shù)原型聲明的左括號,結束于函數(shù)原型聲明的右括號。例如:
intinformation(intage,char*name);上例中,標識符age和name是可有可無的,即等價于“intinformation(int,char*);”,但是標識符可以增強可讀性。上例中帶標識符的函數(shù)原型使人一看就明白第一個參數(shù)是年齡值,第二個參數(shù)是姓名。2.生命周期 生命期也叫生存期。變量之間為什么會有不同的作用域?這和生命周期有關。生命周期和作用域之間的關系是:如果一個變量沒有了生命周期,那么它也就沒有了作用域;但是另一方面,如果一個變量超出了它的作用域,它的生命周期未必就結束了,例如局部靜態(tài)變量。程序運行時,需要操作系統(tǒng)為它分配內(nèi)存。存放程序代碼的內(nèi)存空間稱為代碼區(qū)。存放數(shù)據(jù)的內(nèi)存區(qū)域有三種:數(shù)據(jù)區(qū)、棧區(qū)和堆區(qū)。為什么要把存放數(shù)據(jù)的內(nèi)存空間分成三個部分呢?這是因為程序中要用到的數(shù)據(jù)具有不同的生存周期要求,為方便實現(xiàn)要求,編譯器把它們分別放到不同空間。
對于數(shù)據(jù)區(qū)的數(shù)據(jù),它們的生命周期和程序一樣長久。只要程序一開始運行,這種生命期的變量就存在,當程序結束時,其生命期就結束。這些數(shù)據(jù)如何產(chǎn)生和釋放,都由程序自動完成,程序員不必關心。正因為程序自動為數(shù)據(jù)區(qū)的數(shù)據(jù)分配內(nèi)存,所以這些數(shù)據(jù)所需要的內(nèi)存必須已知。數(shù)據(jù)未被初始化時,將默認為0。堆區(qū)的數(shù)據(jù)由程序員決定它們何時分配內(nèi)存,何時釋放內(nèi)存。此外,堆里的數(shù)據(jù)可以是明確已知的,也可以是不確定的。數(shù)據(jù)的初始化需由程序員來完成,否則未初始化的數(shù)據(jù)值是不確定的。由于程序員負責內(nèi)存的分配與釋放,因此容易產(chǎn)生錯誤。比如使用還未分配內(nèi)存的變量。最常見的是“內(nèi)存泄漏”:一個已經(jīng)分配內(nèi)存的數(shù)據(jù),當程序中不再需要它時,卻始終沒有為它釋放內(nèi)存。這樣會白白消耗系統(tǒng)資源,降低內(nèi)存的使用率。對于棧區(qū)的數(shù)據(jù),它們?nèi)绾萎a(chǎn)生和釋放都由程序自動完成,程序員不必關心。同時這些數(shù)據(jù)所需要的內(nèi)存必須已知,否則程序就不能自動為它們分配內(nèi)存了。數(shù)據(jù)若沒有初始化,那么值是不定的。生命期與存儲區(qū)域密切相關,存儲區(qū)域主要有代碼區(qū)、數(shù)據(jù)區(qū)、棧區(qū)和堆區(qū),對應的生命期為靜態(tài)生命期、局部生命期和動態(tài)生命期。1)動態(tài)生命期放在堆區(qū)的數(shù)據(jù)具有動態(tài)生命期。該生命期由程序中特定的函數(shù)調(diào)用(C語言中的malloc()和free()或操作符(C++語言中的new和delete))來創(chuàng)建和釋放。當用函數(shù)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)局部生命期在函數(shù)內(nèi)部聲明的變量或者是塊中聲明的變量具有局部生命期。這種生命期始于其聲明點,直到其作用域結束處。具有局部生命期的變量放在內(nèi)存的棧區(qū)。注意,具有局部生命期的變量也具有局部作用域,但是具有局部作用域的變量不一定具有局部生命期,比如靜態(tài)局部變量具有靜態(tài)生命期。
【程序8.12】#include<iostream>usingnamespacestd;voidprint(){//具有局部生命期的變量
inta=10;cout<<a++<<endl;}intmain(){ //具有局部生命期的變量i for(inti=0;i<3;i++) { print(); } return0;}
運行結果如下:
雖然print()函數(shù)中的變量a在輸出其值的同時,自身值也增加1,但是任何一次調(diào)用print()函數(shù)都是從頭開始,變量a將重新定義,而不保存上次調(diào)用結束時的值。3)靜態(tài)生命期在固定的數(shù)據(jù)區(qū)中分配空間的變量,具有靜態(tài)生命期。全局變量、靜態(tài)全局變量和靜態(tài)局部變量都具有靜態(tài)生命期。函數(shù)駐在代碼區(qū)也具有靜態(tài)生命期,這種生命期與程序的運行相同,只要程序一開始運行,這種生命期的變量就存在;當程序結束時,其生命期就結束。
【程序8.13】#include<iostream>usingnamespacestd;//首先聲明三個函數(shù)
voidstaticLocal();voidglobal();voidstaticGlobal();//靜態(tài)全局變量staticintb=9;//全局變量intc=9;//主函數(shù)intmain(){ cout<<"staticlocalvarible:\n"; staticLocal();staticLocal(); cout<<"staticglobalvarible:\n"; staticGlobal(); staticGlobal(); cout<<"globalvarible:\n"; global(); global(); return0;}//對三個已聲明函數(shù)的定義
voidstaticLocal(){ staticinta=9; cout<<a<<endl; a++;}voidstaticGlobal(){ cout<<b<<endl; b++;}voidglobal(){ cout<<c<<endl; c++;}
運行結果如下:
從上面的例子可以看出,只要程序沒有結束,具有靜態(tài)生命期的變量仍然存在著,并且保留著最近一次運行時得到的值。局部靜態(tài)變量a更是特殊,盡管超出了函數(shù)staticLocal()的作用域,它已經(jīng)失去作用不可見了,但仍然存在,第二次調(diào)用時保留的是第一次運行結束后的值。
3.作用解析符當一局部變量和一全局變量同名時,所有對該變量名的引用都會指向局部變量,此時,如果使用全局變量,就要利用全局作用域解析符?::?來通知編譯器。全局作用域解析符作為前綴用在變量名前,如::?varname?!境绦?.14】#include<iostream>usingnamespacestd;intnumber=1;intmain(){intnumber=4;cout<<::number<<endl;//顯示1,因為用了全局解析符::
cout<<number<<endl;//顯示4,引用的是局部變量
return0;}8.1.2名字空間
1.名字空間的作用缺省情況下,在全局名字空間域中聲明的每個對象、函數(shù)、模板或類型都引入了一個全局實體。在全局名字空間引入的全局實體必須具有唯一的名字??紤]這樣的問題,如果我們在程序中使用一個庫,該庫的全局實體名字和程序中的全局實體名字相同,也即產(chǎn)生了名字沖突。一種解決辦法是使用冗長、難懂的名字,以使沖突盡可能減少。但這種方案不是很理想。試想在一個大項目中,用C++寫的程序中可能有相當數(shù)目的全局類、函數(shù)以及模板在整個程序中都是可見的,名字沖突的問題更趨嚴重,用冗長難懂的名字寫程序效率低下。而且,編寫不會和其他符號沖突的對象名、函數(shù)名、模板名或類型名是一個挑戰(zhàn)。名字空間是解決名字沖突的更好方法。簡而言之,名字空間能把一個全局名字空間分成多個可管理的小空間。不同名字空間可以有同名的標識符,使用時不會產(chǎn)生沖突。
2.名字空間的創(chuàng)建名字空間定義以關鍵字namespace開頭,后面是名字空間的名字。該名字在它被定義的域中必須是唯一的。在名字空間之后是由花括號({})括起來的聲明塊。一個名字空間可以包含多種類型的標識符:變量名、常量名、函數(shù)名、結構名、類名和名字空間(嵌套名字空間)。
創(chuàng)建一個名字空間和創(chuàng)建一個類非常相似。但有兩點需要注意:
(1)名字空間定義的結尾,右花括號后面沒有分號。
(2)名字空間只能在全局范圍內(nèi)定義,它們之間可以互相嵌套。
【程序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; }}
名字空間的定義不一定是連續(xù)的。一個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(){}
可以給名字空間創(chuàng)建別名。特別地,當一個名字空間的名字冗長繁復時,一個簡短的別名有助于提高工作效率。
【程序8.19】namespaceInternational_Business_Machines{/*...*/}namespaceIBM=International_Business_Machines;
也可以創(chuàng)建未命名的名字空間——不用標識符而只用namespace增加一個名字空間。使用未命名的名字空間不會和已有的名字空間名重復,防止由于名字沖突造成的錯誤,并且使用未命名的名字空間可以命名較易記的名稱,方便記憶等?!境绦?.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(){}
總是使用作用域運算符修飾的名字形式來引用名字空間成員是非常麻煩的,尤其是當名字空間名很長的時候。如果不得不一直使用作用域運算符,我們可能會希望創(chuàng)建一些短名字的名字空間,因為它們不但易讀而且易于鍵入,但是使用短的名字空間名會增加與程序中的其他全局名沖突的可能性。所以用長的名字空間名來發(fā)行我們的庫更為合適一些。
(2)關鍵字using和namespace的搭配使用稱為使用指令。關鍵字using表明一個名字空間中的所有名字都在當前范圍內(nèi)。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;}
上一節(jié)講過,名字空間可以包含名字空間,即名字空間是可以嵌套的??梢宰饔胾sing指令實現(xiàn)名字空間的嵌套。
(1)?using指令能把名字空間甲的所有名字引入到名字空間乙中,讓甲中的名字嵌套在乙中?!境绦?.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指令也可以將名字空間包含在函數(shù)中,這樣名字空間中的所有名字都嵌套在這個函數(shù)中。
【程序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。編譯器不能識別主函數(shù)中所指的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聲明可以放在任何一般的聲明可以出現(xiàn)的地方,它可以出現(xiàn)在全局域中、任意名字空間中,也可以出現(xiàn)在局部域中。
(5)?using聲明允許在不同的名字空間中聲明同樣的函數(shù),不會引起二義性?!境绦?.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中的函數(shù)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為靜態(tài)變量聲明符。它所聲明的變量在聲明它的程序塊、子程序塊或函數(shù)內(nèi)部有效,其值保持在整個程序期間分配存儲器空間,編譯器默認值為0。static是C++中很常用的修飾符,常被用來控制變量的存儲方式和可見性。為什么要引入static呢??函數(shù)內(nèi)部定義的變量,在程序執(zhí)行到它的定義處時,編譯器為它在棧上分配空間。大家知道,函數(shù)在棧上分配的空間在此函數(shù)執(zhí)行結束時會釋放掉,這樣就產(chǎn)生了一個問題:如果想將函數(shù)中此變量的值保存至下一次調(diào)用時,如何實現(xiàn)?最簡單的方法是定義一個全局的變量,但定義為一個全局變量有許多缺點,最明顯的缺點是破壞了此變量的訪問范圍(使得在此函數(shù)中定義的變量不僅僅受此函數(shù)控制)。題引入了static靜態(tài)變量。此外,當需要一個數(shù)據(jù)對象為整個類而非某個對象服務,同時又力求不破壞類的封裝性,即要求此成員隱藏在類的內(nèi)部,對外不可見時,可引入類的static成員變量。8.2.1內(nèi)存分配方式我們知道,C++將內(nèi)存劃分為三個邏輯區(qū)域:堆、棧和靜態(tài)存儲區(qū)。既然如此,我們可以將位于它們之中的對象分別稱為堆對象、棧對象和靜態(tài)對象。一個完整的程序在內(nèi)存中的分布情況如圖8.1所示。棧一般用于存放局部變量或?qū)ο?,如我們在函?shù)定義中用類似下面語句聲明的對象:
Typestack_object;圖8.1內(nèi)存分布stack_object便是一個棧對象,它的生命期是從定義點開始的當所在函數(shù)返回,作用域結束時,生命期結束。另外,幾乎所有的臨時對象都是棧對象。堆又叫自由存儲區(qū),它是在程序執(zhí)行的過程中動態(tài)分配的,所以它最大的特性就是動態(tài)性。在C++中,所有堆對象的創(chuàng)建和銷毀都要由程序員負責,因此,如果處理不好,就會發(fā)生內(nèi)存問題。如果分配了堆對象,卻忘記了釋放,就會產(chǎn)生內(nèi)存泄漏;而如果已釋放了對象,卻沒有將相應的指針置為NULL,該指針就是所謂的“懸掛指針”,再度使用此指針時,就會出現(xiàn)非法訪問,嚴重時甚至會導致程序崩潰。
那么,C++中是怎樣分配堆對象的?唯一的方法是用new(當然,用類malloc指令也可獲得C式堆內(nèi)存),只要使用new,就會在堆中分配一塊內(nèi)存,并且返回指向該堆對象的指針。所有的靜態(tài)對象和全局對象都在靜態(tài)存儲區(qū)分配。全局對象是在main()函數(shù)執(zhí)行前就分配好了的。局部靜態(tài)對象通常也是在函數(shù)中定義的,就像棧對象一樣,只不過其前面多了個static關鍵字。局部靜態(tài)對象的生命期是從其所在函數(shù)第一次被調(diào)用開始的,更確切地說,是當?shù)谝淮螆?zhí)行到該靜態(tài)對象的聲明代碼時,產(chǎn)生該靜態(tài)局部對象,直到整個程序結束時才銷毀該對象。8.2.2static用于限制存儲
1.函數(shù)內(nèi)部的static變量當函數(shù)中的局部變量為static時,該變量只在函數(shù)第一次被調(diào)用時初始化,函數(shù)調(diào)用之間保持變量的值不變。那為什么不使用全局變量呢?static變量的優(yōu)點就是在函數(shù)的范圍之外它是不可用的。
【程序8.31】#include<iostream>usingnamespacestd;voidf(){staticints=0; //此句只會被執(zhí)行一次
cout<<"s="<<++s<<endl;}intmain(){for(inti=0;i<10;i++)f();return0;}
運行結果如下:static的另一層含義是“在某個作用域外不可訪問”。當應用static于函數(shù)名和所有函數(shù)外部的變量時,它的意思是“在文件的外部不可以使用這個名字”。函數(shù)名或變量是局部于文件的;我們說它具有文件作用域(filescope)。例如,編譯和連接下面兩個文件會引起連接器錯誤:
//file1staticintfs;//filescopemeansonlyavailableinthisfile:intmain(){fs=1;}//file2externintfs; //tryingtoreferencefsvoidfunc(){fs=1000;}2.static成員變量
(1)內(nèi)存中的位置:靜態(tài)存儲區(qū)。
(2)初始化和定義:
a.靜態(tài)數(shù)據(jù)成員定義時要分配空間,所以不能在類聲明中定義。
b.靜態(tài)數(shù)據(jù)成員在程序中只能提供一個定義,所以靜態(tài)數(shù)據(jù)成員的初始化不能在類的頭文件中進行。
(3)訪問:
a.<類對象名>.<靜態(tài)數(shù)據(jù)成員>。
b.<類類型名>::<靜態(tài)數(shù)據(jù)成員>。
(4)說明:
a.?static數(shù)據(jù)成員和普通數(shù)據(jù)成員一樣遵守public、protected、private訪問規(guī)則。b.對于非靜態(tài)數(shù)據(jù)成員,每個類對象都有自己的拷貝。靜態(tài)數(shù)據(jù)成員被當作類的全局對象,無論這個類的對象被定義了多少個,靜態(tài)數(shù)據(jù)成員在程序中也只有一份拷貝,由該類類型的所有對象共享訪問。
(5)同全局對象相比,使用靜態(tài)數(shù)據(jù)成員有兩個優(yōu)勢:
a.靜態(tài)數(shù)據(jù)成員沒有進入程序的全局名字空間,因此不存在與程序中其他全局名字沖突的可能性。
b.可以實現(xiàn)信息隱藏。靜態(tài)成員可以是private成員,而全局對象不能。
(6)應用:
classAccount{Account(doubleamount,conststring&owner);stringgetOwner(){returnowner;}private:staticdoubleinterestRate;doubleamount;stringowner;};
為什么把interestRate聲明為static,而amount和owner不呢?這是因為每個Account對應不同的主人,有不同數(shù)目的錢,而所有Account的利率卻是相同的。
因為在整個程序中只有一個interestRate數(shù)據(jù)成員,它被所有Account對象共享,所以把interestRate聲明為靜態(tài)數(shù)據(jù)成員減少了每個Account所需的存儲空間。
interestRate值可能變化,所以不能聲明為const。因為interestRate是靜態(tài)的,所以它只需要更新一次就可以保證每個Account對象都能訪問到更新后的值。要是每個類對象都維持自己的一個拷貝,那么每個拷貝都必須更新,這將導致效率低下和發(fā)生更大的錯誤。
(7)靜態(tài)數(shù)據(jù)成員的“唯一性”本質(zhì)(獨立于類的任何對象而存在的唯一實例),使它能夠以獨特的方式被使用,這些方式對于非static數(shù)據(jù)成員來說是非法的。a.靜態(tài)數(shù)據(jù)成員的類型可以是其所屬類,而非static數(shù)據(jù)成員只能被聲明為該類對象的指針或引用。例如:
classBar{public://...private:staticBarmember1; //正確
Bar*member2; //正確
Barmember3; //錯誤
};b.靜態(tài)數(shù)據(jù)成員可以被作為類成員函數(shù)的缺省實參,而非static數(shù)據(jù)成員不可以。例如:
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成員函數(shù)●聲明:在類的成員函數(shù)返回類型之前加上關鍵字static,它就被聲明為一個靜態(tài)成員函數(shù)。靜態(tài)成員函數(shù)不能聲明為const或volatile,這與非靜態(tài)成員函數(shù)不同?!穸x:出現(xiàn)在類體外的函數(shù)定義不能指定關鍵字static。●作用:主要用于對靜態(tài)數(shù)據(jù)成員的操作?!耢o態(tài)成員函數(shù)與類相聯(lián)系,不與類的對象相聯(lián)系。●靜態(tài)成員函數(shù)不能訪問非靜態(tài)數(shù)據(jù)成員。因為非靜態(tài)數(shù)據(jù)成員屬于特定的類實例。●靜態(tài)成員函數(shù)沒有this指針,因此在靜態(tài)成員函數(shù)中隱式或顯式地引用這個指針都將導致編譯錯誤。試圖訪問隱式引用this指針的非靜態(tài)數(shù)據(jù)成員也會導致編譯時錯誤?!裨L問:可以用成員訪問操作符(?.?)和箭頭(->)為一個類對象或指向類對象的指針調(diào)用靜態(tài)成員函數(shù),也可以用限定修飾符名直接訪問或調(diào)用靜態(tài)成員函數(shù),而無需聲明類對象。
當一個源程序由多個源文件組成時,C++語言根據(jù)函數(shù)能否被其他源文件中的函數(shù)調(diào)用,將函數(shù)分為內(nèi)部函數(shù)和外部函數(shù)。
1.內(nèi)部函數(shù)如果在一個源文件中定義的函數(shù),只能被本文件中的函數(shù)調(diào)用,而不能被同一程序其他文件中的函數(shù)調(diào)用,則這種函數(shù)稱為內(nèi)部函數(shù)。定義一個內(nèi)部函數(shù),只需在函數(shù)類型前再加一個“static”關鍵字即可,如下所示:
static函數(shù)類型函數(shù)名(函數(shù)參數(shù)表){…}
關鍵字“static”譯成中文就是“靜態(tài)的”,所以內(nèi)部函數(shù)又稱靜態(tài)函數(shù)。但此處“static”的含義不是指存儲方式,而是指對函數(shù)的作用域僅局限于本文件。使用內(nèi)部函數(shù)的好處是:不同的人編寫不同的函數(shù)時,不用擔心自己定義的函數(shù)是否會與其他文件中的函數(shù)同名,因為同名也沒有關系。
2.外部函數(shù)在定義函數(shù)時,如果沒有加關鍵字“static”,或冠以關鍵字“extern”,則稱此函數(shù)為外部函數(shù)。其格式如下:
[extern]函數(shù)類型函數(shù)名(函數(shù)參數(shù)表){…}
調(diào)用外部函數(shù)時,需要對其進行說明:
[extern]函數(shù)類型函數(shù)名(參數(shù)類型表)[,函數(shù)名2(參數(shù)類型表2)…];靜態(tài)成員函數(shù)為類的全部對象服務而不是為某一個類的具體對象服務。靜態(tài)成員函數(shù)與靜態(tài)數(shù)據(jù)成員一樣,都是類的內(nèi)部實現(xiàn),屬于類定義的一部分。普通的成員函數(shù)一般都隱含了一個this指針,this指針指向類的對象本身,因為普通成員函數(shù)總是具體的屬于某個類的具體對象的。通常情況下,this是缺省的。如函數(shù)fn()實際上是this->fn()。但是與普通函數(shù)相比,靜態(tài)成員函數(shù)由于不是與任何對象相聯(lián)系,因此它不具有this指針。從這個意義上講,它無法訪問屬于類對象的非靜態(tài)數(shù)據(jù)成員,也無法訪問非靜態(tài)成員函數(shù),它只能調(diào)用其余的靜態(tài)成員函數(shù)。下面舉個靜態(tài)成員函數(shù)的例子。【程序8.32】#include<iostream>usingnamespacestd;classMyclass{public:
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
- 4. 未經(jīng)權益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責。
- 6. 下載文件中如有侵權或不適當內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 住宅綠化養(yǎng)護合同
- 《榜樣9》觀后感:新時代共產(chǎn)黨人的精神力量
- 電影評論中背景設定的藝術分析
- 2024高中地理第2章區(qū)域可持續(xù)發(fā)展第6節(jié)區(qū)域工業(yè)化與城市化進程-以珠江三角洲為例精練含解析湘教版必修3
- 2024高中物理第三章相互作用2彈力課后作業(yè)含解析新人教版必修1
- 2024高中語文第6單元墨子蚜第3課尚賢練習含解析新人教版選修先秦諸子蚜
- 2024高中語文第六課語言的藝術第4節(jié)入鄉(xiāng)問俗-語言和文化練習含解析新人教版選修語言文字應用
- 2024高考化學一輪復習課練22化學反應的方向與限度含解析
- 校長在新學期第一次年級組長會議上講話
- 小學一年級綜合與實踐教學計劃
- 專項債券培訓課件
- 2025年1月普通高等學校招生全國統(tǒng)一考試適應性測試(八省聯(lián)考)語文試題
- 《立式輥磨機用陶瓷金屬復合磨輥輥套及磨盤襯板》編制說明
- CNAS-CL01-G001:2024檢測和校準實驗室能力認可準則的應用要求
- 校園重點防火部位消防安全管理規(guī)定(3篇)
- 臨時施工圍擋安全應急預案
- ICP-網(wǎng)絡與信息安全保障措施-1.信息安全管理組織機構設置及工作職責
- 育肥牛購銷合同范例
- 暨南大學珠海校區(qū)財務辦招考財務工作人員管理單位遴選500模擬題附帶答案詳解
- 碼頭安全生產(chǎn)管理制度
- 部隊冬季常見病的防治
評論
0/150
提交評論