Presentation is loading. Please wait.

Presentation is loading. Please wait.

Chapter 7: 再論類別 目標 能夠動態地建構和解構物件 能夠指定 const(常數)物件和和 const 成員函式

Similar presentations


Presentation on theme: "Chapter 7: 再論類別 目標 能夠動態地建構和解構物件 能夠指定 const(常數)物件和和 const 成員函式"— Presentation transcript:

1 Chapter 7: 再論類別 目標 能夠動態地建構和解構物件 能夠指定 const(常數)物件和和 const 成員函式
了解夥伴(friend)函式與夥伴類別的目的 了解如何使用 static 資料成員與成員函式 了解容器(container)類別的概念 了解具有檢視容器類別元素功能的迭代(iterator)類別概念 了解指標 this 的用途

2 Chapter 7: 再論類別 本章綱要 7.1 簡介 Introduction 7.2 常數物件與常數成員函式
7.2 常數物件與常數成員函式 7.3 合成:將物件當作類別的成員 7.4 夥伴函式與夥伴類別 7.5 this 指標的使用 7.6 使用 new 與 delete 運算子的動態記憶體配置 7.7 static 類別成員 7.8 資料抽象化與資訊隱藏 範例:陣列、字串、佇列 7.9 容器類別和迭代子 7.10 代理類別

3 7.1 簡介 第六章到第八章討論的是屬於物件導向程式設計的基本觀念。
第九章與第十章討論的繼承(inheritance)與多型(polymorphism),這才算是真正物件導向程式設計。

4 7.2 const (常數) 物件與 const 成員函式
最小開放權限原則 Principle of least privilege 只給物件所需要的權限、不給過多的權力 關鍵字 const 說明這個物件不可以被修改 企圖修改這種物件的話會造成語法上的錯誤。 範例 const Time noon( 12, 0, 0 ); //可以作初值化 宣告類別 Time 的常數物件 noon,並作初值化成 12 點。 12 hour minute const Time noon(12,0,0) second Time( 12, 0, 0 );

5 7.2 const (常數) 物件與 const 成員函式
我們只能呼叫 const 物件的 const 函式。沒有修改資料成員的函式必須被宣告成 const 才能被 const 物件使用。 宣告成 const 的成員函式不可修改物件內容 const 必須被寫在函式原型與定義的地方 函式原型: ReturnType FunctionName(param1,param2…) const; 定義: ReturnType FunctionName(param1,param2…) const { …} 範例: int A::getValue() const { return privateDataMember }; const 函式可以傳回資料成員的值,但不能修改其內容。

6 7.2 const (常數) 物件與 const 成員函式
對變數作初值化、就會修改其內容 常見的程式設計錯誤 7.1-4 定義為 const 的成員函式若修改資料成員的內容,會造成語法錯誤。 const 的函式不可呼叫 non-const 函式,否則就造成語法錯誤。 透過 const 物件來呼叫非 const 函式,是語法錯誤。 企圖將建構子或解構子宣告為 const 是語法錯誤。

7 const 函式 非 const 函式 1 // Fig. 7.1: time5.h
2 // Declaration of the class Time. 3 // Member functions defined in time5.cpp 4 #ifndef TIME5_H 5 #define TIME5_H 6 7 class Time { 8 public: 9 Time( int = 0, int = 0, int = 0 ); // default constructor 10 11 // set functions 12 void setTime( int, int, int ); // set time 13 void setHour( int ); // set hour 14 void setMinute( int ); // set minute 15 void setSecond( int ); // set second 16 17 // get functions (normally declared const) 18 int getHour() const; // return hour 19 int getMinute() const; // return minute 20 int getSecond() const; // return second 21 22 // print functions (normally declared const) 23 void printMilitary() const; // print military time 24 void printStandard(); // print standard time 25 private: 26 int hour; // 27 int minute; // 28 int second; // 29 }; 30 31 #endif 非 const 函式 const 函式

8 建構子是非 const 但可以被 const 物件呼叫
32 // Fig. 7.1: time5.cpp 33 // Member function definitions for Time class. 34 #include <iostream> 35 36 using std::cout; 37 38 #include "time5.h" 39 40 // Constructor function to initialize private data. 41 // Default values are 0 (see class definition). 42 Time::Time( int hr, int min, int sec ) 43 { setTime( hr, min, sec ); } 44 45 // Set the values of hour, minute, and second. 46 void Time::setTime( int h, int m, int s ) 47 { 48 setHour( h ); 49 setMinute( m ); 50 setSecond( s ); 51 } 52 53 // Set the hour value 54 void Time::setHour( int h ) 55 { hour = ( h >= 0 && h < 24 ) ? h : 0; } 56 57 // Set the minute value 58 void Time::setMinute( int m ) 59 { minute = ( m >= 0 && m < 60 ) ? m : 0; } 60 61 // Set the second value 62 void Time::setSecond( int s ) 63 { second = ( s >= 0 && s < 60 ) ? s : 0; } 建構子是非 const 但可以被 const 物件呼叫

9 關鍵字 const 必須出現在函式定義與函式原型中
64 65 // Get the hour value 66 int Time::getHour() const { return hour; } 67 68 // Get the minute value 69 int Time::getMinute() const { return minute; } 70 71 // Get the second value 72 int Time::getSecond() const { return second; } 73 74 // Display military format time: HH:MM 75 void Time::printMilitary() const 76 { 77 cout << ( hour < 10 ? "0" : "" ) << hour << ":" << ( minute < 10 ? "0" : "" ) << minute; 79 } 80 81 // Display standard format time: HH:MM:SS AM (or PM) 82 void Time::printStandard() // should be const 83 { 84 cout << ( ( hour == 12 ) ? 12 : hour % 12 ) << ":" << ( minute < 10 ? "0" : "" ) << minute << ":" << ( second < 10 ? "0" : "" ) << second << ( hour < 12 ? " AM" : " PM" ); 88 } 關鍵字 const 必須出現在函式定義與函式原型中 非 const 函式不能被 const 物件使用,就算這些函式沒有修改資料內容也一樣。 (就像printStandard).

