C++ 入門指南 4.01
單元 11 - 物件導向與封裝
物件導向程式設計 (object-oriented programming) 有三大基本特性,分別是封裝 (encapsulation) 、繼承 (inheritance) 及多型 (polymorphism)
繼承的目的是讓類別 (class) 具有像是親屬的垂直關係(父母子女),子類別 (subclass) 可以擁有父類別 (superclass) 的成員 (member) ,而多型像是親屬的平行關係(兄弟姊妹),多個子類別繼承自單一父類別之時,這些子類別就可以用父類別代替,父類別如同家族裡的「姓」,子類別則是「名」。
繼承的英文原文 inherit ,中文意思泛指從什麼得到什麼,生物學上的遺傳也是用這個詞。
至於封裝的意思就是把資料 (data) 封在類別中,這還牽涉到程式設計中另一個重要的概念 資訊隱藏 (information hiding) ,主要就是不讓外界隨意存取類別的資料,也就是說,只讓類別的資料成員 (data member) 給同個類別的成員函數 (member function) 存取。
這就要用到 private 存取標籤 (access label) 了,就是把成員變數放在 private 之後,而其他可供外界存取的成員函數放在 public 之後
// 宣告類別 class Demo { // 宣告 public 的成員 public: void set_a(int n); void set_b(int n); int get_a(); int get_b(); int DoSomething(); // 宣告 private 的成員 private: int a; int b; };
這裡 a 與 b 已經改放到 private 之後,也由於 a 與 b 都是 private 的,因此另外宣告 public 的 set_a() 與 set_b() 設定 a 與 b 之值, get_a() 與 get_b() 取得 a 與 b 之值。
存取標籤後面要接一個冒號,之後的成員依縮排方式加入。
set_a() 與 set_b() 為修改函數 (mutator) ,就是俗稱的 setter ,至於 get_a() 與 get_b() 為存取函數 (accessor) ,也就是是俗稱的 getter 。
因此 set_a() 、 set_b() 、 get_a() 、 get_b() 的實作很簡單,如下
// 實作 setter 與 getter 成員函數 void Demo::set_a(int n) { a = n; } void Demo::set_b(int n) { b = n; } int Demo::get_a() { return a; } int Demo::get_b() { return b; }
C++ 可以用關鍵字 (keyword) this 加上 -> 存取成員變數,例如 this->a 就是成員變數 a ,這樣的好處是可以使用跟成員變數相同識別字的參數。
我們寫成一個完整範例,如下
#include <iostream> using namespace std; // 宣告類別 class Demo { // 宣告 public 的成員 public: void set_a(int n); void set_b(int n); int get_a(); int get_b(); int DoSomething(); // 宣告 private 的成員 private: int a; int b; }; // 實作 DoSomething() 成員函數 int Demo::DoSomething() { // 改成呼叫 getter 成員函數 return get_a() + get_b(); } // 實作 setter 與 getter 成員函數 void Demo::set_a(int n) { a = n; } void Demo::set_b(int n) { b = n; } int Demo::get_a() { return a; } int Demo::get_b() { return b; } // 程式執行的 main() 函數 int main(void) { // 宣告 Demo 型態的物件 Demo t; // 由呼叫 setter 設定成員變數 t.set_a(12); t.set_b(23); cout << endl << t.DoSomething() << endl << endl; return 0; } /* 《程式語言教學誌》的範例程式 http://kaiching.org/ 檔名:class_demo2.cxx 功能:示範定義類別 作者:張凱慶 */
編譯執行結果如下
但是這樣設定成員變數還得額外呼叫 set_a() 與 set_b() ,有點麻煩,我們希望宣告 (declare) 時就能夠直接設定,其實這用建構函數 (constructor) 就可以囉!
中英文術語對照 | |
---|---|
存取標籤 | access label |
存取函數 | accessor |
類別 | class |
建構函數 | constructor |
資料 | data |
資料成員 | data member |
宣告 | declare |
封裝 | encapsulation |
資訊隱藏 | information hiding |
繼承 | inheritance |
關鍵字 | keyword |
成員 | member |
成員函數 | member function |
修改函數 | mutator |
物件導向程式設計 | object-oriented programming |
多型 | polymorphism |
子類別 | subclass |
父類別 | superclass |
重點整理 |
---|
1. 物件導向程式設計有封裝、繼承及多型等三大基本特性。 |
2. 繼承像是親屬的重直關係(父母子女),多型則像是親屬的平行關係(兄弟姊妹)。 |
3. 封裝連帶的觀念就是資訊隱藏,類別的成員變數只能在類別中處理,因此要把資料成員宣告在 private 存取標籤下。 |
4. 外界要存取 private 的資料成員要透過 public 的存取函數與修改函數。 |
問題與討論 |
---|
1. 物件導向程式設計有哪三大基本特性? |
2. 什麼是封裝?為什麼要做封裝? |
3. 什麼是繼承?繼承機制有什麼優點? |
4. 什麼是多型?可以用什麼方式比擬嗎? |
5. 什麼是存取函數與修改函數?為什麼要有存取函數與修改函數? |
練習 |
---|
1. 寫一個程式 exercise1101.cxx ,將上述 class_demo2.cxx 裡頭的成員變數加上 this-> ,並把修改函數的參數識別字改成跟成員變數相同。 參考程式碼 |
2. 寫一個程式 exercise1102.cxx ,利用 exercise1006.cxx 設計的 IntegerDemo ,將 value 宣告在 private 標籤下,替 value 加入存取函數與修改函數。 參考程式碼 |
3. 寫一個程式 exercise1103.cxx ,利用 exercise1007.cxx 計算階層值的類別,將 value 宣告在 private 標籤下,替 value 加入存取函數與修改函數。 參考程式碼 |
4. 寫一個程式 exercise1104.cxx ,利用 exercise1008.cxx 計算費氏數列的類別,將 value 宣告在 private 標籤下,替 value 加入存取函數與修改函數。 參考程式碼 |
5. 寫一個程式 exercise1105.cxx ,利用 exercise1009.cxx 的 Point 類別,將 x 與 y 宣告在 private 標籤下,繼續替 x 與 y 加入存取函數與修改函數。 參考程式碼 |
6. 寫一個程式 exercise1106.cxx ,利用 exercise1010.cxx 的 Guess 類別,將 answer 宣告在 private 標籤下,繼續替 answer 加入存取函數與修改函數。 參考程式碼 |
7. 寫一個程式 exercise1107.cxx ,利用 exercise1011.cxx 的 Member 類別,將 id 與 name 宣告在 private 標籤下,繼續替 id 與 name 加入存取函數與修改函數。 參考程式碼 |
8. 寫一個程式 exercise1108.cxx ,利用 exercise1012.cxx 的 Text 類別,將 content 宣告在 private 標籤下,繼續替 content 加入存取函數與修改函數。 參考程式碼 |
9. 寫一個程式 exercise1109.cxx ,利用 exercise1013.cxx 的 Ball 類別,將 p 宣告在 private 標籤下,繼續替 p 加入存取函數與修改函數,其中存取函數改成回傳座標的字串。 參考程式碼 |
10. 寫一個程式 exercise1110.cxx ,利用 exercise1014.cxx 的 Scene 類別,將 id 與 text 宣告在 private 標籤下,繼續替 id 與 text 加入存取函數與修改函數。 參考程式碼 |
11. 寫一個程式 exercise1111.cxx ,利用 exercise1015.cxx 的 Game 類別,將 guess 宣告在 private 標籤下,繼續替 guess 加入存取函數與修改函數,其中存取函數直接回傳 guess 的 answer 成員。 參考程式碼 |
相關教學影片