溫故知新----再談建構函式 (轉)

worldblog發表於2007-12-10
溫故知新----再談建構函式 (轉)[@more@]

溫故知新

 ----再談構造:namespace prefix = o ns = "urn:schemas--com::office" />

作者:HolyFire

如果不知道建構函式的請先看一下《由始至終----構造與析構》,看過的我就不再多言,直接轉入話題。

定義一個類的的例項的時候,可以看到這樣的形式

classA a;  //建構函式不需要引數

不需要引數的建構函式稱之為預設建構函式

不需要引數有兩種情況

1:建構函式沒有引數

2:建構函式有引數但可以不給出

class A{

public:

  A();  //建構函式沒有引數

  A( int I = 10 );  //建構函式的引數有預設值,可以不用給出

};

這兩種情況都是預設建構函式,但是由於預設建構函式的特殊性(他是被自動的),無法判斷需要呼叫那一個,所以規定預設建構函式只能有一個。

預設建構函式的出現,意味著一個型別可以不依賴條件而被建立,就象一些細小的單元,質子,中子和電子,他們的有很大的類似性,不需要用條件來分辨他們被建立的資訊。當然不需要用條件來分辨他們被建立的資訊也包含了第二種情況,從流水線上生產的統一品種的產品很多都是用同一種方式的,那麼建立他們的資訊基本一致,也就是所符合第二種情況,引數可以採用預設值。

這個例子我們可以舉一個例子,我們建立一個指標類的時候,常常把他指向的內容置為空值,這很容易理解,我們需要一個指標,但是現在還不知道指向誰,等到我們要使用它的時候,不一定是知道他是否指向過別的,為了簡化問題,一開始就將他置空,但是有時候我們需要用引數在建立的時候就給出指向的物件,特別是在產生臨時物件的時候尤為管用,那麼,我們使用一個引數預設值為空的預設建構函式。

classA a( a1 );  //建構函式有引數,而引數為一個相同的型別

這樣的建構函式叫做複製建構函式,意思就是將類一個例項的內容複製到新建立的例項中去,為什麼要這麼做呢。我們來研究一下。

我們平時使用基本型別的時候,可以使用賦值語句,將相同型別的某個物件的內容賦給另一個物件

int a = 3;

int b;

b = a;  //這樣的話,b中就有和a一樣的內容了

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

int a = 3;

long b;

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

我們在設計類的時候應該也是將一個類作為一個個體,一個型別來處理,而且在現實中這樣的行為也是存在的,一個人的個人資料可以寫在不同的紀錄簿上,一個可以複製好幾份。

所以在物件導向中,這個問題不容忽視。

回到基本型別上,基本型別的處理編譯器完成了,在C++中很簡單,基本型別佔用空間是連續的,所以不管原來的內容是什麼,只要照搬照抄就可以了,這種負值方式叫做逐位複製,簡稱位複製

int a = 3;

int b;

假設:物件在中的儲存順序是先高後低,每個記憶體單元為1位元組(BYTE)=8位(BIT)

//假設這是a(int)的儲存空間

0

3

//假設這是b(int)的儲存空間

?

?

b =a ;

//將a的內容複製到b中

0

3

| |  | |

?

?

//a

0

3

//b

0

3

我們設計的類在記憶體中也是連續的,使用這樣的複製方法會得到一個一模一樣的同型別例項。而且編譯器我們處理了這一件事(C++的編譯器真好,它能解決的事,就不用麻煩我們了),也就是說即使我們沒有定義複製建構函式,編譯器也會在需要使用的時候,自己產生一個複製建構函式,使用的方法就是位複製。但是這樣好嗎,使用這種方法產生的新類可以的工作嗎,應該有不少朋友已經產生了疑問。

什麼時候可以讓編譯器自己處理複製建構函式。

#include

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& operator <

{

  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;

}

結果是:

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

可以看出,位複製得出的結果是正確的。

上面的例子中成員變數都是在編譯期間決定的,在記憶體中的位置也相對固定,如果成員變數的內容是在執行期間決定的呢,比如字串成員變數,他需要在堆中動態分配記憶體。還能正常工作嗎,繼續看例子。

#include

#include

#include

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& operator <

{

  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;

}

結果是:

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中釋放了記憶體連帶著b的一起釋放掉了。

這是當然的由於位複製,b中的data只是將a中的data複製過來了而已,並沒有分配記憶體,複製字串的內容。顯而易見,使用位複製不能滿足我們的要求,原來只需要簡單的將成員變數的值簡單的複製,這種我們稱之為:淺複製。現在我們需要處理對應成員變數,用其他方法來得到我們需要的結果,這種我們稱之為:深複製

這樣我們就需要自己寫複製建構函式來實現深複製了。

#include

#include

#include

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& operator <

{

  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;

}

結果是:

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  //哈哈,這正是我想得到的結果。

如果能使用位複製,儘量讓編譯器自己用位複製的方式處理,這樣會提高。但是一定要謹慎,不然會產生不可預料的結果,如果你的類中有一個成員變數也是類,它使用了深複製,那麼你也一定要使用深複製。

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

class A;

class B: public A{

};

B b;

A a(b);

這樣的形式是正確的。事實上,b先切片退化成一個臨時變數tempb,型別是class A,有關A的部分原封不動的保留下來,然後使用A a(tempb)這樣的方式成功的呼叫了。

複製建構函式並非可有可無!不能用其他函式來替代

看這樣的例子

void function( A a);

在函式呼叫的時候按值傳遞引數,那麼將在棧裡產生一個class A的臨時變數,如果沒有複製建構函式,這個過程就無法自動完成,如果沒用設計好淺複製或深複製,那麼可能得不到正確結果。如果複製建構函式正確,那麼我們可以輕鬆的獲得我們想要的結果----按值傳遞的引數在函式後不受影響。

classA a = a1;  //複製建構函式

事實上就是這樣的形式。

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

2001/9/13

丁寧 


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-990938/,如需轉載,請註明出處,否則將追究法律責任。

相關文章