C++之拷貝構造函數的淺copy和深copy

2021-03-02 txp玩Linux

一、深拷貝和淺拷貝構造函數總結:

1、兩個特殊的構造函數:

(1)無參構造函數:

沒有參數的構造函數

Class Test
{
 public:
     Test()
     {
       //這是一個無參構造函數
     }
};

當類中沒有定義構造函數時,編譯器默認提供一個無參構造函數,並且其函數體為空;換句話來說,就是我們在類中,不用我們程序猿自己寫,編譯就自動提供了無參構造函數(只是我們肉眼看不到!)

#include <iostream>
#include <string>

class Test{

//編譯器默認給我們提供了一個無參構造函數,只是我們肉眼看不到

};



int main()
{
   Test t;

  return 0;
}

結果輸出(編譯時能夠通過的):

root@txp-virtual-machine:/home/txp# g++ test.cpp
root@txp-virtual-machine:/home/txp# 


(2)拷貝構造函數:

參數為const class_name&的構造函數

class Test{
public:
   Test(const Test& p)
   {
       
    
   }
}

當類中沒有定義拷貝構造函數時,編譯器默認提供了一個拷貝構造函數,簡單的進行成員變量的值賦值

#include <iostream>
#include <string>

class Test{

private:
   int i;
   int j;
public:
 /*  Test(const Test& p)編譯器默認提供這樣操作的
   {
      i = p.i;
      j = p.j; 
   }*/


};



int main()
{
   Test t;



  return 0;
}

輸出結果(編譯可以通過):

root@txp-virtual-machine:/home/txp# g++ test.cpp
root@txp-virtual-machine:/home/txp# 

(3)注意:

在寫程序的時候,定義的類對象初始化時看屬於哪種類型的:

Test t;//對應無參構造函數

Test t(1);//對應有參構造函數

Test t1;
Test t2=t1;//對應拷貝構造函數

比如下面我定義的類對象屬於無參構造函數(當然前提是你手寫了其他構造函數,雖然說編譯器會默認提供,但是既然要手寫,那麼三種構造函數就在定義類對象的時候按需求來寫),如果只寫了有參數構造函數,那麼編譯器就會報錯:

#include <iostream>
#include <string>

class Test{

private:
   int i;
   int j;
public:
  Test(int a)
  {
     i = 9;
     j=8;
  }
  Test(const Test& p)
   {
      i = p.i;
      j = p.j;
   }


};
int main()
{
   Test t;
  return 0;
}


輸出結果:

root@txp-virtual-machine:/home/txp# g++ test.cpp
test.cpp: In function 『int main()』:
test.cpp:25:9: error: no matching function for call to 『Test::Test()』
    Test t;
         ^
test.cpp:25:9: note: candidates are:
test.cpp:15:3: note: Test::Test(const Test&)
   Test(const Test& p)
   ^
test.cpp:15:3: note:   candidate expects 1 argument, 0 provided
test.cpp:10:3: note: Test::Test(int)
   Test(int a)
   ^
test.cpp:10:3: note:   candidate expects 1 argument, 0 provided

4、拷貝構造函數的意義:

(1)淺拷貝

拷貝後對象的物理狀態相同

(2)深拷貝

拷貝後對象的邏輯狀態相同

(3)編譯器提供的拷貝構造函數隻進行淺拷貝

代碼版本一:

#include <stdio.h>
#include <string>

class Test{
private:
   int i;
   int j;
   int *p;
public:
   int getI()
   {
      return i;
   }
   int getJ()
   {
      return j;
   }
   int *getP()
   {
      return p;
   }
   Test(int a)
   {
      i = 2;
      j = 3;
      p = new int;
      *p = a;
  }
  void free()
  {

    delete p;
  }
};
int main()
{
   Test t1(3);//Test t1 3;
   Test t2 = t1;

  printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
  printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());

   t1.free();
   t2.free();
   
   return 0;
}

輸出結果:

