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 namespac e local class

Class (類別)

  • Upload
    alena

  • View
    77

  • Download
    0

Embed Size (px)

DESCRIPTION

前言 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 - PowerPoint PPT Presentation

Citation preview

Page 1: Class (類別)

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– local class

Page 2: Class (類別)

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

Page 3: Class (類別)

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

Page 4: Class (類別)

Class 的定義語法Class 定義的語法如下:

class class_name {

// class 的成員};

其中的 class 成員又可分為以下兩種:data member (資料成員)

物件所擁有的屬性(即物件內含的資料項目)。member function (成員函式)

物件所能夠執行的運算。

Page 5: Class (類別)

Class 成員的存取限制public:

允許外界存取的成員。這些成員是作為 class 的使用介面。

private:

不允許外界存取的成員。這些成員是用來 implement class 的內部。

protected:

允許 subclass 但不允許其它外界存取的成員。

class class_name {

public:

// public 的成員protected:

// protected 的成員private:

// private 的成員};

Page 6: Class (類別)

範例class CPoint2D {public:

int x(); // get x-coordiateint y(); // get y-coordiatevoid setX(int); // set x-coordiatevoid setY(int); // set y-coordiatevoid set(int, int); // set x and y coordiatesbool isZero(); // is the origin?int distance(); // the distance to the origin

private:int _x, _y;

};

以下是平面點的 class 宣告:

Page 7: Class (類別)

資料成員資料成員只是宣告物件所擁有的資料項目,而不是真正的變數定義。因此我們不可以在 class 定義中設定資料成員的初值。譬如:

class CPoint2D {…int _x = 0, _y = 0; // error

};

資料成員多半用於 class 的內部製作,因此通常被擺在 private 段之中。

Page 8: Class (類別)

資料成員通常是用特別的方式來命名,以便和一般的變數有所區別。比如:有人習慣以底線字元( _ )開頭來命名資料成員,如 _x, _y, 等等。又如:在微軟公司的 MFC c

lass library 中,資料成員名稱的字頭一律是 m_ ,如 m_x,

m_y, 等等。當然,你可以用其它的規則來命名資料成員,只要這個規則能夠保持一致性即可。

資料成員的命名

Page 9: Class (類別)

成員函式若成員函式的定義不是寫在 class 宣告之中(即非 inline 成員函式),而是寫在 class 宣告之外,則它的寫法如下:

return_type class_name::func_name (parameter list){ … }

譬如:void CPoint2D::setXY(int x, int y){

_x = x; _y= y;}

Page 10: Class (類別)

Inline 成員函式如果成員函式非常簡單,它通常被寫成 inline 函式來增加程式的效率。你可以用兩種方法來寫 inline 成員函式: (1) 把函式的定義直接寫在 class 宣告之中。 (2) 在函式宣告之前加上 inline 這個關鍵字,然後在 class 宣告之外寫下函式的 inline 定義。譬如:

class CPoint2D {public:

int x() { return _x; } // inline definitioninline int y(); // inline declaration

private:int _x, _y;

};inline int CPoint2D::y() { return _y; }

} 之後沒有分號 ;

Page 11: Class (類別)

範例#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;

};

由於 CPoint2D 的成員函式非常簡單,因此我們把它們都寫成 inline 函式如下:

Page 12: Class (類別)

const 成員函式如果成員函式不會或不應該更改資料成員,最好把它宣告成為 const 成員函式。宣告方式是在成員函式宣告與定義的參數列之後加上關鍵字 const ,如下所示:

class X {

void foo () const ;

bar () const { … } // inline const member function

}

void X::foo () const

{ … }

Page 13: 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;

};

加上 const 宣告之後的 CPoint2D class :

Page 14: Class (類別)

如果一個成員函式被宣告為 const 之後,就不得在函式中更改資料成員,否則會造成編譯上的錯誤,譬如:

class CPoint2D {public:

...void setX(int x) const { _x = x; } // error ...

private:int _x, _y;

};更改了資料成員 _x

Page 15: Class (類別)

非 public 的成員函式如果成員函式只是供內部的 implementation 之用,而非供外界使用的話,則它應該擺在 private 段(或 protected 段)之中,而不應該擺在 public 段之中。譬如:

class X {public:

// public members private:

void internal_use (); // private member function// other private members

};

Page 16: Class (類別)

安排 class 的程式碼假定有一個名為 X 的 class 。我們通常按照下面的方式來安排 X 的程式碼:

把 class 的宣告與 inline 成員函式的定義寫在 X.h 檔中。如果有非 inline 成員函式,則把它們的定義寫在 X.cpp 檔中。

譬如: // file: X.hclass X {

...void foo ();...

}

// file: X.cppvoid X::foo (){

...}...

Page 17: Class (類別)

範例#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

CPoint2D.h 檔的內容:

