C++类

C++ 程式設計允許程式設計師使用**類別**（class）定義特定程式中的資料類型。這些資料類型的實例被稱為物件，這些實例可以包含程式設計師定義的成員變數、常數、成員函式，以及多載的運算子。語法上，類似 C 中結構體（struct）的擴充，C 中結構體不能包含函式以及多載的運算子。

## C 結構體與 C++ 類別的對比

在 C++ 中，**結構體** 是由關鍵詞 `struct` 定義的一種資料類型。他的成員和基礎類別預設為公有的（public）。由關鍵詞 `class` 定義的成員和基礎類別預設為私有的（private）。這是 C++ 中結構體和類別僅有的區別。

### 聚合類別

聚合類別是一種沒有使用者定義的建構函式，沒有私有（private）和保護（protected）非靜態資料成員，沒有基礎類別，沒有虛擬函式。這樣的類別可以由封閉的大括號用逗號分隔開初始化列表。下列的代碼在 C 和 C++ 具有相同的語法：

### POD 結構

一個 POD 結構（普通舊式資料結構）是一個不包含非 POD 結構、非 POD 聯合（或者這些類別型的陣列）或參照的非靜態成員變數（靜態成員沒有限制），並且沒有使用者定義的設定運算子和解構器的聚合類別。 一個 POD 結構可以說是 C `struct` 在 C++ 中的等價物。在大多數情況下，一個 POD 結構擁有和一個在 C 中聲明的對應的結構相同的記憶體布局。因此，POD 結構有時不正式地被稱為「C 風格結構」（C-style struct）。 

#### C 結構與 C++ POD 結構共有的屬性

* 資料成員被分配使得一個物件中之後的成員有著更高的位址，除非跨越了一個存取描述符。
* 兩個 POD 結構類別型是布局相容的如果它們有相同數量的非靜態資料成員，而且對應的非靜態資料成員（按照順序）是布局相容的。
* 一個 POD 結構可以包含未命名的填充。
* 一個指向 POD 結構物件的指標適合使用 `reinterpret_cast`，指向其初始成員而且反之亦然，說明在 POD 結構的頭部不存在填充。
* 一個 POD 結構可以被 `offsetof` 巨集使用。

## 聲明和使用

C++ 的結構體和類別具有他們自己的成員。這些成員包括變數（包括其他結構體和類別），被看做方法的函式（特定的標示符或多載的運算子），建構函式以及解構函式。成員被聲明成為公共或私有使用說明符 `public:` 和 `private:` 來區分。說明符後出現的任何成員會獲得相應的存取權限直到下一個說明符的出現。對於繼承的類別能夠使用 `protected:` 說明符。

### 基本聲明和成員變數

類別和結構體的聲明使用關鍵詞 `class` 和 `struct`。成員在類別和結構體的內部聲明。

下面的代碼段實例了結構體和類別的聲明：

 

以上兩個聲明在功能上是等價的。每一段代碼都定義了一個類別型 `person`，其含有兩個成員變數：`name` 和 `age`。注意，大括號後面的分號是必需的。

在其中一個聲明之後（不能同時使用兩個），`person` 可以被用來定義新的 `person` 類別型的變數：

執行以上代碼將會輸出

```
Calvin: 30
Hobbes: 20
```

### 成員函式

**成員函式**是 C++ 的類別和結構體的一個重要特性。這些資料類型可以包含作為其成員的函式。成員函式分為靜態成員函式與非靜態成員函式。靜態成員函式只能存取該資料類型的物件的靜態成員。而非靜態成員函式能夠存取物件的所有成員。在非靜態成員函式的函式體內，關鍵詞 `this` 指向了呼叫該函式的物件。這通常是通過 thiscall 呼叫協定，將物件的位址作為隱含的第一個參數傳遞給成員函式。再次以之前的 `person` 類別型作為例子：

在上面的例子中 `print()` 函式在類別中聲明，並在類別的名稱後加上 `::` 來限定它後加以定義。`name` 和 `age` 是私有的（類別的預設修飾詞），`print()` 被聲明為公有，由於一個被用於類別外的成員需要被申明為公有的。

通過使用成員函式 `print()`，輸出可以被簡化為：

上述的 `a` 和 `b` 被稱為呼叫者（sender），當 `print()` 函式被執行時每一個都參照自己的成員變數。
將類別或結構的申明（稱做介面）和定義（稱作實現）放入分開的單元是常見的做法。使用者需要的介面被放入一個標頭檔中而實現則獨立地放入原始碼或者編譯後的形式。

非靜態成員函式，可以用 const 或 volatile 關鍵詞限定。const 限定的成員函式不能修改其他資料成員（除了具有 mutable 的例外），也不能呼叫非 const 限定的其他成員函式。編譯實現時，通常是在 const 限定的成員函式體內，this 所指向的資料成員自動具有 const 限定，因此是唯讀的。const 物件只能呼叫 const 成員函式；volatile 物件只能呼叫 volatile 限定的成員函式。反之，沒有受到限定的普通物件可以呼叫所有的成員函式，不論它是否為 cv 限定。建構函式、解構函式不能 cv 限定。

