• 沒有找到結果。

物件導向程式設計入門

N/A
N/A
Protected

Academic year: 2022

Share "物件導向程式設計入門"

Copied!
121
0
0

加載中.... (立即查看全文)

全文

(1)

1 1

第 8 章

物件導向程式設計入門

(2)

本章提要

8-1 物件導向與類別

8-2 定義類別

8-3 類別與指標

8-4 friend 類別的夥伴

8-5 綜合演練

(3)

3

8-1 物件導向與類別

寫程式的最終目的是為了解決人們的問題, 而當電腦的應用愈來愈廣泛時, 所需設計的 程式也變得愈來愈複雜, 而以傳統程序導向 的程式設計方式, 要解決真實世界中各種問 題也就顯得捉襟見肘。

(4)

物件導向與類別

此時就有人提出一種新的觀念, 亦即程式本 身要能模擬真實世界的運作方式, 設計程式 時的思考方式便是以真實世界的運作方式 來思考如何解決問題, 這種新的計計理念就 稱為物件導向程式設計, 其中模擬真實世界 的方式就是使用物件 (object)。

(5)

5

物件:C++ 舞台劇的演員

如果將 C++ 程式比擬為一齣舞台劇的話, 那麼要說明甚麼是物件導向程式語言就不 難了。要上演一齣舞台劇, 首先必須要有導 演, 先將自己想要表達的理念構思好。然後 再由編劇將劇裡所需要的角色、道具描繪 出來, 並且將這些元素搭配劇情撰寫出劇本

。最後, 再由演員以及道具在舞台上依據劇 本實際的演出。

(6)

物件:C++ 舞台劇的演員

每一個 C++ 程式也一樣可以分解為這些要 素, 導演就是撰寫程式的您, 要先設想程式 想要完成甚麼事情。接下來的編劇當然還 是您, 必須先規劃出如何完成工作。以下就 說明 C++ 程式中相對於一齣舞台劇的各個 元素, 讓讀者瞭解如何運用這樣的觀念設計 程式。

(7)

7

類別 (Class) 與物件

一齣舞台劇有了基本的構思之後, 接下來就 要想像應該有哪些角色來展現這齣劇, 每一 種角色都會有它的特性以及要做的動作。

舞台劇的角色與演員

C++ 的類別與物件

(8)

舞台劇的角色與演員

舉例來說, 如果演出西遊記, 不可缺少的當 然是美猴王這位主角, 它有多種表情、還能 夠進行 72 變。除了美猴王以外, 當猴子猴 孫的小猴子也少不了, 這些小猴子可能有 3 種表情, 而且可以跑步、爬樹等等。想好了 角色、編好劇本後, 還得找演員來演出這些 角色, 否則只是空有劇本, 觀眾根本看不到

。有些角色, 像是美猴王, 在整個劇中只有 一個;但有些角色, 像是小猴子, 可能就會 有好幾個, 就得找多個演員來演出這些角色,

(9)

9

舞台劇的角色與演員

(10)

舞台劇的角色與演員

同樣的道理, 舞台上除了演 員外, 可能還需要一些布景 或道具, 每一種布景也有它 的特性與可能的動作。舉例 來說, 西遊記中美猴王騰雲 駕霧, 舞台上可少不了雲。

雲可能有不同顏色、形狀, 也可以飄來飄去, 而且同時 間舞台上可能還需要很多朵

(11)

11

舞台劇的角色與演員

這些不論是要演員演的、還是要道具來表 現的, 都可以通稱為角色。在劇本中就以文 字來描述這些角色的特性以及行為, 並且描 述整個劇的進行, 然後再找演員或是製作道 具來實際演出。

(12)

C++ 的類別與物件

每種角色都有其自我的屬性和行為, 這些角色 在 C++ 中就稱為類別 (Class), 例如小猴子這 個角色, 應該都具有相似的屬性及行為 (例如 都會爬樹) 而實際演出的演員則稱為物件

(Object), 每個物件都是獨立的個體, 可以表現 出與眾不同的行為。例如在一場戲中, 有的小 猴子在玩水、有的小猴子在爬樹等等。

當我們在設計程式時, 首先就是規劃出參與演 出的有哪些類型的角色, 並定義出相對應的類

(13)

