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的環境下編譯的。