C++ Primer 第七章 7.4 練習和總結

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;
}
發佈了46 篇原創文章 · 獲贊 6 · 訪問量 3112
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章