13

C++ 的類別與物件

接著劇本就是寫在 main() 函式中, 在

main() 函式中各演出者要一一上場或放置 在特定的位置上, 這些演出者就是所謂的物 件, 它們是根據事先定義好的類別所產生的

。例如需要有 5 個小猴子上場, 要由小猴子 的類別產生 5 個小猴子物件上場演出。

當我們構思好故事大綱後, 就需擬定參與角 色的特性, 此時就是要宣告類別, 因此以下 就先看如何宣告及定義類別。

(14)

8-2 定義類別

類別又稱為使用者自訂資料型別 (User

Defined Data Type), 表示它是由程式設計 人員自訂出的資料型別, 而編譯器也會把類 別當成一種資料型別來處理。

定義類別的語法

資料成員

成員函式

靜態成員

(15)

15

定義類別的語法

我們可以用 class 或 struct 關鍵字來定義類 別, 但最常用到的則是 class 定義類別時的 語法如下:

(16)

定義類別的語法

以上就是定義類別的敘述架構, 例如以下就 是定義一個空白的類別, 其名稱為 Car:

完成類別的定義, 就等於告訴編譯器我們已 定義了一個新資料型別, 接著就能用它來建 立物件了, 宣告的方式和使用內建資料型別 宣告變數一樣:

(17)

17

定義類別的語法

在此類別名稱就像是一個新的型別名稱, 並 可用來定義出各個性質相同的物件, 這些物 件就稱為該類別的個體 (Instance), 例如上 例中的 mycar、yourcar 就稱為 Car 類別的 個體。

此外我們也可在定義類別時, 即建立物件, 不過這種用法目前較少使用:

(18)

定義類別的語法

以上雖完成一個自訂資料型別的定義, 不過 這樣的空白類別不能做什麼事, 所以我們要 在裏面加入一些內容來描述類別的屬性及 行為。類別的屬性是透過資料成員 (Data Member) 來模擬, 類別的行為則是以成員函 式 (Member Function) 來模擬, 以下就分別

(19)

19

資料成員

資料成員的宣告方式和一般的變數相同, 只 不過資料成員是被宣告在類別之內, 例如描 述汽車的類別可能需要記錄載油量、耗油 量等屬性, 此時可在類別中宣告 2 個資料成 員來代表這些屬性:

(20)

資料成員

請注意, 資料成員不能設定初始值, 因為基 本上每個物件彼此都不盡相同, 若類別的資 料成員可設定初始值, 就變成每個物件的該 項屬性都相同, 並不合理, 所以在定義類別 時, 不可將資料成員設定初始值。

(21)

21

物件導向程式設計的特性:

封裝

物件導向程式設計具有三項特性:封裝

(Encapsulation )、繼承 (Inheritance)、多 面性 (Polymorphism), 本章我們先來認識什 麼是封裝。封裝的意思是指將類別的屬性 及操作屬性的方法都包裝在其中, 而且只對 外公開一些操作資料的方法, 外人無法直接 存取類別 (物件) 未公開的屬性。

(22)

物件導向程式設計的特性:

封裝

封裝的目的是讓使用類別的人, 不需要理會

、瞭解類別中包裝了什麼樣的內容, 他只需 透過公開的方法來操作物件即可達到使用 類別的目的 (就像只要會操作搖控器就能看 電視, 而不需瞭解電視內部的設計)。這樣的 理念讓設計好的類別有如軟體 IC, 程式設計 人員可將既有的 IC (類別) 組合起來, 實作 出程式的功能, 如此不但讓程式碼可重複使 用, 更能提高工作效率。

(23)

23

物件導向程式設計的特性:

封裝

封裝的另一個意義則是資料隱藏 (Data

Hiding), 類別中的成員預設都無法被外界任 意存取。因為若能被外界任意存取、修改 資料成員, 又失去了物件導向程式設計的意 義。

類別中不能被外部存取的成員, 統稱為私有 (private) 成員;相對於私有成員則是公開 (public) 成員, 它們可被類別外部的程式任 意存取。

(24)

物件導向程式設計的特性:

封裝

為了方便說明, 我們暫時先將範例類別的資 料成員設為公開的成員, 設定的方式就是在 定義資料成員之前, 加上 public 關鍵字及冒 號:

