C++面向物件- -類和物件的使用(一)
這部分算是正式接觸了類和物件,涉及到它們基礎的應用。
目錄
建構函式對類物件進行初始化
當物件在建立時獲得了一個特定的值,我們說這個物件被初始化。初始化不是賦值,初始化的含義是建立變數賦予其一個初始值,而賦值的含義是把當前值擦除,而以一個新值來替代。在組合語言中,為變數分配空間時,初始化過的變數的初值位於可執行檔案程式碼段資料後,會佔用一定空間,不必要的初始化會造成磁碟空間的浪費。而在C/C++語言等高階語言中,為每一個變數賦初值被視為良好的程式設計習慣,有助於減少出現Bug的可能性。因此,是否對不必要的變數初始化依情況而定。初始化時是按照資料成員在類裡被宣告的順序
1、物件的初始化
物件的初始化和之前的那些定義變數時的初始化不一樣,它不能在類宣告中對資料進行初始化,因為類並不是一個實體,而是一種抽象型別,不佔用儲存空間,無法容納資料。
class Time{
int hour=0;
int min=0;
int sec=0;
};
如果一個類中所有的資料成員都是公用的,那麼在定義物件時可以對資料成員進行初始化。但一般來說類中的資料成員都是私有的,因為要保持資訊隱蔽的特性,所以這種方法基本上不會有。
class Time{ public: int hour ; int min ; int sec ; }; Time t= {0,0,0}; //此處注意是中括號,和後邊建構函式初始化的表示不一樣。
2、建構函式實現資料成員的初始化
建構函式是一種特殊的成員函式,與其它成員函式不同,不需要使用者呼叫,也不能被使用者呼叫,而是在建立物件時自動執行。建構函式的名字必須是與類名一樣,不能任意命名,以便系統能識別它作為建構函式處理。且不具有任何型別,不返回任何值,只是對物件進行初始化。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(){
hour=0;
min=0;
sec=0;
}
};
在主函式執行時,一旦類建立物件,該建構函式將會自動執行,執行建構函式的過程中對 物件 中的資料成員賦初值,賦值在建構函式體中實現。另外還可以在類內僅宣告建構函式,在類外對建構函式進行定義
class Time{
int hour ;
int min ;
int sec ;
public:
Time();
};
Time :: Time(){
hour=0;
min=0;
sec=0;
}
當類建立物件時,系統開始為這個物件分配儲存單元,此時便執行建構函式,每建立一個物件,就呼叫一個建構函式。建構函式是定義物件時由系統自動執行的,而且只能執行一次,建構函式一般宣告為 public 。另外還可以用一個類物件對另一個類物件進行初始化。
Time t1 ;
Time t2=t1 ;
在建構函式的函式體中可以有其他語句,像 “cout···”等語句,但一般不提倡在建構函式中加入與初始化無關的內容,以保持程式的清晰。如果使用者自己沒有定義建構函式,系統會自動生成一個建構函式,只是這個建構函式體是空的,也沒有任何引數,不執行任何初始化操作。
3、帶引數的建構函式
在呼叫不同物件的建構函式時,從外面將不同的資料傳遞給建構函式,以實現不同的初始化,即在建立物件時同時指定資料成員的初值。如下:
class Time{
int hour ;
int min ;
int sec ;
public:
Time(int h,int m,int s){
hour=h ;
min=m ;
sec=s ;
}
};
int main(){
Time t(0,0,0);
}
這種初始化物件的方法使用起來很方便,也很直觀,從定義的語句中就能直接看到資料成員的初值。
4、引數初始化表對資料成員的初始化
引數的初始化表法算是帶引數的建構函式的一種,不過減少了函式體的長度,使結構體顯得精煉簡單,可以直接在類體中定義建構函式,尤其當需要初始化的資料成員較多時更具有優越性。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(int h,int m,int s):hour(h),min(m),sec(s){}
//Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
};
int main(){
Time t(0,0,0);
}
一般引數初始化表中的中括號內是空的,不執行任何語句。
5、建構函式的過載
在一個類中可以定義多個建構函式,這些建構函式具有相同的名字,引數的個數或引數的型別不相同,以便為物件提供不同的初始化的方法。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(){ //無引數的建構函式
hour=0;
min=0;
sec=0;
}
Time(int h,int m,int s):hour(h),min(m),sec(s){} //有引數的建構函式,並用初始化表進行初始化。
};
int main(){
Time t1;
Time t2(0,0,0);
}
在建立物件時不必給出實參的建構函式稱為 預設建構函式 ,顯然無引數建構函式屬於預設建構函式。一個類中只能有一個預設建構函式,否則的話系統將不知道去呼叫哪個建構函式進行初始化。另如果使用者未定義建構函式,則系統會1自動提供一個預設建構函式,但它的函式體是空的,不起初始化作用。如果使用者希望在建立物件時就能使資料成員有初值,就必須自己定義建構函式。雖然在一個類中有多個建構函式,但對於一個物件來說,建立一個物件時只調用其中一個建構函式,並非每個建構函式都被執行。另外在建立物件時如果選用的是無參建構函式,注意正確的書寫定義物件的語句:
Time t1;
Time t2(); //錯誤
6、使用預設引數的建構函式
在宣告建構函式時指定預設值而不是在定義建構函式時給出其預設值,宣告建構函式時,形參名可以省略。
Time (int =0 ,int =0,int =0);
在建立物件時不必給出實參,但一個類中只能有一個預設建構函式,為了避免呼叫時的歧義性,另外如果在一個類中定義了全部是預設引數的建構函式後,不能再定義過載建構函式,即此時類中只能存在一個預設引數的建構函式。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){} //有引數的建構函式,並用初始化表進行初始化。
Time(){ //無引數的建構函式
hour=0;
min=0;
sec=0;
}
Time(int h,int m){
hour=h;
min=m;
}
};
int main(){
Time t1; //是呼叫第一個還是第二個?
Time t2(1,1); //是呼叫第一個還是第三個?
}
另外如果建構函式中的引數並非全部為預設值時,要根據1具體情況而定。
Time();
Time(int , int m=0, int s=0);
Time(int , int)
Time t1; //呼叫第一個,無參建構函式
Time t2(1); //呼叫第二個,實參與形參從左到右一一對應
Time t3(1,1); //出現歧義
所以我們一般不同時使用建構函式的過載和有預設引數的建構函式,出現二義性的可能性大。
解構函式
解構函式的功能與建構函式的功能相反,它的名字是 類名的前面加一個“~”符號,“~” 在這裡是表示位取反運算子 ,可以想到解構函式是與建構函式作用相反的函式。
當物件的生命週期結束時,也是自動執行解構函式。有以下幾種情況:
- 如果在一個函式中定義了一個物件(區域性物件),當這個函式被呼叫結束時,物件應該釋放,在物件釋放前自動執行解構函式。
- 靜態(static)區域性物件在函式呼叫結束時物件並不釋放,因此也不用呼叫解構函式,只有在 main 函式結束或者呼叫 exit 函式結束程式時,才呼叫 static 區域性物件的解構函式。
- 如果定義了一個全域性的物件,則在程式的流程離開其作用域時(如 main 函式結束或呼叫 exit 函式),呼叫該全域性的物件的解構函式。
- 如果用 new 運算子動態地建立了一個物件 ,當用 delete 運算子釋放該物件時,先呼叫該物件的解構函式。
解構函式的作用並不是刪除物件,而是撤銷物件佔用的記憶體之前完成一些清理工作,使這部分記憶體可以被程式分配給新物件使用(比如你在構造或者做某個程式的時候,開闢了記憶體空間(如陣列),如果沒有在析構之前釋放它,則記憶體不會自動釋放,會造成記憶體洩露)。程式設計者要事先設計好解構函式,以完成所需的功能,主要物件的生命週期結束,程式就自動執行解構函式來完成這些工作。
解構函式不返回任何值,沒有函式型別,也沒有函式引數,故它不能被過載。一個類可以有多個建構函式,但只能有一個解構函式。解構函式的作用並不僅限於釋放資源方面,它還可以被用來執行 “使用者希望在最後一次使用物件之後所執行的任何操作”,如輸出有關的資訊。
一般情況下,類的設計者應當在宣告類的同時定義解構函式,以指定如何完成 “清理” 的工作。如果使用者沒有定義解構函式,系統也會自動生成一個解構函式,但它只是徒有解構函式的名稱和形式,實際上什麼操作都不執行。想讓解構函式完成任何工作,都必須在定義的解構函式中指定。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
void show(){
cout<<"時間是: "<<hour<<":"<<min<<":"<<sec<<endl;
}
~Time(){
cout <<"清理完成。"<<endl;
}
};
int main(){
Time t;
t.show();
}
在主函式中定義的物件是區域性的,它的生命週期隨著主函式的結束而結束,在撤銷物件之前的最後一項工作是呼叫解構函式。
建構函式與解構函式
二者之間的順序是:先構造的後析構,後構造的先析構。等同於 棧 :先進後出。
歸納一下系統在什麼時候呼叫建構函式和解構函式:
- 如果在全域性範圍內定義物件(即在所有函式之外定義的物件),那麼他的建構函式在本檔案模組中的所有函式(包括 main 函式)執行之前呼叫。但如果一個程式包括多個檔案,而在不同的檔案中都定義了全域性變數,則這些物件的建構函式的執行順序是不確定的。當 main 函式執行完畢或呼叫 exit 函式時(此時程式終止),呼叫解構函式。
- 如果定義的是區域性自動物件(例如在函式中定義物件),則在建立物件時呼叫其建構函式。如果物件所在的函式被多次呼叫,則在每次建立物件時都要呼叫建構函式。在函式呼叫結束、物件釋放時先呼叫解構函式。
- 如果在函式中定義靜態(static)區域性物件,則只在程式第一次呼叫此函式定義物件時呼叫建構函式一次,在呼叫函式結束時物件並不釋放,因此也不呼叫解構函式,只有在 main 函式結束或呼叫 exit 函式結束程式時才呼叫解構函式。
在有靜態物件時,其建構函式和解構函式的呼叫順序不一定就是 “先進後出” ,因為物件間的儲存類別不一樣,生命週期不一樣。
物件陣列
陣列不僅可以由簡單變數組成(例如整型陣列中的每個元素都是整型變數),也可以由類物件組成(物件陣列的每一個元素都是同類的物件)。
如果建構函式有多個函式,則不能用在定義陣列時直接提供所有實參的方法,因為一個數組有多個元素,對每個元素要提供多個實參,如果再考慮到建構函式有預設引數的情況,很容易造成實參與形參的對應關係不清晰,出現歧義性。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
void show(){
cout<<hour<<":"<<min<<":"<<sec<<endl;
}
};
int main(){
Time t[3] = {1,2,3};
t[0].show();
}
這段程式碼就是出現了歧義性,編譯系統並不是把這三個全作為第一個物件的實參,而是這三個實參分別作為這三個陣列元素的第一個實參。編譯系統只為每個物件元素的建構函式傳遞一個實參,所以在這樣定義陣列時提供的實參個數不能超過陣列元素個數。
如果想對每個陣列物件元素都進行初始化,那麼在建立陣列時,分別呼叫建構函式,對每個元素初始化,每一個元素的實參分別用括號括起來,對應建構函式的一組實參,這樣就不會再混淆。
class Time{
int hour ;
int min ;
int sec ;
public:
Time(int h=0,int m=0,int s=0):hour(h),min(m),sec(s){}
void show(){
cout<<hour<<":"<<min<<":"<<sec<<endl;
}
};
int main(){
Time t[3] = {
Time(1,1,1), //元素之間仍用逗號作為分隔
Time(2,2,2),
Time(3,3,3)
}; //注意分號!!!
t[0].show();
}