Page 18: Class (類別)

Class 物件如前所述, class 是一種自定的資料型態,因此我們可以用 class 的名稱來宣告或定義變數。這一類的變數通常稱為物件( object )。譬如:

class X { … };

class Y { … };

X x_obj; // x_obj 是屬於 class X 的物件Y y_obj; // y_obj 是屬於 class Y 的物件

物件定義之後就具有以下所述的兩種性質。

Page 19: Class (類別)

C++ 編譯器會配置一塊記憶體給物件用來存放 class 所宣告的資料成員。譬如:

#include “CPoint2D.h”

CPoint2D a;

CPoint2D b;

CPoint2D 物件 a 和 b 各自含有 _x 和 _y 兩項資料屬性:

_x_y

a_x_y

b

1

Page 20: Class (類別)

物件可以用以下的格式來呼叫 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 和 4b.setX (5); // 把 b 的 _x 設成 5if (a.isZero()) // 測試 a 是否等於 (0, 0)

2

Page 21: Class (類別)

物件指標則用以下的格式來呼叫 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)

Page 22: Class (類別)

譬如:#include “CPoint2D.h”

CPoint2D a;

CPoint2D *b = new CPoint2D;

a._x =3; // error: access nonpublic member

b->_y =3; // error: access nonpublic member

注意 物件或物件指標不允許呼叫非 public 成員函式或存取非 public 資料成員:

Page 23: 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;

}

以下程式示範如使用 CPoint2D class :

輸入Enter x: 3Enter y: 5

輸出The point is (3, 5)

編譯方式: CC main.cpp -o prog

Page 24: Class (類別)

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 呢?

Page 25: Class (類別)

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

class CPoint2D {public:

void setXY( CPoint2D *this , int x, int y)}

void CPoint2D::setXY( CPoint2D *this , int x, int y){

...}

Page 26: Class (類別)

其次, C++ 會把出現在成員函式中的資料成員,改用 this 指標來間接存取。譬如:

void CPoint2D::setXY( CPoint2D *this , int x, int y){

this->_x = x; this->_y = y;}

Page 27: Class (類別)

最後, 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)

Page 28: Class (類別)

從以上的說明,我們就知道了 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 。

Page 29: Class (類別)

this 是 C++ 的關鍵字,因此你不能夠重新宣告和定義它。int this; // error

void foo (int this) { … } // error

C++ 會自動為成員函式加入 this 指標參數,因此你不需要在成員函式的參數列中宣告它,否則會造成語法錯誤。

void CPoint2D::setXY( CPoint2D *this , int x, int y){ … } // error

你可以在成員函式中使用 this 指標來指涉目前的物件。我們會在後面示範它的用法。

Page 30: Class (類別)

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

class X {public:

X (); // default constructorX (int); // another constructor…

}

Page 31: Class (類別)

建構函式可用來設定物件的初值。如: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 clas

s 因為沒有定義任何的建構函式,因此只能作如下的物件定義:CPoint2D a;

CPoint2D *bp = new CPoint2D;

而不能在定義中設定物件的初值,如:CPoint2D c(0, 0); // error

Page 32: Class (類別)

範例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)

我們可以定義 CPoint2D 的建構函式如下:

Page 33: Class (類別)

範例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)

前一頁 CPoint2D 的建構函式可以簡化如下:

Page 34: Class (類別)

假定 X 是一個沒有建構函式的 class 。當用 X 來定義物件時, C++ 編譯器的處理方式通常如下:

如果定義的是 global 或 static 物件, 則 C++ 編譯器會把物件所佔據的記憶體清除成 0 值。

X a; // globalstatic X b; // static

如果定義的是 local 物件, 則 C++ 編譯器不會把物件所佔據的記憶體清除成 0 值,因此物件的初值是一堆垃圾值。

void foo (){

X a; // local}

Page 35: Class (類別)

不過前一頁的規則並不適用以下的情形:如果 X 含有物件資料成員,且此物件所屬的 class 具有預設的建構函式。譬如:

class Y { public:

Y();…

}

class X { // no constructors

private:Y _m;

}

則 C++ 編譯器會自動為 X 提供一個預設的建構函式 X() ,並在其中呼叫 Y() 來設定 _m 的初值。

Page 36: Class (類別)

範例class CPoint2D {public:

CPoint2D () { _x = 0; _y = 0; }// 其它的成員函式

private:int _x, _y;

};

class CRectangle {public:

// no constructorsprivate:

CPoint2D _botomLeft, _topRight;};

CRectangle r; // _botomLeft 和 _topRight 都是 (0, 0)

_botomLeft

_topRight

Page 37: Class (類別)

範例// File: CStack.h#ifndef CSTACK_H_#define CSTACK_H_class CStack {public:

CStack (int sz= 100); // default constructorbool 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

有些 class 一定要定義建構函式才不會出錯。底下我們就以動態配置方法來做的 stack class 為例來說明:

Page 38: Class (類別)

// 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;

}