(25)

25

物件導向程式設計的特性:

封裝

public 稱為存取修飾字 (Access Specifier), 存取修飾字共有 3 個, 分別是:

public:在此段落中的成員都是公開的成員。若

是用 struct 定義類別, 則類別中的成員預設是公開 的成員。

private:在此段落中的成員都是私有的成員。用

class 定義類別時, 則成員預設都是私有的成員。

protected:在此段落中的成員對外部程式而言屬

於私有的成員, 但對繼承的類別而言, 則是公開的 成員。

(26)

物件導向程式設計的特性:

封裝

由於使用 struct 定義類別時, 預設所有的成 員都是公開的成員, 所以上述的例子若改用 struct 定義需寫成:

(27)

27

struct 與 class 的差別

用 struct 與 class 定義類別只有兩個差異:

struct 的成員預設為 public;class 的成員預設為

private。

繼承 struct 時, 預設是以 public 的方式繼承;繼

承 class 時, 則是以 private 的方式繼承。

(28)

struct 與 class 的差別

除了以上兩點外, 用 struct 和 class 是沒有 差別的。但由於 struct 是承襲自 C 語言而 來的, 只不過在 C++ 中增強、擴充為具有 類別的功能;再加上 struct 成員預設可公 開存取又失去封裝的意義, 因此大部份的 C++ 程式設計人員都習慣用 class 來定義 類別, 而較少使用 struct。

(29)

29

存取資料成員

定義了公開的資料成員後, 在類別以外的程 式 中 (例如 main() 函式中), 可透過成員存 取 (class member access) 運算子 ‘.’ 來存 取各物件中的公開資料成員:

例如:

(30)

存取資料成員

透過這種方式, 我們可以用 Car 類別寫一個 模擬汽車行駛狀況的程式如下:

(31)

31

存取資料成員

(32)

存取資料成員

(33)

33

存取資料成員

(34)

存取資料成員

第 20 行的 while 迴圈會在油量大於 0 的狀 況下持續執行, 當油量不足以走完指定里程 時, 就會在第 31 行顯示相關資訊, 並於第 33 行跳出迴圈。

在此要提醒讀者, 類別的公開成員可由外界 自由存取, 所以我們也可在建構物件時, 即 以設定陣列元素、結構體成員初始值的相 同方式來設定其公開成員的初始值, 所以上 例中第 12~14 行可簡化成一行敘述:

(35)

35

存取資料成員

我們用 gas 及 eff 兩個資料成員來表現汽車 物件的屬性及狀態, 這樣的表示方式雖然不 錯, 但上述程式的寫法, 也等於只是將變數 放在類別之中, 完全沒有發揮類別的資料封 裝功效, 因此我們必須再加入可操作物件的 成員函式, 讓汽車類別更能模擬真實汽車的 行為。

(36)

const 資料成員

資料成員也可加上 const 關鍵字成為常數, 由於常數一經宣告即無法更改其值, 所以 const 資料成員可在類別定義中設定初值, 這是它們與一般資料成員不同之處。舉例 來說, 若汽車類別代表的是同款式的車型, 其耗油量是固定的, 即可將之宣告為 const

(37)

37

成員函式

成員函式的種類

定義成員函式

呼叫成員函式

(38)

成員函式的種類

成員函式 (member function) 乃是類別的靈 魂所在, 也是設計類別時的成敗關鍵。它不 僅要提供一個公開的類別操作界面, 有時還 必須完成許多特殊的功能, 我們大致可以將 成員函式依功能分成下列幾種:

用來操作物件的函式:例如我們定義了一種字串

類別, 則複製字串、搜尋字串、替代字串等函式 都是所謂操作物件的方法。

(39)

39

成員函式的種類

用來存取物件內資料成員的函式:由於我們通常

都將資料成員封裝在類別之內 (設為 private), 所 以外界必須經由這類函式才能間接取得代表物件 屬性或狀態的資料成員。

僅供類別內部使用的函式成員:第 6 章提過, 有

一項功能 (或說一段程式碼) 會重複用到時, 即可 將它寫成函式以提高程式撰寫效率。同理, 如果 有多個成員函式需要用到同一個功能, 但又不希 望公開給外界使用, 那麼就可以將這個功能獨立 成一個函式, 並將之設為 private 而封裝在類別之 內。

