日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

拷贝构造函数的调用以及浅拷贝与深拷贝的理解

發(fā)布時間:2025/3/21 编程问答 22 豆豆
生活随笔 收集整理的這篇文章主要介紹了 拷贝构造函数的调用以及浅拷贝与深拷贝的理解 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

???????今天一直在研究拷貝構造函數相關的東西,我這個大四老狗感覺又回到了大一學C++的時候。瞎搗鼓了一天,略微還是有些收獲的,趁著腦子中的概念正熱,把自己的心得趕緊整理出來分享給大家。

????????首先簡單介紹下拷貝構造函數的概念:拷貝構造函數是形參是本類對象的引用的構造函數,它的一般聲明形式諸如這樣:

Location(const Location & obj)

其實我們經常在類中并未顯示地定義類的拷貝構造函數,即當缺省拷貝構造函數時,這個時候系統(tǒng)會自動生成一個默認的拷貝構造函數,把成員值一一復制。這種缺省拷貝構造函數的情況很常見,一般也不會給程序帶來問題,但是需要注意的是,如果一個類需要析構函數來釋放資源,那么它需要定義一個顯式拷貝構造函數來實現(xiàn)深拷貝,關于這一點,本文后面會詳細說明緣由。

一、拷貝構造函數的調用

???????接下來說一下調用拷貝構造函數的三種情況:

  • 當用類的一個對象去初始化該類的另一個對象時。

  • 如果函數的形參是類的對象,調用函數時用實參的值初始化形參時。

  • 如果函數的返回值是類的對象函數執(zhí)行完成返回調用者時。

    附上代碼,在示例代碼中,覆蓋了拷貝構造函數的三種情況。

#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]; //申請堆空間if (name != 0) strcpy(name, c);}// 顯示聲明拷貝函數,自定義實現(xiàn)深拷貝student(const student& s) { std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成員簡單復制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申請堆空間if (name != 0) strcpy(name, s.name); //復制字符串};~student() {// std::cout <<"Destructing..."<< std::endl;delete []name;}int getid() {return id;}private:int id;char* name;int age;float score; };// 函數返回值是類對象,函數執(zhí)行完成返回調用時,系統(tǒng)會自動調用拷貝構造函數 student g() {student s1(10, "Lina", 18, 86);return s1; }// 如果函數的形參是類的對象,調用函數時,用實參的值初始化形參時,系統(tǒng)會自動調用拷貝構造函數 void print_1(student s) {std::cout << "學號:" << s.getid() << std::endl; }int main() { student s1(10, "Wang", 18, 86); //創(chuàng)建和初始化對象std::cout << std::endl;// 第一種情況,用s1初始化s2。第一次調用拷貝構造函數student s2 = s1;std::cout << std::endl;// 第二種情況,對象s2作為print_1的實參。第二次調用拷貝構造函數print_1(s2);std::cout << std::endl;// 第三種情況,函數的返回值是類對象,函數返回時調用拷貝構造函數s2 = g();std::cout << std::endl;return 0; }

需要提及的是,對于第三種情況,在IDE上運行時,并沒有打印出 "Copy Constructing…" 這個結果:

Copy Constructing...Copy Constructing... 學號:10

看到打印出這個結果時,我也是很蒙圈的,搜索了網上的問答了解到這是編譯器做了優(yōu)化,例如我這邊是gcc編譯器,它做了優(yōu)化,使得返回值為對象時,不會再產生臨時對象,因而不會再調用拷貝構造函數。那么如果想要看到結果需要怎么做呢,因為我是linux系統(tǒng),此處就簡單說下Linux的處理方式,在終端輸入:

g++ -w -fno-elide-constructors test.cpp -o test

這時再在終端運行,打印出理想的結果:

Copy Constructing...Copy Constructing... 學號:10Copy Constructing...

二、淺拷貝和深拷貝

????????完成簡單的一一對應的復制的拷貝構造函數稱為淺拷貝。在淺拷貝過程中,如果對象中含有指針變量,將使得所復制的對象中的指針成員與原來對象的指針成員指向相同的內存空間。在退出程序時,析構函數會釋放指針成員指向的內存空間,譬如用對象a初始化對象b,調用拷貝構造函數實現(xiàn)淺拷貝后,程序退出運行時,析構對象a時,析構函數釋放其指針成員指向的內存空間,而析構復制對象b時系統(tǒng)會調用相同的析構函數,也要釋放其指針成員指向的內存空間。則出現(xiàn)同一內存空間,申請一次,釋放兩次的情況, new 與 delete不成對,系統(tǒng)會報錯。附上簡單的示例圖:

給出淺拷貝的代碼: #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]; //申請堆空間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)建和初始化對象student s2 = s1;return 0; }