Page 39: Class (類別)

#include “CStack.h”

CStack s1; // s1 是可容納 100 個元素的 stack

CStack s2(50); // s2 是可容納 50 個元素的 stack

_stack_size_top

100-1

0 1 99s1

_stack_size_top

50-1

0 1 49s2

Page 40: Class (類別)

// 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;

}

編譯方式: CC main.cpp CStack.cpp -o prog

輸出的結果:109...1

Page 41: Class (類別)

如果 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

Page 42: Class (類別)

如果一個 class 有建構函式,但沒有預設的建構函式,則定義物件時,一定要提供初值化的參數,譬如:

class CPoint2D {public:

CPoint2D (int, int); // the only constructor// other members

}

則CPoint2D p1; // error

CPoint2D p2(3, 4); // ok

Page 43: Class (類別)

如果我們不希望外界直接用一個 class 來定義物件時,可以把預設的建構函式擺在 protected 段或 privated 段之中,而且不定義其它的建構函式。譬如:

class CPoint2D {public:

// no other constructorsprivate:

CPoint2D (); // default constructor// other private members

}

則CPoint2D p1; // error

Page 44: Class (類別)

解構函式如果建構函式負責物件之生,則解構函式( Destructor)負責物件之死。一個 class 只能有一個解構函式,且其宣告的格式為:

~class_name()

譬如:class CStack {public:

~CStack () { delete [] _stack; } // destructor// other members

}

Page 45: Class (類別)

範例 假定 CStack 沒有如前一頁般地定義解構函式。請問底下的函式有什麼 bug ?

void foo (){

CStack s;…

}

解答:由於 s 中動態配置的記憶體在離開函式 foo 時, 並沒有被刪除掉,因此會發生 memory leak 的 bug 。

Page 46: Class (類別)

通常我們並不直接呼叫解構函式,因為 C++ 編譯器會在物件的生命期結束時自動地加入其解構函式的呼叫。舉例言之:

block 中的 local objects 在 block 結束時會自動呼叫解構函式。

{CStack s;…

}

C++ 編譯器在這裏加入 s.~CStack() 的呼叫

Page 47: Class (類別)

函式中的 local objects 在函式結束時會自動呼叫解構函式。void foo (CStack p){

CStack s;…

}

C++ 編譯器在這裏加入 p.~CStack() 和s.~CStack() 的呼叫。

刪除物件指標時,也會自動呼叫解構函式。CStack *sp = new CStack;…delete sp;

在刪除 sp 所佔據的記憶體之前,會先呼叫 sp->~CStack() 。

Page 48: Class (類別)

假定物件 obj1 含有其它類別的物件 obj2 。則 obj1 死亡時也會自動呼叫 obj2 的解構函式。

class X {// other members

private:CStack s;

};

{X xobj;…

}C++ 編譯器在這裏加入 xobj.s.~CStack() 的呼叫。

Page 49: Class (類別)

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

Page 50: Class (類別)

local 物件指標和 reference 死亡時,並不會自動呼叫解構函式。譬如:

void foo (CStack &r){

CStack *sp = new CStack;…

}

C++ 編譯器不會在這裏加入 r.~CStack() 和sp->~CStack() 的呼叫。

Page 51: Class (類別)

如果自定了解構函式,要注意它對暫存物件的影嚮,看看是否會造成錯誤,譬如:

#include <iostream> #include <string>string s1, s2;…char *msg = (s1 + s2).c_str();

cout << msg;

指標 msg 指到 string 暫存物件中的字串陣列。然而此暫存物件使用之後即被刪除。輸出 msg 時, msg 所指的字串陣列已經不存在,因此造成程式執行錯誤。

此處會產生一個暫存的 string 物件

Page 52: Class (類別)

Copy Constructor假定 A 和 B 是兩個同類別的物件。我們可以把 A 的內容拷貝給 B 。 C++ 預設的物件拷貝方式是:

把 A 的資料成員一一地複製到 B 之中以下三種情形需要執行物件的拷貝:定義物件時用另一個同類別的物件來設定其初值。譬如:

CPoint2D a(3,4);CPoint2D b = a; // 拷貝 a 至 b ,所以 b = (3, 4)CPoint2D c(a); // 拷貝 a 至 c ,所以 c = (3, 4)

1

Page 53: Class (類別)

又如:CStack s1(100);CStack s2 = s1; // 拷貝 s1 至 s2

由於預設的拷貝方式只是複製資料成員而已,因此 s1 和 s2 會共用同一個動態配置的陣列。

_stack_size_top

100-1

s1

0 1 99

_stack_size_top

100-1

s2

Page 54: Class (類別)