(40)

成員函式的種類

類別的自動管理函式:當一個新物件被產生或生

命期結束時, 我們可能需要做一些特別的處理, 例 如新物件要做一些初始化或配置記憶體的工作;

而物件的生命期結束時, 可能也要釋放所配置的 記憶體或是做一些其他的善後工作。

前兩類成員函式是初學者最常接觸到的, 以 下就來介紹如何定義成員函式, 以設計操作 物件及存取資料成員的成員函式。

(41)

41

定義成員函式

成員函式既然也是函式, 所以定義的方式和 一般函式沒什麼不同:都要有函式名稱、

傳回值、參數等等。定義函式成員有兩種 方式, 第 1 種是直接將函式本體定義在類別 的大括號中, 此法適用在內容只有 1、2 行 敘述的函式:

(42)

定義成員函式

(43)

43

定義成員函式

上例中的 getEff() 及 checkGas() 函式都只 有一行敘述, 因此將函式本體定義在類別內 即可。定義在類別內的函式有項特性:就是 它們預設都是行內函式, 換言之, 上述的定義 對編譯器來說, 就相當於我們有在函式前加 上 inline 關鍵字:

(44)

定義成員函式

另一種定義函式的方式, 則是只在類別內宣 告函式原型, 但將函式本體定義在類別的大 括號之外, 此方法適用在函式內敘述較多的 情況。由於類別的成員函式可與程式中的 一般函式、甚至其它類別的成員函式使用 相同的名稱, 因此在定義成員函式時, 需以 類別名稱及範圍解析運算子 :: 明確讓編譯 器瞭解我們定義的是哪一個類別的成員函 式。

(45)

45

定義成員函式

(46)

定義成員函式

請注意, 成員函式與一般函式或其他類別中 的函式同名並不算多載 (overloading), 因為 它們都分屬不同的視野, 所以彼此互不相關, 根本不會形成多載。只有同一個類別內的

同名函式, 才會型成多載:

(47)

47

定義成員函式

(48)

呼叫成員函式

成員函式為類別的成員, 在使用上和一般函 式有個最大的差異, 即成員函式必須透過類 別的物件來呼叫, 例如:

另外, 大家應會注意到, 在前述的成員函式 定義中, 都直接存取資料成員, 而未透過成 員存取運算子。其道理很簡單, 當我們用物 件呼叫成員函式時, 函式所存取的資料成員,

(49)

49

呼叫成員函式

(50)

呼叫成員函式

由於我們每次都必須以 "物件 . 函式名稱"

的方式來呼叫成員函式, 所以成員函式自然 不會與類別以外的其他函式混淆。

認識成員函式的定義與呼叫方式後, 我們將 前面的 Car 類別重新定義, 將資料成員設為 私有, 而外部則可透過公開的成員函式來操 作物件, 範例程式內容如下:

(51)

51

呼叫成員函式

(52)

呼叫成員函式

(53)

53

呼叫成員函式

(54)

呼叫成員函式

1. 第 15~28 行的 go() 函式即為模擬汽車行走的 函式, 參數為要行走的公里數, 函式中會檢查油量 是否足夠, 並將車子原本的油量減掉此趟耗掉的 部份。

2. 第 30~34 行的 init() 函式則是用於初始化私有 的資料成員, 因我們已將資料成員宣告於 private:

的段落中, 所以只能透過成員函式來設定其值。

其實類別還有一種更佳的成員初始化方式。

(55)

55

呼叫成員函式

類別經過一番重新設計後, main() 函式的內 容變得相對簡單, 而且 main() 函式的功能就 很容易一目瞭然。或許有讀者覺得這樣設 計, 似乎使整個程式變得複雜, 其實這是因 為我們未讓類別發揮重複使用的效果, 如果 目前是在開發中大型的專案, 則只要類別都 妥善定義好, 則開發各部份的程式就相對簡 單許多, 這也是物件導向程式設計的最大好 處。

(56)

類別中成員的宣告慣例

在 Ch08-02.cpp 範例程式第 4~13 行 Car 類別的定義中, 將私有的成員也特地宣告在 private: 段落中。讀者或許會覺得奇怪, 為 何不直接在類別定義的大括號一開始, 就先 宣告私有的成員, 這樣不是可少寫一個

