目錄
- 普通變數的復制
- 復制建構式
- 復制建構式的三種呼叫
- 復制建構式的禁用
- 深拷貝與淺拷貝
- 一定會生成默認復制建構式嗎?
- 參考
普通變數的復制
有時我們會在定義一個變數的同時使用另一個變數來初始化它,
int a_variable=12;
int new_variable(a_variable);
通過已有的同型別變數來初始化自身很有用,
對自定義型別的物件是否可以通過一個存在的物件方便的復制呢?
復制建構式
復制建構式又叫做拷貝建構式,它只有一個引數(既然需要復制,一個就夠了,若傳入兩個相同物件則沒有意義,若傳入兩個不同的物件,就沒必要叫做復制建構式了),引數型別為本類的參考,
如果程式員沒有撰寫復制建構式,編譯器會自動生成復制建構式,在復制建構式中按照成員變數進行逐位元組復制(初學可以這樣理解,實際上,個別編譯器并不總是會自動生成復制建構式,它們可能采用直接將源物件的各個值復制到目標物件對應的成員變數上,后面會介紹這種情況),
class MyDate
{
int day_;
int year_;
public:
MyDate(int day, int year)
{
day_ = day;
year_ = year;
}
MyDate(const MyDate& date)
{
day_ = date.day_;
year_ = date.year_;
cout << "Date類的復制建構式執行了!" << endl;
}
~MyDate() {}
};
void test()
{
MyDate date1(12, 2021);
MyDate date2(date1);
}
int main()
{
test();
system("pause");
return 0;
}
執行為:

對于MyDate(const MyDate& date)引數串列中的const,因為復制建構式引數另一個物件參考,如果不加const修飾,在此復制建構式中可能會改變原物件的內容,為了安全起見,應加盡加,
如果程式員撰寫了復制建構式,則編譯器就不會生成默認復制建構式了(所以撰寫了復制建構式之后就盡量在函式體內實作復制操作,不要定義了復制建構式卻不完全或不實作復制操作),
另一方面,所有的建構式(包括復制建構式)、解構式都無法從父類繼承,只能自己實作,
建構式如果只有一個引數且這個引數為本類物件,就會與復制建構式起沖突,如圖:

復制建構式的三種呼叫
復制建構式在以下3種情況下會被呼叫:
1.當使用一個A型別物件去初始化另一個A型別的物件時(剛創建好的,已創建好的不算),會呼叫復制建構式,注意觀察以下代碼:
Date date1(12,2012);//創建一個date1物件
Date date2(date1);//呼叫復制建構式
Date date3=date1;//也會呼叫復制構造 函式
date2=date1;//date2已存在,不會呼叫復制建構式,會呼叫賦值=操作函式

可以看到復制建構式只呼叫了兩次,
2.我們都知道C++傳參有傳值和傳參考(指標本質上是傳值,傳的是實參的地址),如果函式引數是一個自定義物件,那么會呼叫該自定義物件的復制建構式,
在傳值的時候,編譯器會開辟一個空間(創建了一個臨時物件)存盤實參的值(這個程序會將實參的各個值分配復制給臨時物件),再將該值壓入堆疊中,
//類宣告略
void TestFunction(MyDate date)
{
cout << "TestFunction()執行了!" << endl;
}
void test()
{
MyDate date1(12, 2021);
TestFunction(date1);
}
//main函式略
結果如下:

3.如果函式的回傳值是類MyDate的物件,則函式回傳時,會呼叫該物件的復制建構式,
//類宣告略
MyDate TestFunction2()
{
MyDate date1(12, 2021);
cout << &date1 << endl;
return date1;
}
void test()
{
cout << &TestFunction2() << endl;
}
//main函式省略
從復制建構式內的輸出被兩個地址輸出夾住即可看出在哪里呼叫了復制建構式,

復制建構式的禁用
如果不希望自定義型別的復制建構式被呼叫,
僅僅不撰寫復制建構式是不行的,編譯器可能會自動生成默認的復制建構式,應該使用private修飾復制建構式(這時編譯器就不會生成自動復制建構式),此時不要實作這個復制建構式,如下:

這樣便既禁止了用戶呼叫此復制建構式,又禁止了用戶通過其他成員函式或友元函式間接地呼叫它(如果我們僅僅把復制建構式宣告為private,宣告并實作了復制建構式,雖然避免了用戶直接呼叫,但成員函式和友元函式還是可以呼叫,只有不實作它才能永絕后患),
Bjarne Stroustrup認為如果你希望禁止某些操作,就把它定義為一個私有的成員函式即可,
深拷貝與淺拷貝
如果成員變數含有指標型別,默認復制建構式并不會將指標指向的記憶體中的值進行賦值,僅僅將指標存盤的值(也就是一個地址)復制了一次(與我們所希望的不一致),這時兩個指標指向了同一塊記憶體空間,一旦一種一個指標所屬的物件宣告周期結束,會呼叫它自己的解構式回收指標指向的記憶體空間,這時另一個指標遍指向了一個垃圾值,這個指標也變為了空懸指標,以上就是我們常提到的淺拷貝,
實際開發當中要竭力避免以上清情況的發生(當成員變數含有指標或動態分配記憶體等情況),
深拷貝如下:
class MyDate
{
private:
char* buffer_;
public:
MyDate(const char *init);//實作略
MyDate(const MyDate &date)
{
if(date.buffer_!=nullptr)
{
buffer_=new char[strlen(date.buffer_)+1];
strcpy(buffer_,date.buffer_);
}
else
{
buffer=nullptr;
}
}
}
復制建構式先檢查date中的buffer_的字串大小,然后分配此大小+1的記憶體給新創建的物件的buffer_(strlen函式不會計算’\0’字符),最后使用strcpy函式將date的buffer_指向的記憶體中的內容復制到新創建的物件的buffer_所指向的空間(strcpy函式會吧’\0’字符一并復制),最后實作了兩個指標指向了不同的存盤空間(兩個空間的內容相同),

如果我們要撰寫需要字符的成員時,盡量使用string,它會像其他成員變數一樣進行復制,因為string有自己的復制建構式,
一定會生成默認復制建構式嗎?
我們前面提到如果程式員沒有定義自己的復制建構式,編譯器會為我們生成一個默認復制建構式,
實際上以下4中情況編譯器會為我們生成默認復制建構式,
1.沒有為類撰寫復制建構式,但該類含有自定義型別或string等型別作為成員變數時,
2.沒有為類撰寫復制建構式,但該類繼承了一個含有復制建構式的類時,編譯器會生成默認復制建構式,在該函式中呼叫父類的復制建構式,
3.沒有為類撰寫復制建構式,但是該類定義了虛函式或者該類的父類定義了虛函式,,
4.沒有為類撰寫復制建構式,但是該類有虛基類,
參考
The C++ Programming Language (美) Bjarne Stroustrup
Thinking in C++ Volume One:Introduction toStandard C++ (美)Bruce Eckel
C++新經典 物件模型 王建偉
cpp參考:https://zh.cppreference.com
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301380.html
標籤:其他