若某個函式是以傳值的方式來傳遞參數,則呼叫該函式時,引數的值會拷貝給函式的參數。譬如:

2

void foo (CPoint2D p){

…}

CPoint2D s(3,4);…foo(s);

則進入函式 foo 時,參數 p 的值等於引數 s 的值: (3, 4) 。

Page 55: Class (類別)

非 void 的函式會把傳回值用拷貝的方式傳回來。譬如:3

CPoint2D void foo (){

CPoint2D p;…return p;

}

void print (CPoint2D);…print(foo());

函式 print 的引數是函式 foo 的傳回值,而此傳回值又是從函式 foo 的內部變數 p 拷貝而來的。

Page 56: Class (類別)

C++ 預設的物件拷貝方式有的時候並不適用,甚至可能造成程式執行錯誤。拿 CStack 類別來說,底下的函式 foo 就不正確:

void foo (CStack p){

…}

CStack s(100);…foo(s);s.push(1); // run-time error

原因是:進入函式 foo 之後,參數 p 和引數 s 會共用一個動態配置的陣列(參見前面的說明)。由於 p 是一個 local variable ,因此函式 foo 結束時會呼叫解構函式 p.~CStack ,而刪除掉它和 s 所共用的陣列。如此一來,執行 s.push(1) 時, s 的陣列已經不存在,自然就造成記憶體存取錯誤。

Page 57: Class (類別)

以下的函式 foo 也是不正確的:CStack foo (){

CStack p; …return p;

}

CStack s(foo());...s.push(1); // run-time error

原因是:函式 foo 傳回物件 p 的拷貝,然後再複製給物件 s ,三者共用一個動態的陣列。然而,函式 foo 結束時會自動執行解構函式 p.~CStack() 而把此共用陣列刪給除掉了。如此一來,執行 s.push(1) 時, s 的陣列已經不存在,自然就造成記憶體存取錯誤。

Page 58: Class (類別)

自定 Copy Constuctor當 C++ 預設的拷貝方式不適用時(如對 CStack 而言),我們可以自定一個專用於物件拷貝的建構函式來取代之。假定類別的名稱是 X ,則拷貝建構函式的宣告如下:

X (X &);

一般而言,只有物件內含動態配置的資源時,我們才需要考慮定義拷貝建構函式。

Page 59: Class (類別)

範例 class CStack {public:

CStack (CStack &); // copy constructur// other members

};

CStack::CStack (CStack &s){

_size = s._size; _top = s._top;if (_size) {

_stack = new int[_size ];if (_stack)

for (int k = 0; k <= _top; k++)_stack[k] = s._stack[k];

}else _stack = 0;

}

我們可以定義如右的 CStack 拷貝建構函式來避免共用陣列所引發的問題,藉此解決前述的錯誤:

Page 60: Class (類別)

如果 class Y 內含 class X 型態的資料成員 obj ,而且 X 具有拷貝建構函式,則在拷貝 class Y 物件時,會自動呼叫 class X 的拷貝建構函式來拷貝 obj 的內容。

class X {X (X &);// other members

};class Y {

// other members private:

X obj;};

Y a;

Y b = a;

會呼叫拷貝建構函式b.obj.X(a.obj) 把 a.obj 複製到 b.obj 。

Page 61: Class (類別)

Initializer List我們可以在建構函式中使用 initializer list (初值設定列)來設定資料成員的初值。其用法是在定義建構函式時,在參數列的後面加上序列:

: data_member1(expr1), …, data_membern(exprn)

把 data_member1 的初值設定為 expr1 等等。譬如:class CPoint2D {public:

CPoint2D (int x, int y) : _x(x), _y(y) { }// other members

private:int _x, _y;

}

Page 62: Class (類別)

class CStack {public:

CStack (int sz = 100);CStack (CStack &);// other members

};

CStack::CStack (int sz): _top(-1)

{…

}CStack::CStack (CStack &s)

: _size(s._size), _top(s._top){

...}

注意:initializer list 必須寫在建構函式的定義中,而不是宣告中。此外, initializer list 只能用於建構函式,而不能用於其它成員函式。

Page 63: Class (類別)

C++ 提供 initializer list 主要是為了簡化物件資料成員的初值化設定。譬如我們可以把 CRectangle 的建構函式寫成:

class CRectangle {public:

CRectangle (): _botomLeft(0, 0), _topRight(0, 0) { }

CRectangle (CPoint2D bl, CPoint2D tr): _botomLeft(bl), _topRight(tr) { }

private:CPoint2D _botomLeft, _topRight;

};

而不必像下一頁般地繁頊。

Page 64: Class (類別)

