某個類的對象之間都有哪些不同呢?首先是對象名不同,其次就是對象的數據成員的值不同。我們在聲明一個對象時,也可以同時給它的數據成員賦初值,稱爲對象的初始化。
1.構造函數
我們在聲明一個變量時,如果對它進行了初始化,那麼在爲此變量分配內存空間時還會向內存單元中寫入變量的初始化。聲明對象有相似的過程,程序執行時遇到對象聲明語句時會向操作系統申請一定的內存空間來存放這個對象,但是它能像一般變量那樣初始化時寫入指定的初始值嗎?類的對象太複雜了,要實現這一點不太容易,這就需要構造函數來實現。
構造函數的作用就是在對象被創建時利用特定的初始值構造對象,把對象置於某一個初始狀態,它在對象被創建的時候由系統自動調用,我們只需要使用默認的構造函數或者自己定義構造函數,而不用管怎麼調用的。
構造函數也是類的成員函數,除了有成員函數的所有特徵外,還有一些不同之處:構造函數的函數名跟類名一樣,而且沒有返回值。構造函數一般被聲明爲公有函數,除非我們不允許某個類生成對象則將它聲明爲private或protected屬性。編譯器碰到對象聲明語句時,會自動生成對構造函數的調用語句,所以我們常說構造函數是在對象聲明時由系統自動調用的。
在下面時鐘的例子中,如果沒有定義Clock類的構造函數,編譯器編譯時會自動生成一個默認形式的構造函數,這個構造函數不做任何事,那麼爲什麼還要生成它呢?因爲C++在對象建立時都會調用構造函數,所以如果沒有自己定義構造函數,那麼即使是什麼都不做的構造函數也是要有的。現在在Clock類中加入自己定義的構造函數:
class Clock
{
public:
Clock(int NewH, int NewM, int NewS); //構造函數
void SetTime(int NewH, int NewM, int NewS);
void ShowTime();
private:
int Hour, Minute, Second;
};
構造函數的實現:
Clock::Clock(int NewH, int NewM, int NewS)
{
Hour=NewH;
Minute=NewM;
Second=NewS;
}
建立對象時構造函數的作用:
int main()
{
Clock c(0,0,0); //隱含調用構造函數,將初始值作爲實參。
c.ShowTime();
return 0;
}
main函數中,創建對象c時,其實隱含了構造函數的調用,將初始值0,0,0作爲構造函數的實參傳入。因爲上面Clock類定義了構造函數,那麼編譯器就不會再爲它生成默認構造函數了。這裏的構造函數有三個形參,那麼建立對象就必須給出初始值了。
因爲構造函數也是一個成員函數,所以它可以直接訪問類的所有數據成員,可以是內聯函數,可以帶有形參表,可以帶默認的形參值,也可以重載,就是有若干個名字相同但形參個數或者類型不同的構造函數。
2.拷貝構造函數
我們可以將一個變量的值賦給另一個同類型的變量,那麼可以將一個對象的內容拷貝給相同類的另一個對象嗎?可以,我們可以將第一個對象的數據變量的值分別賦給另一個對象的數據變量,但是,如果數據變量數很多的話那將是很麻煩的,這時候我們就需要有拷貝構造函數。
拷貝構造函數是一種特殊的構造函數,因爲它也是用來構造對象的。它具有構造函數的所有特性。拷貝構造函數的作用是用一個已經存在的對象去初始化另一個對象,這兩個對象的類類型應該是一樣的。定義拷貝構造函數的形式是:
class 類名
{
public :
類名(形參); //構造函數
類名(類名 &對象名); //拷貝構造函數
...
};
類名::類名(類名 &對象名) //拷貝構造函數的實現
{
函數體
}
拷貝構造函數的形參是本類的對象的引用。
程序中如果沒有定義拷貝構造函數系統會生成一個默認的拷貝構造函數,它會將作爲初始值的對象的數據成員的值都拷貝到要初始化的對象中。給大家一個座標點類的例子,X和Y數據成員分別爲點的橫座標和縱座標:
class Point{
public:
Point(int xx=0,int yy=0){X=xx;Y=yy;} //構造函數
Point(Point &p);//拷貝構造函數
int GetM(){return M;}
int GetN(){return N;}
private:
int X,Y;
int M,N;
};
此類中聲明瞭內聯構造函數和拷貝構造函數。拷貝構造函數的實現如下:
Point::Point(Point &p)
{
M=p.X;
N=p.Y;
cout<<"拷貝構造函數被調用了"<<endl;
}
拷貝構造函數在以下三種情況下會被調用:
a.當用類的一個對象去初始化該類的另一個對象時系統自動調用拷貝構造函數實現拷貝賦值。
int main()
{
Point A(1,2);
Point B(A); //拷貝構造函數被調用
cout<<B.GetM()<<endl;
return 0;
}
b.若函數的形參爲類對象,調用函數時,實參賦值給形參,系統自動調用拷貝構造函數。例如:
void fun1(Point p)
{
cout<<p.GetM()<<endl;
}
int main()
{
Point A(1,2);
fun1(A); //調用拷貝構造函數
return 0;
}
c.當函數的返回值是類對象時,系統自動調用拷貝構造函數。例如:
Point fun2()
{
Point A(1,2);
return A; //調用拷貝構造函數
}
int main()
{
Point B;
B=fun2();
cout<<B.GetM()<<endl;
return 0;
}
最後這種情況怎麼調用的拷貝構造函數呢?對象A是局部對象,在fun2函數執行完就釋放了,那怎麼將它拷貝給對象B呢?編譯器在執行B=fun2()時會創建一個臨時的無名對象,在執行return A時實際上是調用了拷貝構造函數將A的值拷貝到了臨時對象中,A就釋放了,然後將臨時對象的值再拷貝到對象B中。
3.析構函數
自然萬物都是有生有滅的,類的對象也一樣是有生命週期的,一樣會消亡。如果在函數中聲明瞭一個對象,那麼在這個函數運行完返回調用函數時,聲明的對象也會釋放,就像上面說的fun2函數中對象A那樣。
在對象釋放時都有什麼工作要做呢?我們經常遇到的情況就是:構造函數時動態申請了一些內存單元,在對象釋放時就要同時釋放這些內存單元。
析構函數和構造函數的作用是相反的,它會在對象被刪除之前做一些清理工作。析構函數是在對象要被刪除時由系統自動調用的,它執行完後對象就消失了,分配的內存空間也釋放了。
析構函數是類的一個公有函數成員,它的名稱是在類名前加“~”形成,不能有返回值,它和構造函數不同的是它不能有任何形參。如果沒有定義析構函數系統也會自動生成一個默認析構函數,默認析構函數也不會做任何工作。一般如果我們想在對象被刪除之前做什麼工作就可以把它寫到析構函數裏。
析構函數的用法:
class Point
{
public:
Point(int xx, int yy);
~Point();
//...其他函數原型
private:
int X, int Y;
char *p;
};
下面是構造函數和析構函數的實現:
Point::Point(int xx,int yy)
{
X=xx;
Y=yy;
p=new char[20]; // 構造函數中動態分配char型內存
}
Point::~Point()
{
delete []char; // 在類析構時釋放之前動態分配的內存
}
//...其他函數的實現略
構造函數和析構函數是C++編程入門時必須掌握的內容,很多公司筆試或面試時都會考到。