Class(類別) 前言 Class 的定義 Class 物件 this pointer Constructors Destructor

Slides:



Advertisements
Similar presentations
C++语言程序设计教程 第5章 构造数据类型 第6章 C++程序的结构.
Advertisements

第一單元 建立java 程式.
Memory Pool ACM Yanqing Peng.
第八章 类和对象.
程式設計概論 1.1 程式設計概論 程式語言的演進 物件導向程式 程式開發流程 1.2 C++開發工具
struct 可以在同一個名稱下擁有多種資料型態。使用struct能讓資料的存取和處理更為靈活。
簡易C++除錯技巧 長庚大學機械系
資料結構設計與C++程式應用 Fundamentals of Data Structures and Their Applications Using C++ 第3章 佇列 資料結構設計與C++程式應用.
Chap 18 類別與物件 夫有土者,有大物也。有大物者,不可以物。 物而不物,故能物物。 明乎物物者之非物也,豈獨治天下百姓而已哉!
4.1 概述 4.2 类与对象的实现 4.3 对象的初始化和析构 4.4 类的包含 4.5 类模板
Derived Class 前言 衍生類別的定義 單一繼承 public, protected, 和 privated 基底類別
列舉(enum).
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
Scope & Lifetime 前言 Local Scope Global Functions & Objects
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
刘胥影 东南大学计算机学院 面向对象程序设计1 2011~2012第3学期 刘胥影 东南大学计算机学院.
101北一女中 資訊選手培訓營 妳不可不了解的指標 Nan.
Classes: A Deeper Look, Part 1
刘胥影 东南大学计算机学院 面向对象程序设计1 2010~2011第3学期 刘胥影 东南大学计算机学院.
授课老师:龚涛 信息科学与技术学院 2018年3月 教材: 《Visual C++程序员成长攻略》 《C++ Builder程序员成长攻略》
C語言簡介 日期 : 2018/12/2.
Object-Oriented Programming in C++ 第一章 C++的初步知识
程序设计期末复习 黎金宁
Class(二) Overloaded Operators User-Defined Conversions 前言 超載運算子
第三章 C++中的C 面向对象程序设计(C++).
第 6 章 函式.
2 C++ 的基本語法和使用環境 親自撰寫和執行程式是學好程式語言的不二法門。本章藉由兩個簡單的程式,介紹C++ 程式的基本結構和開發環境,讓初學者能逐漸建立使用C++ 的信心。
類別(class) 類別class與物件object.
SQL Stored Procedure SQL 預存程序.
Methods 靜宜大學資工系 蔡奇偉副教授 ©2011.
类类型 C++支持的内置类型和操作,如 int i=10; i=i%6; i=i+4;
Java 程式設計 講師:FrankLin.
JAVA 程式設計與資料結構 第四章 陣列、字串與數學物件.
切換Dev c++顯示語言 工具->環境選項(V)->介面->language (Chinese TW)
第一單元 建立java 程式.
10 多載函數 10.1 多載概論 多載一般函數 多載成員函數 10-3
第1章 概述 本章要点: C语言程序结构和特点 C语言程序的基本符号与关键字 C语言程序的编辑及运行 学习方法建议:
Classes (2) Lecture 7.
第 19 章 XML記憶體執行模式.
C++大学基础教程 第11章 多态性 北京科技大学 信息基础科学系 2019/4/8 北京科技大学.
潘爱民 C++ Overview 潘爱民
C++语言程序设计 C++语言程序设计 第九章 类的特殊成员 第十一组 C++语言程序设计.
挑戰C++程式語言 ──第8章 進一步談字元與字串
Oop8 function函式.
Class & Object 靜宜大學資工系 蔡奇偉副教授 ©2011.
樣版.
C++语言程序设计教程 第2章 数据类型与表达式 第2章 数据类型与表达式 制作人:杨进才 沈显君.
C qsort.
<编程达人入门课程> 本节内容 为什么要使用变量? 视频提供:昆山爱达人信息技术有限公司 官网地址: 联系QQ:
第二章 Java语法基础.
OOP9 類別Class.
陣列與結構.
第 3 章 类的基础部分 陈哲 副教授 南京航空航天大学 计算机科学与技术学院.
#include <iostream.h>
第二章 Java基本语法 讲师:复凡.
北一女中 資訊選手培訓營 妳不可不了解的指標 Nan.
C++语言程序设计 第十章 C++标准模板库 成都信息工程学院计算机系.
本章主題 C++的程式結構 資料型態與宣告 算術運算 簡易的輸入輸出指令 程式編譯(Compile)的過程與原理.
第四章 陣列、指標與參考 4-1 物件陣列 4-2 使用物件指標 4-3 this指標 4-4 new 與 delete
JAVA 程式設計與資料結構 第三章 物件的設計.
What is “this”? 在物件導向程式設計中,類別的定義就是在說明如果創建了“這個物件”的話,它會具有那些屬性與功能,以及這些功能是如何實現的。 而所謂的“這個物件”就以 this 來表示。 當我們在JavaScript與jQuery中寫 script 程式(函式)時,“誰”呼叫這個函式,這個“誰”就是該函式中所謂的.
作業系統實習課(二) -Scheduler-Related System Calls-
C++语言程序设计 C++语言程序设计 第十一章 异常处理 C++语言程序设计.
ABAP Basic Concept (2) 運算子 控制式與迴圈 Subroutines Event Block
String類別 在C語言中提供兩種支援字串的方式 可以使用傳統以null結尾的字元陣列 使用string類別
資料結構與C++程式設計進階 C++與資料結構 講師:林業峻 CSIE, NTU 7/ 5, 2010.
Unix指令4-文字編輯與程式撰寫.
方法(Method) 函數.
ABAP Basic Concept (2) 運算子 控制式與迴圈 Subroutines Event Block
InputStreamReader Console Scanner
Presentation transcript:

