C++基礎的不能再基礎的學習筆記——類(二)
類
一、類成員再探
1. 自定義類型別名
在類中,我們可以為資料型別定義別名而使程式碼更清晰簡潔。
class Screen {
public:
typedef string::size_type pos; //pos是string::size_type型別的別名
private:
pos cursor = 0; //游標位置
pos height = 0, width = 0; //螢幕高、寬
string contents; //螢幕內容
};
在上述程式碼段中,pos是string::size_type型別的別名。將它設定為public是因為,這樣在類外也可以使用pos。
2. 可變資料成員
可變資料成員永遠不是const,不論是const函式還是const物件,它都是可以修改的。由關鍵字mutable修飾。
mutable size_t access_ctr;
void some_member() const { access_ctr++; };
其中,access_ctr為可變資料成員,用以追蹤每個物件的函式被呼叫了多少次,可見,即使成員函式為const函式,依然可以改變它的值。
3. 小問題
① 關於行內函數,只要在宣告或定義任一處以關鍵字inline修飾,即為行內函數
② 資料成員初始值
class Window_mgr {
private:
vector<Screen> screens{ Screen(21,20,' ') };
};
類內資料成員賦初值,必須以 = 或者 { }表示。
二、建構函式再探
1. 建構函式初始值列表
Person(string s, int a, bool se) : name(s), age(a), sex(se) {};
Person(string s, int a, bool se) { name = s; age = a; sex = se; };
上述程式碼中的兩個建構函式,雖然使得資料成員的值相同,但是有很大區別。
第一個函式是初始化
並且,當資料成員含有 (1) const和引用 (2) 無預設建構函式的類的物件 時,是必須要對資料成員進行初始化的,此時只能通過建構函式初始值列表的方式。
另外我們需要注意的是,建構函式初始值列表中資料成員出現的順序,並不會影響初始化的順序,資料成員初始化順序是它們的宣告順序。
2. 委託建構函式
C++11新標準提供了委託建構函式,使用其他建構函式執行初始化。
格式為: 類名(引數列表1) : 類名(引數列表2) { 函式體 }; 冒號前的建構函式將自己的初始化任務委託給冒號後的建構函式。
Person(string s, int a, bool se) : name(s), age(a), sex(se)
{
cout << "1" << endl;
};
Person(string s) : Person(s,0,0)
{
cout << "2" << endl;
};
Person fancy("MaYun");
/*
螢幕輸出為:
1
2
*/
執行上述程式碼的步驟為
使用Person(string s)建構函式初始化物件fancy
它是一個委託建構函式,因此轉而執行Person(“MaYun,0,0),按初始值列表初始化,之後執行函式體
該建構函式執行完畢後,再回來執行Person(string s)的函式體
3. 隱式的類型別轉換
① 什麼是隱式的類型別轉換
轉換建構函式:只含有一個形參的建構函式,可以完成 該形參型別—>類型別 的隱式型別轉換。
因此,當我們使用類型別時,可以用該形參型別進行代替,因為有隱式轉換規則。
Person(string s) : name(s) { }; //定義了從string到Person的型別轉換
void GetPerson(Person& p); //形參為Person型別
Person fancy("MaYun");
string temp = "LLLLLLL";
fancy.GetPerson(temp); //實參為string型別
在上述程式碼中編譯器會自動完成temp到Person型別的轉換,生成一個臨時的Person物件。
同樣的,編譯器只會自動執行一步型別轉換。若上述程式碼為fancy.GetPerson("LLLLLLL");
就是錯誤的,要完成”LLLLLLL”到string的轉換,以及string到Person的轉換。
② 抑制隱式的類型別轉換
我們可以通過將轉換建構函式宣告為explicit,使它不定義隱式的型別轉換。
explicit Person(string s) : name(s) { };
Person fancy("MaYun"); //正確,直接初始化
Person sixday = "XiaoGenBan"; //錯誤,拷貝初始化
需要注意的是,explicit只能在宣告時出現,並且被定義為explicit的函式,只能用於直接初始化,不可拷貝初始化。
三、類的靜態成員
1. 什麼是靜態成員?
有的時候,類需要 與類本身相關,與各個物件無關的成員,這時需要關鍵字static來宣告。
如下為一個銀行賬戶記錄類,由於基準利率與每個賬戶記錄無關,但是又與賬目相關,因此我們將基準利率宣告為靜態。
class Account {
public:
void calculate() { amount += amount * interestRate; };
static double rate() { return interestRate; };
static void rate(double);
private:
string owner;
double amount;
static double interestRate;
static constexpr int period = 30;
static double initRate();
};
double Account::interestRate = initRate();
constexpr int Account::period;
類的靜態成員存在於任何物件之外,只有一個且被所有物件共享。
在上述程式碼段中,Account類有三個資料成員owner,amount,interestRate,但是對於每個物件而言,只有owner,amount兩個資料成員。
同樣的,對於靜態函式而言,它不屬於某個物件,因此不存在this指標,也就不能宣告為const函式(const是用來修飾this指標的)。
靜態函式可以定義在類內和類外,但static關鍵字只可出現在類內宣告。
靜態資料成員必須在類外初始化。
靜態資料成員不屬於任何物件,因此不是在建立物件時定義的,不是由建構函式進行初始化的,而我們必須在類外對其進行初始化,因此一旦被定義,它將存在於程式的整個生命週期。如上述程式碼中的interestRate。
然而,存在類內初始化的資料成員,我們稱其為常量靜態資料成員,它必須為const整數型別,而一般在用到該資料成員時,也必須在類外宣告一下。如上述程式碼中的period。
2. 使用類的靜態成員
① 通過作用域運算子直接訪問
cout << Account :: rate();
② 通過物件訪問
Account a;
cout << a.rate();
3. 靜態成員能用於非靜態成員不能用於的情況
靜態成員可以是不完全型別(宣告之後定義之前)。更為重要的是,可以是該類型別本身。
class brid{
static brid a; //正確
brid& b; //正確
brid* c; //正確
brid d; //錯誤
};