class CRectangle {public:

CRectangle (){

_botomLeft.setXY(0, 0);_topRight.setXY(0, 0)

}CRectangle (CPoint2D bl, CPoint2D tr){

_botomLeft.setXY(bl.x(), bl.y());_topRight.setXY(tr.x(), tr.y());

}private:

CPoint2D _botomLeft, _topRight;};

Page 65: Class (類別)

Constant Objects

Page 66: Class (類別)

Static Membersstatic 資料成員

只屬於類別而不屬於個別物件的資料成員。換句話說,就是物件所共有、不隨物件而異的資料成員。

static 成員函式用來處理 static 資料成員而非個別物件的成員函式。

static 成員是用來把和類別關係密切的 global 的變數或函式,置於 class scope 之中,以避免「汙染」 global scope 。換句話說, static 成員本質上和 global 變數或函式是一樣的,但是在使用的語法上卻和類別成員相同。

Page 67: Class (類別)

// File: Account.h

class Account {public:

Account (double amount, const string &owner);string owner () { return _owner; }friend double revenue (Account &);

private:double _amount;string _owner;

};

範例

// File: Account.cpp

static double interestRate = 0.0589;

// 其它成員函式的定義

由於 interestRate是一個與 Account類別合用的 global變數,所以我們應該把它宣告為類別的 static 資料成員。宣告的方式如下一頁所示。

Page 68: Class (類別)

// File: Account.cpp

double Account::interestRate = 0.0589; // 定義// 其它成員函式的定義

// File: Account.h

class Account {public:

Account (double amount, const string &owner);string owner () { return _owner; }friend double revenue (Account &);static double interestRate; // 宣告

private:double _amount;string _owner;

};

在類別宣告中的 static 資料成員只是宣告而已,你必須在 .cpp 檔中加以定義。定義時,也必須在名稱之前加上 class scope 的界稱:

class_name::

Page 69: Class (類別)

物件並不包含類別中的 static 資料成員。譬如以下物件 a 的定義:

Account a(10000, “John Smith”);

使得物件 a 的內容如下圖所示:10000 John Smith

_amount

_owner

其中並沒有 _interestRate 這一項資料。

Page 70: Class (類別)

static 資料成員可以用以下的三種格成來存取:obj.static_data_member

objptr->static_data_member

class_name::static_data_member

譬如: double revenue (Account &a){

return a._amount * a.interestRate ;}

double revenue (Account &a){

return a._amount * Account::interestRate ;}

Page 71: Class (類別)

只處理 static 資料成員的成員函式應該宣告為 static 。譬如:// File: Account.h

class Account {public:

static double interestRate;static raiseInterestRate (double incr) { interestRate += incr; }

};

static 成員函式的呼叫方式如一般的成員函式,即:obj.static_member_function (argument list)

objptr->static_member_function (argument list)

譬如:Account a;

a.raiseInterestRate(0.1);

Page 72: Class (類別)

class Account {public:

Account (double amount, const string &owner);static string owner () { return _owner; } // errorstatic raiseInterestRate (double incr) const; // error

private:double _amount;string _owner;

};

C++ 不會把 this 指標加入 static 成員函式的參數列之中,因此你不應該用 static 成員函式來存取 non-static 資料成員。static 成員函式不可宣告為 const 。

Page 73: Class (類別)

static const 資料成員static 資料成員可宣告成 const ,當作類別 scope 中的常值。譬如:

// File: Account.h

class Account {public:

static const int nameSize = 16; // 宣告static const char name[_nameSize]; // 宣告// 其它成員

};

// static const 資料成員的定義const int Account::nameSize; // 此處不同再設定其值const char Account::name[nameSize] = “Savings Account”;

static const 資料成員仍需要在類別的宣告之外加以定義。

Page 74: Class (類別)

在類別的宣告中,只有 int 型態的 static const 資料成員才能設定其值,而且只能用常值算式( constant expression )來設定。

class X {

static const int c1 = 7; // ok

static const int c2 = c1; // error

const int c3 = 13; // error, not static

static const int c4 = f(17); // error

static const float c5 = 7.0; // error

};

Page 75: Class (類別)

我們可以用 enum 來設定類別所需的 int 常數名稱。譬如:class X {public:

enum {c1 = 7, c2 = 11, c3 = 13, c4 = 17};// ...

}

c1, c2, c3, 和 c4 這些名稱,在 X 的成員函式中可以直接使用 。外界則必須用 X::c1, X::c2, X::c3, 和 X::c4 的方式來使用。

Page 76: Class (類別)

Friend Functions有的時候為了便利某個函式 foo 的撰寫,你可以在類別 X 中宣告函式 foo 為「朋友函式」,讓函式 foo 避開 X 成員存取的限制,可以直接存取 X 所有的成員。

class X {

friend void foo ();

// 其它成員};

Page 77: Class (類別)

範例 假定我們想寫一個列印 CPoint2D 物件的函式。底下是第一種寫法:

// File: CPoint2D.cpp#include “CPoint2D.h”void print (CPoint2D p){

cout << ‘(’ << p.x() << “, ” << p.y() << ‘)’;}