Class(類別) 前言 Class 的定義 Class 物件 this pointer Constructors Destructor 物件導向程式語言講義 2018/11/12 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 靜宜大學資訊管理學系 蔡奇偉 副教授

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

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

Class 的定義語法 Class 定義的語法如下: class class_name { // class 的成員 }; data member(資料成員) 物件所擁有的屬性(即物件內含的資料項目)。 member function(成員函式) 物件所能夠執行的運算。

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

範例 以下是平面點的 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; };

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

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

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

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

由於 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; };

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

範例 加上 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; };

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

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

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

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

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

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

物件可以用以下的格式來呼叫 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

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

物件或物件指標不允許呼叫非 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

以下程式示範如使用 CPoint2D class: 範例 以下程式示範如使用 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

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

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

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

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

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

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

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

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

範例 我們可以定義 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)

範例 前一頁 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)

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

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

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

有些 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

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

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

輸出的結果: 10 9 . 1 // 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

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

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

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

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

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

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

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

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

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

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

如果自定了解構函式,要注意它對暫存物件的影嚮,看看是否會造成錯誤,譬如: #include <iostream> #include <string> string s1, s2; … char *msg = (s1 + s2).c_str(); cout << msg; 指標 msg 指到 string 暫存物件中的字串陣列。然而此暫存 物件使用之後即被刪除。輸出 msg 時,msg 所指的字串陣 列已經不存在,因此造成程式執行錯誤。 此處會產生一個暫存的 string 物件

Copy Constructor 假定 A 和 B 是兩個同類別的物件。我們可以把 A 的內容拷貝給 B。 C++ 預設的物件拷貝方式是: 以下三種情形需要執行物件的拷貝: 定義物件時用另一個同類別的物件來設定其初值。譬如: CPoint2D a(3,4); CPoint2D b = a; // 拷貝 a 至 b,所以 b = (3, 4) CPoint2D c(a); // 拷貝 a 至 c,所以 c = (3, 4) 1

由於預設的拷貝方式只是複製資料成員而已,因此 s1 和 s2 會共用同一個動態配置的陣列。 又如: CStack s1(100); CStack s2 = s1; // 拷貝 s1 至 s2 由於預設的拷貝方式只是複製資料成員而已,因此 s1 和 s2 會共用同一個動態配置的陣列。 s1 _stack _size 100 _top -1 1 99 s2 _stack _size 100 _top -1

若某個函式是以傳值的方式來傳遞參數,則呼叫該函式時,引數的值會拷貝給函式的參數。譬如: 2 void foo (CPoint2D p) { … } CPoint2D s(3,4); … foo(s); 則進入函式 foo 時,參數 p 的值等於引數 s 的值: (3, 4)。

非 void 的函式會把傳回值用拷貝的方式傳回來。譬如: 3 CPoint2D void foo () { CPoint2D p; … return p; } void print (CPoint2D); … print(foo()); 函式 print 的引數是函式 foo 的傳回值,而此傳回值又是從函式 foo 的內部變數 p 拷貝而來的。

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 的陣列已經不存在,自然就造成記憶體存取錯誤。

以下的函式 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 的陣列已經不存在,自然就造成記憶體存取錯誤。

自定 Copy Constuctor 當 C++ 預設的拷貝方式不適用時(如對 CStack 而言),我們可以自定一個專用於物件拷貝的建構函式來取代之。假定類別的名稱是 X,則拷貝建構函式的宣告如下: X (X &); 一般而言,只有物件內含動態配置的資源時,我們才需要考慮定義拷貝建構函式。

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