10 89 // Fig. 7.1: fig07_01.cpp 90 // Attempting to access a const object with 91 // non-const member functions. 92 #include "time5.h" 93 94 int main() 95 { 96 Time wakeUp( 6, 45, 0 ); // non-constant object 97 const Time noon( 12, 0, 0 ); // constant object 98 // MEMBER FUNCTION OBJECT wakeUp.setHour( 18 ); // non-const non-const 101 noon.setHour( 12 ); // non-const const 103 wakeUp.getHour(); // const non-const 105 noon.getMinute(); // const const noon.printMilitary(); // const const noon.printStandard(); // non-const const return 0; 110 } 產生編譯錯誤 Compiling... Fig07_01.cpp d:fig07_01.cpp(14) : error C2662: 'setHour' : cannot convert 'this' pointer from 'const class Time' to 'class Time &' Conversion loses qualifiers d:\fig07_01.cpp(20) : error C2662: 'printStandard' : cannot convert 'this' pointer from 'const class Time' to 'class Time &' Time5.cpp Error executing cl.exe. test.exe - 2 error(s), 0 warning(s)

11 7.2 const (常數) 物件與 const 成員函式
軟體工程的觀點 7.3 const 成員函式可被重載為非 const 的版本,編譯器會依據呼叫此函式的物件是否為 const 來決定所要呼叫的函式是哪一個。 良好的程式設計習慣 7.1 將所有不需要修改資料內容的成員函式宣告為 const ,如此一來,就可以被所需要的 const 物件使用。 常數物件是在建構子設定完資料成員的內容後才成為不可改變的常數。 有些編譯器可以讓設定為常數的變數執行速度變得比較快。

12 7.2 const (常數) 物件與 const 成員函式
前面介紹的是 const 物件、const 函式,下面討論 const 資料成員,要怎麼作呢? 一個 const 變數只能在宣告時順便作初值化,宣告後就不能再改變它的內容了。但是類別中的資料成員在宣告時不能給初值,這怎麼辦呢? 下面的例子中 Increment 這個類別中有一個 increment 的 const 資料成員,其初值化必須在建構子作,寫法如下: Increment 的建構子寫法如下: Increment::Increment( int c, int i ) :increment( i ) { count = c; } : increment( i ) 將 increment 的初值設為 i

13 1 Increment Increment( 0,5 ): count(0), increment(1) count increment
1 // Fig. 7.4: fig07_04.cpp 2 // Using a member initializer to initialize a 3 // constant of a built-in data type. 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 class Increment { 10 public: 11 Increment( int c = 0, int i = 1 ); 12 void addIncrement() { count += increment; } 13 void print() const; 14 15 private: 16 int count; 17 const int increment; // const data member 18 }; 19 20 // Constructor for class Increment 21 Increment::Increment( int c, int i ) 22 : increment( i ) // initializer for const member 23 { count = c; } 24 25 // Print the data 26 void Increment::print() const 27 { 28 cout << "count = " << count << ", increment = " << increment << endl; 30 } 31 32 int main() 33 { 1 count increment Increment Increment( 0,5 ): count(0), increment(1) 若用設定敘述式來作 increment 的初值化設定 (例如 increment = i )會造成語法錯誤

14 10 5 25 20 15 Increment value(10,5) Increment( 10,5 ):
35 36 cout << "Before incrementing: "; 37 value.print(); 38 39 for ( int j = 0; j < 3; j++ ) { value.addIncrement(); cout << "After increment " << j + 1 << ": "; value.print(); 43 } 44 45 return 0; 46 } 10 5 count increment Increment value(10,5) Increment( 10,5 ): count(10), increment(5) 25 20 15 Before incrementing: count = 10, increment = 5 After increment 1: count = 15, increment = 5 After increment 2: count = 20, increment = 5 After increment 3: count = 25, increment = 5

15 // Fig. 7.5: fig07_05.cpp …… 9 class Increment { 11 public: 22 private: int count; const int increment; 26 }; 29 Increment::Increment( int c, int i ) { count = c; increment = i; // ERROR: Cannot modify a const object 34 }

16 7.2 const (常數) 物件與 const 成員函式
實際上每個資料成員都可以用這種方式(成員初值設定語法)來作初值化 而 const 與參照 必須(只能)用成員初值設定語法來作初值化。 如果有多個資料成員需要如此初值化時 可用逗號隔開 測試提醒:當某個成員函式並不需要改變 資料成員的內容時,就將它設為 const,這樣無論物件是否為 const 都可以使用。 常見錯誤7.5:沒有提供 const 資料成員的初值,這是語法錯誤。

17 7.3 合成:將物件當作類別的成員 合成 composition 物件的建構 construction of objects
7.3 合成:將物件當作類別的成員 合成 composition Class has objects of other classes as members,即有其他物件為成員的類別 這種方式是最自然地再使用的作法。 例如員工資料中,有生日、聘用日期等,這些日期資料可以直接使用「日期」的物件。 物件的建構 construction of objects 成員物件按照宣告順序來建構(產生) 而不是照建構子的成員初值設定順序 Constructed before their enclosing class objects (host objects) ,小物件先建構(產生),這是物件的建構順序;而解構順序與此顛倒。

