每個類都會定義自己的作用域,在類的作用域之外,普通數據和函數只能由對象、引用、指針或者成員訪問符來訪問。
一個類就是一個作用域的事實很好地解釋了為什么類外定義成員函數時必須提供類名和函數名。在類的外部,成員的名字被隱藏起來了。
一旦遇到類名,定義的剩余部分就在類的作用域之內,這里的剩余部分包含參數列表和函數體,結果就是可以直接使用類的其它成員而無須再次授權了。
void Window_mgr::clear(ScreenIndex i)
{
Screen &S = screen[i];
s.contents = string(s.height*s.width,' ');
}
編譯器在處理參數列表之前已經明確了當前正位於 Window_mgr 類的作用域之內,所以在參數列表和函數體內無須再聲明類作用域。
但是,如果如果函數的返回類型是位於累的作用域之外,因為返回類型位於函數名之前,所以,此時需要顯示的指明它屬於哪個類。
Window_mgr::ScreenIndx Window_mgr::addScreen(const Screen &S)
{
screens.push_back(s);
}
名字查找與類的作用域
名字查找是尋找與所用名字最匹配的聲明的過程:
- 首先,在名字所在的塊中尋找聲明語句,只考慮在名字的使用之前的出現的聲明。
- 如果沒有找到,繼續查找外層作用域。
- 如果最終沒有找到匹配的名字,則程序報錯。
類的定義分兩步:
- 首先編譯成員的聲明。
- 直到類全部可見后才編譯函數體。
也就是說編譯器處理完類中全部聲明之后才會處理成員函數的定義。
因為成員函數體直到整個類可見后才會被處理,所以它能使用類中定義的任何名字。
用於類成員聲明的名字查找
兩階段的處理方式只適用於成員函數中使用的名字。如果某個成員的聲明使用了類中尚未出現的名字,則編譯器將會在定義該類的作用域中繼續查找 。
typedef double Money;
string bal;
class Account
{
public:
Money balance() { return bal; }
private:
Money bal;
};
編譯器看到 balance
函數的聲明時,會在 Account
類的范圍內查找對應的 Money
聲明,沒有中找到,繼續在外層作用域中查找,將會找到 Money
是定義的類型別名。
Money
被用作數據成員 bal
的定義,所以 balance 函數返回的 bal
是 Account
類的成員而不會是外層作用域的 string
對象。
類型名需要特殊處理
一般來說,內層作用域可以重新定義外層作用域的名字,即使該名字已經在內層作用域中使用過。
但是在類中,如果成員使用外層作用域中的某個名字,而該名字代表了一種類型,則該類不能在之后重新定義該名字。
typedef double Money;
class Account
{
public:
Money balance() { return bal; }
private:
typedef double Money; //錯誤,不能重新定義 Money,即使與外層定義的一致,也是錯誤
Money bal;
};
注意:
- 重新定義類型名字是一種錯誤的行為,但是編譯器不對此負責,一些編譯器可以順利通過這些代碼。
- 類型名一般定義在類的開始處,這樣能夠保證所有使用該類型的成員都出現在該類名定義之后。
成員定義中的普通塊作用域的名字查找
成員函數中使用的名字按照如下方式解析:
- 首先。在成員函數內查找該名字的聲明。只有在函數使用之前出現的聲明才被考慮。
- 如果在成員函數中沒有找到,則在類中繼續查找,這時類的所有成員都可以被考慮。
- 如果類內沒有找到名字,在成員函數定義之前的作用域內繼續查找。
int height;
class Screen
{
public:
typedef std::string::size_type pos;
void dummy_fcn(pos height){
cursor = width * 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
指針來強制訪問成員。
但其實最好的方法是在成員函數的參數列表中不使用類中出現的成員名稱。
void Screen::dummy_fcn(pos ht){
cursor = width * this->height; //成員height
}
類作用域之后,在外圍的作用域中查找
如果編譯器在函數和類的作用域中都沒有做找到名字,它將在接着在外圍的作用域中查找。
例如上面的例子中,外層作用域中的height被累的成員隱藏,如果需要使用外層作用域的名字,可以顯示地通過作用域符來進行訪問:
void Screen::dummy_fcn(pos height){
cursor = width * ::height; //全局作用域的height
}
盡管外層的對象被隱藏了,但仍然可以使用作用域操作符來使用它。
在文件中名字出現處對其進行解析
int height;
class Screen
{
public:
typedef std::string::size_type pos;
void setHeight(pos);
pos height = 0; //隱藏了外層作用域中的height
};
Screen::pos verify(Screen::pos); //全局函數
void Screen::setHeight(pos var)
{
height = verify(var); //height是類的成員,
}
verify
的聲明在 Screen
類的定義之前是不可見的。但是,名字查找的第三步包括了成員函數出現之前的全局作用域,verify
的聲明位於 setHeight
的定義之前,所以可以正常使用。