C++修煉之拷貝構(gòu)造函數(shù)
??文章導讀
本章主要內(nèi)容為6個默認成員函數(shù)之一的拷貝構(gòu)造函數(shù)的認識與學習,充分理解淺拷貝與深拷貝。
??拷貝構(gòu)造函數(shù)的概念
還記得上一章中提到的6個默認成員函數(shù)嗎?當我們定義好一個類,不做任何處理時,編譯器會自動生成以下6個默認成員函數(shù)
:
默認成員函數(shù)
:如果用戶沒有手動實現(xiàn),則編譯器會自動生成
的成員函數(shù)。
同樣,拷貝構(gòu)造函數(shù)
也屬于6個默認成員函數(shù),而且拷貝構(gòu)造函數(shù)
是構(gòu)造函數(shù)
的一種重載形式
。
- 拷貝構(gòu)造函數(shù)的功能就如同它的名字——拷貝。
我們可以用一個已存在的對象來創(chuàng)建一個與已存在對象一模一樣的新的對象
。
??舉例??
class Date { public: //構(gòu)造函數(shù) Date() { cout << "Date()" << endl; } //拷貝構(gòu)造函數(shù) Date(const Date& d) { cout << "Date()" << endl; _year = d._year; _month = d._month; _day = d._day; } //析構(gòu)函數(shù) ~Date() { cout << "~Date()" << endl; } private: int _year = 0; int _month = 0; int _day = 0; }; void TestDate() { Date d1; //調(diào)用拷貝構(gòu)造創(chuàng)建對象 Date d2(d1); }
??拷貝構(gòu)造函數(shù)的特性
拷貝構(gòu)造函數(shù)作為特殊的成員函數(shù)同樣也有異于常人的特性:
- 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的重載;
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個且必須是
類類型對象的引用
。若使用傳值
的方式,則編譯器會報錯,因為理論上這會引發(fā)無窮遞歸
。
??錯誤示例??
class Date { public: //錯誤示例 //如果這樣寫,編譯器就會直接報錯,但我們現(xiàn)在假設(shè)如果編譯器不會檢查, //這樣的程序執(zhí)行起來會發(fā)生什么 Date(const Date d) { _year = d._year; _month = d._month; _day = d._day; } private: int _year = 0; int _month = 0; int _day = 0; }; void TestDate() { Date d1; //調(diào)用拷貝構(gòu)造創(chuàng)建對象 Date d2(d1); }
- 當拷貝構(gòu)造函數(shù)的參數(shù)采用
傳值
的方式時,創(chuàng)建對象d2
,會調(diào)用它的拷貝構(gòu)造函數(shù)
,d1
會作為實參
傳遞給形參d
。不巧的是,實參傳遞給形參本身又是一個拷貝
,會再次調(diào)用形參的拷貝構(gòu)造函數(shù)…如此便會引發(fā)無窮的遞歸。
- 若未顯式定義,編譯器會生成默認的拷貝構(gòu)造函數(shù)。 默認的拷貝構(gòu)造函數(shù)對象按
內(nèi)存存儲按字節(jié)序
完成拷貝,這種拷貝叫做淺拷貝
或者值拷貝
;
??舉例??
class Date { public: //構(gòu)造函數(shù) Date(int year = 0, int month = 0, int day = 0) { //cout << "Date()" << endl; _year = year; _month = month; _day = day; } //未顯式定義拷貝構(gòu)造函數(shù) /*Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; }*/ void print() { cout << _year << " - " << _month << " - " << _day << endl; } private: int _year = 0; int _month = 0; int _day = 0; }; void TestDate() { Date d1(2023, 3, 31); //調(diào)用拷貝構(gòu)造創(chuàng)建對象 Date d2(d1); d2.print(); }
- 有的小伙伴可能會有疑問:編譯器默認生成的拷貝構(gòu)造函數(shù)貌似可以很好的完成任務,那么還需要我們手動來實現(xiàn)嗎?
答案是:當然需要。Date
類只是一個較為簡單的類且類成員都是內(nèi)置類型
,可以不需要。但是當類中含有自定義類型時
,編譯器可就辦不了事兒了。
- 類中如果沒有涉及資源申請時,拷貝構(gòu)造函數(shù)是否寫都可以;一旦涉及到資源申請時,則拷貝構(gòu)造函數(shù)是一定要寫的,否則就是淺拷貝;
??錯誤示例??
class stack { public: stack(int defaultCapacity=10) { _a = (int*)malloc(sizeof(int)*defaultCapacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = defaultCapacity; } ~stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } void push(int n) { _a[_top++] = n; } void print() { for (int i = 0; i < _top; i++) { cout << _a[i] << " "; } cout << endl; } private: int* _a; int _top; int _capacity; }; void TestStack() { stack s1; s1.push(1); s1.push(2); s1.push(3); s1.push(4); s1.print(); stack s2(s1); s2.print(); s2.push(5); s2.push(6); s2.push(7); s2.push(8); s2.print(); }
如圖所示,這段程序的運行結(jié)果是程序崩潰了
,且通過觀察發(fā)現(xiàn),是在第二次析構(gòu)
時出現(xiàn)了錯誤。其實出現(xiàn)錯誤的原因是在第二次析構(gòu)時對野指針
進行free
了。
??一個小tip??
- 多個對象進行析構(gòu)的順序如同
棧
一樣,先創(chuàng)建的對象后析構(gòu),后創(chuàng)建的對象先析構(gòu)
。
為什么會出現(xiàn)對野指針進行free呢?
- 原因是,對象
s1
與對象s2
中的成員_a
,指向的是同一塊空間
。在s2
析構(gòu)完成后,這塊空間已經(jīng)被釋放
,此時的s1._a
就是野指針
。這就是淺拷貝導致的后果。
??理解淺拷貝??
編譯器默認生成的拷貝構(gòu)造函數(shù)是按字節(jié)序拷貝的,在創(chuàng)建s2
對象時,僅僅是把s1._a
的值賦值給s2._a
,并沒有重新開辟一塊與s1._a所指向的空間大小相同內(nèi)容相同的空間
。我們把前者
的拷貝方式稱為淺拷貝
,后者
稱為深拷貝
當開啟監(jiān)視窗口來觀察這一過程,我們可以看到s2
在進行push
時,s1
的內(nèi)容也在跟著改變,且s1._a=s2._a
:
??正確的做法??
class stack { public: stack(int defaultCapacity=10) { _a = (int*)malloc(sizeof(int)*defaultCapacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } _top = 0; _capacity = defaultCapacity; } //用戶自己定義拷貝構(gòu)造函數(shù) stack(const stack& s) { _a= (int*)malloc(sizeof(int) * s._capacity); if (_a == nullptr) { perror("malloc fail"); exit(-1); } memcpy(_a, s._a, sizeof(int) * s._capacity); _top = s._top; _capacity = s._capacity; } ~stack() { cout << "~Stack()" << endl; free(_a); _a = nullptr; _top = _capacity = 0; } void push(int n) { _a[_top++] = n; } void print() { for (int i = 0; i < _top; i++) { cout << _a[i] << " "; } cout << endl; } private: int* _a; int _top; int _capacity; };
- 拷貝構(gòu)造函數(shù)典型調(diào)用場景:
- 使用已存在對象創(chuàng)建新對象;
- 函數(shù)參數(shù)類型為類類型對象;
- 函數(shù)返回值類型為類類型對象。
為了提高程序效率
,一般對象傳參時,盡量使用引用類型
,返回時根據(jù)實際場景,能用引用盡量使用引用
。
本章的內(nèi)容到這里就結(jié)束了,下一章我們將學習運算符重載與取地址操作符的重載~ 覺得內(nèi)容有用的話就支持一下吧~
到此這篇關(guān)于C++修煉之拷貝構(gòu)造函數(shù)的文章就介紹到這了,更多相關(guān)C++拷貝構(gòu)造函數(shù)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
關(guān)于win32 gettimeofday替代方案
下面小編就為大家?guī)硪黄P(guān)于win32 gettimeofday替代方案。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-12-12C++實現(xiàn)LeetCode(160.求兩個鏈表的交點)
這篇文章主要介紹了C++實現(xiàn)LeetCode(160.求兩個鏈表的交點),本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細內(nèi)容,需要的朋友可以參考下2021-07-07C語言sizeof和strlen的指針和數(shù)組面試題詳解
strlen是函數(shù),字符串長度,不包括停止符。而sizeof則是內(nèi)存塊的大小,包括停止符。數(shù)組是一種數(shù)據(jù)類型,數(shù)據(jù)類型的本質(zhì)就是固定大小,內(nèi)存塊的別名??梢杂胹izeof()一般都是數(shù)據(jù)類型2022-04-04