"private:" 嗎?

(57)

57

類別中成員的宣告慣例

(58)

類別中成員的宣告慣例

左邊的寫法看似要多寫一點字, 但它卻是常 見的 C++ 程式的慣用寫法 (convention), 其 寫法是在類別定義中, 依最公開的成員先出 現的原則來宣告資料成員及成員函式, 所以 採這種寫法時, 即是依 public、protected、

private 三種成員的順序宣告。當然這只是 一種寫作習慣, 並非強制的語法規則。

(59)

59

靜態成員

靜態資料成員

靜態成員函式

(60)

靜態資料成員

我們也可將類別中的資料成員前面加上

static 關鍵字, 使其成為靜態的資料成員。

一般的資料成員是每個物件都有自己的資 料成員;但靜態資料成員則是所有同類別 的物件共用的, 換言之類別的靜態資料成員 在記憶體中只有一份, 每個同類別物件所存 取到的靜態資料成員都是相同的, 若有物件 更改了靜態資料成員的值, 則所有物件存取 到的值也跟著改變。由於這項特性, 靜態資

(61)

61

靜態資料成員

舉例來說, 前面範例的汽車類別, 若是用來 代表同型的車款, 則基本的耗油量應該是相 同的, 此時即可將代表耗油量的資料成員, 宣告為靜態成員, 讓每個物件都使用同一個 資料:

(62)

靜態資料成員

靜態資料成員會被視為獨立的個體, 並不專 屬於哪一個物件, 所以我們必須額外定義它 (初始化其值), 定義的形式如下:

(63)

63

靜態資料成員

雖然這樣的定義方式好像在定義全域變數, 但靜態資料成員並非全域變數, 這一點要分 清楚。以下範例示範物件共用靜態資料成 員的情形:

(64)

靜態資料成員

(65)

65

靜態資料成員

第 9 行宣告的靜態資料成員於第 12 行定義 , 因此在 main() 函式中建立物件後, 即可透 過物件立即存取其值。

在第 21 行時以 One 物件呼叫成員函式設 定靜態資料成員的值, 在第 22 行用 Two 物 件取得靜態資料成員的值已經是更動後的 新數值, 印證了各物件共用同一靜態資料成 員的事實。

(66)

靜態成員函式

除了資料成員可加上 static 關鍵字變成靜態 資料成員外, 成員函式也可用同樣的方法設 定為靜態成員函式 (static member

function)。靜態成員函式與一般成員函式最 大的不同包括:

不需透過物件即可呼叫, 但為了與一般非成員函

式區分, 在呼叫時需以類別名稱:: 函式名稱() 的 方式呼叫。

在函式中只能存取靜態的資料成員, 不能存取非

靜態的成員。

(67)

67

靜態成員函式

使用靜態成員函式的目的主有有兩種:首先 就是設計成用來修改靜態資料的函式;此外 基於不需物件即可呼叫的特性, 靜態成員函式 也適合用來設計通用性、工具性的類別。

舉例來說, 在台灣加油時是以公升為單位, 在 美國等地則是以加侖為單位, 我們就可替汽車 類別設計換算這兩種單位的成員函式, 並設為 static, 讓所有程式不建立物件, 也能呼叫該函 式來進行單位換算。請參考以下範例:

(68)

靜態成員函式

(69)

69

靜態成員函式

(70)

8-3 類別與指標

指標在類別上的應用包括:建立指向物件 的指標, 以及在類別中使用指標型別的資料 成員, 其中包括一個所有類別都有的 this 指 標。

指向物件的指標

指標型別的成員

this 指標

(71)

71

指向物件的指標

建立指向基本資料型別的指標時, 要存取變 數值, 需使用間接存取運算子 *。但建立指 向物件的指標時, 要用物件指標存取公開的 成員時, 並不需使用間接存取運算子, 但需 使用另一個成員存取運算子 "->" (橫線加大 於符號) 來存取成員, 例如:

(72)

指向物件的指標

宣告類別指標變數時, 可用 new 運算子建立 新的物件, 讓指標可指向該物件, 請參考以 下範例:

(73)

73

指向物件的指標

(74)

