7.4 類的作用域
一個類就是一個作用域 ,在類中定義的類型,需要通過類作用域運算符訪問。
class A{
public:
using pos = int;
};
//通過作用域限定符來訪問
A::pos temp_a;
因爲類本身就是一個作用域,所以我們在類外定義成員函數時,類的數據成員對我們來說是不可見的,所以在類外定義成員函數,需要加上加上類的作用域。
class A{
public:
using pos = int;
pos get_value(int pos);
};
//返回值類型需要加A::,函數體中的內容以及在類的作用域範圍之內,所以不用加
A::pos A::get_value(int pos) {
//todo
}
我們會在函數名字前面加上A的作用域,表示這個函數是A中定義的,屬於A的作用域範圍,所以A::後面的內容,可以直接訪問函數中的成員。
但是返回值類型在函數名的前面,所以如果成員函數在外部定義,則返回值類型在類的作用域之外,如果返回值類型使用了類中定義的成員,則要使用類作用域運算符。
練習
7.33
因爲pos類型是在Screen類中定義的,從該成員函數的定義可以看出,這個成員函數是在類的外部定義的,而返回值類型pos,是在類中定義的,所以需要爲返回值pos加上類作用域運算符。
Screen::pos Screen::size() const{
return height*width;
}
名字查找和類的作用域
所謂的名字查找,就是說我們定義一個變量,它是變量名,如果我們在某一個地方使用了變量名,我們需要知道這個變量名具體指的是哪個變量。
在沒有學習類得知識時,名字查找按照這樣的流程。
1.首先在定義變量的塊中尋找聲明語句,這隻能從使用該變量的前面的語句開始找。
2.如果沒有找到,就在外層作用域找,只能從定義該塊的前面的語句中找。
3.如果沒有找到就再往更外層的作用域找,沒有找到就報錯。
int number;
{
int height;
int width;
number;
rgb;//rgb在外層作用域中雖然由定義,但是是在塊的後面定義的
}
int rgb;
在類中,規則有些許不同。
書上這麼說:
類的定義分爲兩步:
1.編譯成員的聲明
2.知道類全部可見後才編譯函數體
這麼說可能聽不懂。
換一種方式,
類的定義,首先編譯數據成員和成員函數的聲明。
當數據成員和成員函數的聲明都編譯完成之後,才編譯函數體。
這樣一來,在函數體中,我們可以隨意訪問數據成員和成員函數,而無需關注數據成員和成員函數在類中的位置。
但是數據成員和成員函數的聲明需要關注。
class Account {
public:
Money balance() { return bal; };//報錯
Money bal;//報錯
using Money = double;
};
比如在這個例子中,類型別名定義在最後面,所以前面的兩個語句首先在自己的語句之前的類的作用域中,查找Money,如果沒有,則取外層作用域,如果一直找不到就報錯。
而下面的這段代碼中,函數balance,首先在聲明之前的類的作用域中查找Money,沒有,則從外層作用域開始找,因爲找到了,所以編譯通過。
類中的bal數據成員,同樣是在外層找到了Money的定義。
所以函數的成員的聲明都處理完了,開始編譯函數 體的內容,此時函數體中可以看到類中的全部成員,所以balance中訪問的bal是類定義的bal,而不是外層作用域的bal。
我們知道如果外層作用域和內層作用域中的名字重疊了,那麼內層的名字會隱藏外層的名字,但是我們還是可以調用的。
如果bal是一個全局變量,我們可以使用
::bal來訪問它。
using Money = double;
Money bal = 123;
class Account {
public:
Money balance() {
//這裏的bal是類的bal
cout << bal << endl;
return bal; };
Money bal=3.33;
};
對於類型名的注意
如果我們在類的內部,定義了類型別名,那麼我們最好將類型別名寫在類的最開始,這樣可以保證類中使用該類型的變量,都屬於類自己定義的類型別名,而不是外層作用域的。
C++ Primer中說,如果類中和類外如果定義同樣的類型別名,會重新定義的錯,但是我在VS2017中編譯是通過的。
以下代碼在vs2017中是編譯通過的,也能運行
using Money = double;
Money bal = 123;
class Account {
public:
using Money = string;
Money balance() {
cout << bal << endl;
return bal; };
Money bal= "33333";
};
以下的代碼會導致,類中的類型混亂,所以我們最好將類型別名的定義寫在類的最開始處。
using Money = double;
Money bal = 123;
class Account {
public:
//使用外層的 temp也是外層作用域
Money balance(Money temp) {
//使用類中的
cout << bal << endl;
return bal; };
using Money = string;
//使用類中的
Money bal= "33333";
};
關於數據成員和成員函數的聲明的名字查找就算總結完了。
下面就是函數體中的名字查找。
1.首先會在當前函數體中查找這個名字的聲明(在使用該名字的語句之前查找)
2.沒有找到,則在類中查找
3.沒有類沒有在成員函數定義之前的作用域繼續查找。
注意第3點,因爲有一些成員函數,我們是在類的外部定義的,所以類定義之前和成員函數定義之前並不能表示同一個範圍。
using Money = string;
Money bal = "外層作用域";
class Account {
public:
using Money = string;
Money balance(Money bal) {
cout << bal << endl;
cout << this->bal << endl;
cout << ::bal << endl;
return bal; };
Money bal= "類內作用域";
};
Account account;
account.balance("函數體內作用域");
因爲函數中的變量和類的數據成員同名,所以balance中直接訪問bal其實是訪問該函數中的bal。想要訪問數據成員bal需要顯式的用this->,想要訪問全局作用域的bal,使用::bal;
再次強調,如果成員函數在外部定義,則第3步的名字查找是從成員函數定義之前的語句開始,而不是從類定義之前的語句開始。
練習
7.34
報錯,因爲數據成員和成員函數的聲明還是按照順序來查找名字的,把pos定義在最後面,那麼類中其他使用pos類型的變量就找不到pos類型了。
7.35
typedef string Type;
//stirng 類型
Type initVal();
class Excercise {
public:
typedef double Type;
//double double
Type setVal(Type);
//double
Type initVal();
private:
int val;
};
//string double
Type Excercise::setVal(Type parm) {
// 類內部的
val = parm + initVal();
return val;
}
只要把setVal的返回值類型加上類的作用域就可以了。
Excercise::Type Excercise::setVal(Type parm) {
// 類內部的
val = parm + initVal();
return val;
}