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

7.3 類的其他特性

7.3.1類成員再探

在類中,我們也可以使用爲數據類型啓類型別名

class Screen{
	public:
	using pos = string::size_type;
	private:
	pos width=0,height=0;//類內初始值初始化
	pos cursor {0};//初始化列表初始化
	string contents;
};

至於爲什麼這個類型別名,要使用public訪問限定符來修飾,我認爲這是由於類的使用者,有可能用到這個數據結構。所以需要設置爲public。

類型別名也可以使用typedef來定義。

成員函數作爲內聯函數

直接在類中聲明和定義的函數,會被默認修飾爲內聯函數。

我們也可以顯式的使用inline關鍵字修飾成員函數,表示該函數時內聯的。我們可以在類中聲明的時候就將函數聲明爲inline,也可以在定義的時候聲明爲inline。

Class_A{
	public:
	void func_1(){};//在類中定義爲inline
	inline void func_2();//顯式的定義爲inline
	void func_3();//在外部定義的時候,修飾爲inline
};

void Class_A::func_2(){
	//todo
}

inline void Class_A::func_3(){
	
}

注意,和之前定義內聯函數一樣,我們一般在頭文件中聲明和定義內聯函數。

類的成員函數同樣可以重載,根據形參列表中的形參數量和類型進行區別。

可變數據成員

之前說過,如果一個成員函數的內部沒有修改數據成員的值,或者說,不允許修改數據成員的值,我們可以將這個函數聲明爲常量函數。

但是C++提供了mutable關鍵字,用該關鍵字修飾的數據成員,即使是在常量函數中,也可以修改其值。

Class Class_A{
	private:
	mutable int count;
	public:
	void do_something() const {
		++count;
	}
};

從const變量可以使用const_cast,修改爲非const,和mutable修飾的數據成員可以在常量函數中修改,可以看出C++語言確實靈活,有些規則並不是死的,但是我覺得這種靈活也導致了混亂= =

類數據成員的初始值

在這篇總結的最前面我就寫了,類數據成員的初始值可以使用類內初始值,和列表初始化。

類內初始值使用=進行賦值,而列表初始化,則使用一對花括號。

練習
7.23
7.24

class Screen {
public:
	using pos = string::size_type;
	Screen() = default;
	Screen(pos w, pos h) :width(w), height(h), contents(w*h,' ') {};
	Screen(pos w, pos h, char c) :width(w), height(h), contents(w*h, c) {};

private:
	pos width = 0, height = 0;//類內初始值初始化
	pos cursor{ 0 };//初始化列表初始化
	string contents;


};

注意構造函數需要是public的,因爲如果爲private,則在類的外部沒法訪問。

7.25
可以依賴默認的版本,因爲Screen類中,沒有涉及動態分配內存的變量。

7.26

也可以試一下,上文總結的三種將函數定義爲inline的方法。

inline double avg_price()const;

7.3.2 返回*this的成員函數

如果成員函數返回*this,那麼我們就可以使用鏈式調用

如果我沒有記錯的話,應該叫鏈式調用

即:


class Class_A{
public:
	Class_A& move() {
		//todo
		return *this;
	}
	
	const Class_A & move() const {
	return *this;
}
	const Class_A& set() const  {
		//todo
		return *this;
	}
};

這樣在調用Class_A的對象的成員函數時,我們可以這麼寫

Class_A a;
a.move().move().move()

需要注意的是,如果調用的成員函數常量成員函數,返回的類型則必須爲常量引用,如果不這麼寫,函數是會報錯的。

上面代碼的set函數,返回的就是常量引用。

常量成員函數和非常量成員函數是可以重載的。

上面的代碼中兩個move可以實現重載,調用move時,根據類的對象是否是常量,調用相應的類型。
如果對象是常量,則調用常量版本的move,如果不是常量則調用非常量版本的move。

練習

7.27

class Screen {
public:
	using pos = string::size_type;
	Screen() = default;
	Screen(pos w, pos h) :width(w), height(h), contents(w*h, ' ') {};
	Screen(pos w, pos h, char c) :width(w), height(h), contents(w*h, c) {};
	//將光標移動到第幾行的第幾個
	inline Screen& move(pos r, pos c) {
		//假定第一行從0開始計數,如果從1開始計數從row中,r需要減一
		pos row = r * width;
		cursor = row + c;
		return *this;
	}

	inline Screen& set(char c) {
		contents[cursor] = c;
		return *this;
	}

	inline Screen& set(pos row,pos col,char ch) {
		contents[row*width + col ]= ch;
		return *this;
	}
	inline Screen& display(ostream& temp_cout) {
		//temp_cout << contents;
		do_display(temp_cout);
		return *this;
	}
	inline const Screen& display(ostream& temp_cout) const {
		//temp_cout << contents;
		do_display(temp_cout);
		return *this;
	}
	inline void do_display(ostream& temp_cout) const {
		temp_cout << contents;
	}
	//intline Screen& set(cah)
private:
	//屏幕的寬高
	pos width = 20, height = 80;//類內初始值初始化
	//光標的位置
	pos cursor{ 0 };//初始化列表初始化
	//屏幕內容
	string contents;
};