指標型別的成員

我們知道, 指標變數在宣告時並未設定要指 向何處, 所以若在類別中使用指標型別的資 料成員時, 必須記得在物件建立即初始化其 指標成員所指的位址, 例如我們可在初始化 物件的成員函式中, 動態配置記憶體給物件 的資料成員, 請參考以下的範例:

(75)

75

指標型別的成員

(76)

指標型別的成員

(77)

77

指標型別的成員

Str 類別只是個很簡單的字串類別, 其唯一 的資料成員就是指向字串的指標, 由於建立 物件時不會讓指標指向任何字串, 所以設計 了兩個多載的 set() 成員函式, 其中之一是 傳入字串指標, 並將資料成員也指向相同的 位址;另一個不需參數的版本, 則是直接配 置新的記憶體空間, 然後將 "Empty!" 這個字 串內容複製進入。

(78)

指標型別的成員

上述的 Str 類別設計方式仍不夠好, 因為要 初始化字串內容相當不便, 且要具有實用功 能的字串類別, 必須設計相當多處理字串的 成員函式。所幸 C++ 已經幫我們設計好一 個實用的 string 類別, 在第 11 章就會介紹 如何使用 string 類別處理字串。

(79)

79

this 指標

當我們建立物件時, 每個物件都會享有獨自 的記憶體空間, 以存放各自擁有的非靜態資 料成員, 所以每個物件的資料成員都是獨立 互不干擾。但是類別的成員函式則是所有 物件共用的, 在記憶體中只會存一份成員函 式的程式碼, 每個物件呼叫成員函式時, 都 是執行到同一份程式碼。

(80)

this 指標

這時候就出現一個問題, 當我們透過各物件 呼叫成員函式來存取資料成員時, 成員函式 是如何得知目前呼叫它的是哪一個物件?

如何存取到正確物件的資料成員, 而不會存 取到別的物件的資料成員?

答案很簡單, C++ 編譯器在編譯成員函式時 , 偷偷做了幾個動作, 以便讓成員函式能存 取到正確物件的資料成員:

(81)

81

this 指標

1. 編譯器會自動在成員函式的參數列最前面加上一 個該類別的指標參數, 並以 this 為參數名稱, 然後 再將函式定義內所有出現資料成員的部份, 均改

this->資料成員的形式:

(82)

this 指標

2. 替程式中實際呼叫成員函式的敘述, 偷偷加上呼 叫者物件的位址做為第一個參數, 例如:

這樣一來, 就等於讓成員函式都能從 this 指 標取得呼叫物件的資料成員, 所以就不會發 生存取到錯誤的資料成員的情形。

(83)

83

this 指標

為讓讀者體會這個隱含在程式中的 this 指 標確實存在, 我們用以下的範例程式透過 this 指標來取得物件的位址:

(84)

this 指標

(85)

85

this 指標

第 8 行的 showthis() 成員函式將程式未定 義的 this 指標輸出到 cout, 也就是輸出指標 所指的位址。結果程式仍可順利編譯、連 結, 表示 this 指標確實存於程式之中。

(86)

使用 this 指標的時機

在某些狀況下會需要用到 this 指標, 例如當 成員函式的參數名稱與資料成員的名稱相同

、或是要做整個物件的複製, 請參見以下的例 子:

(87)

87

使用 this 指標的時機

(88)

使用 this 指標的時機

(89)

89

使用 this 指標的時機

在第 7 行的 copy 函式中, 將參數物件

source 直接用指定運算子設定給 this 指標 所指的物件, 此敘述的功用就是讓兩個物件 的資料成員值變得完全一樣, 也就是把

source 物件的資料成員值複製給 this 物件 的資料成員。

(90)

使用 this 指標的時機

第 13 行的 Time::set() 成員函式其參數名 稱與類別資料成員的名稱相同, 此時成員函 式預設只會存取到它自己的局部變數, 也就 是函式的參數。因此在函式中, 可 用 "this->

資料成員" 的語法來存取物件的資料成員, 在第 15~17 行就是透過此方式將各參數的 值指定給同名的資料成員。

(91)

91

傳回物件的成員函式

另一種會應用到 this 指位器的情況, 就是希 望類別的成員函式能以有如 cout 和 << 運 算子的使用方式一樣, 可以串接在一起呼叫, 例如:

(92)

傳回物件的成員函式

因為 . 和 -> 運算子的結合順序都是由左到 右, 所以 t.set() 會先執行, 執行完後的傳回 值需為 Time 類別的物件, 然後再以傳回的 物件來呼叫 show()。那麼, 要如何來設計 set() 呢﹖很簡單, 就是把它的傳回值設為 Time 類別的參考型別, 並在函式本體中傳 回 this 指標所指的物件:

(93)

93

傳回物件的成員函式

在執行 t.set(12,10,45).show(); 時,

t.set(12,10,45) 會先執行並傳回 t (時 t 的值 已被更改), 然後再以傳回的 t 來執行

t.show():

請參見以下範例:

(94)

傳回物件的成員函式

(95)

95

傳回物件的成員函式

(96)

傳回物件的成員函式

我們一直使用的 cout 及 << 運算子的串接 輸出方式, 就是這種傳回物件本身的應用。

因為在 C++ 中運算子也算是一種函式, 所 以也可以設計成具有這種串接的特性。

(97)

97

8-4 friend 類別的夥伴

類別是一個封裝良好的機構, 可以將內部私 有的資料與外界完全隔離, 但這有時卻反而 會造成一些困擾, 尤其是當我們想要在類別 以外的 (成員或非成員) 函式來存取已封裝 好的資料時。以字串類別為例, 我們知道在 標準函式庫中有一個 strcmp() 函式可以做 字串的比較, 如果我們想要另外寫一個

strcmp(Str, Str) 的多載函式, 使得二個 Str 物件也可以用 strcmp() 來做比較, 這時除了 將字串的成員設為公開成員外是否還有其 它方法呢?

(98)

friend 類別的夥伴

答案就是將自訂的 strcmp( ) 宣告為 Str 類 別的夥伴 (Friend)。被宣告為類別夥伴的函 式具有存取私有成員的特權, 所以這樣一來 就不必將類別的資料成員設為公開成員而 破壞類別資料隱藏的特性。

要將函式宣告為類別夥伴很簡單, 只需在類 別中加上該函式的原型宣告, 並在宣告最前 面加上

friend 關鍵字:

(99)

99

friend 類別的夥伴

用關鍵字 friend 宣告以後, 在 strcmp(Str&, Str&) 的函式本體中, 便可以直接存取 Str 類 別中任何私有的成員。夥伴的宣告可以放 在類別定義內的任何位置, 不過我們一般都 將它放在最前面, 以方便閱讀。以下就是加 入夥伴比較函式的 Str 類別:

(100)

friend 類別的夥伴

(101)

101

friend 類別的夥伴

(102)

誰可以做為類別的夥伴

由於宣告為夥伴的對象並非類別內的成員, 所以沒有 private 、protected 或 public 之 分。可以做為夥伴的對象有下列三種:

1. 一般函式:例如前面提到的 strcmp() 函式。且 同一個函式可以做為多個類別的夥伴, 例如:

2. 其他類別內的成員函式:但這個類別必須是已定

(103)

103

(104)

誰可以做為類別的夥伴

注意, copy() 函式本身的定義必須放在 String 類

別的定義之後, 這是因為 copy() 要用到 Str 內的 資料成員。所以如果我們想要將 copy() 的定義 放在 MemBlock 類別之內的話, 必須用下面第三 種方法。

3. 其他的類別:這必須以 “friend class 夥伴類別的 名稱” (其中 class 可以省略) 來宣告。當甲類別 被宣告為乙類別的夥伴時, 所有甲類別中的成員 函式均可任意存取乙類別內的非共用資料。例如

(105)

105

(106)

8-5 綜合演練

複數類別

堆疊類別

(107)

107

複數類別

複數是在進階的數學應用中經常用到的數 值, 但在內建資料型別中並沒有複數這樣的 類別, 因此在 C++ 發展初期, 就有不少人都 嘗試設計一個複數 (Complex) 類別來使用

。不過到後來, C++ 標準組織就決定在標準 類別庫中加入複數類別, 讓有需要的程式設 計人員省下不少工夫。

(108)

複數類別