此處用s1初始化s2時,調用了拷貝構造函數,因為缺省,默認用了淺拷貝,那么退出時,調用析構函數,因為同一內存空間釋放了兩次,系統(tǒng)會報錯。正確的方法是定義一個顯式的拷貝構造函數實現(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]; //申請堆空間if (name != 0) strcpy(name, c);}// 顯示聲明拷貝函數,自定義實現(xiàn)深拷貝student(const student& s) { std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成員簡單復制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申請堆空間if (name != 0) strcpy(name, s.name); //復制字符串};~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)建和初始化對象student s2 = s1;return 0; } 這樣的話便成功解決了內存空間多次釋放報錯的問題。

三、普通構造函數、拷貝構造函數、析構函數的調用分析

??????????????前面兩部分把拷貝構造函數已經分析清楚了,接下來就依據上面的程序簡要分析下普通構造函數、拷貝構造函數、析構函數的調用,普通構造函數簡單地說就是非拷貝構造函數范疇的構造函數,析構函數則是用于當對象生存期結束時調用它釋放對象所占的內存。普通構造函數以及析構函數如果程序中沒有顯示定義的話,系統(tǒng)也會提供一個默認的析構函數,需要注意的是默認的析構函數只能用來釋放對象的數據成員所占用的空間,但不包括堆內存空間。在本例中,為了方便觀測,普通構造函數、拷貝構造函數、析構函數在程序中均顯示地定義,下面看下示例代碼:

#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]; //申請堆空間if (name != 0) strcpy(name, c);}// 顯示聲明拷貝函數,自定義實現(xiàn)深拷貝student(const student& s) { std::cout << "Copy Constructing..." << std::endl;id = s.id; //一般成員簡單復制age = s.age;score = s.score;name = new char[strlen(s.name) + 1]; //先申請堆空間if (name != 0) strcpy(name, s.name); //復制字符串};~student() {std::cout << "Destructing..." << std::endl;delete []name;}int getid() {return id;}private:int id;char* name;int age;float score; };// 函數返回值是類對象,函數執(zhí)行完成返回調用時,系統(tǒng)會自動調用拷貝構造函數 student g() {student s1(10, "Lina", 18, 86);return s1; }// 如果函數的形參是類的對象,調用函數時,用實參的值初始化形參時,系統(tǒng)會自動調用拷貝構造函數 void print_1(student s) {std::cout << "學號:" << s.getid() << std::endl; }int main() { student s1(10, "Wang", 18, 86); //創(chuàng)建和初始化對象std::cout << std::endl;// 第一種情況,用s1初始化s2。第一次調用拷貝構造函數student s2 = s1;std::cout << std::endl;// 第二種情況,對象s2作為print_1的實參。第二次調用拷貝構造函數print_1(s2);std::cout << std::endl;// 第三種情況,函數的返回值是類對象,函數返回時調用拷貝構造函數s2 = g();std::cout << std::endl;return 0; } Constructing...Copy Constructing...Copy Constructing... 學號:10 Destructing...Constructing... Copy Constructing... Destructing... Destructing...Destructing... Destructing...

看一下打印的結果,發(fā)現(xiàn)對于拷貝構造函數調用的第二種及第三種情況,既打印了"Copy Constructing…“又打印了"Destructing…”,情況較為復雜,下面仔細地分析下:

  • 第二種情況:函數的形參為類對象,調用函數時,用實參初始化形參,系統(tǒng)自動調用拷貝構造函數
// 如果函數的形參是類的對象,調用函數時,用實參的值初始化形參時,系統(tǒng)會自動調用拷貝構造函數 void print_1(student s) {std::cout << "學號:" << s.getid() << std::endl; }// 第二種情況,對象s2作為print_1的實參。第二次調用拷貝構造函數 print_1(s2); std::cout << std::endl; Copy Constructing... 學號:10 Destructing...

因為print_1()函數的形參為student對象,所以調用它時,用s2初始化形參時,會調用拷貝構造函數生成一個函數內的臨時對象,這個臨時對象那個隨著函數的調用結束也就被析構了。

  • 第三種情況:函數的返回值是類對象,函數返回時調用拷貝構造函數
// 函數返回值是類對象,函數執(zhí)行完成返回調用時,系統(tǒng)會自動調用拷貝構造函數 student g() {student s1(10, "Lina", 18, 86);return s1; }// 第三種情況,函數的返回值是類對象,函數返回時調用拷貝構造函數 s2 = g(); std::cout << std::endl; Constructing... Copy Constructing... Destructing... Destructing...

在g()函數中,初始化s1對象時調用普通構造函數,打印"Constructing…";因為函數返回值是類對象,所以會調用拷貝構造函數返回一個新的匿名對象,這時候會執(zhí)行拷貝構造函數,打印"Copy Constructing…";g()運行結束后,s1被析構,打印"Destructing…";接下來用g()返回的匿名對象賦值給s2,接下來匿名對象任務完成,就會被析構,打印"Destructing…"。


參考博客:
(copy)賦值構造函數的4種調用時機or方法
C++返回值為對象時復制構造函數不執(zhí)行怎么破

總結

以上是生活随笔為你收集整理的拷贝构造函数的调用以及浅拷贝与深拷贝的理解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。