### 繼承

非 POD 類別的記憶體布局沒有被 C++ 標準規定。例如，許多流行的 C++ 編譯器通過將父類別的欄位和子類別的欄位並置來實現單繼承，但是這並不被標準所需求。這種布局的選擇使得將父類別的指標指向子類別的操作是平凡的（trivial）。

例如，考慮：

一個 `P` 的實例和 `P* p` 指向它，在記憶體中可能看起來像這樣：

```
+----+
|P::x|
+----+
↑
p
```

一個 `C` 的實例和 `P* p` 指向它，在記憶體中可能看起來像這樣：

```
+----+----+
|P::x|C::y|
+----+----+
↑
p
```

因此，任何操縱 `P` 物件的欄位的代碼都可以操縱在 `C` 物件中的 `P` 欄位而不需要考慮任何關於 `C` 欄位的定義。一個正確書寫的 C++ 程式在任何情況下都不應該對被繼承欄位的布局有任何假定。使用 static\_cast 或者 dynamic\_cast 類別型轉換運算子會確保指標正確的從一個類別型轉換為另一個。

多重繼承並不那麼簡單。如果一個類別 `D` 繼承了 `P1` 和 `P2`，那麼兩個父類別的欄位需要被按照某種順序儲存，但是（在大多數情況下）只有一個父類別可以被放在子類別的頭部。每當編譯器需要將一個指向 `D` 的指標轉換為 `P1` 或 `P2` 中的任一個，編譯器需要提供一個自動轉換從子類別的位址轉換為父類別欄位的位址（典型地，這是一個簡單的偏移量計算）。

關於多重繼承的更多資訊，參看虛繼承。

### 多載運算子

C++ 容許程式員重載某些運算子，目的是補充庫中未能提供的針對特定類的運算子。同理，很多時自訂類也因為內建庫不能提供指定運算子而需要重載。

另外，當程式設計師沒有重載或定義某些運算子時，編譯器會自動地建立它們，例如三法則中的複製指定運算子（`=`）。

依照慣例，重載運算子時應類比運算子本身意義的功能，例如重載運算子「\*」時，程式員義務重載為兩數之乘法（或其他，視數學或程式上的意義）。另外，宣告一結構如 `integer` 類，當重載運算子如「\*」就要回傳 `integer` 類：

或

在這裡，`const` 關鍵字出現兩次。表達式 `const integer &k` 中的 `const` 關鍵字代表函式不能修改此常數值，而第二個 `const` 關鍵字代表此函式不會修改類物件本身（`*this`）。

而 `integer &k` 之中，符號（`&`）表示以參照形式呼叫。當呼叫函式時會直接傳遞變數位址，並以變數本身取代這裡的變數 `k`。

#### 二元可多載運算子

二元運算子會用函式方式並以「`operator 運算符`」識別來進行重載，這裡的參數會是單一參數。實際使用時，二元運算子左方的變數會成為類物件本身（`*this`），而右方變數則成為傳入參數。

以下是二元可重載運算子列表：

運算子（`=`）可以被用作賦值，意思是原定功能是由右方變數抄寫內部資料到左方變數，但視乎需要也可以被用作其他用途。

每個運算子是互相獨立存在，並不依賴其他運算子。例如運算子（`<`）並不需要運算子（`>`）存在從而運作。

#### 一元可多載運算子

一元運算子跟上述的運算子相似，只是一元運算子只會載入類物件本身（`*this`），而不接受其他參數。另外，一元運算子有分前置運算子和後置運算子，分別在於前置運算子會放到變數前方，後置運算子則是後方。例如負值運算子（`-`）和邏輯取反運算子（`!`）都是一元前置運算子。

以下是一元可重載運算子列表：

重載一元運算子時有區分前置和後置式，一元前置運算子按以下格式編寫：

　
: `回傳資料型態 operator 運算符 ()`

而後置運算子按以下格式編寫：

　
: `回傳資料型態 operator 運算符 (參數)`

#### 括號多載

括號運算子有兩種，分別是方形括號運算子（`[]`）和圓形括號運算子（`()`）。方形括號運算子又名陣列運算子，只能傳入單一參數，而圓形括號運算子卻可以傳入任意數量的參數。

方形括號運算子按以下格式重載：

　
: `回傳資料型態 operator[] (參數)`

圓形括號運算子按以下格式重載：

　
: `回傳資料型態 operator() (參數1, 參數2, ...)`

注意，參數是指定在第二個括號之中，第一個括號只是運算子符號。

### 建構函式

有時軟體工程師會想要他們的變數在聲明時有一個預設值。這可以通過聲明建構函式做到。

成員變數可以像下面的例子一樣，利用一個冒號，通過一個初始化序列初始化。這與上面不同，它進行了初始化（使用建構函式），而不是使用設定運算子。這對類別類別型來說更有效，因為它只需要直接構造；而賦值時，它們必須先使用預設建構函式進行第一次初始化，然後再賦予一個不同的值。而且一些類別型（例如參照和 const 類別型）不能被賦值，因而必須通過初始化序列進行初始化。

