沈陽(yáng)教做網(wǎng)站市場(chǎng)營(yíng)銷策劃
🔥個(gè)人主頁(yè):Quitecoder
🔥專欄:c++筆記倉(cāng)
朋友們大家好,本篇文章帶大家認(rèn)識(shí)賦值運(yùn)算符重載,const成員,取地址及const取地址操作符重載等內(nèi)容
目錄
- 1.賦值運(yùn)算符重載
- 1.1運(yùn)算符重載
- 1.1.1特性:
- 1.2賦值運(yùn)算符重載
- 1.3 賦值運(yùn)算符的其他性質(zhì)
- 1.4前置++和后置++重載
- 2.const成員函數(shù)
- 3.取地址及const取地址操作符重載
1.賦值運(yùn)算符重載
1.1運(yùn)算符重載
運(yùn)算符重載是一種編程語(yǔ)言特性,它允許開發(fā)者為已有的運(yùn)算符提供自定義的實(shí)現(xiàn)。這意味著你可以改變某些運(yùn)算符在你自定義的類或數(shù)據(jù)類型上的行為。比如,你可以定義加號(hào)運(yùn)算符(+)如何在你自定義的數(shù)據(jù)結(jié)構(gòu)上進(jìn)行運(yùn)算
什么意思呢,我們來(lái)講解:首先我們定義日期類Date,并實(shí)例化兩個(gè)對(duì)象:
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}int _year;int _month;int _day;
};
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);return 0;
}
我們?nèi)绾闻袛鄡蓚€(gè)年份相等呢?
如果是常規(guī)方法,我們會(huì)寫一個(gè)比較函數(shù),來(lái)判斷是否相同:
bool issame(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout << issame(d1, d2) << endl;return 0;
}
那如果我們想直接通過(guò)用d1==d2
來(lái)判斷是否相同呢?這里就用到了操作符重載
運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似,注意這里說(shuō)的重載與我們的函數(shù)重載不是一個(gè)意思
函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號(hào)
bool operator==(const Date& d1, const Date& d2)
{return d1._year == d2._year&& d1._month == d2._month&& d1._day == d2._day;
}
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout << (d1==d2) << endl;return 0;
}
我們發(fā)現(xiàn),直接進(jìn)行判斷時(shí),調(diào)用了比較函數(shù)
但是這里是全局的定義的operator==
,這里會(huì)發(fā)現(xiàn)運(yùn)算符重載成全局的就需要成員變量是公有的,即我的成員不能是private
私有的,那么封裝性如何保證?
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}bool operator==(const Date & d2){return _year == d2._year&& _month == d2._month&& _day == d2._day;}
private:int _year;int _month;int _day;
};
bool operator==(const Date& d2)
{return _year == d2._year&& _month == d2._month&& _day == d2._day;
}
這部分是Date
類中==
運(yùn)算符的重載。這個(gè)重載讓你可以使用==
來(lái)比較兩個(gè)Date
對(duì)象是否相等,即它們的年、月、日是否都相同
關(guān)鍵點(diǎn)講解
- 參數(shù):
operator==
函數(shù)接受一個(gè)類型為const Date&
的參數(shù)d2
,它是比較操作的右側(cè)操作數(shù)。左側(cè)操作數(shù)是調(diào)用這個(gè)函數(shù)的對(duì)象,即this
指針指向的對(duì)象 const
關(guān)鍵字:參數(shù)使用const
修飾符和引用傳遞來(lái)保證效率和避免不必要的拷貝,同時(shí)確保不會(huì)修改傳入的對(duì)象- 函數(shù)體:函數(shù)體中,通過(guò)比較兩個(gè)
Date
對(duì)象的年、月、日字段來(lái)決定這兩個(gè)對(duì)象是否相等。如果所有字段都相等,則返回true
;否則,返回false
我們接著調(diào)用這個(gè)函數(shù):
int main()
{Date d1(2018, 9, 26);Date d2(2024, 3, 29);cout << d1.operator==(d2) << endl;cout << (d1==d2) << endl;return 0;
}
注意
注意這里的順序,d1在前與在后是不同的,如果我們寫一個(gè)小于函數(shù)的運(yùn)算符重載,順序不同,意思剛好相反
我們有兩種方式進(jìn)行調(diào)用,這兩種方式是相同的:
在上面的講解之后,相信大家對(duì)運(yùn)算符重載有了一定的了解,他就是允許自定義對(duì)象使用運(yùn)算符,它的返回值是根據(jù)運(yùn)算符來(lái)決定的,比如完成加減操作,我們就返回int類型,判斷是否大于小于,就用bool類型
1.1.1特性:
- 不能通過(guò)連接其他符號(hào)來(lái)創(chuàng)建新的操作符:比如operator@
- 重載操作符必須有一個(gè)類類型參數(shù)(自定義類型參數(shù))
- 用于內(nèi)置類型的運(yùn)算符,其含義不能改變,例如:內(nèi)置的整型+,不能改變其含義作為類成員函數(shù)重載時(shí),其形參看起來(lái)比操作數(shù)數(shù)目少1,因?yàn)槌蓡T函數(shù)的第一個(gè)參數(shù)為隱藏的this
.*
::
sizeof
?:
.
注意以上5個(gè)運(yùn)算符不能重載。這個(gè)經(jīng)常在筆試選擇題中出現(xiàn)
1.2賦值運(yùn)算符重載
我們知道,拷貝賦值有兩種,拷貝構(gòu)造和賦值重載,我們看拷貝構(gòu)造:
Date d1(2018, 9, 26);
Date d2(d1);
那如果我們用賦值運(yùn)算符重載呢?可以寫成下面的形式:
d2=d1;
關(guān)鍵區(qū)別:
- 拷貝構(gòu)造函數(shù)在對(duì)象創(chuàng)建時(shí)使用,用于初始化新對(duì)象。賦值運(yùn)算符重載在對(duì)象已存在時(shí)使用,用于將一個(gè)對(duì)象的值賦給另一個(gè)對(duì)象
- 目的:拷貝構(gòu)造函數(shù)的目的是創(chuàng)建一個(gè)新的、狀態(tài)相同的對(duì)象副本。賦值運(yùn)算符的目的是改變一個(gè)已存在對(duì)象的狀態(tài),使其與另一個(gè)對(duì)象的狀態(tài)相同
- 拷貝構(gòu)造函數(shù)通常接收一個(gè)對(duì)同類對(duì)象的常引用。賦值運(yùn)算符重載通常返回對(duì)象的引用,并接收一個(gè)對(duì)同類對(duì)象的常引用作為參數(shù)
我們接下來(lái)講解賦值運(yùn)算符重載的具體實(shí)現(xiàn)來(lái)體現(xiàn)上面的特點(diǎn):
能不能直接這么寫呢?:
void operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;
}
這個(gè)在單個(gè)賦值是可以的,那如果,我想像c語(yǔ)言一樣同時(shí)實(shí)現(xiàn)多個(gè)變量的連續(xù)賦值的場(chǎng)景呢?
int b;
int c;
b=c=10;
那我們這個(gè)函數(shù)就無(wú)法滿足要求了,我們?cè)撊绾涡薷哪?#xff1f;
我們不妨探討連續(xù)賦值的本質(zhì):
b=c=10;
這里執(zhí)行步驟:
- 10賦值給c,
c=10
這個(gè)表達(dá)式返回值為左操作數(shù)c - c再作為
b=c
的有操作數(shù)給b賦值,返回值為左操作數(shù)b
所以,我們的自定義類型也要符合這里的行為
所以,我們需要對(duì)函數(shù)進(jìn)行修改:
第一次修改:
Date operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}
返回左操作數(shù),返回*this
我們這里用的是傳值返回,意味著這里返回的不是*this
,返回的是*this
的拷貝,則需要調(diào)用拷貝構(gòu)造函數(shù):
所以我們需要再次修改:
第二次修改:
Date& operator=(const Date& d)
{_year = d._year;_month = d._month;_day = d._day;return *this;
}
我們返回引用
還有一個(gè)問(wèn)題,如果自身給自身賦值呢?
d1=d1;
為什么自賦值不行?
自賦值在大多數(shù)情況下是可以工作的,但是在特定的情況下,如果沒有正確處理,它可能會(huì)引起錯(cuò)誤或意外的行為??紤]自賦值的主要原因是為了確保當(dāng)對(duì)象賦值給自身時(shí),程序仍然能夠正確、安全地運(yùn)行
特別是在類中涉及到動(dòng)態(tài)內(nèi)存管理時(shí),不正確處理自賦值可能會(huì)導(dǎo)致問(wèn)題。例如,假設(shè)一個(gè)類內(nèi)部分配了動(dòng)態(tài)內(nèi)存,如果在賦值操作中首先釋放了這塊內(nèi)存(預(yù)備重新分配),而源對(duì)象和目標(biāo)對(duì)象實(shí)際上是同一個(gè)對(duì)象,那么這個(gè)操作實(shí)際上會(huì)破壞源對(duì)象的狀態(tài),導(dǎo)致未定義行為
我們還需要再次修改一次:
Date& operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}
我們這里判斷條件是地址的比較,如果地址不相同說(shuō)明不是同一個(gè)對(duì)象,可以賦值
1.3 賦值運(yùn)算符的其他性質(zhì)
賦值運(yùn)算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)
我們首先得把成員類型設(shè)置為公有的,發(fā)現(xiàn)還是報(bào)錯(cuò),
原因:賦值運(yùn)算符如果不顯式實(shí)現(xiàn),編譯器會(huì)生成一個(gè)默認(rèn)的。此時(shí)用戶再在類外自己實(shí)現(xiàn)一個(gè)全局的賦值運(yùn)算符重載,就和編譯器在類中生成的默認(rèn)賦值運(yùn)算符重載沖突了,故賦值運(yùn)算符重載只能是類的成員函數(shù)
如果我們不寫賦值運(yùn)算符重載,編譯器是否會(huì)默認(rèn)生成呢?
結(jié)果是會(huì)生成的
所以這里與我們拷貝構(gòu)造等函數(shù)性質(zhì)一致:
用戶沒有顯式實(shí)現(xiàn)時(shí),編譯器會(huì)生成一個(gè)默認(rèn)賦值運(yùn)算符重載,以值的方式逐字節(jié)拷貝。注意:內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對(duì)應(yīng)類的賦值運(yùn)算符重載完成賦值
既然編譯器生成的默認(rèn)賦值運(yùn)算符重載函數(shù)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己實(shí)現(xiàn)嗎?
答案是需要的,如遇到下面的動(dòng)態(tài)內(nèi)存管理
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申請(qǐng)空間失敗");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2;s2 = s1;return 0;
}
- s1對(duì)象調(diào)用構(gòu)造函數(shù)創(chuàng)建,在構(gòu)造函數(shù)中,默認(rèn)申請(qǐng)了10個(gè)元素的空間,然后存了4個(gè)元素1 2 3 4
- s2對(duì)象調(diào)用構(gòu)造函數(shù)創(chuàng)建,在構(gòu)造函數(shù)中,默認(rèn)申請(qǐng)了10個(gè)元素的空間,沒有存儲(chǔ)元素
- 由于Stack沒有顯式實(shí)現(xiàn)賦值運(yùn)算符重載,編譯器會(huì)以淺拷貝的方式實(shí)現(xiàn)一份默認(rèn)的賦值運(yùn)算符重載即只要發(fā)現(xiàn)Stack的對(duì)象之間相互賦值,就會(huì)將一個(gè)對(duì)象中內(nèi)容原封不動(dòng)拷貝到另一個(gè)對(duì)象中
- s2 = s1;當(dāng)s1給s2賦值時(shí),編譯器會(huì)將s1中內(nèi)容原封不動(dòng)拷貝到s2中,這樣會(huì)導(dǎo)致兩個(gè)問(wèn)題:
- s2原來(lái)的空間丟失了,存在內(nèi)存泄漏
- s1和s2共享同一份內(nèi)存空間,最后銷毀時(shí)會(huì)導(dǎo)致同一份內(nèi)存空間釋放兩次而引起程序崩潰
注意
如果類中未涉及到資源管理,賦值運(yùn)算符是否實(shí)現(xiàn)都可以;一旦涉及到資源管理則必須要實(shí)現(xiàn)。
1.4前置++和后置++重載
在C++中,前置++和后置++運(yùn)算符都可以被重載,以提供用戶定義類型(比如類)的自增功能。它們之間的主要區(qū)別在于參數(shù)和返回值,這影響了它們的使用和效率
前置++
前置++直接對(duì)對(duì)象進(jìn)行自增操作,并返回自增后的對(duì)象引用。這意味著它在自增后立即返回對(duì)象的狀態(tài),使得操作可以立即反映在對(duì)象上
Date& operator++(){_day += 1;return *this;}
后置++
前置++和后置++都是一元運(yùn)算符,為了讓前置++與后置++形成能正確重載C++規(guī)定:后置++重載時(shí)多增加一個(gè)int類型的參數(shù),但調(diào)用函數(shù)時(shí)該參數(shù)不用傳遞,編譯器自動(dòng)傳遞
注意:后置++是先使用后+1,因此需要返回+1之前的舊值,故需在實(shí)現(xiàn)時(shí)需要先將this保存一份,然后給this+1
Date operator++(int){Date temp(*this);_day += 1;return temp;}
temp是臨時(shí)對(duì)象,因此只能以值的方式返回,不能返回引用
2.const成員函數(shù)
假如我們現(xiàn)在定義一個(gè)const對(duì)象,想訪問(wèn)它的Print函數(shù),我們發(fā)現(xiàn)是調(diào)用不了的:
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
這里權(quán)限進(jìn)行放大了,接著,我們來(lái)介紹const成員函數(shù)
原來(lái)是const Date*
,而我的this類型是Date*
,意味著需要將this指針也改為const Date*
,所以才有了下面的函數(shù)
將const修飾的“成員函數(shù)”稱之為const成員函數(shù),const修飾類成員函數(shù),實(shí)際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對(duì)類的任何成員進(jìn)行修改,內(nèi)容是只讀的
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print(){cout << "Print()" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; // 年int _month; // 月int _day; // 日
};
void Test()
{Date d1(2022,1,13);d1.Print();const Date d2(2022,1,13);d2.Print();
}
我們查看結(jié)果:
如果沒有const修飾的函數(shù)呢,我Date類型的對(duì)象能否調(diào)用const成員函數(shù)呢?
class Date
{
public:Date(int year, int month, int day){_year = year;_month = month;_day = day;}void Print() const{cout << "Print()const" << endl;cout << "year:" << _year << endl;cout << "month:" << _month << endl;cout << "day:" << _day << endl << endl;}
private:int _year; int _month; int _day;
};
void main()
{Date d1(2022, 1, 13);d1.Print();
}
可以的,這里是權(quán)限的縮小
請(qǐng)思考下面的幾個(gè)問(wèn)題:
- const對(duì)象可以調(diào)用非const成員函數(shù)嗎? 不可以,權(quán)限放大
- 非const對(duì)象可以調(diào)用const成員函數(shù)嗎? 可以,權(quán)限縮小
- const成員函數(shù)內(nèi)可以調(diào)用其它的非const成員函數(shù)嗎? 不可以,權(quán)限放大
- 非const成員函數(shù)內(nèi)可以調(diào)用其它的const成員函數(shù)嗎?可以,權(quán)限縮小
指針和引用才存在權(quán)限放大
3.取地址及const取地址操作符重載
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date* operator&(){return this;}const Date* operator&()const{return this;}
private:int _year; // 年int _month; // 月int _day; // 日
};
這兩個(gè)運(yùn)算符一般不需要重載,使用編譯器生成的默認(rèn)取地址的重載即可,只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容!
這里是默認(rèn)成員函數(shù),我們刪去這兩個(gè)函數(shù)照樣可以取地址
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}
private:int _year; // 年int _month; // 月int _day; // 日
};
所以,我們沒有必要深究這個(gè)東西究竟有什么用,我們只進(jìn)行簡(jiǎn)單的語(yǔ)法了解即可
當(dāng)然,如果你想讓它普通對(duì)象定義后只能返回空值,你可以這么寫:
class Date
{
public:Date(int year = 1, int month = 1, int day = 1){_year = year;_month = month;_day = day;}Date* operator&(){return nullptr;}
private:int _year; int _month; int _day;
};
日常并沒有這種需要
本節(jié)內(nèi)容到此結(jié)束!!!感謝大家閱讀!!