第7章 類
7.1 定義抽象數據類型
7.2 訪問控制與封裝
7.3 類的其他特性
7.4 類的作用域
7.5 構造函數再探
7.6 類的靜態成員
7.1 定義抽象數據類型
-
類的基本思想是
數據抽象
和封裝
。數據抽象是一種依賴於接口
和實現
分離的編程技術。類的實現則包括類的數據成員、負責接口實現的函數體以及定義類所需的各種私有函數。封裝實現了類的接口和實現的分離
-
定義成員函數的方式與普通函數差不多。成員函數的聲明必須在類的內部,它的定義既可以在類的內部也可以在類的外部。作爲接口組成的非成員函數,定義和聲明都在類的外部。
定義在類內部的函數是隱式的inline函數
-
儘管所有成員都必須在類的內部聲明,但是成員函數體可以定義在類內也可以定義在類外。
-
成員函數通過一個名爲
this
的額外隱式參數來訪問調用對象。當調用一個成員函數的時候,用請求該函數的對象地址初始化this。 -
任何自定義this的參數或變量的行爲都是非法的。我們可以在成員函數體內部使用this,儘管沒有必要。
-
引入const成員函數.
緊隨參數列表之後的const關鍵字。不能把this綁定到一個常量對象上,不能在常量對象上調用普通成員函數。常量對象,以及常量對象的引用或指針都之內調用常量成員函數
-
編譯器分兩步處理類:首先編譯成員的聲明,然後才輪到成員函數體。因此成員函數體可以隨意使用類中的其他成員而無須在意這些成員出現的次序
-
在類的外部定義成員函數.
成員函數的定義必須與它的聲明匹配。如果成員被聲明成常量成員函數,那麼它的定義也必須在參數列表後明確指定const屬性。類外部定義的成員的名字必須包含它所屬的類名。如
使用類作用域運算符,一旦編譯器看到函數名,就能理解剩餘的代碼是位於類的作用域內。當avg_price使用revenue和units_sold時,隱式地使用了Sales_data的成員 -
定義一個返回this對象的函數.
-
一般來說,如果非成員函數是類接口的組成部分,則這些函數的聲明應該與類在同一個頭文件內。默認情況下,拷貝類的對象其實是拷貝對象的數據成員
-
類通過一個或幾個特殊的成員函數來控制其對象的初始化過程,這些函數叫做
構造函數
。構造函數的任務是初始化類對象的數據成員,無論何時只要類的對象被創建,就會執行構造函數 -
構造函數的名字和類名相同。和其他函數不同,構造函數沒有返回類型,構造函數也有一個(可能爲空的)參數列表和一個(可能爲空的)函數體。類可以包含多個構造函數,類似於重載函數。構造函數不能被聲明成const
-
類通過一個特殊的構造函數來控制默認初始化過程,這個函數叫做
默認構造函數
。默認構造函數無須任何實參 -
編譯器構造的函數又被稱爲
合成的默認構造函數
。按照如下規則初始化類的數據成員:如果存在類內初始值,用它來初始化成員;否則。默認初始化該成員。 -
某些類不能依賴於合成的默認構造函數.
三個原因:一,只有當類沒有聲明任何構造函數時,編譯器纔會自動地生成默認構造函數。二,含有內置類型或複合類型成員的類應該在類的內部初始化這些成員,或者定義一個自己的默認構造函數。否則,用戶在創建類的對象時就可能得到未定義的值。三,有的時候編譯器不能爲某些類合成默認的構造函數。
Sales_data()=default;
//該構造函數不接受任何實參,所以它是一個默認構造函數。定義目的時因爲既需要其他形式的構造函數,也需要默認構造函數。在參數列表後面寫上=default來要求編譯器生成構造函數。其中-=default既可以和聲明一起出現在類的內部(默認構造函數是內聯的),也可以作爲定義出現類的外部
-
構造函數初始值列表.
構造函數初始值是成員名字的一個列表,每個名字後面緊跟括號括起來的(或者在花括號內的)成員初始值。不同的成員的初始化通過逗號分隔開來。
-
構造函數沒有返回類型。當在類外部定義構造函數時,必須指明構造函數是哪個類的成員。
Sales_data::Sales_data()
-
類還需要控制拷貝、賦值和銷燬對象時發生行爲。不主動定義,編譯器將會合成。當類需要分配類對象之外的資源時,合成的版本常常會失效。使用vector對象或者string對象管理必要的存儲空間
7.2 訪問控制與封裝
訪問說明符.
定義在public
說明符之後的成員在整個程序內可被訪問,public成員定義類的藉口。定義在private
說明符之後的成員可以被類的成員函數訪問,但是不能被使用該類的代碼訪問,private部分封裝(即隱藏了)類的實現細節使用class和struct定義類唯一的區別就是默認的訪問權限.
如果使用struct關鍵字,則定義在第一個訪問說明符之前的成員是public的,相反使用class關鍵字,則這些成員是private的友元.
類可以允許其他類或者函數訪問它的非公有成員,方法是令其他類或者函數成爲它的友元。如果類想把一個函數作爲友元,只需要增加一條以friend
關鍵字開始的函數聲明語句即可。友元聲明只能出現在類定義的內部。友元的聲明.
友元的聲明僅僅指定類訪問的權限,如果希望調用某個友元函數,需要在友元聲明之外再專門對函數進行一次聲明。
7.3 類的其他特性
-
定義一個類型成員.
類可以自定義某些類型在類總對別名。由類定義的類型名字和其他成員一樣存在訪問限制。定義類型的成員必須先定義後使用 -
令成員作爲內聯函數.
可以在類的內部也可以在類的外部用inline關鍵字修飾函數的定義。inline成員函數也應該與相應的類定義在同一個頭文件 -
成員函數也可以被重載
-
可變數據成員.
修改類某個數據成員,即使是在一個const成員函數內,也可以通過在變量的聲明中加入mutable
關鍵字來做到。 -
類數據成員初始值.
提供一個類內初始值時,必須以符號=或者花括號表示 -
基於const的重載
-
每個類定義了唯一的類型。對於兩個類來說,即使它們的成員完全一樣,這兩個類也是兩個不同的類型
-
類的聲明,有時候被稱作
前向聲明
。在它聲明之後定義之前時一個不完全類型
。 -
類還可以把其他的類定義成友元,也可以把其他類(之前已經定義過的)成員函數定義成友元。
友元關係不存在傳遞性
。
除了另一個類作爲友元,還可以只爲這個類的成員函數聲明成友元。
-
如果一個類想把一組重載函數聲明成它的友元,它需要對這組函數中每一個分別聲明。
-
友元聲明和作用域
7.4 類的作用域
-
當成員函數定義在類的外部時,返回類型中使用的名字都位於類的作用之外。這個時候返回類型必須指明它是哪個類的成員,哪個類定義了它
-
名字查找
,分三步:首先在名字所在塊中尋找其聲明的語句,只考慮在名字的使用之前的聲明。如果沒找到繼續查找外層作用域。否則程序報錯。 -
類的定義處理
,分兩步:首先,編譯成員的聲明;直到類全部可見後才編譯函數體。編譯器處理完類中全部聲明後纔會處理成成員函數的定義
-
類型名要特殊處理.
一般來說,內層作用域可以重新定義外層作用域中的名字。但是在類中,如果成員使用了外層作用域中的某個名字,則類不能在之後重新定義該名字。
-
成員定義中的普通塊作用域的名字查找.
首先,在成員函數內查找該名字的聲明。如果成員函數內沒有找到,在類內繼續查找,這時類的所有成員都可以被考慮。如果類內也沒有找到,在成員函數定義之前的作用域內繼續查找。 -
類作用域之後,在外圍的作用域中查找。儘管外層的對象被屏蔽掉了,但仍然可以用作用域運算符訪問
在文件中名字的出現處對其進行解析.
當成員定義在類的外部時,不僅要考慮類定義之前的全局作用域中的聲明,還要考慮成員函數定義之前的全局作用域的聲明。
7.5 構造函數再探
-
如果沒有在構造函數的初始值列表中顯式地初始化成員,則該成員將在構造函數體之前執行默認初始化.
-
構造函數的初始值有時必不可少.
如果成員時const或引用的話,必須將其初始化。當成員屬於某種類型且該類沒有定義默認構造函數時,也必須將這個成員初始化。初始化const或引用類型的數據成員的唯一機會就是通過構造函數初始化
-
成員初始化的順序.
構造函數初始值列表只說明用於初始化成員的值,而不限定初始化的具體執行順序。成員的初始化順序與它們在類定義中的出現順序一致。如果一個成員是用另一個成員來初始化的,那這兩個成員的初始化順序很關鍵。
class X{
int i;
int j;
public:
//未定義的:i在j之前被初始化
X(int val): j(val),i(j){}
};
//實際上,i先被初始化,因此這個初始化效果是試圖使用未定義的值j初始化i
//改爲, X(int val):i(val),j(val) {}
-
如果一個構造函數爲所有參數都提供了默認實參,則它實際上也定義了默認構造函數
-
委託構造函數.
一個委託構造函數使用它所屬類的其他構造函數執行它自己的初始化過程。
當一個構造函數委託給另一個構造函數時,受委託的構造函數的初始值列表和函數體被依次執行。假如函數體包含有代碼時,先執行代碼,然後控制權纔會交還給委託者的函數體。 -
默認構造函數的作用
在實際中,如果定義了其他構造函數,那麼最好也提供一個默認構造函數。 -
隱式的類類型轉換.
如果構造函數只接受了一個實參,則它實際上定義了轉換爲此類類型的隱式轉換機制,又稱轉換構造函數
。能通過一個實參調用的構造函數定義了一條從構造函數的參數類型向類類型隱式轉換的規則。編譯器只會自動地執行一步類型轉換
-
抑制構造函數定義的隱式轉換.
可以通過將構造函數聲明爲explicit
加以抑制。
關鍵字explicit
只對一個實參的構造函數有效,多個實參的構造函數不能執行隱式轉換。只能在類內聲明構造函數時使用explicit
關鍵字,在類外部定義是不應重複。explicit構造函數只能用於直接初始化,使用(=) -
爲轉換顯式地使用構造函數.
-
標準庫中含有顯式構造函數的類.
-
聚合類.
使得用戶可以直接訪問成員,並具有特殊的初始化語法形式。當一個類滿足如下條件時,我們說它是聚合的:當所有的成員都是public的
沒有定義任何構造函數
沒有類內初始只
沒有基類,也沒有virtual函數
。我們可以一個話括號括起來的成員初始值列表,並且用它初始化聚合類的數據成員。初始值的順序必須與聲明順序一致 -
字面值常量類.
某些類也是字面值類型,字面值類型的類可能含有constexpr函數成員。這樣的成員必須符合constexpr函數所有要求,它們是隱式const的。
-
constexpr構造函數.
儘管構造函數不能是const的,但是字面值常量類的構造函數可以是constexpr函數。一個字面值常量類必須至少提供一個constexpr構造函數。constexpr構造函數可以聲明爲=default的形式。否則constexpr構造函數就必須既符合構造函數的要求,又符合constexpr函數的要求。綜合可知,constexpr構造函數體一般來說應該是空的
7.6 類的靜態成員
- 有時候類需要它的一些成員與類本身直接相關,而不是與類的各個對象保持關聯。
聲明靜態成員static.
類的靜態成員成員存在於任何對象之外,對象不包含任何與靜態數據成員有關的數據。靜態成員函數不能聲明成const的,也不能在static函數體內使用this指針使用類的靜態成員.
使用作用域運算符直接訪問靜態成員。使用類的對象、引用或者指針來訪問靜態成員。成員函數不用通過作用域運算符就能直接使用靜態成員定義靜態成員.
既可以在類的內部也可以在類的外部定義靜態成員函數。當在類的外部定義靜態成員時,不能重複static關鍵字,該關鍵字只能出現在類內部的聲明語句。一個靜態數據成員只能定義一次,類似於全局變量,靜態數據成員定義在任何函數之外,一旦定義,就一直存在於程序的整個生命週期中。
靜態成員的類內初始化.
通常情況下,類的靜態成員不應該在類的內部初始化,然而,可以爲靜態成員提供const整數類型的類內初始化,不過要求靜態成員必須是字面值常量類型的constexpr。