如果我們把 print (CPoint2D p) 視為處理 CPoint2D 物件的一項基本功能,與 CPoint2D 類別應該整合在一起,則比較好的寫法應該如下一頁所示。

Page 78: Class (類別)

// File: CPoint2D.h

class CPoint2D {friend void print (CPoint2D p); // 其它成員

};void print (CPoint2D p);

// File: CPoint2D.cpp void print (CPoint2D p){

cout << ‘(’ << p._x << “, ” << p._y << ‘)’;}// 其它非 inline 成員函式的定義

Page 79: Class (類別)

範例 假定我們想寫一個把 CStack 物件內容列印出來的函式,作為 debug 之用。

// File: CStack.h

class CStack {

friend void dump (CStack s);

// 其它成員};#ifdef _DEBUGvoid dump (CStack s);#endif

Page 80: Class (類別)

// File: CStack.cpp#include <iostream.h>#include “CStack.h”

#ifdef _DEBUGvoid dump (CStack s){

cout << “stack size: ” << s._size << endl;cout << “stack top: ” << s._top << endl;cout << “stack contents:” << endl;cout << “== Top ==” << endl;for (int k = s._top ; k >= 0; k--)

cout << s._stack [k];cout << “== Bottom ==” << endl;

}#endif// 其它非 inline 成員函式的定義

Page 81: Class (類別)

在決定是否要把某個函式 foo 設定成類別 X 的朋友時,應該考量:函式 foo 是否真正屬於類別 X 整體功能的一部份?

你不應該只為了規避類別成員的存取限制,就輕率地把函式 foo 設定成類別 X 的朋友。你只能在自己所寫的類別中宣告朋友函式。你不應該修改別人的類別宣告(如 C++ 標準類別庫的標頭檔),加入朋友函式的宣告。藉此來存取其內部的成員。

Page 82: Class (類別)

Friend Classes當希望讓某個類別 X 的成員函式可以直接存取另一個類別 Y 的所有的成員時,你可以用以下的格式:

#include “X.h”

class Y {

friend class X;

// 其它成員};

在類別 Y 中宣告類別 X 是一個「朋友類別」。

Page 83: Class (類別)

範例 假定我們想在 CRectangle 類別中加入一個用來計算矩形面積的成員函式 area() 。

// File: CRectangle.h

#include “CPoint2D.h”

class CRectangle {public:

int area();// 其它的成員函式

private:CPoint2D _bottomLeft, _topRight;

};

// File: CRectangle.cpp

#include “CRectangle.h”

int CRectangle:: area(){

return (_topRight.x() - _bottomLeft.x()) * (_topRight.y() - _bottomLeft.y())

}// 其它非 inline 成員函式的定義

Page 84: Class (類別)

// File: CRectangle.cpp

#include “CRectangle.h”

int CRectangle:: area(){

return (_topRight. _x - _botomLeft. _x ) * (_topRight. _y - _botomLeft. _y )

}// 其它非 inline 成員函式的定義

// File: CPoint2D.h

class CPoint2D {friend class CRectangle; // 其它成員

};void print (CPoint2D p);

如果 CPoint2D 類別和 Crectangle 類別同屬一套類別庫,則我們可以把 Crectangle 類別宣告成 CPoint2D 類別的一個朋友類別,讓 CRectangle 類別的成員函式可以更方便地存取 CPoint2D 類別所有的成員。

Page 85: Class (類別)

在決定是否要把類別 X 設定成類別 Y 的朋友時,應該考量:類別 X 是否真正需要直接存取類別 Y 所有的成員? 你不應該只為了規避存取的限制,就輕率地把類別 X 設定成類別 Y 的朋友。你只能在自己所寫的類別中宣告朋友類別。你不應該修改別人的類別宣告(如 C++ 標準類別庫的標頭檔),加入朋友類別的宣告。藉此來存取其內部的成員。

Page 86: Class (類別)

Class Object I/O我們可以為類別設計輸出 / 入的功能,讓輸出運算子 << 和輸入運算子 >> 也適用於該類別物件的輸出與輸入。作法很簡單:假定類別的名稱是 X ,則我們只要定義以下兩個函式即可:

ostream& operator<< (ostream &, X);

istream& operator>> (istream &, X&);

為了程式撰寫方便,我通常把上面兩個函式宣告成 X 的朋友函式,讓它們能夠直接使用 X 所有的成員。

Page 87: Class (類別)

範例 我們為 CPoint2D 類別加上 << 輸出功能和 >> 輸入功能。

// File: CPoint2D.h

#include <iostream.h>

class CPoint2D {friend ostream& operator<< (ostream&, CPoint2D );friend istream& operator>> (istream&, CPoint2D&);// 其它成員

};extern ostream& operator<< (ostream&, CPoint2D );extern istream& operator>> (istream&, CPoint2D&);

Page 88: Class (類別)

// File: CPoint2D.cpp

#include “CPoint2D.h”ostream& operator<< (ostream &os, CPoint2D p){

os << ‘(‘ << p._x << “, “ << p._y << ‘)’;return os;

}istream& operator>> (istream &is, CPoint2D &p);{

is >> p._x >> p._y; // get two integersreturn is;

}// 其它成員函式的定義

