C++ 類

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

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

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

1.1 聚合類

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

struct C

{

int a;

double b;

};

struct D

{

int a;

double b;

C c;

};

//initialize an object of type C with an initializer-list

C c = {1, 2};

// D has a sub-aggregate of type C. In such cases initializer-clauses can be nested

D d = {10, 20, { 1, 2} };

1.2 POD 結構

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

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

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

宣告和使用

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

2.1 基本宣告和成員變數

類和結構體的宣告使用關鍵詞 class 和 struct。成員在類和結構體的內部宣告。

下面的程式碼段例項了結構體和類的宣告：

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

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

include

using namespace std;

class person

{

public:

string name;

int age;

};

int main

{

person a, b;

a.name = "Calvin";

b.name = "Hobbes";

a.age = 30;

b.age = 20;

cout

執行以上程式碼將會輸出

Calvin: 30

Hobbes: 20

2.2 成員函式

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

class person

{

std::string name;

int age;

public:

person : age (5) { }

void print const;};

void person::print const

{

cout age

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

透過使用成員函式 print ，輸出可以被簡化為：

a.print ;

b.print ;

上述的 a 和 b 被稱為呼叫者（sender），當 print 函式被執行時每一個都引用自己的成員變數。

將類或結構的申明（稱做介面）和定義（稱作實現）放入分開的單元是常見的做法。使用者需要的介面被放入一個頭檔案中而實現則獨立地放入原始碼或者編譯後的形式。

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

2.3 繼承

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

例如，考慮：

class P

{int x;};

class C : public P

{int y;};

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

+----+

+----+

↑

p

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

+----+----+

+----+----+

↑

p

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

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

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

2.4 過載運算子

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

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

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

struct integer

{

int i;

integer (int j = 0) : i (j)

integer operator*(const integer &k) const

{return integer (i * k.i);

}

};

或

struct integer

{

int i;

integer (int j = 0) : i (j)

integer operator*(const integer &k) const;

};

integer integer::operator*(const integer &k) const

{return integer (i * k.i);

}

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

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

2.4.1 二元可過載運算子

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

integer a = 1;

/* 這裡的等號是其中一種二元運算子，

我們可利用過載運算子 (=) 的方式

來提供初始化功能，而左方的變數 i

就是類物件本身，右方的數字 1 則是

傳入引數。 */

integer b = 3;

/ 變數名字跟類物件內的變數無關 /

integer k = a * b;

cout

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

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

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

2.4.2 一元可過載運算子

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

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

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

回傳資料型態 operator 運算子

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

回傳資料型態 operator 運算子 (引數)

2.4.3 括號過載

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

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

回傳資料型態 operator (引數)

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

回傳資料型態 operator (引數 1, 引數 2, ...)

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

2.5 建構函式

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

person (string N, int A)

{

name = N;

age = A;

}

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

person (std::string N, int A) : name (N), age (A)

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

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

person (std::string N = "", int A = 0) : name (N), age (A)

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

person : name (""), age (0)

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

int main

{person r = person ("Wales", 40);

r.print ;}

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

int main

{person r ("Wales", 40);

r.print ;}

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

person

{

std::cout

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

2.6 預設建構函式

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

class A {int b;};

// 使用括號建立物件

A *a = new A ; // 呼叫預設建構函式，b 會被初始化為 '0'

// 不使用括號建立物件

A *a = new A; // 僅分配記憶體，不呼叫預設建構函式，b 會有一個未知值

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

2.7 解構函式

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

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

~person

{

cout

另外要注意的是，解構函式是不容許引數傳遞。然而，與建構函式一樣，解構函式可以被顯式呼叫：

int main

{person someone ("Wales", 40);

someone.~person ; // 此時會輸出一次 "I'm deleting Wales with age 40"

return 0; // 第二次輸出 "I'm deleting with age 40"

}

/* 在這裡，程式結束時會自動呼叫解構函式，

而 person.name 在第一次呼叫解構函式時已被清除，

但 person.age 會按編譯器而定，

沒能在第一次呼叫解構函式時清零。 */

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

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

類模板

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

屬性

C++ 語法試圖使一個結構的所有方面看起來像一個基本資料型別。因此，運算子過載允許結構像整數和浮點數一樣操作，結構的陣列可以透過方括號宣告（somestructure variablenamesize），而且指向結構的指標可以透過和指向內建型別的指標通用的方法解引用。

5.1 記憶體消耗

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

struct twonums

{

int a;

int b;

};

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

+----+----+

+----+----+

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

struct bytesandsuch

{

char c;

char C;

short int s;

int i;

double d;

};

可看成

+-+-+--+--+--+--+--------+

+-+-+--+--+--+--+--------+

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

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

5.1.1 位欄位

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

struct A

{

	unsigned a:2; // 可以儲存 0-3 的數字，佔據一個 int 前 2 bit 的空間.

	unsigned b:3; // 可以儲存 0-7 的數字，佔據之後 3 bit 的空間.

	unsigned :0; // 移動到下一個內建型別的末尾

	unsigned c:2;

	unsigned :4; // 在 c 和 d 中間加 4bit 的空白

	unsigned d:1;

	unsigned e:3;

};

// 記憶體結構

/*	4 byte int 4 byte int

	1234 5678

	1 2 3 4

	aabbb

	5 6 7 8

	ccde ee

/

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

5.2 按引用傳參

5.3 this 關鍵字

complex& operator+=(const complex & c)

{

realPart += c.realPart;

imagPart += c.imagPart;

return *this;

}