C++拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載詳解
一,拷貝構(gòu)造函數(shù)
1. 什么是拷貝構(gòu)造函數(shù)
拷貝構(gòu)造函數(shù)是特殊的構(gòu)造函數(shù)。是用一個(gè)已經(jīng)存在的對(duì)象,賦值拷貝給另一個(gè)新創(chuàng)建的已經(jīng)存在的對(duì)象。
本質(zhì):用同類型的對(duì)象拷貝初始化。
2. 拷貝構(gòu)造函數(shù)的特性
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征如下:
2.1 拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
2.2 拷貝構(gòu)造函數(shù)的函數(shù)名域類名相同,參數(shù)只有一個(gè)且必須是類類型對(duì)象的引用,使用傳值方式編譯器直接報(bào)錯(cuò),因?yàn)樵谡Z(yǔ)法上引發(fā)無(wú)窮遞歸調(diào)用。
注意:
Date d2(d1); 這句代碼也等價(jià)于Date d2 = d1;也是拷貝構(gòu)造的寫(xiě)法。
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//拷貝構(gòu)造函數(shù)
//參數(shù)只有一個(gè),必須是類類型對(duì)象的引用。
//Date d2(d1);
Date(Date& d) //傳引用,正確寫(xiě)法
//Date(Date d) //傳值,錯(cuò)誤寫(xiě)法
{
//用來(lái)檢測(cè)是否調(diào)用該拷貝構(gòu)造函數(shù)
cout << "Date(Date& d)" << endl;
//d1是d的別名,隱含的this就是d2,相當(dāng)于把d1的值拷貝給d2
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1(2024,4,21);
d1.Print();
//d2對(duì)象不在按指定年月日初始化,而是想和d1對(duì)象初始化一樣
//拷貝構(gòu)造:用同類型的對(duì)象拷貝初始化
Date d2(d1);//這句代碼也等價(jià)于Date d2 = d1;也是拷貝構(gòu)造的寫(xiě)法
d2.Print();
return 0;
}
如果是傳值的方式,如上述代碼中的錯(cuò)誤寫(xiě)法,程序會(huì)直接報(bào)錯(cuò)。

這是為什么呢?
這是因?yàn)樽远x類型傳值傳參要調(diào)用拷貝構(gòu)造,而內(nèi)置類型就是直接拷貝。
通過(guò)下面的代碼來(lái)側(cè)面說(shuō)明:
定義一個(gè) func 函數(shù),把類對(duì)象 d1 傳值過(guò)去,運(yùn)行的結(jié)果是調(diào)用func之前會(huì)先調(diào)用拷貝構(gòu)造函數(shù)。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
//用來(lái)檢測(cè)是否調(diào)用該拷貝構(gòu)造函數(shù)
cout << "Date(Date& d)" << endl;
//d1是d的別名,隱含的this就是d2,相當(dāng)于把d1的值拷貝給d2
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void func(Date d)
{
d.Print();
}
int main()
{
Date d1(2024, 4, 12);
//調(diào)用func之前會(huì)先進(jìn)入拷貝構(gòu)造函數(shù)
func(d1);
return 0;
}
所以在 2.2 的代碼中,如果進(jìn)行傳值調(diào)用,則在語(yǔ)法邏輯上會(huì)出現(xiàn)如下的無(wú)窮遞歸:

