明輝手游網(wǎng)中心:是一個(gè)免費(fèi)提供流行視頻軟件教程、在線學(xué)習(xí)分享的學(xué)習(xí)平臺(tái)!

溫故知新----再談構(gòu)造函數(shù)(轉(zhuǎn):不轉(zhuǎn)了)

[摘要]溫故知新 ----再談構(gòu)造函數(shù)作者:HolyFire如果不知道構(gòu)造函數(shù)的請先看一下《由始至終----構(gòu)造與析構(gòu)》,看過的我就不再多言,直接轉(zhuǎn)入話題。定義一個(gè)類的的實(shí)例的時(shí)候,可以看到這樣的形式cla...
溫故知新

 ----再談構(gòu)造函數(shù)

作者:HolyFire

如果不知道構(gòu)造函數(shù)的請先看一下《由始至終----構(gòu)造與析構(gòu)》,看過的我就不再多言,直接轉(zhuǎn)入話題。

定義一個(gè)類的的實(shí)例的時(shí)候,可以看到這樣的形式

classA a;//構(gòu)造函數(shù)不需要參數(shù)

不需要參數(shù)的構(gòu)造函數(shù)稱之為缺省構(gòu)造函數(shù)。

不需要參數(shù)有兩種情況

1:構(gòu)造函數(shù)沒有參數(shù)

2:構(gòu)造函數(shù)有參數(shù)但可以不給出

class A{

public:

 A();//構(gòu)造函數(shù)沒有參數(shù)

 A( int I = 10 );//構(gòu)造函數(shù)的參數(shù)有缺省值,可以不用給出

};

這兩種情況都是缺省構(gòu)造函數(shù),但是由于缺省構(gòu)造函數(shù)的特殊性(他是被自動(dòng)調(diào)用的),編譯器無法判斷需要調(diào)用那一個(gè),所以規(guī)定缺省構(gòu)造函數(shù)只能有一個(gè)。

缺省構(gòu)造函數(shù)的出現(xiàn),意味著一個(gè)類型可以不依賴約束條件而被創(chuàng)建,就象一些細(xì)小的單元,質(zhì)子,中子和電子,他們的有很大的類似性,不需要用條件來分辨他們被創(chuàng)建的信息。當(dāng)然不需要用條件來分辨他們被創(chuàng)建的信息也包含了第二種情況,從流水線上生產(chǎn)的統(tǒng)一品種的產(chǎn)品很多都是用同一種方式的,那么創(chuàng)建他們的信息基本一致,也就是所符合第二種情況,參數(shù)可以采用缺省值。

這個(gè)例子我們可以舉一個(gè)例子,我們創(chuàng)建一個(gè)指針類的時(shí)候,常常把他指向的內(nèi)容置為空值,這很容易理解,我們需要一個(gè)指針,但是現(xiàn)在還不知道指向誰,等到我們要使用它的時(shí)候,不一定是知道他是否指向過別的對(duì)象,為了簡化問題,一開始就將他置空,但是有時(shí)候我們需要用參數(shù)在創(chuàng)建的時(shí)候就給出指向的對(duì)象,特別是在產(chǎn)生臨時(shí)對(duì)象的時(shí)候尤為管用,那么,我們使用一個(gè)參數(shù)缺省值為空的缺省構(gòu)造函數(shù)。

classA a( a1 );//構(gòu)造函數(shù)有參數(shù),而參數(shù)為一個(gè)相同的類型

這樣的構(gòu)造函數(shù)叫做拷貝構(gòu)造函數(shù),意思就是將類一個(gè)實(shí)例的內(nèi)容復(fù)制到新創(chuàng)建的實(shí)例中去,為什么要這么做呢。我們來研究一下。

我們平時(shí)使用基本類型的時(shí)候,可以使用賦值語句,將相同類型的某個(gè)對(duì)象的內(nèi)容賦給另一個(gè)對(duì)象

int a = 3;

int b;

b = a; //這樣的話,b中就有和a一樣的內(nèi)容了

還可在允許的情況下使用不同類型的賦值

int a = 3;

long b;

b = a;//這樣的話,b也能包含有和a一樣的內(nèi)容了

我們在設(shè)計(jì)類的時(shí)候應(yīng)該也是將一個(gè)類作為一個(gè)個(gè)體,一個(gè)類型來處理,而且在現(xiàn)實(shí)中這樣的行為也是存在的,一個(gè)人的個(gè)人資料可以寫在不同的紀(jì)錄簿上,一個(gè)軟件可以拷貝好幾份。