18 Date 10 month 1 day 1900 year 檢查日期是否正確的函式
1 // Fig. 7.6: date1.h,這個範例共有五個檔案 2 // Declaration of the Date class. 3 // Member functions defined in date1.cpp 4 #ifndef DATE1_H 5 #define DATE1_H 6 7 class Date { 8 public: 9 Date( int = 1, int = 1, int = 1900 ); // default constructor 10 void print() const; // print date in month/day/year format 11 ~Date(); // provided to confirm destruction order 12 private: 13 int month; // 1-12 14 int day; // 1-31 based on month 15 int year; // any year 16 17 // utility function to test proper day for month and year 18 int checkDay( int ); 19 }; 20 21 #endif 10 1900 day year Date 1 month 檢查日期是否正確的函式

19 當建構子被呼叫時會輸出一行文字 22 // Fig. 7.4: date1.cpp
23 // Member function definitions for Date class. 24 #include <iostream> 25 26 using std::cout; 27 using std::endl; 28 29 #include "date1.h" 30 31 // Constructor: Confirm proper value for month; 32 // call utility function checkDay to confirm proper 33 // value for day. 34 Date::Date( int mn, int dy, int yr ) 35 { 36 if ( mn > 0 && mn <= 12 ) // validate the month month = mn; 38 else { month = 1; cout << "Month " << mn << " invalid. Set to month 1.\n"; 41 } 42 43 year = yr; // should validate yr 44 day = checkDay( dy ); // validate the day 45 46 cout << "Date object constructor for date "; 47 print(); // interesting: a print with no arguments 48 cout << endl; 49 } 50 當建構子被呼叫時會輸出一行文字

20 解構子被呼叫時、也會輸出一行文字 這是檢查日期是否合法的函式
51 // Print Date object in form month/day/year 52 void Date::print() const 53 { cout << month << '/' << day << '/' << year; } 54 55 // Destructor: provided to confirm destruction order 56 Date::~Date() 57 { 58 cout << "Date object destructor for date "; 59 print(); 60 cout << endl; 61 } 62 63 // Utility function to confirm proper day value 64 // based on month and year. 65 // Is the year 2000 a leap year? 66 int Date::checkDay( int testDay ) 67 { 68 static const int daysPerMonth[ 13 ] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; 70 71 if ( testDay > 0 && testDay <= daysPerMonth[ month ] ) return testDay; 73 74 if ( month == 2 && // February: Check for leap year testDay == 29 && ( year % 400 == 0 || ( year % 4 == 0 && year % 100 != 0 ) ) ) return testDay; 79 80 cout << "Day " << testDay << " invalid. Set to day 1.\n"; 81 82 return 1; // leave object in consistent state if bad value 83 } 解構子被呼叫時、也會輸出一行文字 這是檢查日期是否合法的函式

21 10 Employee firstname lastname Date birth Date hire
84 // Fig. 7.4: emply1.h 85 // Declaration of the Employee class. 86 // Member functions defined in emply1.cpp 87 #ifndef EMPLY1_H 88 #define EMPLY1_H 89 90 #include "date1.h" 91 92 class Employee { 93 public: 94 Employee( char *, char *, int, int, int, int, int, int ); 95 void print() const; 96 ~Employee(); // provided to confirm destruction order 97 private: 98 char firstName[ 25 ]; 99 char lastName[ 25 ]; const Date birthDate; const Date hireDate; 102 }; 103 104 #endif firstname lastname Employee 10 1900 day year Date birth 1 month Date hire 合成:有其他類別物件的成員,且是常數物件 物件建構的順序與宣告的順序相同。

22 要將兩個類別的 介面檔案都 include 進來
105 // Fig. 7.4: emply1.cpp 106 // Member function definitions for Employee class. 107 #include <iostream> 108 109 using std::cout; 110 using std::endl; 111 112 #include <cstring> 113 #include "emply1.h" 114 #include "date1.h" 115 116 Employee::Employee( char *fname, char *lname, int bmonth, int bday, int byear, int hmonth, int hday, int hyear ) : birthDate( bmonth, bday, byear ), hireDate( hmonth, hday, hyear ) 121 { // copy fname into firstName and be sure that it fits int length = strlen( fname ); length = ( length < 25 ? length : 24 ); strncpy( firstName, fname, length ); firstName[ length ] = '\0'; 127 // copy lname into lastName and be sure that it fits length = strlen( lname ); length = ( length < 25 ? length : 24 ); strncpy( lastName, lname, length ); lastName[ length ] = '\0'; 133 cout << "Employee object constructor: " << firstName << ' ' << lastName << endl; 136 } 要將兩個類別的 介面檔案都 include 進來 成員初值設定串列,兩個常數物件的初值設定 建構子被呼叫時會輸出一行文字

23 print 函式是 const 且當 Date 物件被建立或解構時會執行,因為它是 const 函式,所以可以輸出 const 物件。
137 138 void Employee::print() const 139 { cout << lastName << ", " << firstName << "\nHired: "; hireDate.print(); cout << " Birth date: "; birthDate.print(); cout << endl; 145 } 146 147 // Destructor: provided to confirm destruction order 148 Employee::~Employee() 149 { cout << "Employee object destructor: " << lastName << ", " << firstName << endl; 152 } print 函式是 const 且當 Date 物件被建立或解構時會執行,因為它是 const 函式,所以可以輸出 const 物件。 Print 不需要引數,它隱含地連結到呼叫它的物件 解構子被呼叫時會輸出一行文字

24 Employee manager( "Bob", "Jones", birth, hire )
153 // Fig. 7.4: fig07_04.cpp 154 // Demonstrating composition: an object with member objects. 155 #include <iostream> 156 157 using std::cout; 158 using std::endl; 159 160 #include "emply1.h" 161 162 int main() 163 { Employee e( "Bob", "Jones", 7, 24, 1949, 3, 12, 1988 ); 165 cout << '\n'; e.print(); 168 cout << "\nTest Date constructor with invalid values:\n"; Date d( 14, 35, 1994 ); // invalid Date values cout << endl; return 0; 173 } Bob Jones firstname lastname Employee manager( "Bob", "Jones", birth, hire ) birth hire 只有 emply.h 需要被載入,該檔案就有載入 date.h. 10 1949 day year Date birth(7,24,1949) 24 7 month 10 1988 day year Date hire(3,12,1988) 12 3 month