Page 89: Class (類別)

測試程式:#include “CPoint2D.h”

int main (){

CPoint2D p1, p2;cin >> p1;cin >> p2;cout << p1 << endl;cout << p2 << endl;return 0;

}

輸入:1 2

3 4

輸出:(1, 2)

(3, 4)

Page 90: Class (類別)

Structure (結構)

C++ 提供 struct 的主要目的是為了與 C 相容,讓舊有的 C 程式也能夠用 C++ 編譯器來編譯。你應該在 C++ 程式中避免再使用 struct 。

struct struct_name { field1 declaration; field2 declaration; ...

};

class struct_name {public: field1 declaration; field2 declaration; ...

};

等同於

Page 91: Class (類別)

Union

一個 union 由若干成員所組成。預設的成員存取模式是public 。與 class 和 struct 不同的是: union 中的資料成員共用記憶體的空間,藉此達到節省記憶體的目的。

union union_name { member1 declaration; member2 declaration; …

};

Page 92: Class (類別)

union 和記憶體間的對應union DataCell { char charValue; /* 1 byte */ short intValue; /* 2 byte */ float floatValue; /* 4 byte */};

m

m + 4

struct instruction { char opcode; union { int intValue; char strValue[256]; } data;};

m

m + 257

m+1

m+5

Page 93: Class (類別)

union 的資料存取DataCell dc;

dc.charValue = ‘A’;

m

m + 4

A

dc.intValue = 100;

m

m + 4

100

dc.floatValue = 3.14;

m

m + 4

3.14

Page 94: Class (類別)

Bit-Field Members

在 class 、 struct 、 union 的結構中,我們可以把一些資料成員合併裝入一個整數變數的位元中,藉此來節省記憶體。這種資料成員稱為 bit-field (位元欄)。它的宣告方式如下:

int_type member_name : number_of_bits;

其中, int_type 必須是整數類的資料型態、 number_of_bits 必須是一個整常數。註: & 參照運算子不可用於 bit-field 型態的資料成員。

Page 95: Class (類別)

typedef unsigned int Bit;class File {public:

void write ();void close ();bool isRead () { return mode & READ; }bool isWrite () { return mode & WRTIE; }bool isModified () { return modified ; }

private:enum {READ = 01, WRTIE = 02};Bit mode : 2;Bit modified : 1;Bit prot_owner : 3;Bit prot_group: 3;Bit prot_world : 3;// …

};

範例m

ode

modifie

dprot_ow

nerprot_w

orld

prot_group

031

unused

void File::write (){

modified = 1;// …

}

void File::close (){

if (modified) {// save

}}

Page 96: Class (類別)

bitset 樣板類別C++ 的 bitset 樣板類別提供比前述的 bit-field 更方便的使用介面。使用 bitset 時,你必須加入其宣告的標頭檔:

#include <bitset>

然後用以下的方式來定義 bitset 物件:bitset<number_of_bits> obj_name;bitset< number_of_bits > obj_name (initial_value);

譬如:bitset<32> bits;

定義 bits 是一個 32 位元的 bitset 物件,且所有的位元均為 0 。

Page 97: Class (類別)

bitset 的成員函式成員函式 功能 用法示範test(pos) 第 pos 位元等於 1 ? a.test(4)any() 有任何位元等於 1 ? a.any()none() 所有位元都等於 0 ? a.none()count() 等於 1 的位元個數 a.count()size() 位元數 a.size()[ pos ] 存取 第 pos 的位元 a[4]flip() 把所有的位元 0 和 1 互換 a.flip()flip(pos) 把第 pos 的位元 0 和 1 互換 a.flip(4)set() 把所有的位元設定為 1 a.set()set(pos) 把第 pos 的位元設定為 1 a.set(4)reset() 把所有的位元設定為 0 a.reset()reset(pos) 把第 pos 的位元設定為 0 a.reset(4)to_string() 轉換成 0 和 1 組成的字串 a.to_string()to_long() 轉換成 unsigned long a.to_long()

Page 98: Class (類別)

定義 bitset 物件時,若沒有指定初值,則所有的位元都預設為 0 。你可以用以下的方法來設定 bitset 物件的初值:bitset<16> b1(128); // b1 = 0000000010000000

