今天同事問了一個關於拷貝構造函數的問題,類中包含指針的狀況,今天就來講說c++的拷貝構造函數。ios
c++的拷貝構造函數是構造函數的一種,是對類對象的初始化,拷貝構造函數只有一個參數就是本類的引用。c++
注意,默認構造函數(即無參構造函數)不必定存在,可是拷貝構造函數老是會存在。函數
下面是一個拷貝構造函數的例子。優化
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 A(int value){ 7 a = value; 8 } 9 void show(){ 10 cout<<a<<endl; 11 } 12 }; 13 int main(){ 14 A test_a(10); 15 test_a.show(); 16 17 A test_b(test_a); 18 test_b.show(); 19 20 return 0; 21 }
輸出結果爲:spa
10 10
若是編寫了拷貝構造函數,則默認拷貝構造函數就不存在了。下面是一個非默認拷貝構造函數的例子。指針
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 A(int value){ 7 a = value; 8 } 9 A(A& tmp){ 10 a = tmp.a; 11 cout<<"call copy construct"<<endl; 12 } 13 void show(){ 14 cout<<a<<endl; 15 } 16 }; 17 int main(){ 18 A test_a(10); 19 test_a.show(); 20 21 A test_b(test_a); 22 test_b.show(); 23 24 return 0; 25 }
輸出結果爲:code
10 call copy construct 10
拷貝構造函數在如下三種狀況下會被調用。對象
1) 當用一個對象去初始化同類的另外一個對象時,會引起拷貝構造函數被調用。例如,下面的兩條語句都會引起拷貝構造函數的調用,用以初始化 test_b。blog
1 A test_b(test_a); 2 A test_b = test_a;
這兩條語句是等價的。
注意,第二條語句是初始化語句,不是賦值語句。賦值語句的等號左邊是一個早已有定義的變量,賦值語句不會引起拷貝構造函數的調用。例如:內存
1 A test_a,test_b; 2 test_b = test_a;
這條語句不會引起拷貝構造函數的調用,由於 test_b 早已生成,已經初始化過了。
2) 若是函數 F 的參數是類 A 的對象,那麼當 F 被調用時,類 A 的拷貝構造函數將被調用。換句話說,做爲形參的對象,是用複製構造函數初始化的,並且調用拷貝構造函數時的參數,就是調用函數時所給的實參。
3) 若是函數的返冋值是類 A 的對象,則函數返冋時,類 A 的拷貝構造函數被調用。換言之,做爲函數返回值的對象是用拷貝構造函數初始化 的,而調用拷貝構造函數時的實參,就是 return 語句所返回的對象。例以下面的程序:
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 A(int value){ 7 a = value; 8 } 9 A(A& tmp){ 10 a = tmp.a; 11 cout<<"call copy construct"<<endl; 12 } 13 void show(){ 14 cout<<a<<endl; 15 } 16 }; 17 A Func() { 18 A test_a(4); 19 return test_a; 20 } 21 int main(){ 22 Func().show(); 23 24 return 0; 25 }
輸出結果:
call copy construct 4
針對於第三條,有些編譯器可能會有如下的結果:
4
這是由於編譯器編譯的時候進行了優化,函數返回值對象就不用拷貝構造函數初始化了,這其實並不符合 C++的標準。
重頭戲來了,內含指針的拷貝構造函數,C++是如何實現的呢,來看個例子:
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 int *p; 7 A(int value1, int value2){ 8 a = value1; 9 p = new int(value2); 10 } 11 ~A(){ 12 delete p; 13 } 14 15 void show(){ 16 cout<<a<<endl; 17 cout<<p<<endl; 18 cout<<*p<<endl; 19 } 20 }; 21 22 int main(){ 23 A test_a(10,20); 24 test_a.show(); 25 26 A test_b(test_a); 27 test_b.show(); 28 29 return 0; 30 }
輸出結果以下:
10 0xf19010 20 10 0xf19010 20 *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x0000000000f19010 *** ...
能夠看到對於class A 的對象 test_a 和 test_b 指針p 指向了同一塊內存,在對象析構的時候被析構了兩次致使了crash,這就是咱們常說的淺拷貝。
所以,在咱們平常編寫代碼的時候特別須要注意這一點,對於指針咱們須要相應的開闢一塊新的內存,將指向的值拷貝過來,也就是所謂的深拷貝,下面是正確的寫法:
1 #include<iostream> 2 using namespace std; 3 class A{ 4 public: 5 int a; 6 int *p; 7 A(int value1, int value2){ 8 a = value; 9 p = new int(value2); 10 } 11 A(A& tmp){ 12 a = tmp.a; 13 p = new int(* tmp.p); 14 } 15 ~A(){ 16 delete p; 17 } 18 19 void show(){ 20 cout<<a<<endl; 21 cout<<p<<endl; 22 cout<<*p<<endl; 23 } 24 }; 25 26 int main(){ 27 A test_a(10,20); 28 test_a.show(); 29 30 A test_b(test_a); 31 test_b.show(); 32 33 return 0; 34 }
輸出結果以下:
10 0xd4d010 20 10 0xd4d030 20