所以在面向?qū)ο缶幊讨校@個(gè)問題不容忽視。

回到基本類型上,基本類型的處理編譯器完成了,在C++中很簡單,基本類型占用存儲(chǔ)空間是連續(xù)的,所以不管原來的內(nèi)容是什么,只要照搬照抄就可以了,這種負(fù)值方式叫做逐位拷貝,簡稱位拷貝。

int a = 3;

int b;

假設(shè):對(duì)象在內(nèi)存中的存儲(chǔ)順序是先高后低,每個(gè)內(nèi)存單元為1字節(jié)(BYTE)=8位(BIT)

//假設(shè)這是a(int)的存儲(chǔ)空間

0
3


//假設(shè)這是b(int)的存儲(chǔ)空間

?
?


b =a ;

//將a的內(nèi)容拷貝到b中

0
3


 

?
?


//a

0
3


//b

0
3


我們設(shè)計(jì)的類在內(nèi)存中也是連續(xù)的,使用這樣的拷貝方法會(huì)得到一個(gè)一模一樣的同類型實(shí)例。而且編譯器我們處理了這一件事(C++的編譯器真好,它能解決的事,就不用麻煩我們了),也就是說即使我們沒有定義拷貝構(gòu)造函數(shù),編譯器也會(huì)在需要使用的時(shí)候,自己產(chǎn)生一個(gè)拷貝構(gòu)造函數(shù),使用的方法就是位拷貝。但是這樣好嗎,使用這種方法產(chǎn)生的新類可以安全的工作嗎,應(yīng)該有不少朋友已經(jīng)產(chǎn)生了疑問。

什么時(shí)候可以讓編譯器自己處理拷貝構(gòu)造函數(shù)。

#include <iostream>

using namespace std;

class A{

private:

int x;

int y;

int z;

public:

A():x(0),y(0),z(0){ }

A( int _x = 0 , int _y = 0 , int _z = 0 ):x(_x),y(_y),z(_z){ }

friend ostream& operator <<( ostream& , A const& );

};

ostream& operator <<( ostream& out , A const& arg )

{

out << "This is a Instance of A" << endl;

out << "Member Data x is : " << arg.x << endl;

out << "Member Data y is : " << arg.y << endl;

out << "Member Data z is : " << arg.z << endl;

return out;

}

void main()

{

A a( 1 , 12 ,123 );

A b(a);

cout << "This is a!" << endl;

cout << a << endl;

cout << "b is a copy of a!" << endl;

cout << b;

}

結(jié)果是:

This is a!

This is a Instance of A

Member Data x is : 1

Member Data y is : 12

Member Data z is : 123

b is a copy of a!

This is a Instance of A

Member Data x is : 1

Member Data y is : 12

Member Data z is : 123

可以看出,位拷貝得出的結(jié)果是正確的。

上面的例子中成員變量都是在編譯期間決定的,在內(nèi)存中的位置也相對(duì)固定,如果成員變量的內(nèi)容是在運(yùn)行期間決定的呢,比如字符串成員變量,他需要在堆中動(dòng)態(tài)分配內(nèi)存。還能正常工作嗎,繼續(xù)看例子。

#include <iostream>

#include <string.h>

#include <mem.h>

using namespace std;

class A{

private:

 char * data;

public:

 A():data(NULL){ }

 A( char * _data ):data(NULL)

{

if( !_data )

 return;

int length = strlen(_data) +1;

data = new char[length];

memcpy( data , _data , length );

}

 ~A()

{

if( data )

 delete data;

}

 void Clear( void )

{

if( data )

 {

 memset( data , 0 , strlen( data ) );

 delete data;

 }

data = NULL;

}



 friend ostream& operator <<( ostream& , A const& );

};

ostream& operator <<( ostream& out , A const& arg )

{

 out << "This is a Instance of A" << endl;

 if( arg.data && *arg.data )

out << "Member Data data is : " << arg.data << endl;

 else

out << "Member Data data is : NULL" << endl;

 return out;

}

void main()

{

 A a( "abcdefg" );

 A b(a);

 cout << "This is a!" << endl;

 cout << a << endl;

 cout << "b is a copy of a!" << endl;

 cout << b << endl;

 a.Clear();

 cout << "Where a's mem clear!" << endl;

 cout << a;

 cout << "God! b's mem clear!" << endl;

 cout << b << endl;

}

結(jié)果是:

This is a!

This is a Instance of A

Member Data data is : abcdefg

b is a copy of a!

This is a Instance of A

Member Data data is : abcdefg

Where a's mem clear!

This is a Instance of A

Member Data data is : NULL

God! b's mem clear!

This is a Instance of A

Member Data data is : NULL//不!a中釋放了內(nèi)存連帶著b的一起釋放掉了。

這是當(dāng)然的由于位拷貝,b中的data只是將a中的data復(fù)制過來了而已,并沒有分配內(nèi)存,拷貝字符串的內(nèi)容。顯而易見,使用位拷貝不能滿足我們的要求,原來只需要簡單的將成員變量的值簡單的復(fù)制,這種我們稱之為:淺拷貝,F(xiàn)在我們需要處理對(duì)應(yīng)成員變量,用其他方法來得到我們需要的結(jié)果,這種我們稱之為:深拷貝。

這樣我們就需要自己寫拷貝構(gòu)造函數(shù)來實(shí)現(xiàn)深拷貝了。

#include <iostream.h>

#include <string.h>

#include <mem.h>

class A{

private:

 char * data;

public:

 A():data(NULL){ }

 A( char * _data ):data(NULL)

{

if( !_data )

 return;

int length = strlen(_data) +1;

data = new char[length];

memcpy( data , _data , length );

}

 A( A const& arg )

{

if( !arg.data )

 return;

int length = strlen(arg.data) +1;

data = new char[length];

memcpy( data , arg.data , length );

}

 ~A()

{

if( data )

 delete data;

}

 void Clear( void )

{

if( data )

 {

memset( data , 0 , strlen( data ) );

 delete data;

 }

data = NULL;

}

 friend ostream& operator <<( ostream& , A const& );

};

ostream& operator <<( ostream& out , A const& arg )

{

 out << "This is a Instance of A" << endl;

 if( arg.data && *arg.data )

out << "Member Data data is : " << arg.data << endl;

 else

out << "Member Data data is : NULL" << endl;

 return out;

}

void main()

{

 A a( "abcdefg" );

 A b(a);

 cout << "This is a!" << endl;

 cout << a << endl;

 cout << "b is a copy of a!" << endl;

 cout << b << endl;

 a.Clear();

 cout << "Where a's mem clear!" << endl;

 cout << a;

 cout << "Good! b's mem not clear!" << endl;

 cout << b << endl;

}

結(jié)果是:

This is a!

This is a Instance of A

Member Data data is : abcdefg

b is a copy of a!

This is a Instance of A

Member Data data is : abcdefg

Where a's mem clear!

This is a Instance of A

Member Data data is : NULL

Good! b's mem not clear!

This is a Instance of A

Member Data data is : abcdefg //哈哈,這正是我想得到的結(jié)果。

如果能使用位拷貝,盡量讓編譯器自己用位拷貝的方式處理,這樣會(huì)提高效率。但是一定要謹(jǐn)慎,不然會(huì)產(chǎn)生不可預(yù)料的結(jié)果,如果你的類中有一個(gè)成員變量也是類,它使用了深拷貝,那么你也一定要使用深拷貝。

另外,我在《白馬非馬----繼承》中說到,一個(gè)類型的的派生類是該類型的一種。那么。

class A;

class B: public A{

};

B b;

A a(b);

這樣的形式是正確的。事實(shí)上,b先切片退化成一個(gè)臨時(shí)變量tempb,類型是class A,有關(guān)A的部分原封不動(dòng)的保留下來,然后使用A a(tempb)這樣的方式成功的調(diào)用了。

拷貝構(gòu)造函數(shù)并非可有可無!不能用其他函數(shù)來替代

看這樣的例子

void function( A a);

在函數(shù)調(diào)用的時(shí)候按值傳遞參數(shù),那么將在棧里產(chǎn)生一個(gè)class A的臨時(shí)變量,如果沒有拷貝構(gòu)造函數(shù),這個(gè)過程就無法自動(dòng)完成,如果沒用設(shè)計(jì)好淺拷貝或深拷貝,那么可能得不到正確結(jié)果。如果拷貝構(gòu)造函數(shù)正確,那么我們可以輕松的獲得我們想要的結(jié)果----按值傳遞的參數(shù)在函數(shù)執(zhí)行后不受影響。

classA a = a1;//拷貝構(gòu)造函數(shù)

事實(shí)上就是這樣的形式。

ClassA a(a1);//可以改成這種形式