全局做用域是最大的名字空間做用域,不一樣於用戶自定義的名字空間做用域,全局做用域不須要顯示地定義,它自然存在於C++程序中。全局做用域是一個最外層的容器,是全部做用域的父做用域。在全局做用域中,能夠定義其餘的名字空間,類型,函數,變量,模版等。函數
在全局做用域中定義的函數是全局函數,在全局做用域中定義的變量是全局對象。全局函數和全局對象在整個全局做用域及其子做用域中有效,它們的生命週期貫穿於整個程序的運行。從定義它們開始直到整個程序運行結束。spa
變量能夠被聲明屢次,但只能被定義一次。聲明和定義是兩個不一樣的概念。線程
在變量定義的時候,除了向程序代表變量的類型和名稱外,還要爲變量分配存儲空間以及進行初始化操做。在定義全局變量的時候,若是沒有爲變量賦初值,那麼系統將執行默認的初始化工做,如:整型變量賦值0,字符串變量賦空值等。全局變量的值存儲在程序的全局存儲區,在全局存儲區中還存儲着常量的值,以及靜態變量的值。指針
聲明操做是定義操做的子集,在聲明變量的時候,僅僅向程序代表變量的類型和名稱,它不會爲變量分配存儲空間,所以也不會有初始化工做。對象
全局變量和全局函數的聲明和定義的格式以下:blog
//聲明接口 Void myFunction(int);//聲明一個函數,指定該函數的名稱,返回值,以及參數列表。生命週期 Extern void myFunction(int);//在聲明函數的時候,extern關鍵字可選,加上該關鍵字表示顯//示聲明。進程 Extern in myVar; //聲明一個變量。必須加關鍵字extern。代表此處僅僅是聲明,而定義在其//他地方內存 Extern const char* const myConstVar;//聲明一個指向常量的常量指針。 //定義 Void myFunction(int Para)//函數定義,包括函數的聲明部分和函數體 { //函數的功能代碼 } Int myVar = 10;//變量定義 const char* const myConstVar = new char[10];//常量定義。
Extern int myIntVar = 50;//變量的定義。 |
在聲明全局變量的時候,必須加關鍵字extern,而且不能對變量進行初始化。若是對變量執行了初始化操做,即便在前面加了extern關鍵字,也會被認爲是變量的定義,而不是變量的聲明。
聲明和定義的關係是:定義也是聲明,定義包含了聲明,但聲明僅僅是定義的一個子步驟。有的時候,聲明和定義能夠合在一塊兒進行,如局部變量的聲明和定義,而在某種狀況下,聲明和定義是須要分開進行的。當一個變量須要在多個文件中被使用的時候,就須要將聲明和定義分開(通常爲全局變量);不然使用一個定義語句同時完成變量的聲明和定義便可(通常爲局部變量)。
一個變量能夠被聲明屢次,但它只能被定義一次。在這裏存在一個法則,一次定義法則:全局對象和全局函數或者只有一次定義,或者在一個程序中有多個徹底相同的定義(內兩函數和全局常量的狀況)。
爲了遵照一次定義法則,須要合理的文件組織形式。在通常狀況下,全局做用域中定義的全局變量和全局函數都須要在多個文件中被使用。在這種狀況下,最合理的文件組織形式就是:「頭文件+cpp文件」的形式。具體的過程描述以下:
頭文件是用來聲明而不是定義函數和變量的,而源文件則是用來實現變量和函數定義的。頭文件中通常包含類的定義,枚舉的定義,符號常量的定義,內聯函數的定義,extern變量的聲明,函數的聲明,typedef聲明。若是將變量定義到頭文件中,那麼當其餘文件引入該頭文件的時候,就會認爲該對頭文件中的變量又執行了一次定義。根據一次定義法則,這是不容許的。可是也有三個例外,類,內聯函數,在編譯時值能夠肯定的符號常量是能夠在頭文件中定義的。
按照頭文件的方式處理全局變量和全局函數的具體代碼的格式以下:
//頭文件中實現函數和變量的聲明 --------------------A.h---------------------------------- Extern int myIntVar;//聲明一個變量,必須以關鍵字extern開頭。 Void myFunction(int);//聲明一個函數,指定函數名稱,返回值,參數列表。 Const double mydlVar = 3.14;//定義一個常量。該常量的值在編譯時可知。 Inline double GetdlVar()//定義內聯函數 { Return mydlVar; } Extern const char* const myConstVar;//聲明全局常量,該常量的值在編譯時不可知。
//源文件中實現變量和函數的定義。 ---------------------------A.cpp----------------------------------- #include 「A.h」 Int myIntVar = 100;//定義變量 Void myFunction(int Para) { //函數的實現代碼 } const char* const myConstVar = new char[100];//常量的值在運行時才能肯定,所以在這裏實現它的定義,而在頭文件中僅僅是聲明。 //在其餘文件中使用定義的全局變量和全局函數,首先引入該頭文件。 ----------------------------other.cpp-------------------------------- #include 「A.h」 --如下是具體使用。 |
在C++中,編譯一個程序能夠劃分爲以下的階段:
若是在一個源文件中使用了「#Include」命令,將一個頭文件引入到該源文件中。那麼在編譯器執行編譯以前,會首先執行該預處理命令。即:將頭文件中所包含的代碼所有合併的目標源文件中,合併後的文件將做爲一個文件存在。也就是說,編譯器在執行編譯的時候,只會看到源文件,頭文件在編譯階段是不可見的。
那麼,假設有以下的實現方式:在頭文件A.h中,實現了一個變量的定義(不是聲明),如:「int a = 10;」。每當該頭文件被其餘源文件引用一次之後(#include 「A.h」),那麼編譯器就會認爲對該變量執行了一次定義。若是該頭文件被引用兩次以上,就會發生重定義錯誤。這是違反一次定義法則的。
基於以上緣由,對於普通的全局變量,正確的使用方法是:首先在頭文件中使用extern關鍵字實現該全局變量的聲明;而後在源文件中引入該頭文件,而且實現該全局變量的定義;最後,在使用該全局變量的源文件中引入聲明瞭該全局變量的頭文件,而後使用之。
對於普通的全局變量,只能使用此方法處理。
在全局做用域中定義的常量只具備文件做用域。也就是說,在全局做用域中定義的常量只在定義它的文件內有效,它不會影響到其餘文件中定義的同名常量。舉例以下:
--------------------------A.h---------------------------- Const int myIntVar = 100;//在頭文件中定義了一個常量 ------------------------B.cpp---------------------------- #include 「A.h」 Int a = myIntVar; ------------------------C.cpp---------------------------- #include 「A.h」 Int b = myIntVar;
|
示例1
上面的代碼可以正確運行。雖然「A.h」頭文件被引用了屢次,可是因爲全局常量只具備文件做用域,因此在B.cpp文件中定義的全局常量「myIntVar」與在C.cpp文件中定義的全局常量「myIntVar」互不影響。
這是使用全局常量的一種方式,只要該全局常量的值在編譯時刻是肯定的。在上面的示例中,全局常量「myIntVar」不會被正真地存儲到全局區。在編譯階段,編譯器會用該全局常量的值去替換使用「myIntVar」名稱的地方。所以,要求在使用全局常量的地方,該全局常量的定義是可見的。示例1中的方式可以知足這種要求。當對符號常量使用extern關鍵字,或者取符號常量的地址的時候,編譯器會爲符號常量分配存儲空間,不然只是執行編譯時刻的替換。
如今考慮以下問題,若是全局常量的值在編譯時刻不肯定呢?好比:使用new操做符定義一個全局常量,那麼狀況會如何?舉例以下:
-------------------A.h--------------------- Char* const pChar = new char[100];//定義一個常量指針。注意與:const char* pChar = new char[100]的區別。 -------------------B.cpp------------------ #include 「A.h」
------------------C.cpp-------------------- #include 「A.h」 |
示例2
每當在源文件中引用一次該頭文件,那麼就會執行一次內存分配。這顯然與咱們指望的結果不符。咱們想要的是一個常量指針,該指針指向一個字符串。而後,咱們能夠在其餘多個文件中使用該常量指針。
可使用關鍵字extern打破const常量的文件做用域,使之具備全局做用域。具體的做法舉例以下:
-------------------------A.h--------------------- Extern char* const pChar;//聲明全局常量,該頭文件能夠被其餘文件引用 ------------------------A.cpp-------------------- #include 「A.h」 Char* const pChar = new char[100];//定義常量,編譯器分配存儲空間 Memset(pChar,’\0’,100); -------------------------B.cpp------------------- #include 「A.h」//引入頭文件,能夠在此使用聲明的全局常量 …. |
示例3
該做法是:使用關鍵字extern在頭文件中聲明全局常量。使用extern關鍵字後,該全局常量具備全局做用域,而不在侷限於文件做用域。而後在源文件中實現該全局常量的定義。最後,在使用該全局常量的源文件中引用聲明瞭該全局常量的頭文件便可。這是使用全局常量的第二種方式,當全局常量的值在編譯時刻不肯定的時候,將採用這種方式。
由此咱們能夠看出,全局常量有兩種使用方式。當全局常量的值在編譯階段是能夠肯定的狀況下,咱們可使用第一種方式,如:示例1;當全局常量的值在編譯階段是不肯定的,但在運行階段是能夠肯定的狀況下,咱們可使用第二種方式,如:示例3。
在全局做用域中定義的靜態變量也具備文件做用域。該靜態變量只在定義它的文件中有效。在多個文件中定義的同名靜態變量互不影響,不會出現重定義錯誤。而且,這些同名靜態變量各自保持一份獨立的數據拷貝,一個文件中的靜態變量值發生變化的時候,不會影響到另一個文件中的同名靜態變量的值。
靜態變量的文件做用域是不可打破的,不能使用關鍵字extern使在全局做用域中定義的靜態變量具備全局做用域。
所以,全局靜態變量只有一種使用方式,相似於使用全局常量的第一種方式。舉例以下:
-----------------------A.h------------------------- Static in myIntVar = 100; ----------------------B.h------------------------- Int myFunction1(); --------------------C.h---------------------------- Int myFunction2(); ----------------------B.cpp------------------------- #include 「A.h」 //引用定義靜態變量的頭文件,至關於在該源文件中定義了一次靜態變量 #cindlue 「B.h」 Void myFunction1() { Int a = myIntVar; myIntVar++; return a; } ----------------------------c.cpp--------------------------- #include 「A.h」//引用定義靜態變量的頭文件,至關於在該源文件中定義了一次靜態變量 #include 「C.h」 Int myFunction2() { Int b = myIntVar; myIntVar++; return b; } -----------------------------.main.cpp----------------------------- #include 「B.h」 #include 「c.h」 Void main() { Int a = myFunction1(); Int b = myFunction2(); //在執行完畢後,a,b的值均爲100。說明在文件做用域中的靜態變量各自保持一份獨立的數據拷貝,互相不影響。 } |
基於以上緣由,全局靜態變量定義在源文件中便可,哪裏須要,哪裏定義。不須要事先定義到頭文件中。
注意全局常量與全局靜態變量的區別:默認狀況下,全局常量和全局靜態變量都具備文件做用域。可是,能夠適應關鍵字extern,打破全局常量的文件做用域,使其在其餘文件中也具備可訪問性;不能使用關鍵字extern打破全局靜態變量的文件做用域。
在編譯階段,編譯器會將內聯函數在調用點展開,將函數體中的代碼合併到調用點。而不是在運行階段執行壓棧,出棧方式的函數調用。在編譯器展開內聯函數的時候,在當前文件中,內聯函數的定義必須是可見的。所以,內聯函數必須定義在頭文件中,當其餘的源文件引入了該頭文件後,就至關於該內聯函數的定義在該源文件中是可見的。根據一次定義法則,內聯函數能夠屢次定義,只要保證屢次定義的形式是相同的便可。
使用頭文件和源文件結合的方式完成一個類的定義。類的定義,內聯函數的定義,靜態成員變量的聲明實如今頭文件中;類成員函數的定義,靜態成員變量的定義實如今源文件中。在實現源文件的時候,必須引入定義該類的頭文件。具體的示例代碼以下:
---------------------------A.h------------------------------------ Class myClass { Typedef int sb4;//typedef定義,引入int類型的助記符。 Public: myClass(sb4 Para);//聲明構造函數 //內聯函數的聲明在當前位置解析,內聯函數的//定義在整個類域解析 inline void setValue(sb4 Para)//內聯函數的聲明部分。sb4能夠直接被使用,由於在它以前已經聲明瞭sb4。 { M_IVar = Para;//內聯函數的定義部分。在整個類域解析,因此能夠直接使用成員變量m_IVar。 } Static sb4 getValue();//聲明靜態成員函數 Private: Sb4 m_IVar;//定義成員變量 Static sb4 m_StaticVar;//聲明靜態變量 };//在整個花括號範圍內,都屬於類域 ----------------------------A.cpp--------------------------------- #include 「A.h」//必須引入頭文件 myClass::sb4 myClass::m_StaticVar = getValue();//靜態成員變量的定義,並初始化。紅色部分屬於類域,成員函數getValue()能夠被直接調用;綠色部分不屬於類域,必須使用修飾限定名稱引用。類型sb4必須使用修飾限定名稱引用。 myClass::myClass(sb4 Para) { M_IVar = Para;//成員變量的初始化 }//紅色部分屬於類域,類型sb4能夠直接使用,成員變量m_IVar能夠直接使用。 myClass::sb4 myClass::getValue()//靜態成員函數的定義。綠色部分不屬於類域,必須使用修飾限定名稱引用。類型sb4必須使用修飾限定名稱引用。 { Return m_StaticVar; } |
在上面的代碼中,將類定義分紅了兩大部分,分別在頭文件和源文件中實現。頭文件是對外的接口,源文件中封裝具體實現。
每定義一個類就會引入一個類域,不一樣的類具備不一樣的類域。每個類成員,包括成員變量和成員函數都屬於該類域。在類域內部,能夠直接使用成員名稱訪問類成員;在類域外部,必須經過成員訪問操做符或域解析操做符訪問類成員。類域由以下三部分組成:
在源文件中,成員函數修飾限定名稱或者靜態變量修飾限定名稱以前的部分不屬於類域。若是要在這部分引用類域中的名稱,必須使用修飾限定名。見2.4.1節的綠色部分。
在源文件中,屬於類域中的成員名稱能夠被直接使用,不須要修飾限定。見2.4.1節紅色部分。
當須要在某個程序文本文件中使用某個類的時候,須要在該文件中引入實現該類定義的頭文件。在使用類成員的時候,有兩種形式,分別是:在類域外部使用類的某個成員和在類域內部使用類的某個成員。
狀況1:在類域外部使用某個類的成員的時候,必須使用成員訪問操做符或域解析操做符。具體代碼以下:
//開始在類外部使用該類的成員 Void main() { myClass objClass;//定義類對象; myClass* pClass = new myClass;//定義類指針。 objClass. setValue (100);//在類域外部,使用類成員訪問操做符-點號的形式訪問類成員。 pClass-> setValue (100);//在類域外部,使用類成員訪問操做符-箭頭的形式訪問類成員。 myClass:: getValue ();//在類域外部,使用域解析操做符的形式訪問類的靜態成員。 myClass::sb4 myVar = 100;//在類域外部,使用域解析操做符的形式使用類中定義的類型。 } |
在類域外部,對於類對象或指向類對象的指針,須要使用成員訪問操做符訪問類成員;對於類的靜態成員或在類中定義的類型,須要使用域解析操做符進行訪問。
狀況2:在類域內部使用某個類的成員的時候,能夠直接使用成員名稱。可是,該成員名稱在使用之間必須被聲明,不然沒法使用。具體代碼以下:
----------------------A.h----------------------------- Class myClass { Void setValue(sb4 Para);//錯誤。不能在此位置使用類型sb4,由於在使用以前沒有聲明該類型。 Typedef int sb4; Void setValue(sb4 Para);//正確。能夠在此位置使用類型sb4,由於在使用之間已經聲明瞭該類型。 }; |
由此能夠看出,類成員的聲明順序很重要,它會影響到在類域內部對類成員的使用。
特例:在這裏存在一個特殊狀況,當在內聯函數中使用類成員的時候,內聯函數的聲明部分在當前位置解析,內聯函數的定義部分,在整個類域中解析。所以,在內聯函數的聲明部分,只能使用在它以前所聲明的名稱,遵照狀況2所描述的規則;在內聯函數的定義部分,能夠直接使用整個類域中聲明的名稱。具體代碼見2.4.1部分關於內聯函數的示例。
在局部做用域中定義的變量是局部變量,局部變量又能夠進一步劃分爲:自動變量,寄存器變量,以及靜態局部變量。在C++程序中,除了局部變量外,還存在着其餘類型的變量,如:全局變量,常量,靜態變量,以及使用new操做符定義的變量,這些變量在內存中的分佈狀況以下圖所示:
當啓動一個應用程序的時候,就會啓動一個進程。在32的系統中,爲該進程分配4G的內存空間。一個進程下面,又會根據須要啓動若干個線程,其中一個線程是主線程。在這4G的內存空間中,包含以下類型的存儲區域:
不管是變量,函數,枚舉,指針,引用仍是類型(包括內置類型或類類型),在使用以前,它們必須被聲明或者定義。舉例以下:
Int a = 10; a = a + 1; class myClass;//聲明一個類
void myFunction(myClass& objClass)//定義一個函數。該函數的參數爲myClass類型的引用。雖然在這裏myClass類尚未被定義,可是這是容許的。 { } myClass * pClass = NULL;
class myClass { Public: Int m_IValue; } pClass = new myClass; myClass objClass; myFunction(objClass);//調用函數。 |
在語句「int a = 10;」中,若是要定義變量「a」,那麼類型「int」必須首先被定義。由於類型int爲內置類型,在使用該類型以前,它已經被定義了。因此在語句「int a = 10;」中,編譯器知道爲變量a分配四個字節的內存,而且在該內存位置存儲數據10。若是在執行該語句以前,類型int是未知的,那麼將會出現未定義錯誤。
在語句「a = a + 1;」中,若是要執行該語句,那麼變量a必須在該語句以前被定義。在執行該語句的時候,從變量a所對應的內存中取出數據,加1後再存儲到變量a所對應的內存中。若是在以前沒有對變量a定義,也就是說沒有爲變量a分配內存,那麼在執行該語句的時候,將會出現錯誤。
在語句「myClass * pClass = NULL;」中,該語句可以被正確執行,由於在該語句的前面,已經聲明瞭myClass類。注意,這裏僅僅是聲明,類myClass尚未被定義。但這是容許的,由於對於任何類型的指針變量,它的大小都是固定的四個字節,在知道該類聲明的狀況下,就能夠定義指針變量,而且爲該指針變量分配四個字節的內存。在這時候,屬於該指針的內存並無存儲myClass對象的地址,由於myClass類尚未被定義,編譯器不知道爲該對象分配多少內存,該指針被初始化爲NULL。當完成myClass類的定義之後,在執行語句「pClass = new myClass;」的時候,編譯器知道了須要爲myClass類型對象分配多大的內存。因此,開始定義一個myClass類型的對象,而且將它的地址存儲在指針pClass所屬的內存中。
在定義類對象指針的時候,能夠分兩步進行:第一步:完成類的聲明,在類聲明以後能夠定義該類的指針,該指針還不能被初始化爲有意義的值,通常指向NULL。第二布:完成類的定義,在此以後,能夠定義類的對象,而且將類對象的地址賦給指針。
根據上面的規則,在一個類定義的內部,能夠定義該類自身類型的指針,可是不容許定義該類自身類型的對象。舉例以下:
Class Node { Public: Node * pNext;//正確,這是容許的。編譯器只分配四個字節的內存。 Node objNext;//錯誤。類定義還沒有完成,編譯器不知道分配多少內存。 };
Node objNode;//正確,類定義已經完成,編譯器知道應該分配多少內存。 |
在定義某個類型的指針或引用的時候,該類型能夠先聲明,而後在後續部分實現定義;在使用變量,函數,枚舉等對象的時候,這些對象必須被提早定義。
在C++中,若是要使用一個對象實體(如:內置類型,類類型,用戶定義的變量,函數,指針等),那麼該對象實體在被使用以前必需要被聲明或定義。編譯器在編譯C++程序的時候,若是在程序代碼中發現一個實體名稱(如上面代碼中的int,a,myClass等),那麼編譯器就會在C++的各類做用域中查找該名稱的聲明或定義(由於須要知道該對象實體內存的大小,內存地址,以及內存中的值)。咱們將這一過程叫作名字解析。
這些可以被編譯器查找的做用域包括:全局做用域,名字空間做用域,類域,局部做用域等。在該節主要講述兩個問題:
名字解析的過程和順序以下圖所示:
在名字解析的過程當中,名字解析的順序是:從名字的被使用位置開始,從小做用域到大做用域的順序進行查找,當查找到該名字的聲明或定義後,名字查找動做結束。
當在兩個不一樣的做用域中定義相同名稱的實體的時候,小做用域中定義的實體會隱藏大做用域中定義的實體。由於編譯器從小做用域開始查找,找到第一個名稱的定義或聲明後,名字查找動做中止,因此小做用域中定義的名稱會隱藏大做用域中定義的名稱。
在上圖中,線索1的查找順序是:類域,名字空間做用域,全局做用域;線索2的查找順序是:局部做用域,名字空間做用域,全局做用域;線索3的查找順序是:局部做用域,類域,名字空間做用域,全局做用域;線索4的查找順序是:名字空間做用域,全局做用域。
根據名字被使用的位置,能夠將名字解析劃分紅以下的狀況:
Int a = 10;//被隱藏 Namespace mySpace [ Int a = 100;//在此處找到a變量的定義,查找中止。若是這裏不定義a變量,那麼查找將會繼續向前,直到在到全局做用域中找到a變量的定義。 a = a + 1;//在使用a的時候,開始從該位置向前查找a的定義。 a = ::a + 1//使用全局做用域中被隱藏的變量。 } |
Typedef short myData;//該類型定義被mySpace中的定義隱藏。 Namespace mySpace { Typedef int myData;//查找到此處,找到myData名稱的定義,名字解析結束。 Class myClass { myClass(myData Para);//今後位置向前查找。Para的類型爲int。
typedef double myData;//該位置不會被查找到。 } } |
-------------------------------------A.h-------------------------------------- Int myData = 10;//被隱藏 Namespace mySpace { Int myData = 100;//被隱藏 Class myClass { Public: Void myFunction(); int myData;//變量的名字解析在此處找到。 };
Void DealData()//函數的名字解析在此處找到 { } }
-------------------------------A.cpp-------------------------------- #include 「A.h」 Void myClass::myFunction() { DealData(myData);//在這裏,須要進行兩次名字解析。首先是函數DealData()的名字解析,由於在調用該函數的時候,在前面的全局做用域中已經完成了該函數的定義,所以名字解析成功。第二個名字解析的是變量myData,在整個類域中找到了該變量的定義。 } |