C++實現拷貝構造函數的方法詳解
引入
對于普通類型的對象來說,他們之間的復制很簡單:
int a = 10;
int b = a;
但是對于類對象來說,其中會存在許多的成員變量。
#include <iostream> using namespace std; class CExample { private: int a; public: //構造函數 CExample(int b) { a = b;} //一般函數 void Show () { cout<<a<<endl; } }; int main() { CExample A(100); CExample B = A; //注意這里的對象初始化要調用拷貝構造函數,而非賦值 B.Show (); return 0; }
從以上代碼可以看出系統為對象 B 分配了內存并完成了與對象 A 的復制過程。就類對象而言,相同類型的類對象是通過拷貝構造函數來完成整個復制過程的。
下面這個則是拷貝構造函數的工作過程
#include <iostream> using namespace std; class CExample { private: int a; public: //構造函數 CExample(int b) { a = b;} //拷貝構造函數 CExample(const CExample& C) { a = C.a; } //一般函數 void Show () { cout<<a<<endl; } }; int main() { CExample A(100); CExample B = A; // CExample B(A); 也是一樣的 B.Show (); return 0; }
在這里CExample(const CExample& C) 就是我們自定義的拷貝構造函數。
一.什么是拷貝構造函數
同一個類的對象在內存中有完全相同的結構,如果作為一個整體進行復制或稱拷貝是完全可行的。這個拷貝過程只需要拷貝數據成員,而函數成員是共用的(只有一份拷貝)。在建立對象時可用同一類的另一個對象來初始化該對象的存儲空間,這時所用的構造函數稱為拷貝構造函數。
拷貝構造函數本質上來說也是構造函數。
二.什么情況下使用拷貝構造函數
一般來說有以下三種情況:
- 用舊對象去初始化新對象
- 值傳遞—參數是類類型的值類型,從實參傳遞給形參的過程,是用實參去構造形參
- 函數返回值是值類型–用局部對象去構造臨時對象調用拷貝構造
class A { public: A(int i = 0):m_i(i) { cout<<"A(int) "<<m_i<<endl; } A(const A &a):m_i(a.m_i) { cout<<"A(A) "<<m_i<<endl; } ~A() { cout<<"~A "<<m_i<<endl; } private: int m_i; }; void fn(A t) //2.將c傳遞給t的過程,是值傳遞,此時臨時對象形參t是新對象,用c去構造t,調用拷貝構造 { cout<<"fn end"<<endl; //在退出fn函數時,將臨時對象t釋放,調用析構函數 } A test() { A d(40); //調用普通構造A(int)構造對象d return d; /* 3.在返回的時候,將局部對象的值給了臨時對象(值傳遞,調用拷貝構造,用舊局部對象去構造新的臨時對象) 然后局部對象d就釋放了,臨時對象將值帶回到主調函數中后,臨時對象的值才釋放 */ } A fnn() { A s(50); //A(int) 50 return s; // /*1.s->臨時對象 拷貝構造 2.析構函數局部對象s */ } void main() { A a(20); //A(int) 20 A b = a; //1.用a初始化b A(A) cout<<"fn"<<endl; A c(30); //A(int) 30 fn(c); cout<<"test"<<endl; c = test(); //臨時對象將值賦給c(調用賦值函數)之后,調用析構,析構臨時對象 cout<<"fnn"<<endl; A t = fnn(); //臨時對象初始化新對象t,是否會調用拷貝構造?--調用了,不過編譯器做過優(yōu)化 cout<<"main end"<<endl; }
三.使用拷貝構造函數需要注意什么
拷貝構造函數是構造函數的一個重載形式。
拷貝構造函數的參數只有一個且必須使用引用傳參,使用傳值方式會引發(fā)無窮遞歸調用。
若未顯示定義,系統生成默認的拷貝構造函數。默認的拷貝構造函數對象按內存存儲按字節(jié)序完成拷貝,稱為:位拷貝。
四.深拷貝淺拷貝
4.1 淺拷貝
所謂淺拷貝,指的是在對象復制時,只對對象中的數據成員進行簡單的賦值,默認拷貝構造函數執(zhí)行的也是淺拷貝。大多情況下“淺拷貝”已經能很好地工作了,但是一旦對象存在了動態(tài)成員,指針,那么淺拷貝就會出現一些問題。
對于下面函數來說有指針作為數據成員,則用s1對象去構造s2對象的時候,調用默認拷貝構造,用s1中的數據成員指針m_str去初始化s2對象中的數據成員m_str,即是s2.m_str = s1.m_str,那么導致兩個對象中的指針指向同一塊內存單元,指向的都是構造s1對象時開辟的內存單元,所以在主函數退出時候要析構s2和s1時,將同一段空間釋放兩次出現內存錯誤。
class Str { public: Str(const char *str = "") { m_str = new char[strlen(str)+1]; strcpy(m_str,str); } ~Str() { delete[]m_str; } void Print() { cout<<m_str<<endl; } private: char *m_str; }; void main() { Str s1("pangpang"); Str s2(s1); cout<<sizeof(Str)<<endl; s1.Print(); s2.Print(); }
上述程序的內存布局:
對于指針作為數據成員的類,用s1對象去構造s2對象的時候,調用默認拷貝構造函數時,二者指向同一內存單元,即二者的初始地址相同這里均為0X0002000,當我們構造完以后將要進行析構時,這里將會出現錯誤:因為析構函數要釋放空間,而這里我們的空間對應的是一塊空間,當我們析構完s2后:這一塊空間的內容已經被delete,而我們還需要析構s1,即:一個內存空間析構了兩次,出現內存錯誤。
為了解決上述問題 我們就需要給s2中的m_str也開辟和s1中的m_str一樣大小的空間,所以我們就需要 深拷貝 。
4.2 深拷貝
在“深拷貝”的情況下,對于對象中動態(tài)成員,就不能僅僅簡單地賦值了,而應該重新動態(tài)分配空間,如上面的例子就應該按照如下的方式進行處理:
class Str { public: Str(const char *str = "") { m_str = new char[strlen(str)+1]; strcpy(m_str,str); } ~Str() { cout<<"~Str"<<endl; delete[]m_str; } Str(const Str& s) { cout<<"Str(Str)"<<endl; m_str = new char[strlen(s.m_str)+1]; //開辟同樣大小的空間 strcpy(m_str,s.m_str); //將內容拷貝成一樣的 } void Print() { cout<<m_str<<endl; } private: char *m_str; }; void main() { Str s1("pangpang"); Str s2(s1); //拷貝構造 s1.Print(); s2.Print(); }
現在的程序內存布局為:
各自指向一段內存空間,但它們指向的空間具有相同的內容,這就是所謂的“深拷貝”。
到此這篇關于C++實現拷貝構造函數的方法詳解的文章就介紹到這了,更多相關C++拷貝構造函數內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
C++動態(tài)內存分配(new/new[]和delete/delete[])詳解
這篇文章主要介紹了C++動態(tài)內存分配(new/new[]和delete/delete[])詳解的相關資料,需要的朋友可以參考下2017-05-05