c++ 拷貝構造函數(重點在內含指針的淺拷貝和深拷貝)

今天同事問了一個關於拷貝構造函數的問題,類中包含指針的狀況,今天就來講說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
相關文章
相關標籤/搜索