25 Date object constructor for date 7/24/1949
Employee object constructor: Bob Jones Jones, Bob Hired: 3/12/1988 Birth date: 7/24/1949 Test Date constructor with invalid values: Month 14 invalid. Set to month 1. Day 35 invalid. Set to day 1. Date object constructor for date 1/1/1994 Date object destructor for date 1/1/1994 Employee object destructor: Jones, Bob Date object destructor for date 3/12/1988 Date object destructor for date 7/24/1949 注意內部的物件先被建立、且較慢被解構

26 7.3 合成:將物件當作類別的成員 當類別中有其他類別的成員時,一定要有預設的建構子。
7.3 合成:將物件當作類別的成員 當類別中有其他類別的成員時,一定要有預設的建構子。 兩個類別的 print() 函式都是 const,不需要改變資料成員的函式最好都設為 const。且這兩個 print 都沒有 augments。 firstName 與 lastName 兩個字串長度都是 25,比較浪費空間,而且若輸入長度超過 25 的字串時,會被切掉。後面使用動態記憶體管理時,可以改善這些情形。

27 7.4 夥伴(friend)函式和夥伴類別 friend 函式與 friend 類別
可以存取其他類別宣告在 private 或 protected 裡面的資料 friend 函式不是類別的成員函式,所以存取其 資料成員的方式不同。 定義在類別範圍之外 朋友關係的性質 Properties of friendship 朋友關係只能他人授權、而非自行取得 非對稱 (若 B 是 A 的朋友,A 未必就是 B 的朋友) 沒有遞移性(若 A 是 B 的朋友、 B 是 C 的朋友,A 未必是 C 的朋友)

28 7.4 夥伴函式和夥伴類別 friend 宣告 宣告夥伴(friend)函式 宣告夥伴(friend)類別
7.4 夥伴函式和夥伴類別 friend 宣告 宣告夥伴(friend)函式 將 friend 寫在要授權的類別中之函式原型的前面,如: friend int myFunction( int x ); 這一行應出現在要授權的類別裡面 宣告夥伴(friend)類別 若 Classtwo 要成為 Classone 的夥伴類別,必須在 Classone 中宣告 friend class ClassTwo;

29 這裡宣告 setX 是 Count 的 friend (可存取 private 資料).
1 // Fig. 7.5: fig07_05.cpp 2 // Friends can access private members of a class. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 // Modified Count class 9 class Count { 10 friend void setX( Count &, int ); // friend declaration 11 public: 12 Count() { x = 0; } // constructor 13 void print() const { cout << x << endl; } // output 14 private: 15 int x; // data member 16 }; 17 18 // Can modify private data of Count because 19 // setX is declared as a friend function of Count 20 void setX( Count &c, int val ) 21 { 22 c.x = val; // legal: setX is a friend of Count 23 } 24 25 int main() 26 { 27 Count counter; 28 29 cout << "counter.x after instantiation: "; 30 counter.print(); 這裡宣告 setX 是 Count 的 friend (可存取 private 資料). 可以改變 Count 的 private 變數,但存取方式與 Count 的成員函式不同 setX 被正常定義,且不是 Count 的成員函式Count.

30 雖然夥伴函式的原型出現在類別的定義裡,但它仍然不是類別的成員函式。
31 cout << "counter.x after call to setX friend function: "; 32 setX( counter, 8 ); // set x with a friend,一般涵數 33 counter.print(); 34 return 0; 35 } counter.x after instantiation: 0 counter.x after call to setX friend function: 8 private 資料已被改變。 軟體工程的觀點 7.9 雖然夥伴函式的原型出現在類別的定義裡,但它仍然不是類別的成員函式。

31 cannotSetX 不是 Count 的friend,不能存取其 private 資料
1 // Fig. 7.6: fig07_06.cpp 2 // Non-friend/non-member functions cannot access 3 // private data of a class. 4 #include <iostream> 5 6 using std::cout; 7 using std::endl; 8 9 // Modified Count class 10 class Count { 11 public: 12 Count() { x = 0; } // constructor 13 void print() const { cout << x << endl; } // output 14 private: 15 int x; // data member 16 }; 17 18 // Function tries to modify private data of Count, 19 // but cannot because it is not a friend of Count. 20 void cannotSetX( Count &c, int val ) 21 { 22 c.x = val; // ERROR: 'Count::x' is not accessible 23 } 24 25 int main() 26 { 27 Count counter; 28 29 cannotSetX( counter, 3 ); // cannotSetX is not a friend 30 return 0; 31 } cannotSetX 不是 Count 的friend,不能存取其 private 資料 cannotSetX 試著去修改private 變數 x

32 夥伴關係的宣告與 private、protected、public 的符號無關,因此夥伴關係的宣告可置於類別定義裡的任何位置。
Compiling... Fig07_06.cpp D:\books\2000\cpphtp3\examples\Ch07\Fig07_06\Fig07_06.cpp(22) : error C2248: 'x' : cannot access private member declared in class 'Count' D:\books\2000\cpphtp3\examples\Ch07\Fig07_06\ Fig07_06.cpp(15) : see declaration of 'x' Error executing cl.exe. test.exe - 1 error(s), 0 warning(s) 因此產生語法錯誤 -不可存取 private 資料 軟體工程的觀點 7.10 夥伴關係的宣告與 private、protected、public 的符號無關,因此夥伴關係的宣告可置於類別定義裡的任何位置。

