chapter 7: 再論類別
DESCRIPTION
Chapter 7: 再論類別. 目標 能夠動態地建構和解構物件 能夠指定 const ( 常數 ) 物件和和 const 成員函式 了解夥伴( friend ) 函式與夥伴類別的目的 了解如何使用 static 資料成員與成員函式 了解容器( container) 類別的概念 了解具有檢視容器類別元素功能的迭代( iterator) 類別概念 了解指標 this 的用途. Chapter 7: 再論類別. 本章綱要 7.1 簡介 Introduction 7.2 常數物件與常數成員函式 7.3 合成:將物件當作類別的成員 - PowerPoint PPT PresentationTRANSCRIPT
1
目標• 能夠動態地建構和解構物件• 能夠指定 const( 常數 ) 物件和和 const 成員函式• 了解夥伴 (friend) 函式與夥伴類別的目的• 了解如何使用 static 資料成員與成員函式• 了解容器 (container) 類別的概念• 了解具有檢視容器類別元素功能的迭代 (iterator)類別概念• 了解指標 this 的用途
Chapter 7: 再論類別
2
本章綱要7.1 簡介 Introduction7.2 常數物件與常數成員函式 7.3 合成:將物件當作類別的成員7.4 夥伴函式與夥伴類別 7.5 this 指標的使用 7.6 使用 new 與 delete 運算子的動態記憶體配置7.7 static 類別成員7.8 資料抽象化與資訊隱藏
範例:陣列、字串、佇列7.9 容器類別和迭代子7.10 代理類別
Chapter 7: 再論類別
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
0
hour
minute
const Time noon(12,0,0)
second0
Time( 12, 0, 0 );
5
7.2 const ( 常數 ) 物件與 const 成員函式
• 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 成員函式
• 建構子 / 解構子不可宣告為 const– 對變數作初值化、就會修改其內容
常見的程式設計錯誤 7.1-4• 定義為 const 的成員函式若修改資料成員的內容,
會造成語法錯誤。• const 的函式不可呼叫 non-const 函式,否則就造
成語法錯誤。• 透過 const 物件來呼叫非 const 函式,是語法錯
誤。• 企圖將建構子或解構子宣告為 const 是語法錯誤。
7
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; // 0 - 23
27 int minute; // 0 - 59
28 int second; // 0 - 59
29 };
30
31 #endif
const 函式
非 const 函式
8
32 // Fig. 7.1: time5.cpp33 // Member function definitions for Time class.34 #include <iostream>3536 using std::cout;3738 #include "time5.h"3940 // 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 ); }4445 // 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 }5253 // Set the hour value54 void Time::setHour( int h ) 55 { hour = ( h >= 0 && h < 24 ) ? h : 0; }5657 // Set the minute value58 void Time::setMinute( int m ) 59 { minute = ( m >= 0 && m < 60 ) ? m : 0; }6061 // Set the second value62 void Time::setSecond( int s )63 { second = ( s >= 0 && s < 60 ) ? s : 0; }
建構子是非 const 但可以被 const 物件呼叫
9
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 << ":"
78 << ( 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 ) << ":"
85 << ( minute < 10 ? "0" : "" ) << minute << ":"
86 << ( second < 10 ? "0" : "" ) << second
87 << ( hour < 12 ? " AM" : " PM" );
88 }
關鍵字 const 必須出現在函式定義與函式原型中
非 const 函式不能被 const 物件使用,就算這些函式沒有修改資料內容也一樣。 ( 就像printStandard).
10
89 // Fig. 7.1: fig07_01.cpp90 // Attempting to access a const object with91 // non-const member functions.92 #include "time5.h"9394 int main()95 {96 Time wakeUp( 6, 45, 0 ); // non-constant object97 const Time noon( 12, 0, 0 ); // constant object9899 // MEMBER FUNCTION OBJECT100 wakeUp.setHour( 18 ); // non-const non-const101102 noon.setHour( 12 ); // non-const const103104 wakeUp.getHour(); // const non-const105106 noon.getMinute(); // const const107 noon.printMilitary(); // const const108 noon.printStandard(); // non-const const109 return 0;110}
Compiling...Fig07_01.cppd:fig07_01.cpp(14) : error C2662: 'setHour' : cannot convert 'this' pointer from 'const class Time' to 'class Time &'Conversion loses qualifiersd:\fig07_01.cpp(20) : error C2662: 'printStandard' : cannot convert 'this' pointer from 'const class Time' to 'class Time &'Conversion loses qualifiersTime5.cppError 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 // Fig. 7.4: fig07_04.cpp2 // Using a member initializer to initialize a3 // constant of a built-in data type.4 #include <iostream>56 using std::cout;7 using std::endl;89 class Increment {10 public:11 Increment( int c = 0, int i = 1 );12 void addIncrement() { count += increment; }13 void print() const;1415 private:16 int count;17 const int increment; // const data member18 };1920 // Constructor for class Increment21 Increment::Increment( int c, int i )22 : increment( i ) // initializer for const member23 { count = c; }2425 // Print the data26 void Increment::print() const27 {28 cout << "count = " << count29 << ", increment = " << increment << endl;30 }3132 int main()33 {
若用設定敘述式來作 increment 的初值化設定 ( 例如 increment = i ) 會造成語法錯誤
0
1
count
increment
Increment
Increment( 0,5 ):count(0), increment(1)
14
34 Increment value( 10, 5 );
35
36 cout << "Before incrementing: ";
37 value.print();
38
39 for ( int j = 0; j < 3; j++ ) {
40 value.addIncrement();
41 cout << "After increment " << j + 1 << ": ";
42 value.print();
43 }
44
45 return 0;
46 }
Before incrementing: count = 10, increment = 5After increment 1: count = 15, increment = 5After increment 2: count = 20, increment = 5After increment 3: count = 25, increment = 5
10
5
count
increment
Increment value(10,5)
Increment( 10,5 ):count(10), increment(5)
152025
15
1 // Fig. 7.5: fig07_05.cpp……9 class Increment {11 public:……22 private:23 int count;24 const int increment; 26 }; 29 Increment::Increment( int c, int i ) { 31 count = c; 32 increment = i; // ERROR: Cannot modify a const object 34 }……
16
7.2 const ( 常數 ) 物件與 const 成員函式
• 實際上每個資料成員都可以用這種方式 ( 成員初值設定語法 ) 來作初值化
• 而 const 與參照 必須 ( 只能 ) 用成員初值設定語法來作初值化。
• 如果有多個資料成員需要如此初值化時– 可用逗號隔開
• 測試提醒:當某個成員函式並不需要改變 資料成員的內容時,就將它設為 const ,這樣無論物件是否為 const 都可以使用。
• 常見錯誤 7.5 :沒有提供 const 資料成員的初值,這是語法錯誤。
17
7.3 合成:將物件當作類別的成員• 合成 composition
– Class has objects of other classes as members ,即有其他物件為成員的類別
– 這種方式是最自然地再使用的作法。– 例如員工資料中,有生日、聘用日期等,這些日期資料可以直接使用「日期」的物件。
• 物件的建構 construction of objects– 成員物件按照宣告順序來建構 ( 產生 )
• 而不是照建構子的成員初值設定順序– Constructed before their enclosing class
objects (host objects) ,小物件先建構 ( 產生 ) ,這是物件的建構順序;而解構順序與此顛倒。
18
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
檢查日期是否正確的函式
101900
dayyear
Date
1
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
37 month = mn;
38 else {
39 month = 1;
40 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/year52 void Date::print() const53 { cout << month << '/' << day << '/' << year; }5455 // Destructor: provided to confirm destruction order56 Date::~Date()57 { 58 cout << "Date object destructor for date ";59 print();60 cout << endl;61 }6263 // Utility function to confirm proper day value64 // 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 ] = 69 {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};7071 if ( testDay > 0 && testDay <= daysPerMonth[ month ] )72 return testDay;7374 if ( month == 2 && // February: Check for leap year75 testDay == 29 &&76 ( year % 400 == 0 || 77 ( year % 4 == 0 && year % 100 != 0 ) ) ) 78 return testDay;7980 cout << "Day " << testDay << " invalid. Set to day 1.\n";8182 return 1; // leave object in consistent state if bad value83 }
解構子被呼叫時、也會輸出一行文字
這是檢查日期是否合法的函式
21
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 ];
100 const Date birthDate;
101 const Date hireDate;
102};
103
104#endif
合成:有其他類別物件的成員,且是常數物件物件建構的順序與宣告的
順序相同。
firstname
lastname
Employee
101900
dayyear
Date birth
11
month 101900
dayyear
Date hire
11
month
22
105// Fig. 7.4: emply1.cpp106// Member function definitions for Employee class.107#include <iostream>108109using std::cout;110using std::endl;111112#include <cstring>113#include "emply1.h"114#include "date1.h"115116Employee::Employee( char *fname, char *lname,117 int bmonth, int bday, int byear,118 int hmonth, int hday, int hyear )119 : birthDate( bmonth, bday, byear ), 120 hireDate( hmonth, hday, hyear )121{122 // copy fname into firstName and be sure that it fits123 int length = strlen( fname );124 length = ( length < 25 ? length : 24 );125 strncpy( firstName, fname, length );126 firstName[ length ] = '\0';127128 // copy lname into lastName and be sure that it fits129 length = strlen( lname );130 length = ( length < 25 ? length : 24 );131 strncpy( lastName, lname, length );132 lastName[ length ] = '\0';133134 cout << "Employee object constructor: "135 << firstName << ' ' << lastName << endl;136}
建構子被呼叫時會輸出一行文字
要將兩個類別的 介面檔案都 include 進來
成員初值設定串列,兩個常數物件的初值設定
23
137
138void Employee::print() const
139{
140 cout << lastName << ", " << firstName << "\nHired: ";
141 hireDate.print();
142 cout << " Birth date: ";
143 birthDate.print();
144 cout << endl;
145}
146
147// Destructor: provided to confirm destruction order
148Employee::~Employee()
149{
150 cout << "Employee object destructor: "
151 << lastName << ", " << firstName << endl;
152}
print 函式是 const 且當 Date 物件被建立或解構時會執行,因為它是 const 函式,所以可以輸出 const 物件。
Print 不需要引數,它隱含地連結到呼叫它的物件
解構子被呼叫時會輸出一行文字
24
153// Fig. 7.4: fig07_04.cpp
154// Demonstrating composition: an object with member objects.
155#include <iostream>
156
157using std::cout;
158using std::endl;
159
160#include "emply1.h"
161
162int main()
163{
164 Employee e( "Bob", "Jones", 7, 24, 1949, 3, 12, 1988 );
165
166 cout << '\n';
167 e.print();
168
169 cout << "\nTest Date constructor with invalid values:\n";
170 Date d( 14, 35, 1994 ); // invalid Date values
171 cout << endl;
172 return 0;
173}
只有 emply.h 需要被載入,該檔案就有載入 date.h.
101949
day
year
Date birth(7,24,1949)
24
7 month10
1988day
year
Date hire(3,12,1988)
12
3 month
Bob
Jones
firstname
lastname
Employee manager( "Bob", "Jones", birth, hire )
birth hire
25
Date object constructor for date 7/24/1949Date object constructor for date 3/12/1988Employee object constructor: Bob Jones Jones, BobHired: 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/1994Employee object destructor: Jones, BobDate object destructor for date 3/12/1988Date object destructor for date 7/24/1949
注意內部的物件先被建立、且較慢被解構
26
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 寫在要授權的類別中之函式原型的前面,如:
friend int myFunction( int x );
這一行應出現在要授權的類別裡面– 宣告夥伴 (friend) 類別若 Classtwo 要成為 Classone 的夥伴類別,必須在 Classone 中宣告friend class ClassTwo;
29
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 的成員函式Count.
這裡宣告 setX 是 Count 的 friend ( 可存取 private 資料 ).
可以改變 Count 的 private 變數,但存取方式與 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: 0counter.x after call to setX friend function: 8
private 資料已被改變。
軟體工程的觀點 7.9
• 雖然夥伴函式的原型出現在類別的定義裡,但它仍然不是類別的成員函式。
31
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
Compiling...Fig07_06.cppD:\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
7.5 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
2. Function call
1 // Fig. 7.7: fig07_07.cpp 2 // Using the this pointer to refer to object members.3 #include <iostream>45 using std::cout;6 using std::endl;78 class Test {9 public:10 Test( int = 0 ); // default constructor11 void print() const;12 private:13 int x;14 };1516 Test::Test( int a ) { x = a; } // constructor1718 void Test::print() const // ( ) around *this required19 {20 cout << " x = " << x21 << "\n this->x = " << this->x22 << "\n(*this).x = " << ( *this ).x << endl;23 }2425 int main()26 {27 Test testObject( 12 );2829 testObject.print();3031 return 0;32 }
直接輸出 x
使用 this 指標與箭頭 (->) 運算子來輸出 x
用點 (.) 運算子輸出 x 。括號是必須的,因為點運算子的優先順序比 * 還高,沒寫括號的話就變成 *(this.x) 的意思,造成語法錯誤。
Test
this
12x
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
1 // Fig. 7.8: time6.h2 // Cascading member function calls.34 // Declaration of class Time.5 // Member functions defined in time6.cpp6 #ifndef TIME6_H7 #define TIME6_H89 class Time {10 public:11 Time( int = 0, int = 0, int = 0 ); // default constructor1213 // set functions14 Time &setTime( int, int, int ); // set hour, minute, second15 Time &setHour( int ); // set hour16 Time &setMinute( int ); // set minute17 Time &setSecond( int ); // set second1819 // get functions (normally declared const)20 int getHour() const; // return hour21 int getMinute() const; // return minute22 int getSecond() const; // return second2324 // print functions (normally declared const)25 void printMilitary() const; // print military time26 void printStandard() const; // print standard time27 private:28 int hour; // 0 - 2329 int minute; // 0 - 5930 int second; // 0 - 5931 };3233 #endif
注意函式傳回的資料型態是 Time & - 指向 Time 物件的參照 .
40
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
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 << ":"
94 << ( minute < 10 ? "0" : "" ) << minute;
傳回 *this 因此可以作連續的函式呼叫
42
95 }9697 // Display standard format time: HH:MM:SS AM (or PM)98 void Time::printStandard() const99 {100 cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 ) 101 << ":" << ( minute < 10 ? "0" : "" ) << minute 102 << ":" << ( second < 10 ? "0" : "" ) << second103 << ( hour < 12 ? " AM" : " PM" );104}105// Fig. 7.8: fig07_08.cpp106// Cascading member function calls together107// with the this pointer108#include <iostream>109110using std::cout;111using std::endl;112113#include "time6.h"114115int main()116{117 Time t;118119 t.setHour( 18 ).setMinute( 30 ).setSecond( 22 );120 cout << "Military time: ";121 t.printMilitary();122 cout << "\nStandard time: ";123 t.printStandard();124125 cout << "\n\nNew standard time: ";126 t.setTime( 20, 20, 20 ).printStandard();
注意這裡作連續函式呼叫
連續函式呼叫, printStandard 必須在 setTime 之後再呼叫,因為 printStandard 並沒有傳回指向物件的參照。
t.printStandard().setTime(); 若如此呼叫會造成語法錯誤。
printStandard 沒有傳回指向物件的參照
43
127 cout << endl;
128
129 return 0;
130}
Military time: 18:30Standard time: 6:30:22 PM New standard time: 8:20:20 PM
44
7.6 使用 new 和 delete 運算子的動態記憶體配置
• new 和 delete – 作動態記憶體配置之用, C 語言中也有類似功能的函式呼叫- malloc 與 free , C++ 的 new 與 delete 比較好。
– new• 建立適當大小的物件,呼叫其建構子並傳回正確型態的指標
– delete• 解構物件並釋放記憶體空間
– delete 的範例 delete typeNamePtr;
• 呼叫 TypeName 物件的解構子並釋放記憶體delete [] arrayPtr;
• 用來動態地殺掉一個陣列
45
7.6 使用 new 和 delete 運算子的動態記憶體配置
• new 的範例TypeName *typeNamePtr;
– 建立一個指向 TypeName 物件的指標typeNamePtr = new TypeName;
• new 建立 TypeName 物件,傳回指標 (存到 typeNamePtr 裡面 )
Time *timePtr;timePtr = new Time;
– 對物件作初值化double *ptr = new double( 3.14159 );Time *timePtr = new Time( 12, 0, 0 );
– c.f. : double v = 3.14159; ptr = &v;
timePtr
00 hour
minute
second0
Time( 0, 0, 0 );
timePtr
120
hourminute
second0
Time( 12, 0, 0 );
ptr3.14259
46
7.6 使用 new 和 delete 運算子的動態記憶體配置
• 對物件作初值化– 建立一 10 個元素的 int 陣列並設定給
arrayPtrint *gradesArray = new int[ 10 ];
• 用 new 建立物件時,會自動呼叫建構子;而 delete 會自動呼叫解構子。
gradesArray
0
0
0
0
0
0
0
0
0
0
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 變數內容,而不需改每個物件內容– 可視為類別範圍的全域變數
• 只能被同一個類別的物件存取– 用檔案範圍的方式作初值化– 就算沒有任何物件被宣告,此種成員也存在、可使用– 變數與函式都可被宣告為 static– 可設成 public, private 或 protected
49
7.7 static 類別成員• static 變數
– 可以透過類別名稱來存取 static 成員的值。– public static 變數
• 可以用範圍解析運算子 (::) 來存取 Employee::count
– private static 變數• 當沒有該類別的物件存在時,必須透過 public static 成員函式來存取。
– 用類別名稱加範圍解析運算子呼叫 public static 成員函式Employee::getCount()
– 一般成員函式或資料成員只能透過物件來呼叫,但是 static 資料成員或成員函式可以透過類別名稱來使用。
c2c1class C {int x;static int s;};C c1, c2;
C::sx x
50
7.7 static 類別成員• static 函式
– static 成員函式不可存取非 static 的資料或呼叫非 static 的函式。
– static 函式沒有 this 指標,他們的存在與物件無關。
51
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
static 成員函式與變數宣告
Employee object
Employee::count
firstName
lastName
52
25 // Fig. 7.9: employ1.cpp26 // Member function definitions for class Employee27 #include <iostream>2829 using std::cout;30 using std::endl;3132 #include <cstring>33 #include <cassert>34 #include "employ1.h"3536 // Initialize the static data member37 int Employee::count = 0;3839 // Define the static member function that40 // returns the number of employee objects instantiated.41 int Employee::getCount() { return count; }4243 // Constructor dynamically allocates space for the44 // first and last name and uses strcpy to copy45 // the first and last names into the object46 Employee::Employee( const char *first, const char *last )47 {48 firstName = new char[ strlen( first ) + 1 ];49 assert( firstName != 0 ); // ensure memory allocated50 strcpy( firstName, first );5152 lastName = new char[ strlen( last ) + 1 ];53 assert( lastName != 0 ); // ensure memory allocated54 strcpy( lastName, last );5556 ++count; // increment static count of employees
static 資料成員 count 和成員函式 getCount( ) 函式需用檔案範圍作初值化
注意 assert 用來測試 firstName != 0 這個式子若不成立 ( 等於記憶體配置失敗 ) ,就輸出錯誤訊息,並呼叫 abort() 來作異常結束。
當建構子或解構子被呼叫時就改變 static 資料成員 count 的內容
53
57 cout << "Employee constructor for " << firstName
58 << ' ' << lastName << " called." << endl;
59 }
60
61 // Destructor deallocates dynamically allocated memory
62 Employee::~Employee()
63 {
64 cout << "~Employee() called for " << firstName
65 << ' ' << lastName << endl;
66 delete [] firstName; // recapture memory
67 delete [] lastName; // recapture memory
68 --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
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 "
100 << Employee::getCount() << endl; // use class name
101
102 Employee *e1Ptr = new Employee( "Susan", "Baker" );
103 Employee *e2Ptr = new Employee( "Robert", "Jones" );
104
105 cout << "Number of employees after instantiation is "
106 << e1Ptr->getCount();
107
108 cout << "\n\nEmployee 1: "
109 << e1Ptr->getFirstName()
110 << " " << e1Ptr->getLastName()
111 << "\nEmployee 2: "
112 << e2Ptr->getFirstName()
113 << " " << e2Ptr->getLastName() << "\n\n";
114
115 delete e1Ptr; // recapture memory
116 e1Ptr = 0;
117 delete e2Ptr; // recapture memory
118 e2Ptr = 0;
沒有 Employee 物件存在時 getCount 必須用類別名稱加 (::) 來存取。
count 增加 1, 因為 new 使建構子被呼叫
Employee::count
0
Employee *e2Ptr
Robert
firstName
Jones
lastName
Susan
firstName
Baker
lastName
Employee *e1Ptr
12
Employee *e1Ptr
0
0
Employee *e2Ptr
55
119
120 cout << "Number of employees after deletion is "
121 << Employee::getCount() << endl;
122
123 return 0;
124}
Number of employees before instantiation is 0Employee constructor for Susan Baker called.Employee constructor for Robert Jones called.Number of employees after instantiation is 2 Employee 1: Susan BakerEmployee 2: Robert Jones ~Employee() called for Susan Baker~Employee() called for Robert JonesNumber of employees after deletion is 0
最後 count 變成 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 資料抽象化與資訊隱藏
• 資訊隱藏– 類別將實作細節對客戶端隱藏起來– 範例:堆疊資料結構
• 資料從上面加入 (push) 、也從上面移除 (pop)• 後進先出 Last-in, first-out (LIFO) 的資料結構• 客戶端不需注意堆疊如何被實作,只需關心它是有提供堆疊功能的資料結構即可。
58
7.8 資料抽象化與資訊隱藏• 抽象資料型態 Abstract data types (ADTs)
– 包含:資料表示方式與運算– 用來模擬真實世界的物件,類別的功用與實作方式無關
• int, float 模擬數字的概念。
• C++ 是可擴展的語言– 標準的資料型態不能改變,但可建立新的資料型態。
• 軟體工程觀察 7.15: 程式設計者可透過類別的機制來建立新的型態,這些新的型態可以與內建的型態同樣方便使用,因此 C++ 是可擴展的程式語言,雖然 C++ 容易擴展 ( 建立新的型態 ) ,但它的基本觀念沒有改變。
59
7.8.1 範例:陣列抽象資料型態• 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
– 就像排隊 (超市、加油站、公車站、投票、餐廳、… )• FIFO — First in, first out( 先進先出 )
– 存入佇列 enqueue• 每次從後面加一個資料到佇列中
– 取出佇列 dequeue• 每次從佇列前面取出一個資料
– 實作方式對客戶端隱藏• 佇列的抽象資料型態
– 客戶端不直接操作資料結構– 只有佇列的成員函式才存取內部資料– 第十五章會討論佇列的資料結構
62
7.9 容器類別與迭代子 (iterators)
• 容器 (container) 類別,或稱集合 (collection) 類別– 設計來存放物作集合的類別– 提供像:插入、刪除、搜尋、排序、測試某元素是否屬在該容器的測試函式
– 範例:陣列、堆疊、佇列、樹、鏈結串列
• 迭代子物件 ( 簡稱迭代子 iterators)– 用來傳回容器中下一個項目的物件 ( 或對下個項目執行一些動作 )
– 每個容器可以有好幾個迭代子• 就像很多人一起讀一本書時,書中會夾好幾張書籤一樣
– 每個迭代子都有存有自己「位置」的資訊– 二十章會有更多討論
63
7.10 代理類別 proxy class• 代理類別
– 用來將類別實作細節完全隱藏起來– 只讓客戶端看到 interface 的資料,看不到類別中有哪些 private 資料。
– 使客戶端使用類別提供的服務,而無法存取類別實作。• 先置類別宣告 Forward class declaration
– 當類別定義中只用指標指到另一個類別時– 避免將另一個類別的標頭檔載入– 在使用到這個類別之前就先宣告– 格式:
class ClassToLoad;
64
1 // Fig. 7.10: implementation.h
2 // Header file for class Implementation
3
4 class Implementation {
5 public:
6 Implementation( int v ) { value = v; }
7 void setValue( int v ) { value = v; }
8 int getValue() const { return value; }
9
10 private:
11 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:
19 Interface( int );
20 void setValue( int ); // same public interface as
21 int getValue() const; // class Implementation
22 ~Interface();
23 private:
24 Implementation *ptr; // requires previous
25 // forward declaration
26 };
Implementation 中要隱藏的 private 資料 (value)
Forward class declaration先置類別宣告 .
代理類別 Interface 的公開介面幾乎與 Implementation 完全一樣
只用指標指到類別 Implementation. 這樣允許我們將實作細節隱藏起來。
65
27 // Fig. 7.10: interface.cpp28 // Definition of class Interface29 #include "interface.h"30 #include "implementation.h"3132 Interface::Interface( int v ) 33 : ptr ( new Implementation( v ) ) { }3435 // call Implementation's setValue function36 void Interface::setValue( int v ) { ptr->setValue( v ); }3738 // call Implementation's getValue function39 int Interface::getValue() const { return ptr->getValue(); }4041 Interface::~Interface() { delete ptr; }42 // Fig. 7.10: fig07_10.cpp43 // Hiding a class’s private data with a proxy class.44 #include <iostream>4546 using std::cout;47 using std::endl;4849 #include "interface.h"5051 int main()52 {53 Interface i( 5 );54 55 cout << "Interface contains: " << i.getValue() 56 << " before setValue" << endl;57 i.setValue( 10 );58 cout << "Interface contains: " << i.getValue() 59 << " 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 setValInterface contains: 10 after setVal