那如何讓它不調(diào)用拷貝構(gòu)造呢?
方式1:傳地址(相當(dāng)于變?yōu)閮?nèi)置類型)
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
//用來(lái)檢測(cè)是否調(diào)用該拷貝構(gòu)造函數(shù)
cout << "Date(Date& d)" << endl;
//d1是d的別名,隱含的this就是d2,相當(dāng)于把d1的值拷貝給d2
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void func(Date* d)
{
d->Print();
}
int main()
{
Date d1(2024, 4, 12);
func(&d1);
return 0;
}
方式2:傳引用(一般都是傳引用)。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
Date(Date& d)
{
//用來(lái)檢測(cè)是否調(diào)用該拷貝構(gòu)造函數(shù)
cout << "Date(Date& d)" << endl;
//d1是d的別名,隱含的this就是d2,相當(dāng)于把d1的值拷貝給d2
_year = d._year;
_month = d._month;
_day = d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
void func(Date& d)//d是d1的別名
{
d.Print();
}
int main()
{
Date d1(2024, 4, 12);
//調(diào)用func之前會(huì)先進(jìn)入拷貝構(gòu)造函數(shù)
func(d1);
return 0;
}
那有些人會(huì)想,拷貝構(gòu)造函數(shù)能不能用指針呢?這能不能避免無(wú)窮遞歸?
答案:可以的。可以完成拷貝,但是此時(shí)這個(gè)函數(shù)就不是拷貝構(gòu)造函數(shù)了,而是一個(gè)普通的構(gòu)造函數(shù)。
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
//用指針就不會(huì)形成無(wú)窮遞歸,但此時(shí)它就是一個(gè)普通構(gòu)造了,不是拷貝構(gòu)造。
//感覺(jué)怪怪的,所以一般用引用
Date(Date* d)
{
_year = d->_year;
_month = d->_month;
_day = d->_day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d2(2024, 4, 21);
Date d3(&d2);
d3.Print();
return 0;
}
說(shuō)明:
雖然傳地址可以避免無(wú)窮遞歸,并且可以完成拷貝,但是這樣怪怪的。在C++中,一般是傳引用。并且傳引用可以減少拷貝,提高了效率。
在拷貝函數(shù)中還有一點(diǎn)就是:在傳引用時(shí)一般要加 const 修飾。
在顯式寫(xiě)拷貝構(gòu)造函數(shù)時(shí),參數(shù)寫(xiě)反了……(這就有點(diǎn)尷尬了)

運(yùn)行結(jié)果是:拷貝一堆隨機(jī)值。

加上 const 之后:

2.3.若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。默認(rèn)的拷貝構(gòu)造函數(shù)對(duì)象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
注意:
在編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)中,內(nèi)置類型是按照字節(jié)方式直接拷貝的,而自定義類型是調(diào)用其拷貝構(gòu)造函數(shù)完成拷貝的。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time(const Time& t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
//檢測(cè)是否調(diào)用了這個(gè)拷貝構(gòu)造函數(shù)
cout << "Time::Time(const Time&)" << endl;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year, int month, int day)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
// 基本類型(內(nèi)置類型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定義類型
Time _t;
};
int main()
{
Date d1(2024, 4, 23);
// 用已經(jīng)存在的d1拷貝構(gòu)造d2,此處會(huì)調(diào)用Date類的拷貝構(gòu)造函數(shù)
// 但Date類并沒(méi)有顯式定義拷貝構(gòu)造函數(shù),則編譯器會(huì)給Date類生成一個(gè)默認(rèn)的拷貝構(gòu)造函數(shù)
Date d2(d1);
d2.Print();
return 0;
}
2.4.編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己顯式實(shí)現(xiàn)嗎?當(dāng)然像日期類這樣的類是沒(méi)必要的。那么下面的類呢?驗(yàn)證一下試試?
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 3)
{
_array = (DataType*)malloc(sizeof(DataType) * capacity);
if (NULL == _array)
{
perror("malloc申請(qǐng)空間失敗!!!");
return;
}
_capacity = capacity;
_size = 0;
}
void Push(DataType data)
{
// CheckCapacity();
_array[_size] = data;
_size++;
}
//注意:如果沒(méi)有顯示寫(xiě)析構(gòu)函數(shù),編譯器也會(huì)自動(dòng)生成。
//自動(dòng)生成的析構(gòu)對(duì)內(nèi)置類型不做處理,自定義類型才會(huì)去調(diào)用它的析構(gòu)
~Stack()
{
if (_array)
{
free(_array);
_array = NULL;
_capacity = 0;
_size = 0;
}
}
private:
DataType* _array;
int _capacity;
int _size;
};
int main()
{
Stack st1;
st1.Push(1);
st1.Push(1);
st1.Push(1);
Stack st2 = st1;
return 0;
}運(yùn)行結(jié)果:完成了拷貝,但程序崩潰!

