C++修煉之拷貝構(gòu)造函數(shù)
??文章導(dǎo)讀
本章主要內(nèi)容為6個(gè)默認(rèn)成員函數(shù)之一的拷貝構(gòu)造函數(shù)的認(rèn)識(shí)與學(xué)習(xí),充分理解淺拷貝與深拷貝。
??拷貝構(gòu)造函數(shù)的概念
還記得上一章中提到的6個(gè)默認(rèn)成員函數(shù)嗎?當(dāng)我們定義好一個(gè)類,不做任何處理時(shí),編譯器會(huì)自動(dòng)生成以下6個(gè)默認(rèn)成員函數(shù):
默認(rèn)成員函數(shù):如果用戶沒(méi)有手動(dòng)實(shí)現(xiàn),則編譯器會(huì)自動(dòng)生成的成員函數(shù)。

同樣,拷貝構(gòu)造函數(shù)也屬于6個(gè)默認(rèn)成員函數(shù),而且拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一種重載形式。
- 拷貝構(gòu)造函數(shù)的功能就如同它的名字——拷貝。
我們可以用一個(gè)已存在的對(duì)象來(lái)創(chuàng)建一個(gè)與已存在對(duì)象一模一樣的新的對(duì)象。
??舉例??
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)建對(duì)象
Date d2(d1);
}

??拷貝構(gòu)造函數(shù)的特性
拷貝構(gòu)造函數(shù)作為特殊的成員函數(shù)同樣也有異于常人的特性:
- 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的重載;
- 拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是
類類型對(duì)象的引用。若使用傳值的方式,則編譯器會(huì)報(bào)錯(cuò),因?yàn)槔碚撋线@會(huì)引發(fā)無(wú)窮遞歸。
??錯(cuò)誤示例??
class Date
{
public:
//錯(cuò)誤示例
//如果這樣寫,編譯器就會(huì)直接報(bào)錯(cuò),但我們現(xiàn)在假設(shè)如果編譯器不會(huì)檢查,
//這樣的程序執(zhí)行起來(lái)會(huì)發(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)建對(duì)象
Date d2(d1);
}
- 當(dāng)拷貝構(gòu)造函數(shù)的參數(shù)采用
傳值的方式時(shí),創(chuàng)建對(duì)象d2,會(huì)調(diào)用它的拷貝構(gòu)造函數(shù),d1會(huì)作為實(shí)參傳遞給形參d。不巧的是,實(shí)參傳遞給形參本身又是一個(gè)拷貝,會(huì)再次調(diào)用形參的拷貝構(gòu)造函數(shù)…如此便會(huì)引發(fā)無(wú)窮的遞歸。

- 若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按
內(nèi)存存儲(chǔ)按字節(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)建對(duì)象
Date d2(d1);
d2.print();
}

- 有的小伙伴可能會(huì)有疑問(wèn):編譯器默認(rèn)生成的拷貝構(gòu)造函數(shù)貌似可以很好的完成任務(wù),那么還需要我們手動(dòng)來(lái)實(shí)現(xiàn)嗎?
答案是:當(dāng)然需要。Date類只是一個(gè)較為簡(jiǎn)單的類且類成員都是內(nèi)置類型,可以不需要。但是當(dāng)類中含有自定義類型時(shí),編譯器可就辦不了事兒了。
- 類中如果沒(méi)有涉及資源申請(qǐng)時(shí),拷貝構(gòu)造函數(shù)是否寫都可以;一旦涉及到資源申請(qǐng)時(shí),則拷貝構(gòu)造函數(shù)是一定要寫的,否則就是淺拷貝;
??錯(cuò)誤示例??
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();
}

如圖所示,這段程序的運(yùn)行結(jié)果是程序崩潰了,且通過(guò)觀察發(fā)現(xiàn),是在第二次析構(gòu)時(shí)出現(xiàn)了錯(cuò)誤。其實(shí)出現(xiàn)錯(cuò)誤的原因是在第二次析構(gòu)時(shí)對(duì)野指針進(jìn)行free了。
??一個(gè)小tip??
- 多個(gè)對(duì)象進(jìn)行析構(gòu)的順序如同
棧一樣,先創(chuàng)建的對(duì)象后析構(gòu),后創(chuàng)建的對(duì)象先析構(gòu)。
為什么會(huì)出現(xiàn)對(duì)野指針進(jìn)行free呢?
- 原因是,對(duì)象
s1與對(duì)象s2中的成員_a,指向的是同一塊空間。在s2析構(gòu)完成后,這塊空間已經(jīng)被釋放,此時(shí)的s1._a就是野指針。這就是淺拷貝導(dǎo)致的后果。
??理解淺拷貝??
編譯器默認(rèn)生成的拷貝構(gòu)造函數(shù)是按字節(jié)序拷貝的,在創(chuàng)建s2對(duì)象時(shí),僅僅是把s1._a的值賦值給s2._a,并沒(méi)有重新開(kāi)辟一塊與s1._a所指向的空間大小相同內(nèi)容相同的空間。我們把前者的拷貝方式稱為淺拷貝,后者稱為深拷貝

當(dāng)開(kāi)啟監(jiān)視窗口來(lái)觀察這一過(guò)程,我們可以看到s2在進(jìn)行push時(shí),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)用場(chǎng)景:
- 使用已存在對(duì)象創(chuàng)建新對(duì)象;
- 函數(shù)參數(shù)類型為類類型對(duì)象;
- 函數(shù)返回值類型為類類型對(duì)象。
為了提高程序效率,一般對(duì)象傳參時(shí),盡量使用引用類型,返回時(shí)根據(jù)實(shí)際場(chǎng)景,能用引用盡量使用引用。
本章的內(nèi)容到這里就結(jié)束了,下一章我們將學(xué)習(xí)運(yùn)算符重載與取地址操作符的重載~ 覺(jué)得內(nèi)容有用的話就支持一下吧~
到此這篇關(guān)于C++修煉之拷貝構(gòu)造函數(shù)的文章就介紹到這了,更多相關(guān)C++拷貝構(gòu)造函數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
C++ Custom Control控件向父窗體發(fā)送對(duì)應(yīng)的消息
這篇文章主要介紹了C++ Custom Control控件向父窗體發(fā)送對(duì)應(yīng)的消息的相關(guān)資料,需要的朋友可以參考下2015-06-06
詳解C++中String類模擬實(shí)現(xiàn)以及深拷貝淺拷貝
這篇文章主要介紹了詳解C++中String類模擬實(shí)現(xiàn)以及深拷貝淺拷貝的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家實(shí)現(xiàn)這樣的方法,需要的朋友可以參考下2017-10-10
C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單版三子棋
這篇文章主要為大家詳細(xì)介紹了C語(yǔ)言實(shí)現(xiàn)簡(jiǎn)單版三子棋,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10

