1. 程式人生 > 實用技巧 >【C++】《C++ Primer 》第七章

【C++】《C++ Primer 》第七章

第七章 類

一、定義抽象資料型別

  • 類背後的基本思想資料抽象(data abstraction)封裝(encapsulation)
  • 資料抽象是一種依賴於介面(interface)實現(implementation)分離的程式設計技術。

1. 類成員

  • 必須在類的內部宣告,不能在其他地方增加成員。
  • 成員可以是資料,函式,函式別名。

2. 類的成員函式

  • 成員函式的宣告必須在類的內部。
  • 成員函式的定義既可以在類的內部也可以在外部。
  • 使用 點運算子 . 呼叫成員函式。
  • 必須對任何const或者引用型別成員以及預設建構函式的類型別的任何成員使用初始化式。ConstRef::ConstRef(int ii): i(ii), ci(ii), ri(ii) { };
  • 預設實參:Sales_item(const std::string &book): isbn(book), units_sold(0), revenue(0.0) { };
  • *this
    • 每個成員函式都有一個額外的,隱含的形參this
    • this總是指向當前物件,因此this是一個常量指標
    • 形參表後面的const,該表了隱含的形參this的型別,例如 bool same_isbn(const Sales_item &rhs) const; ,這種函式稱為“常量成員函式”(this指向的當前物件是常量)。
    • return *this; 可以讓成員函式連續呼叫。
    • const成員函式:this是指向const類型別的const指標(不可以改變this所指向的值,不可以改變this儲存的地址)。
    • 非const成員函式:this是指向類型別的const指標(可以改變this所指向的值,不可以改變this儲存的地址)。

3. 非成員函式

  • 和類相關的非成員函式,定義和宣告都應該在類的外部。

4. 類的建構函式

  • 類通過一個或者幾個特殊的成員函式來控制其物件的初始化過程,這些函式叫做建構函式
  • 建構函式是特殊的成員函式,且與類同名。它在類的public部分。
  • =default 要求編譯器合成預設的建構函式。通常在定義自己的建構函式前呼叫它一次。(C++11)
  • 初始化列表:冒號和花括號之間的程式碼: Sales_item(): units_sold(0), revenue(0.0) { }

5. 拷貝、賦值和析構

  • 類中所有分配的資源都應該直接以類的資料成員形式儲存。

二、訪問控制與封裝

  • 訪問說明符(access specifiers)
    • public:定義在 public後面的成員在整個程式內可以被訪問; public成員定義類的介面。
    • private:定義在 private後面的成員可以被類的成員函式訪問,但不能被使用該類的程式碼訪問; private隱藏了類的實現細節。
  • 使用class或者struct:都可以被用於定義一個類,唯一的區別就是預設的訪問許可權
    • 使用class:在第一個訪問說明符之前的成員都是private的。
    • 使用struct:在第一個訪問說明符之前的成員都是public的。

1. 友元

  • 允許特定的非成員函式訪問一個類的私有成員
  • 友元的宣告以關鍵字 friend 開始。通常將友元宣告成組地放在類定義的開始或者結尾
class Sales_data {
friend Sales_data add(const Sales_data&, const Sales_data&);

public:
    Sales_data() = defalut;
    Sales_data(const std::string &s, unsigned n, double p): bookNo(s), units_sold(n), revenue(p*n) {}

private:
    std::string bookNo;
    unsigned units_sold = 0;
    double revenue = 0.0;
};

2. 封裝的益處

  • 確保使用者的程式碼不會無意間破壞封裝物件的狀態。
  • 被封裝的類的具體實現細節可以隨時改變,而無需調整使用者級別的程式碼

三、類的其他特性

  • 成員函式作為行內函數inline:
    • 在類的內部,常有一些規模較小的函式適合於被宣告為行內函數
    • 定義在類內部的函式是自動內聯的。
    • 在類外部定義的成員函式,也可以在宣告時顯式地加上inline。
  • 可變資料成員(mutable data member):
    • mutable size_t access_ctr;
    • 永遠不會是const,即使它是const物件的成員。
  • 類型別
    • 每個類定義了唯一的型別。