原因:
當(dāng)棧調(diào)用默認(rèn)生成的拷貝構(gòu)造函數(shù)時(shí),這種函數(shù)進(jìn)行的是淺拷貝(值拷貝) 本質(zhì)是按字節(jié)進(jìn)行拷貝的。這可能會(huì)導(dǎo)致當(dāng)兩個(gè)對(duì)象指向同一塊空間時(shí),如_array,當(dāng)st1 和st2生命周期結(jié)束時(shí),兩個(gè)對(duì)象會(huì)分別析構(gòu),就相當(dāng)于釋放了兩次。 常見(jiàn)的數(shù)據(jù)結(jié)構(gòu),棧,隊(duì)列,鏈表,樹(shù)等都有這個(gè)問(wèn)題。

解決方案:
深拷貝:當(dāng)有指針指向資源時(shí),會(huì)開(kāi)辟建立一塊和要拷貝的一模一樣的空間,形狀,再進(jìn)行拷貝。
//實(shí)現(xiàn)棧的深拷貝
//Stack st2 = st1; //st是st1的別名
Stack(const Stack& st)
{
_array = (DataType*)malloc(sizeof(DataType) * st._capacity);
if (NULL == _array)
{
perror("malloc申請(qǐng)空間失敗!!!");
return;
}
//開(kāi)辟好空間后再把值拷貝進(jìn)去
memcpy(_array, st._array, sizeof(DataType) * st._size);
_size = st._size;
_capacity = st._capacity;
}
把上面棧的深拷貝的代碼插入 2.4 的類中,調(diào)試結(jié)果是:

