1 / 112

Class (類別)

前言 Class 的 定義 Class 物件 this pointer Constructors Destructor Copy Constructor Initializer List Constant Objects Static Members Friend Functions. Friend Classes Class Object I/O Struct Union Bit-Field Members Class Scope Other Issues nested class class and namespace

alena
Download Presentation

Class (類別)

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. 前言 Class 的定義 Class 物件 this pointer Constructors Destructor Copy Constructor Initializer List Constant Objects Static Members Friend Functions Friend Classes Class Object I/O Struct Union Bit-Field Members Class Scope Other Issues nested class class and namespace local class Class(類別)

  2. 前言 我們知道「資料型態 = 物件 + 運算」。C++ 除了提供字元、整數、浮點數、等等的基本資料型態以外,也允許我們利用 class 結構來自定資料型態。在 class 的定義中,我們制定物件共同的屬性與運算。 此外,C++ 也提供了一些機制來分隔 class 的使用介面與製作細節,達到所謂的 information hiding。

  3. Class 的定義 • 定義語法 • Class 成員的存取限制 • 資料成員 • 資料成員的命名 • 成員函式 • Inline 成員函式 • const 成員函式 • 非 public 的成員函式 • 安排 class 的程式碼

  4. Class 的定義語法 • Class 定義的語法如下: • classclass_name { • // class 的成員 • }; • 其中的 class 成員又可分為以下兩種: • data member(資料成員) • 物件所擁有的屬性(即物件內含的資料項目)。 • member function(成員函式) • 物件所能夠執行的運算。

  5. Class 成員的存取限制 • public: • 允許外界存取的成員。這些成員是作為 class的使用介面。 • private: • 不允許外界存取的成員。這些成員是用來 implement class 的內部。 • protected: • 允許 subclass 但不允許其它外界存取的成員。 • classclass_name { • public: • // public 的成員 • protected: • // protected 的成員 • private: • // private 的成員 • };

  6. 範例 以下是平面點的 class 宣告: • class CPoint2D { • public: • int x(); // get x-coordiate • int y(); // get y-coordiate • void setX(int); // set x-coordiate • void setY(int); // set y-coordiate • void set(int, int); // set x and y coordiates • bool isZero(); // is the origin? • int distance(); // the distance to the origin • private: • int _x, _y; • };

  7. 資料成員 • 資料成員只是宣告物件所擁有的資料項目,而不是真正的變數定義。因此我們不可以在 class 定義中設定資料成員的初值。譬如: • class CPoint2D { • … • int _x = 0, _y = 0; // error • }; • 資料成員多半用於 class 的內部製作,因此通常被擺在 private 段之中。

  8. 資料成員的命名 資料成員通常是用特別的方式來命名,以便和一般的變數有所區別。比如:有人習慣以底線字元(_)開頭來命名資料成員,如 _x, _y, 等等。又如:在微軟公司的 MFC class library 中,資料成員名稱的字頭一律是 m_,如 m_x, m_y, 等等。 當然,你可以用其它的規則來命名資料成員,只要這個規則能夠保持一致性即可。

  9. 成員函式 • 若成員函式的定義不是寫在 class 宣告之中(即非 inline 成員函式),而是寫在 class 宣告之外,則它的寫法如下: • return_type class_name::func_name (parameter list) • { … } • 譬如: • void CPoint2D::setXY(int x, int y) • { • _x = x; _y= y; • }

  10. Inline 成員函式 如果成員函式非常簡單,它通常被寫成 inline 函式來增加程式的效率。你可以用兩種方法來寫 inline 成員函式:(1) 把函式的定義直接寫在 class 宣告之中。(2) 在函式宣告之前加上 inline 這個關鍵字,然後在 class 宣告之外寫下函式的 inline 定義。譬如: } 之後沒有分號 ; • class CPoint2D { • public: • int x() { return _x; } // inline definition • inline int y(); // inline declaration • private: • int _x, _y; • }; • inline int CPoint2D::y() { return _y; }

  11. 由於 CPoint2D 的成員函式非常簡單,因此我們 把它們都寫成 inline 函式如下: 範例 • #include “math.h” /* for sqrt() */ • class CPoint2D { • public: • int x() { return _x; } • int y() { return _y; } • void setX(int x) { _x = x; } • void setY(int y) { _y = y; } • void set(int x, int y) { _x = x; _y = y; } • bool isZero() { return _x == 0 && _y == 0; } • int distance() { return sqrt(_x*_x+_y*_y); } • private: • int _x, _y; • };

  12. 如果成員函式不會或不應該更改資料成員,最好把它宣告成為 const 成員函式。宣告方式是在成員函式宣告與定義的參數列之後加上關鍵字 const,如下所示: • class X { • void foo () const ; • bar () const { … } // inline const member function • } • void X::foo () const • { … } const 成員函式

  13. 範例 加上 const 宣告之後的CPoint2D class: • #include “math.h” /* for sqrt() */ • class CPoint2D { • public: • int x() const { return _x; } • int y() const { return _x; } • void setX(int x) { _x = x; } • void setY(int y) { _y = y; } • void set(int x, int y { _x = x; _y = y; } • bool isZero() const { return _x == 0 && _y == 0; } • Int distance() const { return sqrt(_x*_x+_y*_y); } • private: • int _x, _y; • };

  14. 如果一個成員函式被宣告為 const 之後,就不得在函式中更改資料成員,否則會造成編譯上的錯誤,譬如: • class CPoint2D { • public: • ... • void setX(int x) const { _x = x; } // error • ... • private: • int _x, _y; • }; 更改了資料成員 _x

  15. 非 public 的成員函式 • 如果成員函式只是供內部的 implementation 之用,而非供外界使用的話,則它應該擺在 private 段(或 protected 段)之中,而不應該擺在 public 段之中。譬如: • class X { • public: • // public members • private: • void internal_use (); // private member function • // other private members • };

  16. 安排 class 的程式碼 • 假定有一個名為 X 的 class。我們通常按照下面的方式來安排 X的程式碼: • 把 class 的宣告與 inline 成員函式的定義寫在 X.h 檔中。 • 如果有非 inline 成員函式,則把它們的定義寫在 X.cpp 檔中。 • 譬如: • // file: X.h • class X { • ... • void foo (); • ... • } • // file: X.cpp • void X::foo () • { • ... • } • ...

  17. 範例 CPoint2D.h 檔的內容: • #ifndef CPOINT2D_H_ • #define CPOINT2D_H_ • #include <math.h> /* for sqrt() */ • class CPoint2D { • public: • int x() const { return _x; } • int y() const { return _y; } • void setX(int x) { _x = x; } • void setY(int y) { _y = y; } • void set(int x, int y { _x = x; _y = y; } • bool isZero() const { return _x == 0 && _y == 0; } • Int distance() const { return sqrt(_x*_x+_y*_y); } • private: • int _x, _y; • }; • #endif

  18. Class 物件 • 如前所述,class 是一種自定的資料型態,因此我們可以用 class 的名稱來宣告或定義變數。這一類的變數通常稱為物件(object)。譬如: • class X { … }; • class Y { … }; • X x_obj; // x_obj 是屬於 class X 的物件 • Y y_obj; // y_obj 是屬於 class Y 的物件 • 物件定義之後就具有以下所述的兩種性質。

  19. a b _x _x _y _y • C++ 編譯器會配置一塊記憶體給物件用來存放 class 所宣告的資料成員。譬如: • #include “CPoint2D.h” • … • CPoint2D a; • CPoint2D b; • CPoint2D 物件 a 和 b 各自含有 _x 和 _y 兩項資料屬性: 1

  20. 物件可以用以下的格式來呼叫 public 成員函式或存取 public 資料成員: • object.pubic_member_function(argument list) • object.pubic_data_member • 譬如: • #include “CPoint2D.h” • CPoint2D a, b; • a.setXY(3, 4); // 把 a 的 _x 和 _y 分別設成 3 和 4 • b.setX (5); // 把 b 的 _x 設成 5 • if (a.isZero()) // 測試 a 是否等於 (0, 0) 2

  21. 物件指標則用以下的格式來呼叫 public 成員函式或存取 public 資料成員: • objectPtr->pubic_data_member • objectPtr->pubic_member_function(argument list) • 譬如: • #include “CPoint2D.h” • CPoint2D *a = new CPoint2D; • a->setXY(3, 4); // 把 a 的 _x 和 _y 分別設成 3 和 4 • if (a->isZero()) // 測試 a 是否等於 (0, 0)

  22. 物件或物件指標不允許呼叫非 public 成員函式或存取非 public 資料成員: 注意 • 譬如: • #include “CPoint2D.h” • CPoint2D a; • CPoint2D *b = new CPoint2D; • a._x =3; // error: access nonpublic member • b->_y =3; // error: access nonpublic member

  23. 範例 以下程式示範如使用 CPoint2D class: • // File: main.cpp • #include <iostream> • #include “CPoint2D.h” • using namespace std; • int main () • { • int x, y; • cout << “Enter x: “; • cin >> x; • cout << “Enter y: “; • cin >> y; • CPoint2D p; • p.setXY(x, y); • cout << “The point is (“ << p.x() << “, “ << p.y() << ‘)’ << endl; • return 0; • } 輸入 Enter x: 3 Enter y: 5 輸出 The point is (3, 5) 編譯方式:CC main.cpp -o prog

  24. this 指標 • 假定 a 和 b 是兩個 CPoint2D 類別的物件,並用以下的方式來設定 a 和 b 的座標值: • a.setXY(3,4); b.setXY(3,4); • 由 setXY() 的定義看來: • void CPoint2D::setXY(int x, int y) • { • _x = x; _y= y; • } • 其中並沒有指定物件, setXY() 又怎麼知道要設定那一個物件的 _x 和 _y 呢?

  25. 我們必須暸解 C++ 內部處理成員函式的方式,才有辦法回答上面的問題。 • 首先,所有成員函式都有一個隱含的參數。此參數的名稱是 this,且其型態是一個指標,譬如 C++ 把 setXY() 的宣告在內部改成如下的形式: • class CPoint2D { • public: • void setXY( CPoint2D *this , int x, int y) • } • void CPoint2D::setXY( CPoint2D *this , int x, int y) • { • ... • }

  26. 其次,C++ 會把出現在成員函式中的資料成員,改用 this 指標來間接存取。譬如: • void CPoint2D::setXY( CPoint2D *this , int x, int y) • { • this->_x = x; this->_y = y; • }

  27. 最後,C++ 更改成員函式的呼叫如下: • obj.member_func (argumet list) • 改成 • class_name:: member_func(&obj, argumet list) • 以及 • objptr->member_func (argumet list) • 改成 • class_name:: member_func(objptr, argumet list)

  28. 從以上的說明,我們就知道了 setXY() 的內部定義是: • void CPoint2D::setXY( CPoint2D *this , int x, int y) • { • this->_x = x; this->_y = y; • } • 以及 • a.setXY(3,4) 和 b.setXY(3,4); • 分別被改成: • CPoint2D::setXY(&a, 3,4) 和 CPoint2D::setXY(&b, 3,4) • 因而能夠正確地設定 a 和 b 內部的 _x 和 _y。

  29. this 是 C++ 的關鍵字,因此你不能夠重新宣告和定義它。 • int this; // error • void foo (int this) { … } // error • C++ 會自動為成員函式加入 this 指標參數,因此你不需要在成員函式的參數列中宣告它,否則會造成語法錯誤。 • void CPoint2D::setXY( CPoint2D *this , int x, int y) • { … } // error 你可以在成員函式中使用 this 指標來指涉目前的物件。我們會在後面示範它的用法。

  30. 建構函式 • 如果我們想在定義物件時,能夠設定物件的初值,就必須定義 class的建構函式(constructor)。建構函式的名稱一定要與 class 相同,而且不能有傳回值型態(即使 void 也不允許)。 • 建構函式可以有多個(即 overloading)。其中沒有參數的稱為預設的建構函式(default constructor)。譬如: • class X { • public: • X (); // default constructor • X (int); // another constructor • … • }

  31. 建構函式可用來設定物件的初值。如: • X a; // call a.X() to do initialization • X b(3); // call b.X(3) to do initialization • X *cp = new X(5); // call cp->X(5) to do initialization • 如果 class 並沒有定義建構函式的話,就無法用類似以上的方式來設定物件的初值。比方說,前面所舉的 CPoint2D class因為沒有定義任何的建構函式,因此只能作如下的物件定義: • CPoint2D a; • CPoint2D *bp = new CPoint2D; • 而不能在定義中設定物件的初值,如: • CPoint2D c(0, 0); // error

  32. 範例 我們可以定義 CPoint2D 的建構函式如下: • class CPoint2D { • public: • CPoint2D () { _x = 0; _y = 0; } • CPoint2D (int x, int y) { _x = x; _y = y; } • // 其它的成員函式 • private: • int _x, _y; • }; • 則 • CPoint2D a; // a = (0, 0) • CPoint2D b(3,4); // b = (3, 4) • CPoint2D *cp = new CPoint2D(4,5); // *c = (4, 5)

  33. 範例 前一頁 CPoint2D 的建構函式可以簡化如下: • class CPoint2D { • public: • CPoint2D (int x = 0, int y = 0) { _x = x; _y = y; } • // 其它的成員函式 • private: • int _x, _y; • }; • 則 • CPoint2D a; // a = (0, 0) • CPoint2D b(3,4); // b = (3, 4) • CPoint2D *cp = new CPoint2D(4,5); // *c = (4, 5)

  34. 假定 X 是一個沒有建構函式的 class。當用 X 來定義物件時,C++ 編譯器的處理方式通常如下: • 如果定義的是 global 或 static 物件, 則 C++ 編譯器會把物件所佔據的記憶體清除成 0 值。 • X a; // global • static X b; // static • 如果定義的是 local 物件, 則 C++ 編譯器不會把物件所佔據的記憶體清除成 0 值,因此物件的初值是一堆垃圾值。 • void foo () • { • X a; // local • }

  35. 不過前一頁的規則並不適用以下的情形:如果 X 含有物件資料成員,且此物件所屬的 class 具有預設的建構函式。譬如: • class Y { • public: • Y(); • … • } • class X { • // no constructors • private: • Y _m; • } 則 C++ 編譯器會自動為 X 提供一個預設的建構函式 X(),並在其中呼叫 Y() 來設定 _m 的初值。

  36. _topRight _botomLeft 範例 • class CPoint2D { • public: • CPoint2D () { _x = 0; _y = 0; } • // 其它的成員函式 • private: • int _x, _y; • }; • class CRectangle { • public: • // no constructors • private: • CPoint2D _botomLeft, _topRight; • }; • CRectangle r; // _botomLeft和_topRight 都是 (0, 0)

  37. 有些 class 一定要定義建構函式才不會出錯。底下我們就以動態配置方法來做的 stack class 為例來說明: 範例 • // File: CStack.h • #ifndef CSTACK_H_ • #define CSTACK_H_ • class CStack { • public: • CStack (int sz= 100); // default constructor • bool push (int); • bool pop (int &); • bool peek (int &); • bool isEmpty () { return _top == -1; } • bool isFull () { return _top == _size -1; } • private: • int *_stack; • int _size; • int _top; • } • #endif

  38. // File: CStack.cpp • #include “CStack.h” • CStack:: CStack (int sz) • { • _top = -1; • if (sz > 0) { • _stack = new int[sz]; • _size = (_stack) ? sz : 0; • } • else _size = 0; • } • bool CStack::pop (int &e) • { • if (isEmpty()) return false; • e = _stack[_top--]; • return true; • } • bool CStack::peek (int &e) • { • if (isEmpty()) return false; • e = _stack[_top]; • return true; • } • bool CStack::push (int e) • { • if (isFull()) return false; • _stack[++_top] = e; • return true; • }

  39. s1 0 1 99 _stack _size 100 _top -1 s2 0 1 49 _stack _size 50 _top -1 #include “CStack.h” … CStack s1; // s1 是可容納 100 個元素的 stack CStack s2(50); // s2 是可容納 50 個元素的 stack

  40. // File: main.cpp • #include <iostream> • #include “CStack.h” • using namespace std; • int main () • { • CStack s(10); • k = 0; • while (!s.isFull()) • s.push(++k); • int e; • while (s.pop(e)) • cout << e << endl; • return 0; • } 輸出的結果: 10 9 . . . 1 編譯方式:CC main.cpp CStack.cpp -o prog

  41. 如果 CStack 沒有建構函式,則我們必須寫一個配置記憶體的成員函式,如: • class CStack { • public: • alloc (int sz= 100); // allocate memory • // other members • } • 然後用以下兩個步驟來建立 CStack 物件: • CStack s; • s.alloc(50); • 這樣不僅繁頊,而且容易造成錯誤,譬如: • s.alloc(50); • s.alloc(100); // this may lead to memory leak

  42. 如果一個 class 有建構函式,但沒有預設的建構函式,則定義物件時,一定要提供初值化的參數,譬如: • class CPoint2D { • public: • CPoint2D (int, int); // the only constructor • // other members • } • 則 • CPoint2D p1; // error • CPoint2D p2(3, 4); // ok

  43. 如果我們不希望外界直接用一個 class 來定義物件時,可以把預設的建構函式擺在 protected 段或 privated 段之中,而且不定義其它的建構函式。譬如: • class CPoint2D { • public: • // no other constructors • private: • CPoint2D (); // default constructor • // other private members • } • 則 • CPoint2D p1; // error

  44. 解構函式 • 如果建構函式負責物件之生,則解構函式(Destructor)負責物件之死。一個 class 只能有一個解構函式,且其宣告的格式為: • ~class_name() • 譬如: • class CStack { • public: • ~CStack () { delete [] _stack; } // destructor • // other members • }

  45. 假定 CStack沒有如前一頁般地定義解構函式。請問 底下的函式有什麼 bug? 範例 • void foo () • { • CStack s; • … • } 解答:由於 s 中動態配置的記憶體在離開函式 foo 時, 並沒有被刪除掉,因此會發生 memory leak 的 bug。

  46. 通常我們並不直接呼叫解構函式,因為 C++ 編譯器會在物件的生命期結束時自動地加入其解構函式的呼叫。舉例言之: • block 中的 local objects 在 block 結束時會自動呼叫解構函式。 • { • CStack s; • … • } C++ 編譯器在這裏加入 s.~CStack() 的呼叫

  47. 函式中的 local objects 在函式結束時會自動呼叫解構函式。 • void foo (CStack p) • { • CStack s; • … • } C++ 編譯器在這裏加入 p.~CStack() 和 s.~CStack() 的呼叫。 • 刪除物件指標時,也會自動呼叫解構函式。 • CStack *sp = new CStack; • … • delete sp; 在刪除 sp 所佔據的記憶體之前, 會先呼叫 sp->~CStack()。

  48. 假定物件 obj1 含有其它類別的物件 obj2。則 obj1 死亡時也會自動呼叫 obj2 的解構函式。 • class X { • // other members • private: • CStack s; • }; • { • X xobj; • … • } C++ 編譯器在這裏加入 xobj.s.~CStack() 的呼叫。

  49. 並非所有的 class 都需要提供解構函式。事實上,只有那些擁有動態配置系統資源(如記憶體、檔案、或 lock)的 class 才需要自定解構函式。比方說,CPoint2D class 並沒有動態配置的記憶體,因此我們不需要在 CPoint2D 中定義解構函式 ~CPoint2D()。反過來說, CStack class 含有動態配置的記憶體,因此我們必須定義解構函式 ~CStack() 來解除動態配置的記憶體。

  50. local 物件指標和 reference 死亡時,並不會自動呼叫解構函式。譬如: • void foo (CStack &r) • { • CStack *sp = new CStack; • … • } C++ 編譯器不會在這裏加入 r.~CStack() 和 sp->~CStack() 的呼叫。

More Related