爲什麼重載的display中,寫的邏輯只有那麼一丟丟,卻還是要調用一個do_display,總結書上的內容就是一句話,因爲這些代碼往往是重複的,通過放入另一個函數中,可以有效的減少重複代碼。也讓我們在修改代碼的時候,減輕工作量。

7.28

第一次打印會照常輸出,但是第二次打印只會輸出X。

7.30
優點:
顯式調用可讀性更高,尤其是數據成員的變量名字和形參的變量名字一樣時

缺點:
很枯燥,每個數據成員前面都要寫this->,這增加的程序員的工作量。

7.3.3 類類型

兩個類名不同,但是數據成員和成員函數一摸一樣的類,它們不是同一個類。

我們可以認爲類名就是類類型。

Class Class_A{
	//todo
};

Class_A a;
class Class_A a;

這兩個定義變量的方式是等價的。

我們可以在文件的最前面聲明一個類,然後再後面定義它。

class Class_A;

這樣的聲明叫做前向聲明,此時它是一個不完整類型,即使沒有對該類進行定義,我們仍然可以用該類作一些有限的操作,比如,定義Class_A的引用或者指針。

對於其他的操作,由於Class_A沒有定義,所以無法操作。

只有我們定義完一個類,編譯器才能夠知道一個類所需的存儲空間大小,所以我們無法在一個類中定義該類的變量(引用和指針除外);

練習

7.31

struct X {
	Y* y;
};

class Y {
	X x;
};

7.3.4 友元再探

之前我們只是將外部函數聲明爲某一個類的友元。其實我們可以將另一個類,或者另一個類的成員函數聲明爲其他類的友元。

需要注意的是,如果把一個類或者一個類的成員函數聲明爲另外一個類的友元

這種情況下,需要記住。

1.如果B中使用A類作爲友元,則需要在B類之前聲明A類。


class A
class B{
	friend class A;
	public:
	void f_b_1();
};
class A{
public:
	void f_a_1();
};

B可以訪問A中的所有非公開成員。

2.如果B類,僅聲明類A的成員函數,爲類B的友元, 則需要在類B之前,聲明類A的成員函數。

class A{
	void f_a_1();//聲明
};
class B{
	friend void A::f_a_1();
	void f_b_1();
};
void A::f_a_1(){};

對於重載函數,每個都要聲明爲友元,否則只有對對應的函數纔是友元函數。

通常對於類和非成員函數,都需要在聲明爲友元之前聲明它們。但是有些編譯器並不強制這一行爲,總的來說爲了統一還是這樣作好點。

需要注意的是,友元修飾非成員函數,只是表明它的訪問狀態,並不是聲明。

就算我們在類的內部定義友元類,在外部使用的時候,還是需要聲明。
在這裏插入圖片描述
我覺得已經沒有人這麼做。。這純屬給自己添亂。

練習
7.32
這個題我認爲是有錯誤的。無法將clear聲明爲友元,只能將Window_mgr聲明爲友元

class Window_mgr;
class Screen {
	friend Window_mgr;
public:
	using pos = string::size_type;
	Screen() = default;
	Screen(pos w, pos h) :width(w), height(h), contents(w*h, ' ') {};
	Screen(pos w, pos h, char c) :width(w), height(h), contents(w*h, c) {};
	//將光標移動到第幾行的第幾個
	inline Screen& move(pos r, pos c) {
		//假定第一行從0開始計數,如果從1開始計數從row中,r需要減一
		pos row = r * width;
		cursor = row + c;
		return *this;
	}

	inline Screen& set(char c) {
		contents[cursor] = c;
		return *this;
	}

	inline Screen& set(pos row,pos col,char ch) {
		contents[row*width + col ]= ch;
		return *this;
	}
	inline Screen& display(ostream& temp_cout) {
		//temp_cout << contents;
		do_display(temp_cout);
		return *this;
	}
	inline const Screen& display(ostream& temp_cout) const {
		//temp_cout << contents;
		do_display(temp_cout);
		return *this;
	}
	inline void do_display(ostream& temp_cout) const {
		temp_cout << contents;
	}
	//intline Screen& set(cah)
private:
	//屏幕的寬高
	pos width = 20, height = 80;//類內初始值初始化
	//光標的位置
	pos cursor{ 0 };//初始化列表初始化
	//屏幕內容
	string contents;
};
class Window_mgr {
public:
	void clear(std::vector<int>::size_type index);

private:
	std::vector<Screen> screen_list{ Screen(20,20,' ') };
};
void Window_mgr::clear(std::vector<int>::size_type index) {
	//screen_list[index].contents = 
	Screen &s = screen_list[index];
	s.contents=string(s.width*s.height,' ');
}

這裏是把Window_mgr聲明爲友元類

如果要把clear聲明爲友元函數, 需要將Window_mgr的定義寫在Screen前面,而Window_mgr中有Screen的變量,所以把類Window_mgr的定義寫在Screen前面,則會造成Screen未定義,而寫在後面則會造成函數clear未定義。

也就是說,按照書上的這個步驟是沒法進行的
在這裏插入圖片描述
注:我是在vs2017的環境下編譯的。

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