bitset<16> b2(0xff); // b2 = 0000000011111111

bitset<16> b3(012); // b3 = 0000000000001010

bitset<16> b4(“0101010101010101”);// b4 = 0101010101010101

string bitval = “1111110101011111”

bitset<16> b5(bitval); // b5 = 1111110101011111

bitset<16> b6(bitval, 5, 4); // b6 = 0000000000001010

bitset<16> b7(bitval, 5); // b7 = 0000010101011111

Page 99: Class (類別)

位元邏輯運算子可用於 bitset 物件。譬如:bitset<16> b8 = b2 & b5; // bit AND

bitset<16> b9 = b2 | b5; // bit OR

bitset<16> b10 = ~b2; // bit NOT

Page 100: Class (類別)

Class Scopeclass 的宣告與其成員函式的定義構成一個 scope ,界定出名稱的可見度範圍與指涉的對象。

class X {public:

typedef int mode_type;enum {READ = 0, WRITE = 1};void set (mode_type);

private:mode_type _mode;

};

void X::set (mode_type m){

_mode = m;// …

}

// 其它成員函式的定義

class scope

Page 101: Class (類別)

在 class 的 scope 或其朋友中,使用這些在 class 內的名稱,我們不需要用 class 的名字和 :: 運算子來界定它們。但是當外界想要使用 class 的 public 名稱時,就必須用以下的方式來指定:成員名稱: obj.member 或 obj_pointer->member 。如:

a.set(1);

非成員和 sataic 成員的名稱: class_name::name 。如:X::mode_type mode = X::READ;

當然, class 的非 public 的名稱,外界就無法使用。

Page 102: Class (類別)

之前我們一直說: class 非 public 的名稱不可供外界使用。那什麼又是「外界」呢?它是指除了以下三個 scopes 之外的其它 scopes :

• class 本身的 scope

• 朋友函式的 scope

• 朋友類別的 scope

我們用下表來總結 class 內部名稱的存取限制: class 本身 朋友函式 朋友類別 外界

public

private x

Page 103: Class (類別)

Other Issues

• nested class• local class• class and namespace

Page 104: Class (類別)

Nested Class我們可以在一個 class 的宣告之中,宣告另一個 class 。這種結構稱為 nested class (巢狀類別)。譬如:

class CList {class CListItem {

// …};// …

};

外部類別 內部類

一般而言,內部類別是用來輔助外部類別的製作。

Page 105: Class (類別)

內部類別的宣告可以擺在外部類別之外。譬如前一頁的宣告也可以改寫成:

class CList {class CListItem; // 必須要有這一行 // …

};class CList::CListItem {

// …};

我們建議你儘量採用這一種寫法,一來比較乾淨,二來也比較符合 information hiding 的原則。

Page 106: Class (類別)

內部類別和外部類別無法使用對方的 private 成員與名稱,除非對方將其宣告成朋友類別。內部類別通常是用來輔助外部類別,毋須直接使用外部類別的成員,因此我們只需要在內部類別宣告外部類別為其朋友類別,讓外部類別可以直接使用內部類別的成員即可。

class CList::CListItem {friend class CList;// …

};

Page 107: Class (類別)

如果內部類別是擺在外部類別的 private 段之中的話,則外部類別的外界將無法使用內部類別的任何成員與名稱。

class CList {// …

private:class CListItem;// …

};

class CList::CListItem {friend class CList;// …

};

Page 108: Class (類別)

內部類別的成員函式定義格式如下:external_class_name::internal_class_name func (parameter list){

// …}

譬如:CList::CListItem::CListItem () // default constructor// 不是寫成 CList::CListItem::CList::CListItem () {

// …}

Page 109: Class (類別)

CList::CListItem item;item.foo ( CList::CListItem::cv );

如果內部類別可供外界使用時,其成員的存取方式和一般的類別相同。非成員和 static 成員的名稱必須冠上外部類別的名字。譬如:

class CList {public:

class CListItem;// …

};

class CList::CListItem {public:

void foo(int);static int cv = 10;// …

};

Page 110: Class (類別)

Class and Namespace類別也可宣告於 namespace 之中。如:

// File: X.hnamespace N {

class X {void foo();

};}

// File: X.cpp#include “X.h”namespace N {

void X::foo() { … }}

Page 111: Class (類別)

你可以用以下的方式來使用 namespace 中的類別:#include “X.h”

N::X obj;

obj.foo();

或利用 using 指令或 using namespace 宣告,如:#include “X.h”

using namespace N;

X obj;

obj.foo();

Page 112: Class (類別)

Local Class宣告在函式之內的類別稱為 local class ,它只能用在函式之中。如:

類別的宣告

成員函式的定義

void foo (){

class X {public:

void bar ();// …

};X::bar (){

// …}// …X obj;obj.bar();

}