四、類的作用域

  • 每個類都會定義它自己的作用域。在類的作用域之外,普通的資料和函式成員只能由引用、物件、指標使用成員訪問運算子來訪問。
  • 函式的返回型別通常在函式名前面,因此當成員函式定義在類的外部時,返回型別中使用的名字都位於類的作用域之外。
  • 如果成員使用了外層作用域中的某個名字,而該名字代表一種型別,則類不能在之後重新定義該名字。
  • 類的定義分兩步處理
    • 首先,編譯成員的宣告。
    • 直到類全部可見後才編譯函式體。
  • 型別名的定義通常出現在類的開始處,這樣就能確保所有使用該型別的成員都出現在類名的定義之後。

五、建構函式再探

1. 建構函式初始化列表

  • 建構函式的初始值有時必不可少。如果成員是const、引用,或者屬於某種未提供預設建構函式的類型別,我們必須通過建構函式初始值列表為這些成員提供初值。
  • 建議使用建構函式初始值
  • 最好令建構函式初始值的順序與成員宣告的順序保持一致。建議儘量避免使用某些成員初始化其他成員。

2. 委託建構函式

  • 委託建構函式將自己的職責委託給了其他建構函式。
class Sales {
public:
    // 非委託構造使用對應的實參初始化成員
    Sales(std::string s, unsigned cnt, double price): bookNo(s), unit(cnt), revenue(cnt*price) {}  
    
    // 其餘建構函式全部委託給另一個建構函式
    Sales(): Sales("", 0, 0) {}
    Sales(std::string s): Sales(s, 0, 0) {}
    Sales(std::istream &is): Sales() {read(is, *this);}
};

3. 預設建構函式的作用

  • 當物件被預設初始化或值初始化時自動執行預設建構函式。預設初始化的發生情況:
    • 當在塊作用域內不使用任何初始值定義一個非靜態變數或者陣列時。
    • 當一個類本身含有類型別的成員且使用合成的預設建構函式時。
    • 當類型別的成員沒有在建構函式初始值列表中顯式地初始化時。
  • 值初始化的發生情況:
    • 在陣列初始化的過程中如果提供的初始值數量少於陣列的大小時。
    • 當不使用初始值定義一個區域性靜態變數時。

4. 隱式的類型別轉換

  • 如果建構函式只接受一個實參,則它實際上定義了轉換為此類型別的隱式轉換機制。這種建構函式又叫轉換建構函式(converting constructor)
  • 編譯器只會自動地執行僅一步型別轉換。
  • 抑制建構函式定義的隱式轉換:
    • 將建構函式宣告為explicit加以阻止。
    • explicit建構函式只能用於直接初始化不能用於拷貝形式的初始化

5. 聚合類

  • 聚合類使得使用者可以直接訪問其使用者,它需要滿足以下要求:
    • 所有成員都是public的。
    • 沒有定義任何建構函式。
    • 沒有類內初始值。
    • 沒有基類,也沒有virtual函式。
// 聚合類例子
struct Data {
    int val;
    string s;
};
  • 可以使用一個花括號括起來的成員初始值列表,初始值的順序必須和宣告的順序一致。
// 聚合類使用
Data d = {0, "DD"};

6. 字面值常量類

  • constexpr函式的引數和返回值必須是字面值。
  • 字面值型別:除了算術型別、引用和指標外,某些類也是字面值型別。
  • 資料成員都是字面值型別的聚合類是字面值常量類
  • 如果不是聚合類,則必須滿足下面所有條件:
    • 資料成員都必須是字面值型別。
    • 類必須至少含有一個constexpr建構函式。
    • 如果一個數據成員含有類內部初始值,則內建型別成員的初始值必須是一條常量表達式;或者如果成員屬於某種類型別,則初始值必須使用成員自己的constexpr建構函式。
    • 類必須使用解構函式的預設定義,該成員負責銷燬類的物件。

六、類的靜態成員

  • 非static資料成員存在於類型別的每個物件中。但是static資料成員獨立於該類的任意物件而存在。
  • 每個static資料成員是與類關聯的物件,並不與該類的物件相關聯。
  • 宣告
    • 宣告之前加上關鍵詞static。
  • 使用
    • 使用作用域運算子::直接訪問靜態成員:r = Account::rate();
    • 也可以使用物件訪問:r = ac.rate();
  • 定義
    • 在類外部定義時不用加static。
  • 初始化
    • 通常不在類的內部初始化,而是在定義時進行初始化,如 double Account::interestRate = initRate();
    • 如果一定要在類內部定義,則要求必須是字面值常量型別的constexpr。