每一個類都會定義它本身的做用域。在類的做用域以外,普通的數據和函數成員只能由對象、引用或者指針使用成員訪問運算符來訪問。對於類類型成員則使用做用域運算符訪問。不論哪一種狀況,跟在運算符以後的名字都必須是對應類的成員:函數
Screen::pos ht=24,wd=80; //使用Screen定義的pos類型this
Screen scr(ht,wd,' ');spa
Screen *p=&scr;指針
char c=scr.get(); //訪問scr對象的get成員code
c=p->get(); //訪問p所指對象的get成員對象
做用域和定義在類外部的成員blog
一個類就是一個做用域的事實可以很好地解釋爲何當咱們在類的外部定義成員函數時必須同時提供類名和函數名。在類的外部,成員的名字被隱藏起來了。作用域
一旦遇到了類名,定義的剩餘部分就在類的做用域以內了,這裏的剩餘部分包括參數列表和函數體。結果就是,咱們能夠直接使用類的其餘成員而無須再次受權了。get
例如,Window_mgr類的clear成員,該函數的參數用到了Window_mgr類定義的一種類型:編譯器
void Window_mgr::clear(ScreenIndex i) { Screen &s=screens[i]; s.contents=string(s.height*s.width,' '); }
由於編譯器在處理參數列表以前已經明確了咱們當前正位於Window_mgr類的做用域中,全部咱們沒必要再專門說明ScreenIndex是Window_mgr類定義的。出於一樣的緣由,編譯器也能知道函數體中用到的screens也是在Window_mgr類中定義的。
另外一方面,函數的返回類型一般出如今函數名以前。所以當成員函數定義在類的外部時,返回類型中使用的名字都位於類的做用域以外。這時,返回類型必須指明它是哪一個類的成員。例如,咱們可能想Window_mgr類添加一個新的名爲addScreen的函數,它負責先顯示器添加一個新的屏幕。這個成員的返回類型將是ScreenIndex,用戶能夠經過它定位到指定的Screen:
class Window_mgr{ public: //向窗口添加一個Screen,返回它的編號 SceenIndex addScreen(const Screen&); }; //首先處理返回類型,以後咱們才進入Window_mgr的做用域 Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen &s) { screens.push_back(s); return screens.size()-1; }
由於返回類型出如今類名以前,全部事實上它是位於Window_mgr類的做用域以外的。在這種狀況下,要想使用ScreenIndex做爲返回類型,咱們必須明確指定哪一個類定義了它。
名字查找與類的做用域
到目前爲止,咱們編寫的程序中,名字查找的過程比較直截了當:
對於定義的類內部的成員函數來講,解析其中名字的方式與上述的查找規則有所區別。類的定義分兩步處理:
編譯器處理完類中的所有聲明後纔會處理成員函數的定義。
按照這種兩階段的方式處理類能夠簡化類代碼的組織方式。由於成員函數體直到整個類能夠後纔會被處理,因此它能使用類中定義的任何名字。相反,若是函數的定義和成員的聲明被同時處理,那麼咱們將不得不在成員函數中只使用那些已經出現的名字。
用於類成員聲明的名字查找
這種兩階段的處理方式只適用於成員函數中使用的名字。聲明中使用的名字,包括返回類型或者參數列表中使用的名字,都必須在使用前確保可見。若是某個成員的聲明使用了類中還沒有出現的名字,則編譯器將會在定義該類的做用域中繼續查找。例如:
typedef double Money; string bal; class Account{ public: Money balance() { return bal;} private: Money bal; //... };
當編譯器看到balance函數的聲明語句時,它將在Account類的範圍內尋找對Money的聲明。編譯器只考慮Account中在使用Money前出現的聲明,由於沒找到匹配的成員,因此編譯器會接着到Account的外層做用域中查找。在這個例子中,編譯器會找到Money的typedef語句,該類型被這樣balance函數的返回類型以及數據成員bal的類型。另外一方面,balance函數體在整個類可見後才被處理,所以,該函數的return語句返回名爲bal的成員,而非外層做用域的string對象。
類型名要特殊處理
通常來講,內層做用域能夠從新定義外層做用域中的名字,即便該名字已經在內層做用域中使用過。然而在類中,若是成員使用了外層做用域中的某個名字,而該名字表明一種類型,則類不能在以後從新定義該名字:
typedef double Money; string bal; class Account{ public: Money balance() { return bal;} //使用外層做用域的Money private: typedef double Money; //錯誤:不能從新定義Money Money bal; //... };
須要特別注意的是,即便Account中定義的Money類型與外層做用域一致,上述代碼仍然是錯誤的。
儘管從新定義類型名字是一種錯誤的行爲,可是編譯器並不爲此負責。一些編譯器仍將順序經過這樣的代碼,而忽略代碼有錯的事實。
類型名的定義一般出如今類的開始處,這樣就能確保全部使用該類型的成員都出如今類名的定義以後。
成員定義中的普通塊做用域的名字查找
成員函數中使用的名字按照以下方式解析:
通常來講,不建議使用其餘成員的名字做爲某個成員函數的參數。不過爲了更好的理解名字的解析過程,咱們不妨在dummy_fcn函數中暫時違反一下這約定:
//一般狀況下不建議爲參數和成員使用一樣的名字
int height; //定義了一個名字,稍後將在Screen中使用
class Screen{
public:
typedef string::size_type pos;
void dump_fcn(pos height){
cursor=width*height; //哪一個height?是哪一個參數
}
private:
pos cursor=0;
pos height=0,width=0;
};
當編譯器處理dummy_fcn中的乘法表達式時,它首先在函數做用域內查找表達式中用到的名字,函數的參數位於函數做用域內,所以,dummy_fcn函數體內用到的名字height指的是參數聲明。
在此例中,height參數隱藏了同名的成員。若是想繞開上面的查找規則,應該將代碼變爲:
//不建議的寫法:成員函數中的名字不該該隱藏同名的成員
void Screen::dummy_fcn (pos height){
cursor=width*this->height; //成員height
//另一種表示該成員的方式
cursor=width*Screen::height; //成員height
}
儘管類的成員被隱藏了,但咱們仍然能夠經過加上類的名字或顯式地使用this指針來強制訪問成員。
其實最好的確保咱們使用height成員的方法是給參數起個其餘的名字:
//建議的寫法:不要把成員名字做爲參數或其餘局部變量使用
void Screen::dummy_fcn(pos ht){
cursor=width*height; //成員height
}
在此例中,當編譯器查找名字height時,顯然在dummy_fcn函數內部是找不到的。編譯器接着會在Screen內查找匹配的聲明,即便height的聲明出如今dummy_fcn使用它以後,編譯器也能正確地解析函數使用的是名爲height的成員。
類做用域以後,在外圍的做用域中查找
若是編譯器在函數和類的做用域中都沒有找到名字,它將接着在外圍的做用域中查找。在咱們的例子中,名字height定義在外層的做用域中,且位於Screen的定義以前。然而,外層做用域中的對象被名爲height的成員隱藏了。所以,若是咱們須要的是外層做用域中的名字,能夠顯式地經過做用域運算符來進行請求:
//不建議的寫法:不要隱藏外層做用域中可能被用到的名字
void Screen::dummy_fcn(pos ht){
cursor=width*::height; //成員height
}
儘管外層的對象被隱藏了,但咱們仍然能夠用做用域運算符訪問它。
在文件中名字的出現處對其進行解析
當成員定義在類的外部時,名字查找的第三步不只要考慮類定義以前的全局做用域中的聲明,還須要考慮在成員函數定義以前的全局做用域中的聲明。例如:
int height; //定義了一個名字,稍後將在Screen中使用 class Screen{ public: typedef string::size_type pos; void setHeight(pos); pos height=0; //隱藏了外層做用域中的height }; Screen::pos verify(Screen::pos); void Screen::setHeight(pos var) { //var: 參數 //height:類的成員 //verify:全局函數 height=verify(var); }
請注意,全局函數verify的聲明在Screen類的定義以前是不可見的。然而,名字查找的第三步包括了成員函數出現以前的全局做用域。在此例中,verify的聲明位於setHeight的定義以前,所以能夠被正常使用。