33 7.5 this 指標的使用 this 指標 是指向物件本身的指標,允許物件存取自己的位置
不是物件本身的一部份,不會反應在 sizeof 中 呼叫物件的 non-static 成員函式時,隱含的第一個參數。 Implicitly reference member data and functions,呼叫本身的成員時沒寫物件名稱,就等於是加上 this-> 的意思。 this 指標的型態是根據該物件的型態與成員函式是否為 const 而定。

34 const Employee * const this
對於 Employee 的 non-const 成員函式, this 的型態 Employee * const 指向 Employee 物件的常數指標 在 Employee 的 const 成員函式,this 的型態為 const Employee * const 指向常數物件 Employee 的常數指標 Employee Employee * const this Employee const Employee * const this

35 7.5 this 指標的使用 使用 this 的範例 一連串的成員函式呼叫 成員函式中存取資料成員 x,有以下三種寫法
X, this->x, 或 (*this).x 一連串的成員函式呼叫 函式傳回一個指向相同物件的指標 { return *this; } 其他函式可以使用這個指標繼續作運算 沒有傳回這種指標參照的函必須最後被呼叫

36 1. Class definition 1.1 Function definition 1.2 Initialize object
1 // Fig. 7.7: fig07_07.cpp 2 // Using the this pointer to refer to object members. 3 #include <iostream> 4 5 using std::cout; 6 using std::endl; 7 8 class Test { 9 public: 10 Test( int = 0 ); // default constructor 11 void print() const; 12 private: 13 int x; 14 }; 15 16 Test::Test( int a ) { x = a; } // constructor 17 18 void Test::print() const // ( ) around *this required 19 { 20 cout << " x = " << x << "\n this->x = " << this->x << "\n(*this).x = " << ( *this ).x << endl; 23 } 24 25 int main() 26 { 27 Test testObject( 12 ); 28 29 testObject.print(); 30 31 return 0; 32 } Test this 12 x 直接輸出 x 使用 this 指標與箭頭(->)運算子來輸出 x 1. Class definition 1.1 Function definition 1.2 Initialize object 2. Function call 用點(.)運算子輸出 x。括號是必須的,因為點運算子的優先順序比 * 還高,沒寫括號的話就變成 *(this.x)的意思,造成語法錯誤。

37 x = 12 this->x = 12 (*this).x = 12 三個方法輸出的結果都相同

38 7.5 this 指標的使用 連續呼叫成員函式的例子
成員函式 setHour, setMinute, 與 setSecond 都傳回 *this (指向物件) 對物件 t來說,考慮下列呼叫 t.setHour(1).setMinute(2).setSecond(3); 先執行 t.setHour(1), 傳回 *this (指向物件)成為 t.setMinute(2).setSecond(3); 再執行 t.setMinute(2), 傳回 *this 成為 t.setSecond(3); 最後執行 t.setSecond(3), 傳回 *this 成為 t; 不會再產生其他結果

39 注意函式傳回的資料型態是 Time & - 指向 Time 物件的參照.
1 // Fig. 7.8: time6.h 2 // Cascading member function calls. 3 4 // Declaration of class Time. 5 // Member functions defined in time6.cpp 6 #ifndef TIME6_H 7 #define TIME6_H 8 9 class Time { 10 public: 11 Time( int = 0, int = 0, int = 0 ); // default constructor 12 13 // set functions 14 Time &setTime( int, int, int ); // set hour, minute, second 15 Time &setHour( int ); // set hour 16 Time &setMinute( int ); // set minute 17 Time &setSecond( int ); // set second 18 19 // get functions (normally declared const) 20 int getHour() const; // return hour 21 int getMinute() const; // return minute 22 int getSecond() const; // return second 23 24 // print functions (normally declared const) 25 void printMilitary() const; // print military time 26 void printStandard() const; // print standard time 27 private: 28 int hour; // 29 int minute; // 30 int second; // 31 }; 32 33 #endif 注意函式傳回的資料型態是 Time & - 指向 Time 物件的參照.

40 傳回 *this 因此可以作連續的函式呼叫 34 // Fig. 7.8: time.cpp
35 // Member function definitions for Time class. 36 #include <iostream> 37 38 using std::cout; 39 40 #include "time6.h" 41 42 // Constructor function to initialize private data. 43 // Calls member function setTime to set variables. 44 // Default values are 0 (see class definition). 45 Time::Time( int hr, int min, int sec ) 46 { setTime( hr, min, sec ); } 47 48 // Set the values of hour, minute, and second. 49 Time &Time::setTime( int h, int m, int s ) 50 { 51 setHour( h ); 52 setMinute( m ); 53 setSecond( s ); 54 return *this; // enables cascading 55 } 56 57 // Set the hour value 58 Time &Time::setHour( int h ) 59 { 60 hour = ( h >= 0 && h < 24 ) ? h : 0; 61 62 return *this; // enables cascading 63 } 64 傳回 *this 因此可以作連續的函式呼叫

