?? 06章 類與數(shù)據(jù)抽象(一).txt
字號:
C++大學教程(第6章 類與數(shù)據(jù)抽象(一)-01)
教學目標
●了解封裝與數(shù)據(jù)隱藏的軟件工程概念
●了解數(shù)據(jù)抽象和抽象數(shù)據(jù)類型(ADT)的符號
●生成C++的ADT(即類)
●了解如何生成、使用和刪除類對象
●控制對象數(shù)據(jù)成員和成員函數(shù)的訪問
●開始認識面向對象的價值
6.1 簡介
下面開始介紹C++中的面向對象。為什么把C++中的面向對象推遲到第6章才開始介紹呢?原因是我們要建立的對象是由各個結構化程序組件構成,因此先要建立結構化程序的基礎知識。
在第1章到第5章的“有關對象的思考”小節(jié)中,我們介紹了C++中的面向對象編程的基本概念(即“對象思想”)和術語(即”對象語言”)。在這些”有關對象的思考”小節(jié)中,介紹了面向對象設計(object-orienteddesign,OOD)的方法:我們分析了典型問題的陳述,要求建立一個系統(tǒng)(電梯模擬程序),確定實現(xiàn)該系統(tǒng)所需的類,確定這些類對象的屬性,確定這些類對象的行為,指定對象之間如何通過交互以完成系統(tǒng)的總體目標。
下面簡要介紹面向對象的一些關鍵概念和術語。OOP將數(shù)據(jù)(屬性)和函數(shù)(行為)封裝(encapsulate)到稱為類(class)的軟件包中,類的數(shù)據(jù)和成員是密切聯(lián)系的。就像藍圖,建筑人員通過藍圖建造房子,而程序雖則通過類生成對象。一個藍圖可以多次復用.建造多幢房子;一個類也可以多次夏用,建立多個對象。類具有信息隱藏(information hiding)屬性,即類對象只知道如何通過定義良好的接口(interface)與其他類對象通信,但通常不知道其他類的實現(xiàn)方法,實現(xiàn)細節(jié)隱藏在類中。我們可以熟練地開車,而不需要知道發(fā)動機、傳遞系統(tǒng)和燃油系統(tǒng)的工作原理。我們可以看到信息隱藏對良好的軟件工程是多么重要。
在C語言和其他過程化編程語言(proceduralprogramminglanguage)中,編程是面向操作的(action-oriented),而在C++中,編程是面向對象的(object-oriented)。在C語言中,編程的單位是函數(shù)(function),而在C++中.編程的單位是類(class),對象最終要通過類實例化。
C語言程序員的主要工作是編寫函數(shù),完成某個任務的一組操作構成函數(shù),函數(shù)的組合則構成程序。數(shù)據(jù)在C語言中當然很重要,但這些數(shù)據(jù)只用于支持函數(shù)所要進行的操作。系統(tǒng)指定中的動詞幫助C語言程序員確定一組用于實現(xiàn)系統(tǒng)的函數(shù)。
C++程序員把重點放在生成稱為類的用戶自定義類型(user-definedtype),類也稱為程序員定義類型(programmer-defined type)。每個類包含數(shù)據(jù)和操作數(shù)據(jù)的一組函數(shù)。類的數(shù)據(jù)部分稱為數(shù)據(jù)成員(data member)。類的函數(shù)部分稱為成員函數(shù)(member function,有些面向對象語言中也稱為方法)。int等內部類型的實例稱為變量(variable),而用戶自定義類型(即類)的實例則稱為對象(object)。在C++中,變量與對象常常互換使用,C++的重點是類而不是函數(shù)。系統(tǒng)指定中的名詞幫助C++程序員確定實現(xiàn)系統(tǒng)所需的用來生成對象的一組類。
C++中的類是由C語言中的struct演變而來的。介紹C++類的開發(fā)之前.我們先使用結構建立用戶自定義類型,通過介紹這種方法的缺點從而說明類的優(yōu)點。
6.2 結構定義
結構是用其他類型的元素建立的聚合數(shù)據(jù)類型。考慮下列結構定義:
struct Time {
int hour; // 0-23
int minute; // 0-59
int second; // 0-59
};
結構定義用關鍵字struct引入。標識符Time是個結構標志(structure tag),命名結構定義并聲明該結構類型(structure type)的變量。本例中,新類型名為Time。結構定義花括號中聲明的名稱是結構的成員(member)。同一結構的成員應有惟一名稱.但兩個不同結構可以包含同名成員而不會發(fā)生沖突。每個結構定義應以分號結尾。上述解釋對后面要介紹的類也適用,C++中的結構和類是非常相似的。
Time的定義包含三個int類型的成員hour、minute和second。結構成員可以是任何類型,一個結構可以包含不同類型的成員。但是,結構不能包含自身的實例。例如,Time類型的成員不能在Time的結構定義中聲明,但該結構定義中可以包含另一Time結構的指針。當結構包含同一類型結構的指針時,稱為自引用結構(self-referential structure)。自引用結構用于形成鏈接數(shù)據(jù)結構,如鏈表、隊列、堆棧和樹等(見第15章介紹)。
上述結構定義并沒有在內存中保留任何空間,而是生成新的數(shù)據(jù)類型,用于聲明變量。結構變量和其他類型的變量一樣聲明。下列聲明:
Time timeObject,timeArray[10] ,*timePtr.
&timeRef=timeobject;
聲明timeObject為Time類型變量,timeArray為10個Time類型元素的數(shù)組,timePtr為Time對象的指針,timeRef為Time對象的引用(用timeObject初始化)。
6.3 訪問結構成員
訪問結構成員或類成員時,使用成員訪問運算符(member access operator),包括圓點運算符(.)和箭頭運算符(—>)。圓點運算符通過對象的變量名或對象的引用訪問結構和類成員。例如,要打印timeObject結構的hour成員,用下列語句:
cout<<timeobject.hour;
要打印timeRef引用的結構的hour成員,用下列語句:
cout << timeRef.hour;
箭頭運算符由負號(—>和大于號(>)組成,中間不能插空格,通過對象指針訪問結構和類成員。假沒指針timePtr聲明為指向Time對象,結構timeObject的地址賦給timePtr。要打印指針為timePtr的timeObjeet結構的hour成員,用下列語句:
tzmePtr=&timeObject;
cout<<timePtr->hour;
表達式timePtr->hour等價于(*timePtr).hour,后者復引用指針并用圓點運算符訪問hour成員。
這里的括號是必需的,因為圓點運算符的優(yōu)先級高于復引用指針運算符(*)箭頭運算符和圓點運算符以及括號與方括號([])的優(yōu)先級較高,僅次于第3章介紹的作用域運算符,結合律為從左向右。
常見編程錯誤6.1
表達式(*timePtr).hour指timePtr所指struct的hour成員。省略括號的*timePtr.hour是個語法錯誤,因為“.”
的優(yōu)先級高于“*”,表達式變成*(timePtrhour)。這是個語法錯誤,因為指針要用箭頭運算符引用成員。
6.4 用struct實現(xiàn)用戶自定義類型Time
圖6.1生成用戶自定義類型Time,有三個整數(shù)成員hour、minute和second。程序定義一個Time類型的結構dinnerTime,并用圓點運算符初始化結構成員hour、minute和second的值分別為18、30和0。然后程序按軍用格式(或所謂“通用格式”)和標準格式打印時間。注意打印函數(shù)接收常量Time結構的引用,從而通過引用將Time類型的結構傳遞給打印函數(shù),避免了按值傳人打印函數(shù)所涉及的復制開銷.并用const防止打印函數(shù)修改Time結構。第7章將介紹const對象與const成員函數(shù)。
1 // Fig. 6.1: fig0601.cpp
2 // Create a structure, set its members, and print it.
3 #include <iostream.h>
5 struct Time { // structure definition
6 int hour; // 0-23
7 int minute; // 0-59
8 int second; // 0-59
9 };
10
11 void printMilitary( const Time & ); // prototype
12 void printStandard( const Time & ); // prototype
13
14 int main()
15 (
16 Time dinnerTime; // variable of new type Time
17
18 // set members to valid values
19 dinnerTime.hour = 18;
20 dinnerTime.minute = 30;
21 dinnerTime.second = O;
22
23 cout << "Dinner will be held at ";
24 printMilitary( dinnerTime );
25 cout << " military time, \nwhich is ";
26 printStandard( dinnerTime );
27 cout << "standard time.\n";
28
29 // set members to invalid values
30 dinnerTime.hour = 29;
31 dinnerTime.minute = 73;
32
33 cout << "\nTime with invalid values: ";
34 printMilitary( dinnerTime );
35 cout << endl;
36 return 0;
37 }
38
39 // Print the time in military format
40 void printMilitary( const Time &t )
41 {
42 cout << ( t.hour < 10? "0" : "" ) << t.hour << ":"
43 << ( t.minute < 10? "0" : "" ) << t.minute;
44 }
45
46 // Print the time in standard format
47 void printStandard( const Time &t )
48 {
49 cout << ( ( t.hour == 0 || t.hour == 12 ) ?
50 12 : t.hour % 12 )
51 << ":" << ( t.minute < 10 ? "0" : "" ) << t.minute
52 << ":" << ( t.second < 10? "0" : "" ) << t.second
53 << ( t.hour < 12 ? "AM" : "PM" );
54 }
輸出結果:
Dinner will be held at 18:30 military time,
which is 6:30:00 PM standard time.
Time with invalid values: 29:73
圖6.1 生成結構、設置結構成員和打印該結構
性能提示6.1
結構通常按值調用傳遞。要避免復制結構的開銷,可以按引用調用傳遞結構。
軟件工程視點6.2
要避免按值調用傳遞的開銷而且保護調用者的原始數(shù)據(jù)不被修改.可以將長度很大的參數(shù)作為const引用
傳遞。
用這種方式通過結構生成新數(shù)據(jù)類型有一定的缺點。由于初始化并不是必須的,因此就可能出現(xiàn)未初始化的數(shù)據(jù),從而造成不良后果。即使數(shù)據(jù)已經初始化,也可能沒有正確地初始化。因為程序能夠直接訪問數(shù)據(jù),所以無效數(shù)據(jù)可能賦給結構成員(如圖6.1)。在第30行和第31行,程序很容易向Time對象dinnerTime的hour和minute成員傳遞錯值。如果struct的實現(xiàn)方法改變(例如時間可以表示為從午夜算起的秒數(shù)),則所有使用這個struct的程序都要改變。這是因為程序員直接操作數(shù)據(jù)類型。沒有一個”接口”保證程序員正確使用數(shù)據(jù)類型并保持數(shù)據(jù)的一致狀態(tài)。
一定要編寫易于理解和易于維護的程序。不斷改變是規(guī)則而不走例外。程序員應預料到代碼要經常改變。
可以看出,類能夠提高程序的可修改性。
還有其他與C語言式結構相關的問題。在C語言中,結構不能作為一個單位打印,而要一次一個地打印和格式化結構成員。可以編寫一個函數(shù),以某種格式打印結構成員。第8章”運算符重載”中演示了如何重載<<運算符,使結構類型或類類型的對象能夠方便地打印。在C語言中,結構不能整體進行比較,而只能一個成員一個成員地比較。第8章還會演示如何重載相等運算符與關系運算符,比較C++結構類型或類類型的對象。
下一節(jié)重新將Time結構實現(xiàn)為C++類,并演示用類生成抽象數(shù)據(jù)類型(abstract data type)的好處。從中將會看到,C++中類和結構的用法基本相同,差別在于各自的成員相關的默認訪問能力不同。
6.5 用類實現(xiàn)Time抽象數(shù)據(jù)類
型
類使程序員可以構造對象的屬性(attribute,表示為數(shù)據(jù)成員)和行為(behavior)或操作(operation,表示為成員函數(shù))。C++中用關鍵字class定義包含數(shù)據(jù)成員和成員函數(shù)的類型。
成員函數(shù)在有些面向對象編程語言中也稱為方法(method),響應對象接收的消息(message)。消息對應于一個對象發(fā)給另一個對象或由函數(shù)發(fā)給對象的成員函數(shù)調用。
一旦定義了一個類,可以用類名聲明該類的對象。圖6.2顯示了Time類的簡單定義。
Time類定義以關鍵字class開始。類定義體放在左右花括號(C1)之間,類定義用分號終止。Time類定義和Time類結構定義各包含三個整型成員hour、minute和second。
1 class Time {
2 public:
3 Time();
4 void setTime( int, int, int);
5 void printMilitary();
6 void printStandard();
7 private:
8 int hour; // 0-23
9 int minute; // 0-59
10 int second; // 0-59
11 };
圖6. 2 Time類的簡單定義
常見編程錯誤6.2
忘記類或結構定義結束時的分號是個語法錯誤。
類定義的其他部分是新內容。public:和private:標號稱為成員訪問說明符(member accessspecifier)。在程序能訪問Time類對象的任何地方都可以訪問任何在成員訪問說明符public后面(和下一個成員訪問說明符之前)聲明的數(shù)據(jù)成員和成員函數(shù)。成員訪問說明符private后面(和下一個成員訪問說明符之前)聲明的數(shù)據(jù)成員和成員函數(shù)只能由該類的成員函數(shù)訪問。成員訪問說明符總是加上冒號,可以在類定義中按任何順序多次出現(xiàn)。本文余下部分使用不帶冒號的成員訪問說明符public和private。第9章還將介紹另一個成員訪問說明符protected,并介紹繼承及其在面向對象編
程中的作用。
編程技巧6.1
每個成員訪問說明符只在類定義中使用一次,這樣可以增加清晰性與可讀性。將public成員放在前面,便
于尋找。
類定義中的訪問說明符public后面是成員函數(shù)Time、setTime、printMihtary和printStandard的函數(shù)原型。這些函數(shù)是類的public成員函數(shù)(或public服務、public行為、類的接口)。類的客戶(client,即程序中的用戶部分)使用這些函數(shù)操作該類的數(shù)據(jù)。
注意與類名相同的成員函數(shù),稱為該類的構造函數(shù)(constructor)。構造函數(shù)是個特殊成員函數(shù),該函數(shù)初始化類對象的數(shù)據(jù)成員。類的構造函數(shù)在生成這個類的對象時自動調用。一個類常常有幾個構造函數(shù),這是通過函數(shù)重載完成的。注意,構造函數(shù)不指定返回類型。
常見編程錯誤6.3
對構造函數(shù)指定返回類型或返回值是個語法錯誤。
成員訪問說明符private后面有三個整型成員,表示類的這些數(shù)據(jù)成員只能讓成員函數(shù)訪問(下一章會介紹還可由類的友元訪問)。這樣,數(shù)據(jù)成員只能由類定義中出現(xiàn)函數(shù)原型的4個函數(shù)(和類的友元)訪問。數(shù)據(jù)成員通常放在類的private部分,成員函數(shù)通常放在Public部分。稍后會介紹,也可以用private成員函數(shù)和public數(shù)據(jù),但比較少見,這不是好的編程習慣。
定義類之后,可以在聲明中將其當作類型,如下所示:
Time sunset, // object of type Time
arrayOfTimes[ 5 ], // array of Time objects
*pointerToTime, // pointer to a Time object
&dinnerTime = sunset; // reference to a Time object
類名成為新的類型說明符。一個類可以有多個對象,就像int類型的變量可以有多個。程序員可以在需要時生成新的類類型,因此C++是個可擴展語言(exlensible language)。
圖6.3使用Time類。程序實例化Time類的一個對象t。當對象實例化時,Time構造函數(shù)自動調用,顯式地將每個private數(shù)據(jù)成員初始化為0。然后按軍用格式和標準格式打印時間,確保成員已經正確地初始化。然后用setTime成員函數(shù)設置時間,并再次按兩種格式打印時間。接著用setTime成員函數(shù)設置時間為無效值.并再次按兩種格式打印時間。
1 // Fig. 6.3: fig06_03.cpp
2 // Time class.
3 #include <iostream.h>
4
5 // Time abstract data type (ADT) definition
6 class Time {
7 public:
8 Time(); // Constructor
9 void setTime( int, int, int ); // set hour, minute, second
10 void printMilitary(); // print military time format
11 void printStandard(); // print standard time format
12 private:
13 int hour; // 0 - 23
14 int minute; // 0 - 59
15 int second; // 0 - 59
16 };
17
18 // Time tructor initiali ...... h data membertt~tzer~'-st t
19 // Ensures all Time objects start in a conchs en s a e.
21
22 // Set a new Time value using military time. Perform validity
25 {
26 hour=e ( h >= 0 && h < 24 ) ? h : 0;
minut = ( m >= 0 && m < 60 ) ? m : 0;
28 second = ( s >= 0 && s < 60 ) ? s : 0;
29}
31 // Print Time in military format
32 void Time::printMilitary()
35 << ( minute < 10 ? "0" : "" ) << minute;
37
38 // Print Time in standard format
39 void Time::printStandard()
4O {
41 cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 )
42 << ":" << ( minute < 10 ? "0" : .... ) << mlnu e
43 << ":" << ( second < 10 ? "0" : "" ) << second
44 << ( hour < 12 ? "AM" : "PM" );
45 }
46
47 // Driver)trna (in ...... imple class Time
48 int main()
49 {
50 Time t; // instantiate object t of class Time
51
52 cout << "The initial military time is ";
53 t.printMilitary();
54 cout << "\nThe initial standard time is ";
55 t.printStandard();
56
57 t.setTime( 13, 27, 6 );
58 cout << "\n\nMilitary time after setTime is ";
59 t.printMilitary();
60 cout << "\nStandard time after setTime is ";
61 t.printStandard();
62
63 t.setTime( 99, 99, 99 ); // attempt invalid settings
64 cout << "\n\nAfter attempting invalid settings:"
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -