1. 程式人生 > >C++基礎的不能再基礎的學習筆記——類(二)

C++基礎的不能再基礎的學習筆記——類(二)

一、類成員再探

1. 自定義類型別名

在類中,我們可以為資料型別定義別名而使程式碼更清晰簡潔。

class Screen {
public:
    typedef string::size_type pos; //posstring::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;       //錯誤
};