41 傳回 *this 因此可以作連續的函式呼叫 65 // Set the minute value
66 Time &Time::setMinute( int m ) 67 { 68 minute = ( m >= 0 && m < 60 ) ? m : 0; 69 70 return *this; // enables cascading 71 } 72 73 // Set the second value 74 Time &Time::setSecond( int s ) 75 { 76 second = ( s >= 0 && s < 60 ) ? s : 0; 77 78 return *this; // enables cascading 79 } 80 81 // Get the hour value 82 int Time::getHour() const { return hour; } 83 84 // Get the minute value 85 int Time::getMinute() const { return minute; } 86 87 // Get the second value 88 int Time::getSecond() const { return second; } 89 90 // Display military format time: HH:MM 91 void Time::printMilitary() const 92 { 93 cout << ( hour < 10 ? "0" : "" ) << hour << ":" << ( minute < 10 ? "0" : "" ) << minute; 傳回 *this 因此可以作連續的函式呼叫

42 printStandard 沒有傳回指向物件的參照
95 } 96 97 // Display standard format time: HH:MM:SS AM (or PM) 98 void Time::printStandard() const 99 { cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 ) << ":" << ( minute < 10 ? "0" : "" ) << minute << ":" << ( second < 10 ? "0" : "" ) << second << ( hour < 12 ? " AM" : " PM" ); 104 } printStandard 沒有傳回指向物件的參照 105 // Fig. 7.8: fig07_08.cpp 106 // Cascading member function calls together 107 // with the this pointer 108 #include <iostream> 109 110 using std::cout; 111 using std::endl; 112 113 #include "time6.h" 114 115 int main() 116 { Time t; 118 t.setHour( 18 ).setMinute( 30 ).setSecond( 22 ); cout << "Military time: "; t.printMilitary(); cout << "\nStandard time: "; t.printStandard(); 124 cout << "\n\nNew standard time: "; t.setTime( 20, 20, 20 ).printStandard(); 注意這裡作連續函式呼叫 連續函式呼叫, printStandard 必須在 setTime 之後再呼叫,因為 printStandard 並沒有傳回指向物件的參照。 t.printStandard().setTime(); 若如此呼叫會造成語法錯誤。

43 New standard time: 8:20:20 PM
cout << endl; 128 return 0; 130 } Military time: 18:30 Standard time: 6:30:22 PM New standard time: 8:20:20 PM

44 7.6 使用 new 和 delete 運算子的動態記憶體配置
作動態記憶體配置之用,C 語言中也有類似功能的函式呼叫- malloc 與 free,C++ 的 new 與 delete 比較好。 new 建立適當大小的物件,呼叫其建構子並傳回正確型態的指標 delete 解構物件並釋放記憶體空間 delete 的範例 delete typeNamePtr; 呼叫 TypeName 物件的解構子並釋放記憶體 delete [] arrayPtr; 用來動態地殺掉一個陣列

45 7.6 使用 new 和 delete 運算子的動態記憶體配置
TypeName *typeNamePtr; 建立一個指向 TypeName 物件的指標 typeNamePtr = new TypeName; new 建立 TypeName 物件,傳回指標(存到 typeNamePtr 裡面) Time *timePtr; timePtr = new Time; 對物件作初值化 double *ptr = new double( ); Time *timePtr = new Time( 12, 0, 0 ); c.f. : double v = ; ptr = &v; timePtr hour minute second Time( 0, 0, 0 ); timePtr 12 hour minute second Time( 12, 0, 0 ); ptr

46 7.6 使用 new 和 delete 運算子的動態記憶體配置
對物件作初值化 建立一10個元素的 int 陣列並設定給 arrayPtr int *gradesArray = new int[ 10 ]; 用 new 建立物件時,會自動呼叫建構子;而 delete 會自動呼叫解構子。 gradesArray

47 7.6 使用 new 和 delete 運算子的動態記憶體配置
常見錯誤7.8: 將 new-和-delete-型式的動態記憶體配置與 malloc-和-free-型式的動態記憶體配置相混雜是邏輯錯誤,也就是用 malloc 配置的空間不可用 delete來釋放;而用 new 建立的物件不可用 free 來刪除。 常見錯誤7.9: 對陣列使用 delete 來取代 delete [] 會引起執行時的邏輯錯誤;謹記:動態建立的陣列空間必須用delete [] 運算子刪除,而動態建立的個別元素就用 delete 運算子刪除。 好的習慣 7.3: C++ 都可以使用 C 的敘述式,所以在 C++ 中也可以使用 malloc 與 free。但最好單單使用 new 與 delete 不要用 malloc 與 free。

48 7.7 static 類別成員 static 類別成員
一般說來,每個物件會記錄一份資料變數的內容 例如:要記錄所建構的物件個數時,使用 static 類別成員比較有效率、又可節省空間 只需改變 static 變數內容,而不需改每個物件內容 可視為類別範圍的全域變數 只能被同一個類別的物件存取 用檔案範圍的方式作初值化 就算沒有任何物件被宣告,此種成員也存在、可使用 變數與函式都可被宣告為 static 可設成 public, private 或 protected

49 7.7 static 類別成員 static 變數 可以透過類別名稱來存取 static 成員的值。 public static 變數
class C { int x; static int s; }; C c1, c2; C::s x static 變數 可以透過類別名稱來存取 static 成員的值。 public static 變數 可以用範圍解析運算子(::)來存取 Employee::count private static 變數 當沒有該類別的物件存在時,必須透過 public static 成員函式來存取。 用類別名稱加範圍解析運算子呼叫 public static 成員函式 Employee::getCount() 一般成員函式或資料成員只能透過物件來呼叫,但是 static 資料成員或成員函式可以透過類別名稱來使用。

50 7.7 static 類別成員 static 函式 static 成員函式不可存取非 static 的資料或呼叫非 static 的函式。
static 函式沒有 this 指標,他們的存在與物件無關。

51 Employee object Employee::count firstName lastName static 成員函式與變數宣告
1 // Fig. 7.9: employ1.h 2 // An employee class 3 #ifndef EMPLOY1_H 4 #define EMPLOY1_H 5 6 class Employee { 7 public: 8 Employee( const char*, const char* ); // constructor 9 ~Employee(); // destructor 10 const char *getFirstName() const; // return first name 11 const char *getLastName() const; // return last name 12 13 // static member function 14 static int getCount(); // return # objects instantiated 15 16 private: 17 char *firstName; 18 char *lastName; 19 20 // static data member 21 static int count; // number of objects instantiated 22 }; 23 24 #endif Employee object Employee::count firstName lastName static 成員函式與變數宣告