root@txp-virtual-machine:/home/txp# g++ test.cpp
root@txp-virtual-machine:/home/txp# ./a.out
t1.i = 2, t1.j = 3, t1.p = 0x1528010
t2.i = 2, t2.j = 3, t2.p = 0x1528010
*** Error in `./a.out': double free or corruption (fasttop): 0x0000000001528010 ***
Aborted (core dumped)

註解:出現了段錯誤,仔細分析,我們發現這裡釋放了堆空間兩次(因為我們這裡沒有調用拷貝構造函數,也就是自己去寫拷貝構造函數;所以這種情況是淺拷貝,不能釋放兩次堆空間):

代碼版本二(加上拷貝構造函數):

#include <stdio.h>
#include <string>

class Test{
private:
   int i;
   int j;
   int *p;
public:
   int getI()
   {
      return i;
   }
   int getJ()
   {
      return j;
   }
   int *getP()
   {
      return p;
   }
   Test(int a)
   {
      i = 2;
      j = 3;
      p = new int;
      *p = a;
  }
  Test(const Test& t)
  {
    i = t.i;
    j = t.j;
    p = new int;

    *p = *t.p;
  }

  void free()
  {

    delete p;
  }
};
int main()
{
   Test t1(4);
   Test t2 = t1;

   printf("t1.i = %d, t1.j = %d, t1.p = %p\n", t1.getI(), t1.getJ(), t1.getP());
   printf("t2.i = %d, t2.j = %d, t2.p = %p\n", t2.getI(), t2.getJ(), t2.getP());
   t1.free();
   t2.free();

   return 0;
}

輸出結果:

root@txp-virtual-machine:/home/txp# g++ test.cpp
root@txp-virtual-machine:/home/txp# ./a.out
t1.i = 2, t1.j = 3, t1.p = 0xb0a010
t2.i = 2, t2.j = 3, t2.p = 0xb0a030

註解:從列印的p地址空間來看,就知釋放的兩個對象的堆空間不同,不再是指向同一堆空間了;同時我們發現淺拷貝只是簡單數值上的進行賦值而已;深拷貝不只是簡單的值賦值,而是從內存的角度來看,是操作不同的內存。

5、什麼時候需要進行深拷貝?

(1)對象中有成員指代了系統中的資源

成員指向了動態內存空間

成員打開了外存中的文件

成員使用了系統中的網絡埠

注意:一般來說,自定義拷貝構造函數(也就是我們自己手寫的),必然需要實現深拷貝!

二、總結:

C++編譯器會默認提供構造函數

無參構造函數用於定義對象的默認初始化狀態

拷貝構造函數在創建對象時拷貝對象的狀態

對象的拷貝有淺拷貝和深拷貝兩種方式。

好了,今天的分享就到這裡,如果文章中有錯誤或者不理解的地方,可以交流互動,一起進步。我是txp,下期見!

相關焦點

  • 面試題-python 淺拷貝和深拷貝(copy模塊)
    前言面試的時候經常會問到深拷貝和淺拷貝,那麼python的深拷貝和淺拷貝有什麼區別呢?,我們說的深拷貝和淺拷貝是針對可變對象:list、dict 和 set集合copy模塊python 中的深拷貝和淺拷貝使用 copy 模塊淺拷貝 A shallow copy constructs a new compound object and then (to the extent possible) inserts references into it to the
  • 圖解 Python 中深淺拷貝(copy)
    注意:l2 = l1是一個指向,是賦值,和深淺copy無關。深copyimport copyl1 = [1, 2, 3, [22, 33]]l2 = copy.deepcopy(l1)l1.append(666)print(l1) print(l2)  圖解:本質如下圖:
  • Python深拷貝和淺拷貝詳解
    對於淺拷貝(shallow copy)和深度拷貝(deep copy),本節並不打算一上來拋出它們的概念,而是先從它們的操作方法說起,通過代碼來理解兩者的不同
  • 深拷貝和淺拷貝之list、dataframe
    python list:b = a是淺拷貝,b = list(a)和b = copy.cpoy(a)是深拷貝。淺拷貝,a和b指向的是一個地址。當b改變後,a也會改變。深拷貝,a和b指向的是兩個地址,當b改變後,a不受影響。
  • JavaScript的深拷貝實現
    在實際開發當中,我們經常會遇到要對對象進行深拷貝的情況。而且深拷貝這個問題在面試過程中也經常會遇到,下面就對本人在學習過程中的收穫,做以簡單的總結。什麼是淺拷貝,什麼是深拷貝?什麼是淺拷貝關於淺拷貝的概念,我在網上看到一種說法,直接上代碼。
  • 為何C++拷貝構造函數參數必須為引用形式
    對於對象類型,會調用拷貝構造函數拷貝一份數據,而內置類型,其拷貝成本相對較低、通常也不會帶來其他性能的影響。然而,若傳遞給函數的數據是一個大的值(比如vector、list或結構體數組等),那麼這個複製過程對於CPU、內存和存儲方面都有較大的速度影響及性能損失。      比如,一個vector中擁有一萬個字符串變量,若以傳值方式給fun函數,絕對不是明智的選擇。
  • 5 張圖徹底理解 Python 中的淺拷貝與深拷貝
    假設你去面試 Python 開發崗,面試官如果對基礎比較看重的話,那麼很可能會問你這樣的問題「談談你對 Python 中的淺拷貝和深拷貝的理解
  • 看看你知道的「淺拷貝」是對的嗎
    她: 「複製對象有深拷貝和淺拷貝...」大佬:」說一下這兩者之間的區別「她: 」我給你寫一段淺拷貝的代碼「var a = { x: 1 };var b = a;大佬:」回去等通知吧 ~.~「2. 於是我去MDN(https://developer.mozilla.org)上查了一下shallow copy的關鍵字,也沒有關於shallow copy的準確定義,這也難怪部分人對於淺拷貝會有不同的認識。第一種定義:一個新的對象直接拷貝已存在的對象的引用,即淺拷貝。
  • 前端面試-深拷貝和淺拷貝
    面試題目:如何實現對一個數組或對象的淺拷貝和深拷貝?WTF,複製還分兩種,第一次遇到這種問題的時候很是無語呢,先來看看一般的答案的理解。淺拷貝是只拷貝一層,深層次的對象級別就只拷貝引用。 深拷貝是拷貝多層,每一級別的數據都拷貝出來。
  • 【編程基礎】System.arraycopy()和 Arrays.copyOf()
  • Effective C++讀書筆記之拷貝構造函數和成員變量初始化
    如何創建一個不可拷貝的類在我們沒有聲明一個拷貝構造函數和賦值運算符時,C++編譯器會幫我們生成一個默認的拷貝構造函數和賦值運算符,且生成的拷貝構造函數和賦值運算符是公有的。如果我們想創建一個不可拷貝的類該如何做呢?
  • C/C++中字符串常量的不相等性及字符串的Copy
    {          cout<<"相等";      }      else      {          cout<<"不相等";      }  }  上面的代碼我們測試兩個內容為test的字符串常量是否相等,按照常理,應該是相等的,這些在一些過程式語言中會得到相等的結論,但在c/c++
  • JavaScript系列--如何優雅簡單的實現深拷貝和淺拷貝
    優雅簡單的實現深拷貝和淺拷貝淺析JavaScript解析賦值、淺拷貝和深拷貝的區別:裡面介紹了解析賦值,淺拷貝,深拷貝的原理和實現。淺拷貝方法:Object.assign(),展開語法Spread,Array.prototype.alice(),array.prototype.concat()。深拷貝方法:JSON.parse(JSON.stringify(object)),對於undefined,symbol和函數的會直接忽略。
  • Pandas和Numpy的視圖和拷貝
    在Numpy和Pandas中,有兩個重要概念,容易混淆,一個是淺拷貝,也稱為視圖,另外一個是深拷貝,或者就稱為拷貝。如果操作不當,Pandas會爆出SettingWithCopyWarning的異常。本文我將就視圖和拷貝問題,結合異常進行總結。
  • copy_{to, from}_user()的思考
    對於我的查閱結果來說,觀點主要分成以下兩種:copy_{to,from}_user()比memcpy()多了傳入地址合法性校驗。例如是否屬於用戶空間地址範圍。理論上說,內核空間可以直接使用用戶空間傳過來的指針,即使要做數據拷貝的動作,也可以直接使用memcpy(),事實上在沒有MMU的體系架構上,copy_{to,from}_user()最終的實現就是利用了mencpy()。
  • copy_{to,from}_user()的思考
    對於我的查閱結果來說,觀點主要分成以下兩種:copy_{to,from}_user()比memcpy()多了傳入地址合法性校驗。例如是否屬於用戶空間地址範圍。理論上說,內核空間可以直接使用用戶空間傳過來的指針,即使要做數據拷貝的動作,也可以直接使用memcpy(),事實上在沒有MMU的體系架構上,copy_{to,from}_user()最終的實現就是利用了memcpy()。
  • C++ string中c_str()、data()、copy(p,n)函數的用法
    string得到c類型的字符數組:c_str()、data()、copy(p,n)。註:①這個數組的數據是臨時的,當有一個改變這些數據的成員函數被調用後,其中的數據就會失效。因此要麼現用先轉換,要麼把它的數據複製到用戶自己可以管理的內存中。注意。
  • 一文讀懂 javascript 深拷貝與淺拷貝
    讀完本文,希望你能明白:淺拷貝與深拷貝淺拷貝是創建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內存地址 ,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。
  • 一篇文章讀懂Python賦值與拷貝
    該功能由 copy 模塊提供。拷貝又分為淺拷貝和深拷貝。>>> s = [1,2,3]>>> sc = copy.copy(s) # 淺拷貝>>> sc[1, 2, 3]>>> sdc = copy.deepcopy(s)  >>> sdc[1, 2, 3]拷貝出來的對象只是值相同,實為不同的對象
  • C++ vector詳解
    5. push_back()同時支持copy構造&和移動語義構造&&,配合std::move或者std::forward效果更佳。6. earse()用來刪除元素,參數是迭代器, 範圍刪除的時候是前閉後開【 )。