3. 實(shí)踐總結(jié)
1.如果沒(méi)有管理資源,一般情況下不需要寫(xiě)拷貝構(gòu)造,用編譯器默認(rèn)生成的拷貝構(gòu)造就可以。如 Date類;
2.如果都是自定義類型成員,內(nèi)置類型成員沒(méi)有指向資源,用編譯器默認(rèn)生成的拷貝構(gòu)造就可以。如 MyQueue ;
(小技巧:一般情況下,不需要寫(xiě)析構(gòu)的,就不需要寫(xiě)拷貝構(gòu)造。)
3.如果內(nèi)部有指針或者有一些值指向資源,需要顯式寫(xiě)析構(gòu)函數(shù)釋放,通常就需要顯式寫(xiě)拷貝構(gòu)造完成深拷貝。如各種數(shù)據(jù)類型棧,隊(duì)列,鏈表,樹(shù)等。
二,賦值運(yùn)算符重載
2.1 運(yùn)算符重載
C++為了增強(qiáng)代碼的可讀性引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號(hào)。
函數(shù)原型:返回值類型 operator操作符(參數(shù)列表)
注意:
不能通過(guò)連接其他符號(hào)來(lái)創(chuàng)建新的操作符:比如operator@ ;
重載操作符必須有一個(gè)類類型參數(shù) ;
用于內(nèi)置類型的運(yùn)算符,其含義不能改變,例如:內(nèi)置的整型+,不能改變其含義 (這條僅供參考);
作為類成員函數(shù)重載時(shí),其形參看起來(lái)比操作數(shù)數(shù)目少1,因?yàn)槌蓡T函數(shù)的第一個(gè)參數(shù)為隱藏的this;
. * (調(diào)用成員函數(shù)的指針) , :: (域作用限定符) , sizeof (計(jì)算變量所占內(nèi)存的大小) ,?: (三目運(yùn)算符), . (結(jié)構(gòu)體變量引用符),注意以上5個(gè)運(yùn)算符不能重載。這個(gè)經(jīng)常在筆試選擇題中出現(xiàn)。
簡(jiǎn)單介紹一下 . * 運(yùn)算符的用法:
class OB
{
public:
void func()
{
cout << "void func()" << endl;
}
};
//重新定義成員函數(shù)指針類型
//注意:typedef void(*)() PtrFunc; 是錯(cuò)誤寫(xiě)法
typedef void (OB::* PtrFunc)();
int main()
{
//成員函數(shù)要加&才能取到函數(shù)指針
PtrFunc fp = &OB::func; //定義成員函數(shù)指針fp 指向函數(shù)func
OB tmp;//定義OB類對(duì)象tmp
(tmp.*fp)(); //調(diào)用成員函數(shù)的指針
return 0;
}運(yùn)算符重載的使用:
class Date
{
public:
Date(int year , int month , int day )
{
_year = year;
_month = month;
_day = day;
}
//d3.operator== (d4)
//d3傳給了隱含的this指針,d是d4的別名
bool operator== (const Date & d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
//但是如果全局重載(沒(méi)有private時(shí))和成員函數(shù)同時(shí)存在,編譯不會(huì)報(bào)錯(cuò),
//調(diào)用時(shí)會(huì)默認(rèn)調(diào)成員函數(shù),相當(dāng)于全局重載沒(méi)有任何意義了。
int main()
{
Date d3(2024, 4, 12);
Date d4(2024, 4, 15);
//顯式調(diào)用(一般不這樣寫(xiě))
//cout << d3.operator== (d4) << endl;
//轉(zhuǎn)換調(diào)用 等價(jià)于d3.operator== (d4) 匯編代碼
//注意:如果是兩個(gè)操作數(shù),從左到右的參數(shù)是對(duì)應(yīng)的,不能顛倒位置
cout << (d3 == d4) << endl;
return 0;
}2.2 賦值運(yùn)算符重載
2.2.1 賦值運(yùn)算符重載格式
- 參數(shù)類型:const 類名 &,傳遞引用可以減少拷貝,提高傳參效率;
- 返回值類型:類名 &,返回引用可以減少拷貝,提高返回的效率,有返回值目的是為了支持連續(xù)賦值;
- 檢測(cè)是否自己給自己賦值;
- 返回*this :要復(fù)合連續(xù)賦值的含義.
class Date
{
public:
Date(int year = 2024, int month = 4, int day = 21)
{
_year = year;
_month = month;
_day = day;
}
//拷貝構(gòu)造
Date(const Date& d)
{
cout << "Date(const Date& d)" << endl;
_year = d._year;
_month = d._month;
_day = d._day;
}
//d1 = d3
//void operator= (const Date& d)
//{
// _year = d._year;
// _month = d._month;
// _day = d._day;
//}
//有時(shí)候會(huì)帶返回值:目的是為了連續(xù)拷貝
//d1 = d2 = d3;
//比如這里,d是d3的別名,從右往左,先d2 = d3,返回值是d2
Date& operator= (const Date& d)
{
//檢測(cè)是否自己給自己賦值
if(this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//這里的d2的地址是this ,*this就是d2。
//這里的*this才是對(duì)象本身,對(duì)象在main的作用域
//里創(chuàng)建的,因此出main作用域才會(huì)析構(gòu)銷毀,
//出了當(dāng)前函數(shù)不會(huì)析構(gòu)。所以可以用引用返回。
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
int main()
{
Date d1(2024, 4, 12);
//拷貝構(gòu)造
//一個(gè)已經(jīng)存在的對(duì)象,拷貝給另一個(gè)要?jiǎng)?chuàng)建初始化的對(duì)象
Date d2 = d1;
Date d3(2024, 5, 1);
//賦值拷貝/賦值重載
//一個(gè)已經(jīng)存在的對(duì)象,拷貝賦值給另一個(gè)已經(jīng)存在的對(duì)象
//d1 = d3;
//連續(xù)賦值
d1 = d2 = d3;
return 0;
}傳值返回和傳引用返回的區(qū)別:
class Date
{
public:
Date(int year, int minute, int day)
{
cout << "Date(int,int,int):" << this << endl;
}
Date(const Date& d)
{
cout << "Date(const Date& d):" << this << endl;
}
~Date()
{
cout << "~Date():" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
Date Test(Date d)
{
Date temp(d);
return temp;
}
int main()
{
Date d1(2022,1,13);
Test(d1);
return 0;
}
總結(jié)一下:
如果返回對(duì)象是一個(gè)局部對(duì)象或者臨時(shí)對(duì)象,出了當(dāng)前函數(shù)作用域就析構(gòu)銷毀了,就不能用引用返回。用引用返回是存在風(fēng)險(xiǎn)的,因?yàn)橐脤?duì)象在當(dāng)前函數(shù)棧幀已經(jīng)被銷毀了。所以雖然引用返回可以減少一次拷貝,但是出了函數(shù)作用域,返回對(duì)象還在,才能用引用返回。

2.2.2 賦值運(yùn)算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)
注意:
重載成全局函數(shù)時(shí)就沒(méi)有this指針了。
2.2.3 和拷貝構(gòu)造類似,用戶沒(méi)有顯式實(shí)現(xiàn)時(shí),編譯器會(huì)生成一個(gè)默認(rèn)賦值運(yùn)算符重載,以值的方式逐字節(jié)拷貝。
注意:
內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對(duì)應(yīng)類的賦值運(yùn)算符重載完成賦值。
到此這篇關(guān)于C++拷貝構(gòu)造函數(shù)和賦值運(yùn)算符重載詳解的文章就介紹到這了,更多相關(guān)C++拷貝構(gòu)造函數(shù)和賦值運(yùn)算符內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
c語(yǔ)言實(shí)現(xiàn)足球比賽積分統(tǒng)計(jì)系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了c語(yǔ)言實(shí)現(xiàn)足球比賽積分統(tǒng)計(jì)系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
C實(shí)現(xiàn)與 uint64_t 相同功能的類
本文給大家分享的是筆者實(shí)現(xiàn)的仿uint64_t的類,可以用在不支持uint64_t的平臺(tái)上,雖然現(xiàn)在功能還不完善,但是還是分享給大家,也算是給大家一個(gè)思路吧。2015-12-12
基于C語(yǔ)言編寫(xiě)一個(gè)簡(jiǎn)單的抽卡小游戲
這篇文章主要為大家介紹了如何利用C語(yǔ)言實(shí)現(xiàn)原神抽卡的小游戲,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-04-04
C/C++多參數(shù)函數(shù)參數(shù)的計(jì)算順序與壓棧順序的示例代碼
這篇文章主要介紹了C/C++多參數(shù)函數(shù)參數(shù)的計(jì)算順序與壓棧順序,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06
C語(yǔ)言新手初階教程之三子棋實(shí)現(xiàn)
相信大家在小時(shí)候都用紙和筆與小伙伴們玩過(guò)一個(gè)經(jīng)典的游戲之井字棋,即三子棋,下面這篇文章主要給大家介紹了關(guān)于C語(yǔ)言新手初階教程之三子棋實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下2022-01-01
C++ 打開(kāi)選擇文件夾對(duì)話框選擇目錄的操作
這篇文章主要介紹了C++ 打開(kāi)選擇文件夾對(duì)話框選擇目錄的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-01-01
C語(yǔ)言實(shí)現(xiàn)輸出鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn)
這篇文章主要介紹了C語(yǔ)言實(shí)現(xiàn)輸出鏈表中倒數(shù)第k個(gè)節(jié)點(diǎn),主要涉及鏈表的遍歷操作,是數(shù)據(jù)結(jié)構(gòu)中鏈表的常見(jiàn)操作。需要的朋友可以參考下2014-09-09