注意花括號不能被省略，即使為裡面為空。

預設值可以給予最後的幾個參數類別幫助初始化預設值。

在上面的例子中，當沒有參數給予建構函式時，等價於呼叫以下的無參建構函式（一個預設建構函式）：

建構函式的聲明看起來像一個名字和資料類型相同的函式。事實上，我們的確可以用函式呼叫的形式呼叫建構函式。在這種情況下一個 `person` 類別型的變數會成為返回值：

以上的例子建立了一個臨時的 person 物件，然後使用複製建構函式將其賦予 r。一個更好的建立物件的方式（沒有不需要的拷貝）：

具體的程式行為，可以也可以不和變數有關係，可以被作為一部分加入建構函式。

通過以上的建構函式，當一個 `person` 變數沒有被具體的值初始化時，「Hello!」會被列印。

### 預設建構函式

當類別沒有定義建構函式時，預設建構函式將被呼叫。

然而如果使用者定義了這個類別的建構函數，兩個聲明都會呼叫使用者定義的建構函式。而在使用者定義的建構函式中的代碼會被執行，並且不會賦予 b 預設值。

### 解構函式

一個解構函式是一個建構函式的逆，當一個類別的一個實例被銷毀時會被呼叫，例如當一個類別在塊（一組花括號「{}」) 中被構造的一個物件會在關閉括號後刪除，之後解構函式被自動呼叫。它會在清空儲存變數的記憶體位置時被呼叫。解構函式可以在類別被銷毀時用來釋放資源，例如堆分配的記憶體和打開的檔案。

聲明一個解構函式的符號類別似於建構函式。它沒有返回值而且方法的名稱和在類別的名稱前加上波浪線（~）相同。

另外要注意的是，析構函式是不容許參數傳遞。然而，與構造函式一樣，析構函式可以被顯式調用：

## 建構函式與解構函式的相似點

* 兩者都和聲明所在的類別有相同的名字。
* 若未宣告，兩者都會執行預設的行為。意即類別在建立或刪除時，會一同分配或刪除記憶體。
* 對衍生類別（subclass）而言，在基礎類別（superclass）的建構函式執行期間，衍生類別的建構函式還未執行；反之，在基礎類別的解構函式執行期間，衍生類別的解構函式已執行完畢。兩種情況下，都無法使用在衍生類別宣告的變數。

## 類別模板

類別模板，是對一批僅僅成員資料類型不同的類別的抽象，程式設計師只要為這一批類別所組成的整個類別家族建立一個類別模板，給出一套程式碼，就可以用來生成多種具體的類別，這類別可以看作是類別模板的實例，從而大大提高程式設計的效率。

## 屬性

C++ 語法試圖使一個結構的所有方面看起來像一個基本資料類型。因此，運算子多載允許結構像整數和浮點數一樣操作，結構的陣列可以通過方括號聲明（`some_structure variable_name[size]`），而且指向結構的指標可以通過和指向內建類別型的指標通用的方法解除參照。

### 記憶體消耗

結構的記憶體消耗至少是組成變數的記憶體大小的總和。參考如下 `twonums` 結構例子。

這個結構包含兩個整型。在當前許多 C++ 編譯器中，整型預設是 32 位整型， 所以每個成員變數消耗 4 個位元組的記憶體．因而整個結構至少（或者正好）消耗 8 個位元組的記憶體，見下圖。

```
+----+----+
| a  | b  |
+----+----+
```

然而，編譯器可能在變數或者結構的結尾添加空的位， 這樣可以保證和給定的電腦結構匹配，通常是把變數添加到 32 位。如下例子所示的結構:

可看成

```
+-+-+--+--+--+--+--------+
|c|C|XX|s |  i  |   d    |
+-+-+--+--+--+--+--------+
```

在記憶體中， XX 表示兩個未被使用的空位元。

因為結構可能會使用指標和陣列去聲明 或者初始化變數，結構的記憶體消耗不一定是固定的。另外一個記憶體消耗不固定的例子是模板結構。

#### 位欄位

位欄位（Bit field）可以被用來定義比內建類別型還要小的類別成員變數。通過這個欄位定義的變數，只可以像使用內建的整數類別型 (例如 int, char, short, long...) 那樣子使用。

位欄位不能在結構體中使用，它只能在使用 struct 或者 class 關鍵字定義的類別中使用。

### 按參照傳參

### _this_ 關鍵字

## 參見

* 物件組合
* 虛繼承
* 類別型轉換

## 參考

## 資料來源

* Cplusplus.com tutorial lesson 5.2（頁面存檔備份，存於網際網路檔案館）, accessed in January 2006
* Chan, S. "Tutorial on C++ and STL", Hong Kong, 2006
* Cplusplus.com tutorial lesson 2.5（頁面存檔備份，存於網際網路檔案館）, accessed in February 2006