如果 class Y 內含 class X 型態的資料成員 obj,而且 X 具有拷貝建構函式,則在拷貝 class Y 物件時,會自動呼叫 class X 的拷貝建構函式來拷貝 obj 的內容。 X (X &); // other members }; class Y { private: X obj; Y a; Y b = a; 會呼叫拷貝建構函式b.obj.X(a.obj) 把 a.obj 複製到 b.obj。

: data_member1(expr1), …, data_membern(exprn) 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; }

initializer list 必須寫在建構函式的定義中,而不是宣告中。 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 只能用於建構函式,而不能用於其它成員函式。

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; }; 而不必像下一頁般地繁頊。

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

Constant Objects

Static Members static 資料成員 static 成員函式 只屬於類別而不屬於個別物件的資料成員。換句話說,就是物件所共有、不隨物件而異的資料成員。 static 成員函式 用來處理 static 資料成員而非個別物件的成員函式。 static 成員是用來把和類別關係密切的 global 的變數或函式,置於 class scope之中,以避免「汙染」 global scope。換句話說, static 成員本質上和 global 變數或函式是一樣的,但是在使用的語法上卻和類別成員相同。

範例 // 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; }; 由於 interestRate 是一個與 Account 類別合用的 global 變數,所以我們應 該把它宣告為類別 的 static 資料成員。 宣告的方式如下一 頁所示。 // File: Account.cpp static double 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:: // File: Account.cpp double Account::interestRate = 0.0589; // 定義 // 其它成員函式的定義

物件並不包含類別中的 static 資料成員。譬如以下物件 a 的定義: Account a(10000, “John Smith”); 使得物件 a 的內容如下圖所示: 10000 John Smith _amount _owner 其中並沒有 _interestRate 這一項資料。

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

只處理 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);

static 成員函式不可宣告為 const。 C++ 不會把 this 指標加入 static 成員函式的參數列之中,因此你不應該用 static 成員函式來存取 non-static 資料成員。 static 成員函式不可宣告為 const。 class Account { public: Account (double amount, const string &owner); static string owner () { return _owner; } // error static raiseInterestRate (double incr) const; // error private: double _amount; string _owner; };

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 資料成員仍需要在類別的宣告之外加以定義。

在類別的宣告中,只有 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 };

我們可以用 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 的方式來使用。

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

範例 假定我們想寫一個列印 CPoint2D 物件的函式。 底下是第一種寫法: // File: CPoint2D.cpp #include “CPoint2D.h” void print (CPoint2D p) { cout << ‘(’ << p.x() << “, ” << p.y() << ‘)’; } 如果我們把 print (CPoint2D p) 視為處理 CPoint2D 物件的一項基本功能,與 CPoint2D 類別應該整合在一起,則比較好的寫法應該如下一頁所示。

// 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 成員函式的定義

假定我們想寫一個把 CStack 物件內容列印出來的 函式,作為 debug 之用。 範例 // File: CStack.h class CStack { friend void dump (CStack s); // 其它成員 }; #ifdef _DEBUG void dump (CStack s); #endif

// File: CStack.cpp #include <iostream.h> #include “CStack.h” #ifdef _DEBUG void 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 成員函式的定義

函式 foo 是否真正屬於類別 X 整體功能的一部份? 你只能在自己所寫的類別中宣告朋友函式。你不應該修改別人的類別宣告(如 C++ 標準類別庫的標頭檔),加入朋友函式的宣告。藉此來存取其內部的成員。

Friend Classes 當希望讓某個類別 X 的成員函式可以直接存取另一個類別 Y 的所有的成員時,你可以用以下的格式: #include “X.h” class Y { friend class X; // 其它成員 }; 在類別 Y 中宣告類別 X 是一個「朋友類別」。

假定我們想在 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 成員函式的定義

// File: CPoint2D.h class CPoint2D { friend class CRectangle; // 其它成員 }; void print (CPoint2D p); 如果 CPoint2D 類別和 Crectangle 類別同屬一套類別庫,則我們可以把 Crectangle 類別宣告成 CPoint2D 類別的一個朋友類別,讓CRectangle 類別的成員函式可以更方便地存取 CPoint2D 類別所有的成員。 // File: CRectangle.cpp #include “CRectangle.h” int CRectangle:: area() { return (_topRight. _x - _botomLeft. _x ) * (_topRight. _y - _botomLeft. _y ) } // 其它非 inline 成員函式的定義

類別 X 是否真正需要直接存取類別 Y 所有的成員? 你只能在自己所寫的類別中宣告朋友類別。你不應該修改別人的類別宣告(如 C++ 標準類別庫的標頭檔),加入朋友類別的宣告。藉此來存取其內部的成員。

Class Object I/O 我們可以為類別設計輸出/入的功能,讓輸出運算子 << 和輸入運算子 >> 也適用於該類別物件的輸出與輸入。作法很簡單:假定類別的名稱是 X,則我們只要定義以下兩個函式即可: ostream& operator<< (ostream &, X); istream& operator>> (istream &, X&); 為了程式撰寫方便,我通常把上面兩個函式宣告成 X 的朋友函式,讓它們能夠直接使用 X 所有的成員。

我們為 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&);

// 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 integers return is; // 其它成員函式的定義

測試程式: #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)

Structure(結構) struct struct_name { field1 declaration; ... }; class struct_name { public: field1 declaration; field2 declaration; ... }; 等同於 C++ 提供 struct 的主要目的是為了與 C 相容,讓舊有的 C 程式也能夠用 C++ 編譯器來編譯。你應該在 C++ 程式 中避免再使用 struct。

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

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+1 m+5 m + 257

union 的資料存取 DataCell dc; dc.charValue = ‘A’; dc.intValue = 100; m m + 4 A dc.intValue = 100; m m + 4 100 dc.floatValue = 3.14; m m + 4 3.14

Bit-Field Members 在 class、struct、union 的結構中,我們可以把一些資料成員合併裝入一個整數變數的位元中,藉此來節省記憶體。這種資料成員稱為 bit-field(位元欄)。它的宣告方式如下: int_type member_name : number_of_bits; 其中, int_type 必須是整數類的資料型態、 number_of_bits必須是一個整常數。 註:& 參照運算子不可用於 bit-field 型態的資料成員。

範例 void File::write () { modified = 1; // … } void File::close () if (modified) { // save 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; // … }; mode modified prot_owner prot_world prot_group 31 unused

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。

bitset 的成員函式 成員函式 功能 用法示範 test(pos) 第 pos 位元等於 1? a.test(4) 成員函式 功能 用法示範 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()

定義 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

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

Class Scope class 的宣告與其成員函式的定義構成一個 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 或其朋友中,使用這些在 class 內的名稱,我們不需要用 class 的名字和 :: 運算子來界定它們。但是當外界想要使用 class 的 public 名稱時,就必須用以下的方式來指定: 成員名稱:obj.member 或 obj_pointer->member。如: a.set(1); 非成員和 sataic 成員的名稱:class_name::name。如: X::mode_type mode = X::READ; 當然,class 的非 public 的名稱,外界就無法使用。

之前我們一直說:class 非 public 的名稱不可供外界使用。那什麼又是「外界」呢?它是指除了以下三個 scopes 之外的其它 scopes: class 本身的 scope 朋友函式的 scope 朋友類別的 scope 我們用下表來總結 class 內部名稱的存取限制: class 本身 朋友函式 朋友類別 外界 public private x

Other Issues nested class local class class and namespace

Nested Class class CList { class CListItem { // … }; 我們可以在一個 class 的宣告之中,宣告另一個 class。這種結構稱為 nested class(巢狀類別)。譬如: class CList { class CListItem { // … }; 外部類別 內部類別 一般而言,內部類別是用來輔助外部類別的製作。

內部類別的宣告可以擺在外部類別之外。譬如前一頁的宣告也可以改寫成: class CList { class CListItem; // 必須要有這一行 // … }; class CList::CListItem { 我們建議你儘量採用這一種寫法,一來比較乾淨,二來也比較符合 information hiding 的原則。

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

如果內部類別是擺在外部類別的 private 段之中的話,則外部類別的外界將無法使用內部類別的任何成員與名稱。 class CList { // … private: class CListItem; }; class CList::CListItem { friend class CList; // … };

內部類別的成員函式定義格式如下: external_class_name::internal_class_name func (parameter list) { // … } 譬如: CList::CListItem::CListItem () // default constructor // 不是寫成 CList::CListItem::CList::CListItem ()

如果內部類別可供外界使用時,其成員的存取方式和一般的類別相同。非成員和 static 成員的名稱必須冠上外部類別的名字。譬如: class CList { public: class CListItem; // … }; class CList::CListItem { public: void foo(int); static int cv = 10; // … }; CList::CListItem item; item.foo ( CList::CListItem::cv );

Class and Namespace 類別也可宣告於 namespace 之中。如: // File: X.h namespace N { class X { void foo(); }; } // File: X.cpp #include “X.h” namespace N { void X::foo() { … } }

你可以用以下的方式來使用 namespace 中的類別: #include “X.h” N::X obj; obj.foo(); 或利用 using 指令或 using namespace 宣告,如: using namespace N; X obj;

Local Class 宣告在函式之內的類別稱為 local class,它只能用在函式之中。如: 類別的宣告 成員函式的定義 void foo () { class X { public: void bar (); // … }; X::bar () } X obj; obj.bar(); 類別的宣告 成員函式的定義