拷贝构造函数的调用以及浅拷贝与深拷贝的理解
???????今天一直在研究拷貝構(gòu)造函數(shù)相關(guān)的東西,我這個(gè)大四老狗感覺又回到了大一學(xué)C++的時(shí)候。瞎搗鼓了一天,略微還是有些收獲的,趁著腦子中的概念正熱,把自己的心得趕緊整理出來分享給大家。
????????首先簡單介紹下拷貝構(gòu)造函數(shù)的概念:拷貝構(gòu)造函數(shù)是形參是本類對(duì)象的引用的構(gòu)造函數(shù),它的一般聲明形式諸如這樣:
Location(const Location & obj)其實(shí)我們經(jīng)常在類中并未顯示地定義類的拷貝構(gòu)造函數(shù),即當(dāng)缺省拷貝構(gòu)造函數(shù)時(shí),這個(gè)時(shí)候系統(tǒng)會(huì)自動(dòng)生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù),把成員值一一復(fù)制。這種缺省拷貝構(gòu)造函數(shù)的情況很常見,一般也不會(huì)給程序帶來問題,但是需要注意的是,如果一個(gè)類需要析構(gòu)函數(shù)來釋放資源,那么它需要定義一個(gè)顯式拷貝構(gòu)造函數(shù)來實(shí)現(xiàn)深拷貝,關(guān)于這一點(diǎn),本文后面會(huì)詳細(xì)說明緣由。
一、拷貝構(gòu)造函數(shù)的調(diào)用
???????接下來說一下調(diào)用拷貝構(gòu)造函數(shù)的三種情況:
-
當(dāng)用類的一個(gè)對(duì)象去初始化該類的另一個(gè)對(duì)象時(shí)。
-
如果函數(shù)的形參是類的對(duì)象,調(diào)用函數(shù)時(shí),用實(shí)參的值初始化形參時(shí)。
-
如果函數(shù)的返回值是類的對(duì)象,函數(shù)執(zhí)行完成返回調(diào)用者時(shí)。
附上代碼,在示例代碼中,覆蓋了拷貝構(gòu)造函數(shù)的三種情況。
需要提及的是,對(duì)于第三種情況,在IDE上運(yùn)行時(shí),并沒有打印出 "Copy Constructing…" 這個(gè)結(jié)果:
Copy Constructing...Copy Constructing... 學(xué)號(hào):10看到打印出這個(gè)結(jié)果時(shí),我也是很蒙圈的,搜索了網(wǎng)上的問答了解到這是編譯器做了優(yōu)化,例如我這邊是gcc編譯器,它做了優(yōu)化,使得返回值為對(duì)象時(shí),不會(huì)再產(chǎn)生臨時(shí)對(duì)象,因而不會(huì)再調(diào)用拷貝構(gòu)造函數(shù)。那么如果想要看到結(jié)果需要怎么做呢,因?yàn)槲沂莑inux系統(tǒng),此處就簡單說下Linux的處理方式,在終端輸入:
g++ -w -fno-elide-constructors test.cpp -o test這時(shí)再在終端運(yùn)行,打印出理想的結(jié)果:
Copy Constructing...Copy Constructing... 學(xué)號(hào):10Copy Constructing...二、淺拷貝和深拷貝
????????完成簡單的一一對(duì)應(yīng)的復(fù)制的拷貝構(gòu)造函數(shù)稱為淺拷貝。在淺拷貝過程中,如果對(duì)象中含有指針變量,將使得所復(fù)制的對(duì)象中的指針成員與原來對(duì)象的指針成員指向相同的內(nèi)存空間。在退出程序時(shí),析構(gòu)函數(shù)會(huì)釋放指針成員指向的內(nèi)存空間,譬如用對(duì)象a初始化對(duì)象b,調(diào)用拷貝構(gòu)造函數(shù)實(shí)現(xiàn)淺拷貝后,程序退出運(yùn)行時(shí),析構(gòu)對(duì)象a時(shí),析構(gòu)函數(shù)釋放其指針成員指向的內(nèi)存空間,而析構(gòu)復(fù)制對(duì)象b時(shí)系統(tǒng)會(huì)調(diào)用相同的析構(gòu)函數(shù),也要釋放其指針成員指向的內(nèi)存空間。則出現(xiàn)同一內(nèi)存空間,申請(qǐng)一次,釋放兩次的情況, new 與 delete不成對(duì),系統(tǒng)會(huì)報(bào)錯(cuò)。附上簡單的示例圖:
給出淺拷貝的代碼: #include <iostream> #include <string.h>class student { public:student(int i, char* c, int a, float s) {// std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申請(qǐng)堆空間if (name != 0) strcpy(name, c);}~student() {// std::cout <<"Destructing..."<< std::endl;delete []name;}private:int id;char* name;int age;float score; };int main() { student s1(10, "Wang", 18, 86); //創(chuàng)建和初始化對(duì)象student s2 = s1;return 0; }此處用s1初始化s2時(shí),調(diào)用了拷貝構(gòu)造函數(shù),因?yàn)槿笔?#xff0c;默認(rèn)用了淺拷貝,那么退出時(shí),調(diào)用析構(gòu)函數(shù),因?yàn)橥粌?nèi)存空間釋放了兩次,系統(tǒng)會(huì)報(bào)錯(cuò)。正確的方法是定義一個(gè)顯式的拷貝構(gòu)造函數(shù)實(shí)現(xiàn)深拷貝,可以見示例代碼:
#include <iostream> #include <string.h>class student { public:student(int i, char* c, int a, float s) {// std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申請(qǐng)堆空間if (name != 0) strcpy(name, c);}// 顯示聲明拷貝函數(shù),自定義實(shí)現(xiàn)深拷貝student(const student& s) { std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成員簡單復(fù)制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申請(qǐng)堆空間if (name != 0) strcpy(name, s.name); //復(fù)制字符串};~student() {// std::cout <<"Destructing..."<< std::endl;delete []name;}private:int id;char* name;int age;float score; };int main() { student s1(10, "Wang", 18, 86); //創(chuàng)建和初始化對(duì)象student s2 = s1;return 0; } 這樣的話便成功解決了內(nèi)存空間多次釋放報(bào)錯(cuò)的問題。三、普通構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)的調(diào)用分析
??????????????前面兩部分把拷貝構(gòu)造函數(shù)已經(jīng)分析清楚了,接下來就依據(jù)上面的程序簡要分析下普通構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)的調(diào)用,普通構(gòu)造函數(shù)簡單地說就是非拷貝構(gòu)造函數(shù)范疇的構(gòu)造函數(shù),析構(gòu)函數(shù)則是用于當(dāng)對(duì)象生存期結(jié)束時(shí)調(diào)用它釋放對(duì)象所占的內(nèi)存。普通構(gòu)造函數(shù)以及析構(gòu)函數(shù)如果程序中沒有顯示定義的話,系統(tǒng)也會(huì)提供一個(gè)默認(rèn)的析構(gòu)函數(shù),需要注意的是默認(rèn)的析構(gòu)函數(shù)只能用來釋放對(duì)象的數(shù)據(jù)成員所占用的空間,但不包括堆內(nèi)存空間。在本例中,為了方便觀測,普通構(gòu)造函數(shù)、拷貝構(gòu)造函數(shù)、析構(gòu)函數(shù)在程序中均顯示地定義,下面看下示例代碼:
#include <iostream> #include <string.h>class student { public:// 普通構(gòu)造函數(shù)student(int i, char* c, int a, float s) {std::cout << "Constructing..." << std::endl;id = i;age = a;score = s;name = new char[strlen(c) + 1]; //申請(qǐng)堆空間if (name != 0) strcpy(name, c);}// 顯示聲明拷貝函數(shù),自定義實(shí)現(xiàn)深拷貝student(const student& s) { std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成員簡單復(fù)制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申請(qǐng)堆空間if (name != 0) strcpy(name, s.name); //復(fù)制字符串};~student() {std::cout << "Destructing..." << std::endl;delete []name;}int getid() {return id;}private:int id;char* name;int age;float score; };// 函數(shù)返回值是類對(duì)象,函數(shù)執(zhí)行完成返回調(diào)用時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用拷貝構(gòu)造函數(shù) student g() {student s1(10, "Lina", 18, 86);return s1; }// 如果函數(shù)的形參是類的對(duì)象,調(diào)用函數(shù)時(shí),用實(shí)參的值初始化形參時(shí),系統(tǒng)會(huì)自動(dòng)調(diào)用拷貝構(gòu)造函數(shù) void print_1(student s) {std::cout << "學(xué)號(hào):" << s.getid() << std::endl; }int main() { student s1(10, "Wang", 18, 86); //創(chuàng)建和初始化對(duì)象std::cout << std::endl;// 第一種情況,用s1初始化s2。第一次調(diào)用拷貝構(gòu)造函數(shù)student s2 = s1;std::cout << std::endl;// 第二種情況,對(duì)象s2作為print_1的實(shí)參。第二次調(diào)用拷貝構(gòu)造函數(shù)print_1(s2);std::cout << std::endl;// 第三種情況,函數(shù)的返回值是類對(duì)象,函數(shù)返回時(shí)調(diào)用拷貝構(gòu)造函數(shù)s2 = g();std::cout << std::endl;return 0; } Constructing...Copy Constructing...Copy Constructing... 學(xué)號(hào):10 Destructing...Constructing... Copy Constructing... Destructing... Destructing...Destructing... Destructing...看一下打印的結(jié)果,發(fā)現(xiàn)對(duì)于拷貝構(gòu)造函數(shù)調(diào)用的第二種及第三種情況,既打印了"Copy Constructing…“又打印了"Destructing…”,情況較為復(fù)雜,下面仔細(xì)地分析下:
- 第二種情況:函數(shù)的形參為類對(duì)象,調(diào)用函數(shù)時(shí),用實(shí)參初始化形參,系統(tǒng)自動(dòng)調(diào)用拷貝構(gòu)造函數(shù)
因?yàn)閜rint_1()函數(shù)的形參為student對(duì)象,所以調(diào)用它時(shí),用s2初始化形參時(shí),會(huì)調(diào)用拷貝構(gòu)造函數(shù)生成一個(gè)函數(shù)內(nèi)的臨時(shí)對(duì)象,這個(gè)臨時(shí)對(duì)象那個(gè)隨著函數(shù)的調(diào)用結(jié)束也就被析構(gòu)了。
- 第三種情況:函數(shù)的返回值是類對(duì)象,函數(shù)返回時(shí)調(diào)用拷貝構(gòu)造函數(shù)
在g()函數(shù)中,初始化s1對(duì)象時(shí)調(diào)用普通構(gòu)造函數(shù),打印"Constructing…";因?yàn)楹瘮?shù)返回值是類對(duì)象,所以會(huì)調(diào)用拷貝構(gòu)造函數(shù)返回一個(gè)新的匿名對(duì)象,這時(shí)候會(huì)執(zhí)行拷貝構(gòu)造函數(shù),打印"Copy Constructing…";g()運(yùn)行結(jié)束后,s1被析構(gòu),打印"Destructing…";接下來用g()返回的匿名對(duì)象賦值給s2,接下來匿名對(duì)象任務(wù)完成,就會(huì)被析構(gòu),打印"Destructing…"。
參考博客:
(copy)賦值構(gòu)造函數(shù)的4種調(diào)用時(shí)機(jī)or方法
C++返回值為對(duì)象時(shí)復(fù)制構(gòu)造函數(shù)不執(zhí)行怎么破
總結(jié)
以上是生活随笔為你收集整理的拷贝构造函数的调用以及浅拷贝与深拷贝的理解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我的旷厂实习经历
- 下一篇: 利用python编写设计多线程web服务