析構函數也是一個特殊的成員函數。它的作用與構造函數相反。它的名字是在類名的前面加一個“~”符號。在C++中“~”是位取反運算符。當對象的生命結束時,會自動執行解析函數。以下幾種情況會執行析構函數:
1.如果在一個函數中定義了一個對象,當這個函數被調用結束時,對象應該釋放,在對象釋放前自動執行析構函數。
2.static局部對象在函數調用結束時對象並不釋放,因此也不調用析構函數,只在main函數結束或調用exit函數時,才調用staitic局部對象的析構函數。
3.如果定義了一個全局對象,則在程序的流程離開其作用域時(如main函數結束或exit函數),調用該全局對象的析構函數。
4.如果用new運算符動態的建立一個對象,當用delete運算符釋放該對象時,先調用該對象的析構函數。
析構函數的作用並不是刪除對象,而是在撤銷對象占用的內存之前完成一些清理工作,使這部分內存可以被程序分配給新的對象使用。程序設計者事先設計好析構函數,以完成所需的功能,只要對象的生命結束,程序就自動執行析構函數來完成這些工作。
析構函數不返回任何值,沒有函數類型,也沒有函數參數。由於沒有函數參數,它不能被重載。並且一個類中只能有一個析構函數。
析構函數還可以被用來執行“用戶希望在最后一次使用對象之后所執行的任何操作”。例如輸出有關的信息。這里說用戶是指類的設計者。析構函數可以完成設計者多指定的任何操作。
想讓析構函數完成任何工作,都必須在定義的析構函數中指定。
調用構造函數和析構函數的順序
在一般情況下,調用析構函數的次序正好與調用調用構造函數的次序相反,最先被調用的構造函數,其對應的析構函數最后被調用,而而最后調用的構造函數,其對應的析構函數最先被調用。先構造的后析構,后構造的先析構。(並非任何時候都是按照這一原則進行的。會在作用域和存儲類別的概念方面的不同)。對象可以在不同的作用域中定義,但是這是有區別的。
下面歸納一下是什么時候調用構造函數和析構函數:
1.在全局范圍內定義的對象(即在所有函數之外定義的對象),它的構造函數在文件中所有函數(包括main函數)執行之前調用。但是如果一個程序中有多 個文件,而不同的文件都定義了全局對象,則這些對象的構造函數的執行順序是不確定的。當main函數執行完畢或調用exit函數(此時程序終止)時,調 用析構函數。
2.如果定義的是局部自動對象(例如在函數中定義對象),則在建立對象時調用其構造函數。如果函數被調用多次,則在每次建立對象時都要調用構造函 數。在函數調用結束、對象釋放時先調用析構函數。
3.如果在函數中定義靜態static局部對象,則只在程序第一次調用此函數建立對象時調用構造函數一次,在調用結束時對像並不釋放,因此也不調用析構 函數,只在main函數結束或調用exit函數結束程序時,才調用析構函數。
對象數組
在建立數組時,同樣要調用構造函數,如果有50個元素,需要調用50次構造函數。在需要時可以在定義數組時提供實參以實現初始化。如果構造函數只有一 個參數,在定義數組時可以直接在等號后面的花括號里提供實參。如
Student stud[3]={60,70,80};
如果構造函數有多個參數,則不能在定義數組時直接提供所有實參的方法,因為一個數組有多個元素,對每個元素提供多個實參,如果在考慮到構造函數有默 認參數的情況,很容易造成實參和形參的對應關系不清晰,出現歧義。
那么,如果構造函數有多個參數,在定義對象數組時應該怎么實現初始化呢?答案:在花括號中分別寫出構造函數並指定實參,如果構造函數有3個參數,分 別代表學號、年齡、成績。則可以這樣定義數組:
Student stud[3]={ //定義對象數組
Student(1001,19,87); // 調用第一個元素的構造函數,為他提供三個參數
Student(1002,19,78); //調用第二個元素的構造函數,為他提供3個參數
Student(1003,18,79); // .......................
};
例子:
#include <iostream>
using namespace std;
class Box
{
public:
Box(int h,int w,int len):height(h),width(w),length(len){}
int volume();
private:
int height;
int width;
int length;
};
int Box::volume()
{
return (height*width*length);
}
int main()
{
Box a[3]={Box(10,12,15),Box(15,18,20),Box(16,20,26)};
cout<<"volume of a[0] is "<<a[0].volume()<<endl;
cout<<"volume of a[1] is "<<a[1].volume()<<endl;
cout<<"volume of a[2] is "<<a[2].volume()<<endl;
return 0;
}
對象指針
指向對象的指針,對象空間的起始地址就是對象的指針。
類名 *對象指針名;
指向對象成員的指針,對象有指針,存放對象初始地址的指針變量就是指向對象的指針變量。對象中的成員也有地址,存放對象成員地址的指針變量就是指 向對象成員的指針變量。
定義指向對象數據成員的指針變量的方法和定義指向普通變量的指針變量方法相同,例如:int *p;
定義指向對象數據成員和指針變量的一般形式:
數據類型名 *指針變量;
如果Time類的數據成員hour為公用的整數數據,則可以在類外通過指向對象數據成員的指針變量訪問對象數據成員hour。
p1=&t1.hour;
cout<<*p1<<endl;
指向對象成員函數的指針,定義指向對象成員函數的指針變量的方法和定義指向普通函數的指針變量方法有所不同。下面是普通函數的指針變量的定義方 法: 數據類型名 (*指針變量名)(參數列表);
如:void (*p)(); //p是指向void型函數的指針變量
可以使它指向一個函數,並通過指針變量調用該函數。
p=fun; //將fun函數的入口地址賦給指針變量p,p就指向了函數fun
(*p)(); //調用fun函數
成員函數與普通函數一個最根本區別是:它是類中的一個成員。編譯系統要求在上面的賦值語句中,指針變量的類型必須與賦值號右側函數的類型相匹配,要 求以下3方面都要匹配:1.函數參數的類型和參數個數;2.函數返回值的類型;3.所屬的類。
應該按照如下形式定義指向成員函數的指針變量:
void(Time::*p2)(); //定義p2為指向Time類中公用成員函數的指針變量。
注意:(Time::*p2)兩側的括號不能省略,因為()的優先級高於*。
定義指向公用成員函數的指針變量的一般形式是:
數據類型名 (類名::*指針變量名)(參數列表);
可以讓它指向一個公用成員函數,只需要把公用成員函數的入口地址賦給一個指向公用成員函數的指針變量即可。例如:
p2=&Time::get_time;
使指針變量指向一個公用成員函數的一般形式:
指針變量名=&類名::成員函數名;
例子:
#include <iostream>
using namespace std;
class Time
{
public:
Time(int,int,int);
int hour;
int minute;
int sec;
void get_time();
};
Time::Time(int h,int m,int s)
{
hour=h;
minute=m;
sec=s;
}
void Time::get_time()
{
cout<<hour<<":"<<minute<<":"<<sec<<endl;
}
int main()
{
Time t1(15,12,55);
int *p1=&t1.hour;
cout<<*p1<<endl;
t1.get_time();
Time *p2=&t1;
p2->get_time();
void(Time::*p3)();
p3=&Time::get_time;
(t1.*p3)();
}