update:2016/11/5
(一)對象和類
1.對象和類的認識
1.對象
對象(object)是問題域中某些事物的抽象,是具有唯一標識的某些屬性、操作和方法的封裝體。
對象之間通過消息(message)進行通信。
消息是從一個對象(發送者)向另一個或幾個對象(接收者)發送信號,或者由一個對象(調用者)調用另一個對象(接受者)的操作。某項任務通常需要多個對象協作完成,而多個對象之間的協作是通過消息傳遞來完成的。稱爲了完成一個特定任務而在對象間產生的一組消息稱爲一個消息序列。
在面向對象的程序設計中,消息可以通過函數調用、程序間內部通信、事件的發生等進行實現。對象具有生命週期(lifetime)。
生命週期是指對象從創建、活動到刪除的過程。在計算機中,對象生命週期中的狀態主要由其所佔用的存儲空間是否有效爲標誌:
對象的創建是指在內存中爲其對象分配空間;
對象的活動是指對象能自主地運行,並且可以接收消息加以處理,通過處理外來消息改變自身狀態,並且向其他對象發送消息等;
對象的刪除是指當對象的使命完成後,可以將內存中其佔用的存儲空間置爲無效並回收。
2.類
類(class)是一組客觀對象的抽象,是一組具有共同的靜態特徵和行爲特徵的對象集合。
- 類是對象的本質,對象是類的實例。
對於同一類的不同實例之間,具有如下特點:①相同的屬性;②相同的操作和方法;③不同的對象名;④不同的屬性值。
3.屬性、操作和方法
類和對象都包含了屬性、操作和方法。
屬性(attribute)是類和對象中的靜態特徵。在面向對象程序中用一個數據單元來表示屬性。屬性可以用屬性名、類型、可修改性、多重性、可見性描述,屬性可以有初始值。對於可以修改的屬性,可以改變其屬性值。
操作(operation)是對象執行某種功能的規格說明。
方法(method)是操作的實現,是說明生成操作結果的算法或過程,通常用函數體或過程體來表示方法。
消息可以是調用者對被調用者操作的調用。被調用者在接收到調用者傳遞的消息時,激活操作相應的方法。執行終了,調用者收回控制,並獲得方法處理的結果。
2.面向對象方法的基本特徵
面向對象(object-orientation programming,OOP)的基本特徵是封裝、繼承和多態。
1.封裝
封裝(encapsulation)就是將屬性和操作封裝在對象中並和外部區隔開來。在面向對象編程中,對象是一種自治、封裝的實體,對象之間通過接口進行消息傳遞。
封裝給程序設計帶來如下的好處:
- 有效控制某個對象內部發生變化時對其他對象的影響。
- 通過對象的接口簡化對象的使用,保持程序結構的穩定。
- 便於對數據和功能的複用。
2.泛化和繼承
泛化(generalization)是一般類和特殊類之間的層次關係。
- 一般類也稱爲基類(base class)或父類(super class);特殊類稱爲派生類(derived class)或子類(subclass)。派生類建立在基類的基礎之上,繼承(inherit)基類所有的屬性、操作,並對其進行擴展。泛化有時也叫做“is-a”關係,因爲派生類的實例也可看成(is a)基類的實例。
繼承(inheritance)是類間數據和操作的共享。
- 繼承按照派生類繼承基類的個數劃分,可以分爲單繼承和多繼承;按照繼承的內容來分,有實現繼承(繼承基類全部的屬性、操作和方法)和接口繼承(只繼承基類的操作)。
泛化和繼承是從不同的角度認識類之間層次。
泛化表達了派生類“就是”基類的語義關係,也就是說在程序中可以用派生類來代替基類,這是多態的基礎;繼承描述了類之間的共享機制,也就是說派生類可以“重用”基類的屬性、操作和方法。
在泛化關係之上應用繼承機制是OOP最主要的特徵,這帶來了如下的好處:
- 可以實現類的分解,從而形成穩定而易於理解的程序結構;
- 實現代碼的共享和重用;
- 支持多態。
3.多態
多態(polymorphism)是指具有泛化和繼承關係的對象接收同一消息時可以有不同行爲。多態提供了不同對象的同一類操作的相同接口。
(二)類
我們將要試着編寫一個time類,用來實現對於時鐘上的時間的抽象,在整篇文章中我們將要不斷豐富直至完成time類。
1.類的定義
1.類的設計
由於類是由類的屬性、操作及方法組成的,因此對於類的定義就是對類的屬性、操作的定義。屬性使用變量描述,操作用函數描述。變量稱爲數據成員,函數則成爲成員函數。定義類採用如下的代碼:
class className
{
blablabla……//declare the class member
}
一般來說在定義一個類時,會將其分爲類的聲明(聲明數據成員和成員函數)和類的實現(定義成員函數)兩部分,因此,定義類可以細化爲如下格式:
class className
{
<declaration of data member>
<declaration of member function>
};
<definition of member function>
2.this
指針
2.類的封裝
按照上述的定義格式我們完成了time
類 。
/* class time 1.0
*
*
*/
class time
{
int h,m,s;
enum method
{
_24Hours,
_12Hours
};
time add(const time&,const time&);
int time2second(const time&);
void iniTime(time*);
void freeTime(time*);
istream &read(istream&,time &);
ostream &print(ostream&,time &);
};
time time::add(const time&a,const time &b){……}
int time::time2second(const time &item){……}
void time::iniTime(time* ptr){……}
void time::freeTime(time* ptr){……}
istream &read(istream& is,time & item){……}
ostream &print(ostream& os,time & item){……}
我們可以發現並沒有實現對於time
類的封裝:在我們的class time v1.0
中,數據成員和所有的操作都是處於同一層級,與此同時,並沒有明確哪些操作是time
類的接口,哪些操作是time
類的實現。這樣一來,我們的time
類對於用戶來說沒有明晰的層次,用戶可以深入到類的最底層進行操作而不是使用接口,這是危險的。
1.訪問說明符
對於類的成員來說,訪問說明符(access specifiers)帶來了三級存取,分別是:公共(public)、保護(protected)和私有(private),通過訪問說明符可以爲類的數據成員和成員函數進行封裝。
public
說明符後的成員可以被任何函數(代碼)訪問。 使用public
的成員定義類的接口。protected
說明符後的成員可以被類的成員函數、類的friend
函數以及類的派生類對象訪問。使用protected
的成員實現類的泛化關係。private
說明符後的成員可以被類的成員函數或是被聲明爲類的friend
的函數訪問,但不能被使用類的代碼或是類的派生類訪問。使用private
的成員封裝類的實現細節。
2.使用class
或struct
關鍵字
在定義類時可以採用class
或struct
關鍵字,二者的區別在於默認訪問權限不同:class
的默認訪問權限爲private
,struct
的默認訪問權限爲public
。
3.友元
在定義類時,常常還需要一些輔助函數用來進行類的輸入、輸出。考慮到一個類的封裝需求,我們儘量少的定義類的成員函數,以增強類的隱蔽性。但是這些函數需要訪問private
的數據成員,因此採用關鍵字friend
,令這些輔助函數作爲類的友元(friend)成爲類的非成員接口函數,訪問類的非公有成員。
3.構造函數和析構函數
1.構造函數
任何一個類都定義了其對象被初始化的方式:類通過一個或幾個特殊的成員函數,我們稱之爲構造函數(constructor)來完成這一件事。
構造函數(constructor)是在定義函數時被編譯器自動調用用來完成對象的創建及初始化的函數。其具有一些特殊性質:
- 在沒有聲明任何構造函數時,編譯器會自動生成默認構造函數(default constructor);
- 構造函數的名字和類名相同;
- 構造函數沒有返回類型;
- 構造函數不能被聲明爲
const
的,在構造函數構造一個const
對象時可以向其寫入值; - 構造函數是
public
函數,但除了在定義函數 - 一個類可以包含多個構造函數,但要求其必須在參數的類型(或數量)上有所區別。
一個典型的構造函數定義如下:
class className
{
pravite:
//類含有兩個數據成員
int itemName1=0;
double itemName2=0.0;
//空參數列表,默認構造函數
className()=default;
//兩個含參數的構造函數
className(int classItem1):itemName1(classItem1) { }
className(int classItem1,double classItem2):itemName1(classItem1), itemName(classItem2) { }
}
1.1 默認構造函數
默認構造函數(default constructor)在類對象執行默認初始化時控制其進程的構造函數。它具有如下特徵:
- 默認構造函數無需任何實參;
- 如果類沒有顯式地定義構造函數,則由編譯器隱式地定義一個默認構造函數,又稱爲合成的默認構造函數(synthesized default constructor);
- 對於大多數類,將按照如下規則初始化類的數據成員:
- 如果存在類內的初始值,則用其初始化數據成員。
- 否則,默認初始化該成員,類中的內置類型或複合類型對象在默認初始化後的值仍將是未定義的。
- 一旦類內顯式定義了其它的構造函數,那麼除非再定義一個默認構造函數,否則類將不含有默認構造函數。
對於某些類而言,不能依賴於合成的默認構造函數,其原因是多樣的:
- A
- B
C
如果我們需要定義一個默認構造函數,可以採用如下的語句:className()=default;
1.2 構造函數
在類的定義中,除了默認構造函數以外,我們還有另外兩個構造函數:
className(int classItem1):itemName1(classItem1){}
className(int classItem1,double classItem2):itemName1(classItem1),itemName(classItem2){}
定義中出現了新的部分,也就是在函數名和函數體之間的部分:itemName1
,稱其爲構造函數初始值列表(constructor initialize list),它爲新創建的對象的某些數據成員初始化(執行了列表初始化)。
構造函數初始值列表是成員名字的列表,每個名字後緊跟一個括號或花括號內的成員初始值,不同成員的初始化以逗號分隔開來,即:
itemName1(initialValue1),itemName2(initialValue2)
itemName3{initialValue3},itemName4{initialValue4}
值得指出的是,當函數初始值列表未包含全部的數據成員時,未包含進去的數據成員將採用與合成默認構造函數相同的方式對其隱式地初始化。
除此之外,上面的兩個函數體都是空的, 這是因爲構造函數的唯一目的是爲數據成員初始化,一旦沒有其他的任務需要執行,函數體也就爲空了。