52 static 資料成員 count 和成員函式 getCount( ) 函式需用檔案範圍作初值化
25 // Fig. 7.9: employ1.cpp 26 // Member function definitions for class Employee 27 #include <iostream> 28 29 using std::cout; 30 using std::endl; 31 32 #include <cstring> 33 #include <cassert> 34 #include "employ1.h" 35 36 // Initialize the static data member 37 int Employee::count = 0; 38 39 // Define the static member function that 40 // returns the number of employee objects instantiated. 41 int Employee::getCount() { return count; } 42 43 // Constructor dynamically allocates space for the 44 // first and last name and uses strcpy to copy 45 // the first and last names into the object 46 Employee::Employee( const char *first, const char *last ) 47 { 48 firstName = new char[ strlen( first ) + 1 ]; 49 assert( firstName != 0 ); // ensure memory allocated 50 strcpy( firstName, first ); 51 52 lastName = new char[ strlen( last ) + 1 ]; 53 assert( lastName != 0 ); // ensure memory allocated 54 strcpy( lastName, last ); 55 count; // increment static count of employees static 資料成員 count 和成員函式 getCount( ) 函式需用檔案範圍作初值化 注意 assert 用來測試 firstName != 0 這個式子若不成立(等於記憶體配置失敗),就輸出錯誤訊息,並呼叫 abort() 來作異常結束。 當建構子或解構子被呼叫時就改變 static 資料成員 count 的內容

53 當建構子或解構子被呼叫時就改變 static 資料成員 count 的內容
57 cout << "Employee constructor for " << firstName << ' ' << lastName << " called." << endl; 59 } 60 61 // Destructor deallocates dynamically allocated memory 62 Employee::~Employee() 63 { 64 cout << "~Employee() called for " << firstName << ' ' << lastName << endl; 66 delete [] firstName; // recapture memory 67 delete [] lastName; // recapture memory count; // decrement static count of employees 69 } 70 71 // Return first name of employee 72 const char *Employee::getFirstName() const 73 { 74 // Const before return type prevents client from modifying 75 // private data. Client should copy returned string before 76 // destructor deletes storage to prevent undefined pointer. 77 return firstName; 78 } 79 80 // Return last name of employee 81 const char *Employee::getLastName() const 82 { 83 // Const before return type prevents client from modifying 84 // private data. Client should copy returned string before 85 // destructor deletes storage to prevent undefined pointer. 86 return lastName; 87 } 當建構子或解構子被呼叫時就改變 static 資料成員 count 的內容

54 Employee::count Employee *e2Ptr firstName lastName Employee *e2Ptr
88 // Fig. 7.9: fig07_09.cpp 89 // Driver to test the employee class 90 #include <iostream> 91 92 using std::cout; 93 using std::endl; 94 95 #include "employ1.h" 96 97 int main() 98 { 99 cout << "Number of employees before instantiation is " << Employee::getCount() << endl; // use class name 101 Employee *e1Ptr = new Employee( "Susan", "Baker" ); Employee *e2Ptr = new Employee( "Robert", "Jones" ); 104 cout << "Number of employees after instantiation is " << e1Ptr->getCount(); 107 cout << "\n\nEmployee 1: " << e1Ptr->getFirstName() << " " << e1Ptr->getLastName() << "\nEmployee 2: " << e2Ptr->getFirstName() << " " << e2Ptr->getLastName() << "\n\n"; 114 delete e1Ptr; // recapture memory e1Ptr = 0; delete e2Ptr; // recapture memory e2Ptr = 0; 沒有 Employee 物件存在時 getCount 必須用類別名稱加(::)來存取。 count 增加 1, 因為 new 使建構子被呼叫 Employee *e2Ptr Robert firstName Jones lastName Employee *e2Ptr Susan firstName Baker lastName Employee *e1Ptr Employee *e1Ptr 2 1 Employee::count

55 Number of employees before instantiation is 0
119 cout << "Number of employees after deletion is " << Employee::getCount() << endl; 122 return 0; 124 } 最後 count 變成 0 Number of employees before instantiation is 0 Employee constructor for Susan Baker called. Employee constructor for Robert Jones called. Number of employees after instantiation is 2 Employee 1: Susan Baker Employee 2: Robert Jones ~Employee() called for Susan Baker ~Employee() called for Robert Jones Number of employees after deletion is 0

56 7.7 static 類別成員 前面範例中有用到 new 與 delete。
前面範例中 getFirstName() 與 getLastName() 的傳回值資料型態是 const char,如果沒有加 const 會如何呢? 與 6.15 傳回值資料型態為 reference 的變數類似。 這個範例中的 firstName 與 lastName 的用法是動態的作法,能改善 Figure07_04 的問題。 static 變數作初值化時,不加 static 這個字。 用到 assert 需要 #include <cassert> 才行。 常見錯誤7.11: 在 static 成員函式中使用 this 指標. 常見錯誤7.12: 將 static 成員函式宣告成 const.

57 7.8 資料抽象化與資訊隱藏 資訊隱藏 類別將實作細節對客戶端隱藏起來 範例:堆疊資料結構
7.8 資料抽象化與資訊隱藏 資訊隱藏 類別將實作細節對客戶端隱藏起來 範例:堆疊資料結構 資料從上面加入(push)、也從上面移除(pop) 後進先出 Last-in, first-out (LIFO) 的資料結構 客戶端不需注意堆疊如何被實作,只需關心它是有提供堆疊功能的資料結構即可。

58 7.8 資料抽象化與資訊隱藏 抽象資料型態 Abstract data types (ADTs) C++ 是可擴展的語言
7.8 資料抽象化與資訊隱藏 抽象資料型態 Abstract data types (ADTs) 包含:資料表示方式與運算 用來模擬真實世界的物件,類別的功用與實作方式無關 int, float 模擬數字的概念。 C++ 是可擴展的語言 標準的資料型態不能改變,但可建立新的資料型態。 軟體工程觀察7.15: 程式設計者可透過類別的機制來建立新的型態,這些新的型態可以與內建的型態同樣方便使用,因此C++是可擴展的程式語言,雖然C++容易擴展(建立新的型態),但它的基本觀念沒有改變。

59 7.8.1 範例:陣列抽象資料型態 C++ 所提供的陣列就是常數指標加上它的空間。 程式設計者可以自己作 ADT 陣列 可包括
範例:陣列抽象資料型態 C++ 所提供的陣列就是常數指標加上它的空間。 程式設計者可以自己作 ADT 陣列 可包括 Subscript range checking,檢查存取範圍 An arbitrary range of subscripts instead of having to start with 0,駐標不一定要由 0 開始 Array assignment,將一個陣列存到另一個陣列中 Array comparison,比較兩陣列是否相同 Array input/output,輸入/出陣列 Arrays that know their sizes ,知道陣列的大小 Arrays that expand dynamically to accommodate more elements ,可動態地容納更多元素 第八章會建 array 的類別

60 7.8.2 範例:字串抽象型態 C++ 的字串 C++ 本身沒有提供字串的資料型態,主要應該是執行效率的考量,因為 C++ 很重視執行效率,所以沒有建很多種基本的資料型態。 C++ 就是提供可以建立字串之抽象資料型態的能力,讓人可以建自己的字串,加入標準程式庫中。 第八章會有我們自己建的字串ADT string 類別有在 ANSI/ISO 標準中 (Chapter 19)

61 7.8.3 範例:佇列抽象型態 佇列 Queue 佇列的抽象資料型態 就像排隊(超市、加油站、公車站、投票、餐廳、…)
範例:佇列抽象型態 佇列 Queue 就像排隊(超市、加油站、公車站、投票、餐廳、…) FIFO — First in, first out(先進先出) 存入佇列 enqueue 每次從後面加一個資料到佇列中 取出佇列 dequeue 每次從佇列前面取出一個資料 實作方式對客戶端隱藏 佇列的抽象資料型態 客戶端不直接操作資料結構 只有佇列的成員函式才存取內部資料 第十五章會討論佇列的資料結構

62 7.9 容器類別與迭代子(iterators) 容器(container)類別,或稱集合(collection)類別
設計來存放物作集合的類別 提供像:插入、刪除、搜尋、排序、測試某元素是否屬在該容器的測試函式 範例: 陣列、堆疊、佇列、樹、鏈結串列 迭代子物件(簡稱迭代子 iterators) 用來傳回容器中下一個項目的物件(或對下個項目執行一些動作) 每個容器可以有好幾個迭代子 就像很多人一起讀一本書時,書中會夾好幾張書籤一樣 每個迭代子都有存有自己「位置」的資訊 二十章會有更多討論

63 7.10 代理類別 proxy class 代理類別 先置類別宣告 Forward class declaration
用來將類別實作細節完全隱藏起來 只讓客戶端看到 interface 的資料,看不到類別中有哪些 private 資料。 使客戶端使用類別提供的服務,而無法存取類別實作。 先置類別宣告 Forward class declaration 當類別定義中只用指標指到另一個類別時 避免將另一個類別的標頭檔載入 在使用到這個類別之前就先宣告 格式: class ClassToLoad;

64 Implementation 中要隱藏的 private 資料(value)
1 // Fig. 7.10: implementation.h 2 // Header file for class Implementation 3 4 class Implementation { 5 public: Implementation( int v ) { value = v; } void setValue( int v ) { value = v; } int getValue() const { return value; } 9 10 private: int value; 12 }; 13 // Fig. 7.10: interface.h 14 // Header file for interface.cpp 15 class Implementation; // forward class declaration 16 17 class Interface { 18 public: Interface( int ); void setValue( int ); // same public interface as int getValue() const; // class Implementation ~Interface(); 23 private: Implementation *ptr; // requires previous // forward declaration 26 }; Implementation 中要隱藏的 private 資料(value) Forward class declaration先置類別宣告. 代理類別 Interface 的公開介面幾乎與 Implementation 完全一樣 只用指標指到類別 Implementation. 這樣允許我們將實作細節隱藏起來。

65 27 // Fig. 7.10: interface.cpp 28 // Definition of class Interface 29 #include "interface.h" 30 #include "implementation.h" 31 32 Interface::Interface( int v ) 33 : ptr ( new Implementation( v ) ) { } 34 35 // call Implementation's setValue function 36 void Interface::setValue( int v ) { ptr->setValue( v ); } 37 38 // call Implementation's getValue function 39 int Interface::getValue() const { return ptr->getValue(); } 40 41 Interface::~Interface() { delete ptr; } 42 // Fig. 7.10: fig07_10.cpp 43 // Hiding a class’s private data with a proxy class. 44 #include <iostream> 45 46 using std::cout; 47 using std::endl; 48 49 #include "interface.h" 50 51 int main() 52 { 53 Interface i( 5 ); 54 55 cout << "Interface contains: " << i.getValue() << " before setValue" << endl; 57 i.setValue( 10 ); 58 cout << "Interface contains: " << i.getValue() << " after setValue" << endl; 60 return 0; 61 } 在實作的檔案 interface.cpp 中包含類別 Interface 所用到的所有成員函式,它也是唯一需要載入標頭檔 implementation.h 的部份 將 interface.cpp 編譯好,在客戶端就只要載入標頭檔 interface.h 即可,客戶端無法看到代理類別與被隱藏的類別間如何互動。 只有標頭檔 Interface.h 完全沒有提到 Implementation,客戶端完全看不到 Implementation 的 private 有什麼資料。

66 Interface contains: 5 before setVal
Interface contains: 10 after setVal


Download ppt "Chapter 7: 再論類別 目標 能夠動態地建構和解構物件 能夠指定 const(常數)物件和和 const 成員函式"

Similar presentations


Ads by Google