溫故知新----再談建構函式 (轉)
溫故知新
----再談構造: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/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 溫故知新——Spring AOPSpring
- 溫故知新——Spring AOP(二)Spring
- 深淺複製,溫故知新
- 溫故知新 - 靶機練習-Toppo
- 預設建構函式、引數化建構函式、複製建構函式、解構函式函式
- 【溫故知新】分散式事務及分散式鎖系列文章總結【石杉的架構筆記】分散式架構筆記
- 溫故知新-分散式鎖的實現原理和存在的問題分散式
- 溫故知新-多執行緒-深入剖析AQS執行緒AQS
- 建構函式與解構函式函式
- ## 建構函式函式
- C++型別轉換建構函式C++型別函式
- C++ 建構函式和解構函式C++函式
- 類的建構函式和解構函式函式
- .net 溫故知新【15】:Asp.Net Core WebAPI 配置ASP.NETWebAPI
- 【溫故知新】 程式設計原則和方法論程式設計
- 靶機練習 - 溫故知新 - Toppo(sudo 提權)
- 再談函式和一等公民函式
- 建構函式定義的隱式型別轉換函式型別
- JavaScript 建構函式JavaScript函式
- .net 溫故知新【14】:Asp.Net Core WebAPI 快取ASP.NETWebAPI快取
- .net 溫故知新:【5】非同步程式設計 async await非同步程式設計AI
- PHP筆記:建構函式與解構函式PHP筆記函式
- 【C++】初始化列表建構函式VS普通建構函式C++函式
- C++中建構函式,拷貝建構函式和賦值函式的詳解C++函式賦值
- 11-建構函式函式
- 初識建構函式函式
- JavaScript Date()建構函式JavaScript函式
- 建構函式建立物件函式物件
- 建構函式詳解函式
- 建構函式和類函式
- .net 溫故知新【16】:Asp.Net Core WebAPI 篩選器ASP.NETWebAPI
- 關於建構函式與解構函式的分享函式
- C++入門記-建構函式和解構函式C++函式
- 建構函式與普通函式的區別函式
- 【譯】JavaScript 工廠函式 vs 建構函式JavaScript函式
- Class:向傳統類模式轉變的建構函式模式函式
- constructor 未指向建構函式Struct函式
- 回顧Javascript建構函式JavaScript函式
- JS 建構函式與類JS函式