在此我們還是利用自訂複數類別來做練習, 讓讀者能熟悉類別的定義及使用。複數的 組成可分為實數的實部與虛數的虛部, 所以 複數類別至少需 有 2 個資料成員來儲存這 兩個值, 而複數類別的四則運算也有些特別 之處, 以下程式示範一個含加減法成員函式 的陽春複數類別:

(109)

109

複數類別

(110)

複數類別

(111)

111

複數類別

這個陽春的類別共有 4 個成員函式, 分別是設 定數值的 set() 及顯示數值的 show(), 以及加 減法的函式。加減法函式分別依照複數實部 加實部、虛部加虛部的計算規則設計, 並傳回 物件本身。

由於我們還未學到一些實用的類別功能設計 方式, 所以這幾個函式的設計方式都不太恰當 , 例如加減法函式都改變呼叫物件的值, 當然 我們也可修改成建立新複數物件, 並將計算結 果設定給新物件, 最後再傳回新物件的方式。

(112)

堆疊類別

初學程式設計者, 免不了會接觸到一些基本 的資料結構 (Dat a Structure), 其中堆疊

(stack) 是初學者最常接觸到的。所謂堆疊 是指一種後進先出 (LIFO, Last In First Out) 的資料結構, 也就是說, 放入此結構 (集合) 的物件 (變數) 要被取出時, 必須等比它後加 入的物件全部被拿出來後, 才能將它拿出來

。例如一端封閉的網球收納筒, 就是一種堆 疊結構:

(113)

113

堆疊類別

(114)

堆疊類別

關於資料結構的探討, 在此不擬深入, 有興 趣者, 可參照其它相關主題的書籍。不過我 們可以發現這種簡單的資料結構, 也很適合 設計成類別, 不但能以更直覺的方式操作, 也能達到堆疊內資料不被外部任意存取 / 修 改的目的。

(115)

115

堆疊類別

舉一個整數堆疊的例子, 它可用來存放整數 的資料, 我們可設計一個整數堆疊類別, 其 中可存放一定數量的整數, 然後再設計用來 將整數存入堆疊的成員函式 push()、將整 數自堆疊取出的成員函式 pop(), 就可以很 方便地將整數存入堆疊或由堆疊取出了。

請看下面的程式:

(116)

堆疊類別

(117)

117

堆疊類別

(118)

堆疊類別

以上是可存放整數資料的堆疊類別, 資料成員 buffer[] 陣列就是用來存放放入堆疊的整數, 而 sp 則記錄目前已存幾筆資料。push()、pop() 函式分別模擬將資料存入堆疊、及從堆疊中取 出資料的動作, 其內容很簡單, 都是先檢查 sp 是否已達最大值 (表示堆疊已滿) 或是 0 (表示 堆疊是空的), 不是就進行存入或取出的動作。

我們將程式存於 .h 檔中, 這樣子別的程式要使 用時, 只需含括這個 .h 檔即可使用整數堆疊類 別, 如以下範例所示:

(119)

119

堆疊類別

(120)

堆疊類別

(121)

121

堆疊類別

在上例的 main() 函式中一共建立了二個堆 疊物件, 並分別用它們來存放 3 個整數, 再 取出之。程式最後故意要 st2 物件多 pop 一次資料, 結果就會執行到私有成員函式 Error(), 顯示堆疊出錯的訊息。

參考文獻

相關文件

在上述的概念面向分析中, Paul Ernest 特別強調有兩個概念叢集 (conceptual

「光滑的」邊界 C。現考慮相鄰的 兩個多邊形的線積分,由於共用邊 的方向是相反的,所以相鄰兩個多

z請利用 struct 記錄 Bob 和 Alice 的相關資訊 z列印出 Bob 及 Alice 分別花多少錢. z再列印出

z請利用 struct 記錄 Bob 和 Alice 的相關資訊 z列印出 Bob 及 Alice 分別花多少錢. z再列印出

z請利用 struct 記錄 Bob 和 Alice 的相關資訊 z列印出 Bob 及 Alice 分別花多少錢. z再列印出

private void closeBTN_Click(object sender, System.EventArgs

zCount 屬性–取得項目個數 zAdd 方法–新增項目. zRemove 方法–移除指定項目

private void answerLB Click(object sender private void answerLB_Click(object sender,. System.EventArgs