C++實(shí)現(xiàn)拷貝構(gòu)造函數(shù)的方法詳解
引入
對(duì)于普通類(lèi)型的對(duì)象來(lái)說(shuō),他們之間的復(fù)制很簡(jiǎn)單:
int a = 10;
int b = a;
但是對(duì)于類(lèi)對(duì)象來(lái)說(shuō),其中會(huì)存在許多的成員變量。
#include <iostream> using namespace std; class CExample { private: int a; public: //構(gòu)造函數(shù) CExample(int b) { a = b;} //一般函數(shù) void Show () { cout<<a<<endl; } }; int main() { CExample A(100); CExample B = A; //注意這里的對(duì)象初始化要調(diào)用拷貝構(gòu)造函數(shù),而非賦值 B.Show (); return 0; }
從以上代碼可以看出系統(tǒng)為對(duì)象 B 分配了內(nèi)存并完成了與對(duì)象 A 的復(fù)制過(guò)程。就類(lèi)對(duì)象而言,相同類(lèi)型的類(lèi)對(duì)象是通過(guò)拷貝構(gòu)造函數(shù)來(lái)完成整個(gè)復(fù)制過(guò)程的。
下面這個(gè)則是拷貝構(gòu)造函數(shù)的工作過(guò)程
#include <iostream> using namespace std; class CExample { private: int a; public: //構(gòu)造函數(shù) CExample(int b) { a = b;} //拷貝構(gòu)造函數(shù) CExample(const CExample& C) { a = C.a; } //一般函數(shù) void Show () { cout<<a<<endl; } }; int main() { CExample A(100); CExample B = A; // CExample B(A); 也是一樣的 B.Show (); return 0; }
在這里CExample(const CExample& C) 就是我們自定義的拷貝構(gòu)造函數(shù)。
一.什么是拷貝構(gòu)造函數(shù)
同一個(gè)類(lèi)的對(duì)象在內(nèi)存中有完全相同的結(jié)構(gòu),如果作為一個(gè)整體進(jìn)行復(fù)制或稱(chēng)拷貝是完全可行的。這個(gè)拷貝過(guò)程只需要拷貝數(shù)據(jù)成員,而函數(shù)成員是共用的(只有一份拷貝)。在建立對(duì)象時(shí)可用同一類(lèi)的另一個(gè)對(duì)象來(lái)初始化該對(duì)象的存儲(chǔ)空間,這時(shí)所用的構(gòu)造函數(shù)稱(chēng)為拷貝構(gòu)造函數(shù)。
拷貝構(gòu)造函數(shù)本質(zhì)上來(lái)說(shuō)也是構(gòu)造函數(shù)。
二.什么情況下使用拷貝構(gòu)造函數(shù)
一般來(lái)說(shuō)有以下三種情況:
- 用舊對(duì)象去初始化新對(duì)象
- 值傳遞—參數(shù)是類(lèi)類(lèi)型的值類(lèi)型,從實(shí)參傳遞給形參的過(guò)程,是用實(shí)參去構(gòu)造形參
- 函數(shù)返回值是值類(lèi)型–用局部對(duì)象去構(gòu)造臨時(shí)對(duì)象調(diào)用拷貝構(gòu)造
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的過(guò)程,是值傳遞,此時(shí)臨時(shí)對(duì)象形參t是新對(duì)象,用c去構(gòu)造t,調(diào)用拷貝構(gòu)造 { cout<<"fn end"<<endl; //在退出fn函數(shù)時(shí),將臨時(shí)對(duì)象t釋放,調(diào)用析構(gòu)函數(shù) } A test() { A d(40); //調(diào)用普通構(gòu)造A(int)構(gòu)造對(duì)象d return d; /* 3.在返回的時(shí)候,將局部對(duì)象的值給了臨時(shí)對(duì)象(值傳遞,調(diào)用拷貝構(gòu)造,用舊局部對(duì)象去構(gòu)造新的臨時(shí)對(duì)象) 然后局部對(duì)象d就釋放了,臨時(shí)對(duì)象將值帶回到主調(diào)函數(shù)中后,臨時(shí)對(duì)象的值才釋放 */ } A fnn() { A s(50); //A(int) 50 return s; // /*1.s->臨時(shí)對(duì)象 拷貝構(gòu)造 2.析構(gòu)函數(shù)局部對(duì)象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(); //臨時(shí)對(duì)象將值賦給c(調(diào)用賦值函數(shù))之后,調(diào)用析構(gòu),析構(gòu)臨時(shí)對(duì)象 cout<<"fnn"<<endl; A t = fnn(); //臨時(shí)對(duì)象初始化新對(duì)象t,是否會(huì)調(diào)用拷貝構(gòu)造?--調(diào)用了,不過(guò)編譯器做過(guò)優(yōu)化 cout<<"main end"<<endl; }
三.使用拷貝構(gòu)造函數(shù)需要注意什么
拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須使用引用傳參,使用傳值方式會(huì)引發(fā)無(wú)窮遞歸調(diào)用。
若未顯示定義,系統(tǒng)生成默認(rèn)的拷貝構(gòu)造函數(shù)。默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,稱(chēng)為:位拷貝。
四.深拷貝淺拷貝
4.1 淺拷貝
所謂淺拷貝,指的是在對(duì)象復(fù)制時(shí),只對(duì)對(duì)象中的數(shù)據(jù)成員進(jìn)行簡(jiǎn)單的賦值,默認(rèn)拷貝構(gòu)造函數(shù)執(zhí)行的也是淺拷貝。大多情況下“淺拷貝”已經(jīng)能很好地工作了,但是一旦對(duì)象存在了動(dòng)態(tài)成員,指針,那么淺拷貝就會(huì)出現(xiàn)一些問(wèn)題。
對(duì)于下面函數(shù)來(lái)說(shuō)有指針作為數(shù)據(jù)成員,則用s1對(duì)象去構(gòu)造s2對(duì)象的時(shí)候,調(diào)用默認(rèn)拷貝構(gòu)造,用s1中的數(shù)據(jù)成員指針m_str去初始化s2對(duì)象中的數(shù)據(jù)成員m_str,即是s2.m_str = s1.m_str,那么導(dǎo)致兩個(gè)對(duì)象中的指針指向同一塊內(nèi)存單元,指向的都是構(gòu)造s1對(duì)象時(shí)開(kāi)辟的內(nèi)存單元,所以在主函數(shù)退出時(shí)候要析構(gòu)s2和s1時(shí),將同一段空間釋放兩次出現(xiàn)內(nèi)存錯(cuò)誤。
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(); }
上述程序的內(nèi)存布局:
對(duì)于指針作為數(shù)據(jù)成員的類(lèi),用s1對(duì)象去構(gòu)造s2對(duì)象的時(shí)候,調(diào)用默認(rèn)拷貝構(gòu)造函數(shù)時(shí),二者指向同一內(nèi)存單元,即二者的初始地址相同這里均為0X0002000,當(dāng)我們構(gòu)造完以后將要進(jìn)行析構(gòu)時(shí),這里將會(huì)出現(xiàn)錯(cuò)誤:因?yàn)槲鰳?gòu)函數(shù)要釋放空間,而這里我們的空間對(duì)應(yīng)的是一塊空間,當(dāng)我們析構(gòu)完s2后:這一塊空間的內(nèi)容已經(jīng)被delete,而我們還需要析構(gòu)s1,即:一個(gè)內(nèi)存空間析構(gòu)了兩次,出現(xiàn)內(nèi)存錯(cuò)誤。
為了解決上述問(wèn)題 我們就需要給s2中的m_str也開(kāi)辟和s1中的m_str一樣大小的空間,所以我們就需要 深拷貝 。
4.2 深拷貝
在“深拷貝”的情況下,對(duì)于對(duì)象中動(dòng)態(tài)成員,就不能僅僅簡(jiǎn)單地賦值了,而應(yīng)該重新動(dòng)態(tài)分配空間,如上面的例子就應(yīng)該按照如下的方式進(jìn)行處理:
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]; //開(kāi)辟同樣大小的空間 strcpy(m_str,s.m_str); //將內(nèi)容拷貝成一樣的 } void Print() { cout<<m_str<<endl; } private: char *m_str; }; void main() { Str s1("pangpang"); Str s2(s1); //拷貝構(gòu)造 s1.Print(); s2.Print(); }
現(xiàn)在的程序內(nèi)存布局為:
各自指向一段內(nèi)存空間,但它們指向的空間具有相同的內(nèi)容,這就是所謂的“深拷貝”。
到此這篇關(guān)于C++實(shí)現(xiàn)拷貝構(gòu)造函數(shù)的方法詳解的文章就介紹到這了,更多相關(guān)C++拷貝構(gòu)造函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于C語(yǔ)言實(shí)現(xiàn)猜數(shù)字游戲
這篇文章主要為大家詳細(xì)介紹了基于C語(yǔ)言實(shí)現(xiàn)猜數(shù)字游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-05-05C++實(shí)現(xiàn)LeetCode(42.收集雨水)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(42.收集雨水),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C++動(dòng)態(tài)內(nèi)存分配(new/new[]和delete/delete[])詳解
這篇文章主要介紹了C++動(dòng)態(tài)內(nèi)存分配(new/new[]和delete/delete[])詳解的相關(guān)資料,需要的朋友可以參考下2017-05-05C語(yǔ)言實(shí)現(xiàn)數(shù)字雨效果
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)數(shù)字雨效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02C語(yǔ)言實(shí)現(xiàn)銀行管理系統(tǒng)(文件操作)
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)銀行管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03如何用C語(yǔ)言、Python實(shí)現(xiàn)棧及典型應(yīng)用
本文先通過(guò)實(shí)例分別介紹了如何用C語(yǔ)言、Python實(shí)現(xiàn)棧,后又介紹棧的典型應(yīng)用,對(duì)大家學(xué)習(xí)棧很有借鑒參考價(jià)值,下面一起來(lái)看看吧。2016-08-08