前言
本文將以日期類為基礎(chǔ),去探尋運算符重載的特性與使用方法,下面先給出日期類的基礎(chǔ)定義:
class Date
{
public:
Date::Date(int year, int month, int day)
{
if (month > 0 && month <= 12
&& day > 0 && day <= GetDay(year, month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
assert(false);
}
}
private:
int _year;//年
int _month;//月
int _day;//日
};
備注:拷貝構(gòu)造函數(shù)和析構(gòu)函數(shù),均可以不寫,因為當(dāng)前日期類的三個成員變量都是內(nèi)置類型,沒有動態(tài)申請空間,使用淺拷貝就可以。
一、運算符重載
??如何比較兩個日期的大???
int main()
{
Date d1(2023, 7, 21);
Date d2(2023, 6, 21);
return 0;
}
現(xiàn)如今,定義了兩個日期類的對象d1
和d2
,該如何比較這兩個對現(xiàn)象的大小呢?首先想到的是,寫一個函數(shù)來比較他倆的大小,向下面這樣:
//以小于比較為例
bool Less(const Date& x, const Date& y)
{
if (x._year > y._year)
{
return false;
}
else if (x._year == y._year && x._month > y._month)
{
return false;
}
else if (x._year == y._year && x._month == y._month && x._day > y._day)
{
return false;
}
else
{
return true;
}
}
存在的問題:首先這個函數(shù)是寫在類外面的,意味著,日期類的成員變量如果是private
私有的話,在類外面就無法訪問,所以在這個函數(shù)里面是訪問不到對象的年、月、日這三個成員變量,即x._year
等都是非法的,要想實現(xiàn)該函數(shù)的功能,日期類的成員變量必須是public
公有。
其次,在比較兩個日期類對象大小的時候,需要寫成Less(d1, d2)
,這和我們平時直接用<
符號比較大小,比起來不夠直觀。
??為什么日期類不能直接使用<
因為日期類是我們自己定義的,屬于一種自定義類型,它的大小比較方式,只有定義它的人知道,而像int
、double
等內(nèi)置類型,是祖師爺創(chuàng)造C++語言時就定好的,祖師爺當(dāng)然知道該如何比較兩個內(nèi)置類型變量的大小,所以提前幫我們設(shè)置好了,我們可以直接用<
去比較兩個內(nèi)置類型變量的大小,而至于祖師爺是怎么設(shè)置的,這里先埋一個伏筆。
??運算符重載
為了解決上面Less
函數(shù)存在的問題,C++引入了運算符重載,它可以讓我們直接使用<
來比較兩個日期類的大小。
運算符重載是具有特殊函數(shù)名的函數(shù),也具有返回值類型,函數(shù)名字、參數(shù)列表、返回值類型都和普通函數(shù)類似。
-
函數(shù)名字:關(guān)鍵字
operator
后面接需要重載的運算符符號。 - 函數(shù)原型:返回值類型 operator操作符(參數(shù)列表)
bool operator<(const Date& x, const Date& y)
{
if (x._year > y._year)
{
return false;
}
else if (x._year == y._year && x._month > y._month)
{
return false;
}
else if (x._year == y._year && x._month == y._month && x._day > y._day)
{
return false;
}
else
{
return true;
}
}
上面就是對<
運算符的一個重載,它的兩個形參是Data
類型的引用,此時兩個日期類對象就可以直接用<
來比較大小啦,d1 < d2
本質(zhì)上就是調(diào)用運算符重載函數(shù),但是由于上面的運算符重載函數(shù)還是寫在類外面,所以當(dāng)日期類的成員變量是private
私有的時候,該運算符重載函數(shù)還是用不了。
//下面兩條語句是等價的本質(zhì)都是調(diào)用運算符重載函數(shù)
d1 < d2;
operator<(d1, d2);//d1 < d2的本質(zhì)
??將運算符重載函數(shù)寫成成員函數(shù)
為了解決上面的私有成員變量在類外面無法訪問的問題,可以把運算符重載函數(shù)寫成類的成員函數(shù)或者友元,這樣就能訪問到私有的成員變量,但是友元一般不建議使用,因為友元會破壞封裝。
bool operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month < d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
上面就是把<
運算符重載成類的成員函數(shù),此時參數(shù)只有一個,因為<
是一個雙目運算符,類的非靜態(tài)成員函數(shù)有一個隱藏的形參this
指針,所以形參就只需要一個。
//它們倆是等價的
d1 < d2;
d1.operator<(d2);//d1 < d2的本質(zhì)
小Tips:一個雙目運算符如果重載成類的成員函數(shù),會把它的左操作數(shù)傳給第一個形參,把右操作數(shù)傳給第二個形參。以上面為例,this
指針接收的是d1
的地址,d
接收的是d2
。
??注意事項:
- 不能通過連接其他符號來創(chuàng)建新的運算符:比如
operator@
。 - 重載操作符必須有一個類類型參數(shù)。
- 用于內(nèi)置類型的運算符,其含義不能改變,例如:內(nèi)置的
+
,不能改變其含義。 - 作為類成員函數(shù)重載時,其形參看起來比操作數(shù)數(shù)目少1,因為成員函數(shù)的第一個參數(shù)為隱藏的
this
。 -
.*
、::
、sizeof
、? :
、.
這五個運算符不能重載。
二、賦值運算符重載
??區(qū)分賦值運算符重載和拷貝構(gòu)造
Date d1(2020, 5, 21);
Date d2(2023, 6, 21);
d1 = d2;//需要調(diào)用賦值運算符重載
Date d3 = d1;//這里是調(diào)用拷貝構(gòu)造函數(shù)
//Date d3(d1);//和上一行等價調(diào)用拷貝構(gòu)造
要區(qū)分賦值運算符重載和拷貝構(gòu)造,前者是針對兩個已存在的對象,將一個對象的值,賦值給另一個,而后者是用一個已存在的對象去初始化創(chuàng)建一個新對象。
賦值運算符重載格式:
-
參數(shù)類型:
const T&
(T是類型),傳引用返回可以提高效率。 -
返回值類型:
T&
,返回引用可以提高效率,有返回值目的是為了支持連續(xù)賦值。 - 檢測是否自己給自己賦值。
-
返回
*this
:要符合連續(xù)賦值的含義。
Date& operator=(const Data& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;//出了作用域*this還在,所以可以用引用返回
}
??只能是類的成員函數(shù)
上面的<
運算符,最開始我們是在類外面把它重載成全局的,后來為了保證類的封裝性,才把它重載成類的成員函數(shù),而賦值運算符天生只能重載成類的成員函數(shù),因為賦值運算符重載屬于類的默認成員函數(shù),我們不寫,編譯器會自動生成,所以,如果我們把賦值運算符重載寫在類外面,就會和編譯器生成的默認賦值運算符重載發(fā)生沖突。
??編譯器生成的干了些什么工作?
用戶沒有顯式實現(xiàn)時,編譯器生成的默認賦值運算符重載,對內(nèi)置類型的成員變量是以值的方式逐字節(jié)進行拷貝(淺拷貝),對自定義類型的成員變量,調(diào)用其對應(yīng)類的賦值運算符重載。
三、完善日期類
有了上面的基礎(chǔ),接下來完善一下日期類,重載其他的運算符。
3.1 重載關(guān)系運算符
關(guān)系運算符有<
、>
、==
、<=
、>=
、!=
,由于它們之間存在的邏輯關(guān)系,可以通過復(fù)用來實現(xiàn),即:要想知道a
是否大于b
,可以通過判斷a
是否小于等于b
來實現(xiàn)。因此,我們只要寫一個<
和==
的比較邏輯,其他的直接復(fù)用即可。
??重載<
bool operator<(const Date& d)
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month < d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
??重載==
bool Date::operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
??重載<=
bool Date::operator<=(const Date& d)
{
return *this < d || *this == d;
}
??重載>
bool Date::operator>(const Date& d)
{
return !(*this <= d);
}
??重載>=
bool Date::operator>=(const Date& d)
{
return !(*this < d);
}
??重載!=
bool Date::operator!=(const Date& d)
{
return !(*this == d);
}
3.2 重載+、+=
有時我們需要知道幾天之后的日期,比如我想知道100天后的日期,此時就需要用當(dāng)前的日期加上100,但是一個日期類型和一個整型可以相加嘛?答案是肯定的,可以通過重載+
來實現(xiàn)。運算符重載只規(guī)定必須有一個類類型參數(shù),并沒有說重載雙目操作符必須要兩個類型一樣的參數(shù)。
??獲取某月的天數(shù)
日期加天數(shù),要實現(xiàn)日期的進位,即:當(dāng)當(dāng)前日期是這個月的最后一天時,再加一天月份就要進一,當(dāng)當(dāng)前的日期是12月31日時,再加一天年份就要進一,因此可以先實現(xiàn)一個函數(shù),用來獲取當(dāng)前月份的天數(shù),在每加一天后,判斷月份是否需要進位。
int GetDay(int year, int month)//獲取某一月的天數(shù)
{
static int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
{
return 29;
}
return arr[month];
}
除了2月,每個月的天數(shù)都是固定的,因此可以設(shè)置一個數(shù)組來存放每個月的天數(shù),并且以月份作為下標(biāo),對應(yīng)存儲該月的天數(shù),這種方法類似于哈希映射。這里還有兩個小細節(jié),第一個:把數(shù)組設(shè)置成靜態(tài),因為這個函數(shù)會重復(fù)調(diào)用多次,把數(shù)組設(shè)置成靜態(tài),它第一次創(chuàng)建之后,一直到程序結(jié)束都還在,可以避免函數(shù)調(diào)用時重復(fù)的創(chuàng)建數(shù)組。第二點:把month == 2
放在前面判斷,因為只有當(dāng)2月的時候才需要判斷是否是閏年,如果不是2月就不用判斷是不是閏年。
??重載+
Date Date::operator+(int x)
{
if(x < 0)//天數(shù)為負的時候
{
return *this - (-x);//復(fù)用-
}
/Date tmp = *this;
//Date tmp(*this);//和上面等價,都是調(diào)用拷貝構(gòu)造函數(shù)
tmp._day = _day + x;
while (tmp._day > GetDay(tmp._year, tmp._month))
{
tmp._day = tmp._day - GetDay(tmp._year, tmp._month);
tmp._month++;
if (tmp._month == 13)
{
tmp._year++;
tmp._month = 1;
}
}
return tmp;//
}
注意:要計算a+b
的結(jié)果,a
是不能改變的,因此一個日期加天數(shù),不能改變原本的日期,也就是不能修改this
指針指向的內(nèi)容,所以我們要先利用拷貝構(gòu)造函數(shù)創(chuàng)建一個和*this
一模一樣的對象,對應(yīng)上面代碼中的tmp
,在該對象的基礎(chǔ)上去加天數(shù)。出了作用域tmp
對象會銷毀,所以不能傳引用返回。
??重載+=
+=
和+
很像,區(qū)別在于+=
是在原來是日期上進行修改,即直接對this
指針指向的日期做修改,所以我們對上面的代碼稍作修改就可以得到+=
。
Date& Date::operator+=(int x)
{
if (x < 0)//當(dāng)天數(shù)為負
{
return *this -= -x;//復(fù)用-=
}
_day += x;
while (_day > GetDay(_year, _month))
{
_day = _day - GetDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
小Tips:加一個負的天數(shù),就是算多少天以前的日期,所以,當(dāng)天數(shù)為負的時候,可以復(fù)用下面的-=
。
??+
和+=
之間的復(fù)用
可以發(fā)現(xiàn),+
和+=
的實現(xiàn)方法十分相似,那是否可以考慮復(fù)用呢?答案是肯定的,他倆其中的一方都可以去復(fù)用另一方。
+
去復(fù)用+=
:
Date Date::operator+(int x)
{
/Date tmp = *this;
//Date tmp(*this);//和上面等價,都是調(diào)用拷貝構(gòu)造函數(shù)
tmp += x;
return tmp;//
}
+=
去復(fù)用+
:
Date& Date::operator+=(int x)
{
*this = *this + x;//這里是調(diào)用賦值運算符重載
return *this;
}
注意:上面的兩種復(fù)用,只能存在一個,不能同時都去復(fù)用,同時存在會出現(xiàn)你調(diào)用我,我調(diào)用你的死穴。
既然只能存在一個,那到底該讓誰去復(fù)用呢?答案是:讓+
去復(fù)用+=
。因為,+=
原本的實現(xiàn)過程中并沒有調(diào)用拷貝構(gòu)造去創(chuàng)建新的對象,而+
原本的實現(xiàn)過程中,會去調(diào)用拷貝構(gòu)造函數(shù)創(chuàng)建新的對象,并且是以值傳遞的方式返回的,期間又會調(diào)用拷貝構(gòu)造。如果讓+=
去復(fù)用+
,原本還無需調(diào)用拷貝構(gòu)造,復(fù)用后反而還要調(diào)用拷貝構(gòu)造創(chuàng)建新對象,造成了沒必要的浪費。
3.3 重載-、-=
有時我們也需要知道,多少天以前的日期,此時就需要重載-
,它的兩個操作數(shù)分別是日期和天數(shù),其次,我們有時還想知道兩個日期之間隔了多少天,這也需要重載-
,但此時的兩個操作數(shù)都是日期。兩個-
重載構(gòu)成了函數(shù)重載。
??重載日期-
天數(shù)
有了上面的經(jīng)驗,我們可以先重載-=
,再讓-
去復(fù)用-=
即可,日期減天數(shù),就是要實現(xiàn)日期的借位。
Date Date::operator-(int x)
{
Date tmp(*this);
return tmp -= x;//復(fù)用-=
}
??重載-=
Date& operator-=(int x)
{
if (x < 0)//天數(shù)天數(shù)小于0
{
return *this += -x;//復(fù)用+=
}
_day -= x;
while (_day <= 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += GetDay(_year, _month);
}
return *this;
}
??重載日期-
日期
日期-
日期,它的形參是一個日期對象,計算的結(jié)果是兩個日期之間的天數(shù),所以返回值是int
,要像知道兩個日期之間相隔的天數(shù),可以設(shè)置一個計數(shù)器,讓小日期一直加到大日期,就可以知道兩個日期之間相隔的天數(shù)。
int operator-(const Date& d)
{
Date max = *this;//存放大日期
Date min = d;//存放小日期
int flag = 1;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int n = 0;
while (max != min)
{
--max;
++n;
}
return n * flag;
}
3.4 重載++、--
++
、--
操作符,無論前置還是后置,都是一元運算符,為了讓前置和后置形成正確的重載,C++規(guī)定:后置重載的時候多增加一個int
類型的參數(shù),但是當(dāng)使用后置,調(diào)用運算符重載函數(shù)時該參數(shù)不用傳遞,編譯器自動傳遞。
??重載前置++
//前置++,返回++之后的值
Date& Date::operator++()
{
return *this += 1;//直接復(fù)用+=
}
??重載后置++
//后置++,返回加之前的值
Date Date::operator++(int)//編譯器會把有int的視為后置++
{
Date tmp(*this);
*this += 1;//復(fù)用+=
return tmp;
}
??重載前置--
Date& operator--()
{
return *this -= 1;//復(fù)用了-=
}
??重載后置--
Date operator--(int)
{
Date tmp(*this);
*this -= 1;//復(fù)用了-=
return tmp;
}
對比前置和后置可以發(fā)現(xiàn),后置會調(diào)用兩次拷貝構(gòu)造函數(shù),一次在創(chuàng)建tmp
的時候,另一次在函數(shù)返回的時候。而前置則沒有調(diào)用拷貝構(gòu)造,所以前置的效率相比后置會高那么一點。
3.5 重載<<、>>
同理,對于自定義類型,編譯器仍然不知道如何打印,所以要想通過<<
去直接打印日期類對象,需要我們對<<
運算符進行重載。
??重識cout
、cin
我們在使用C++進行輸入輸出的時候,會用到cin
和cout
,它們倆本質(zhì)上都是對象,cin
是istream
類實例化的對象,cout
是ostream
類實例化的對象。
內(nèi)置類型可以直接使用<<
、>>
,本質(zhì)上是因為庫中進行運算符重載。而<<
、>>
不用像C語言的printf
和scanf
那樣,int
對應(yīng)%d
,float
對應(yīng)%f
,是因為運算符重載本質(zhì)上是函數(shù),對這些不同的內(nèi)置類型,分別進行了封裝,在運算符重載的基礎(chǔ)上又實現(xiàn)了函數(shù)重載,所以<<
、>>
支持自動識別類型。
??<<
為什么不能重載成成員函數(shù)
要實現(xiàn)對日期類的<<
,要對<<
進行重載。但是<<
和其他的運算符有所不同,上面重載的所有運算符,為了保證類的封裝性,都重載成了類的成員函數(shù),但是<<
不行,因為我們平時的使用習(xí)慣是cout << d1
,前面說過,對于一個雙目運算符的重載,它的左操作數(shù)會傳遞給運算符重載函數(shù)的第一個形參,右操作數(shù)會傳遞給運算符重載函數(shù)的第二個形參,也就是說cout
會傳遞給第一個形參,日期類對象d2
會傳遞給第二個形參,如果運算符重載函數(shù)是類的成員函數(shù)的話,那么它的第一個形參是默認的this
指針,該指針是日期類類型的指針,和cout
的類型不匹配,當(dāng)然也有解決辦法,那就是輸出一個日期類對象的時候,寫成d1 << cout
,此時就相當(dāng)于d1.operator(cout)
,會把d1
的地址傳給this
指針,形參再用一個ostream
類型的對象來接收cout
即可,但是這樣的使用方式,顯然是不合常理的。
??將<<
重載成全局函數(shù)
正確的做法是,把<<
重載成全局函數(shù),此時函數(shù)形參就沒有默認的this
指針,我們可以根據(jù)需要來設(shè)置形參的順序,第一個形參用ostream
類對象來接收cout
,第二個形參用Date
日期類對象來接收d1
。
//重載成全局的
ostream& operator<< (ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
注意:形參out
不能加const
修飾,因為我們就是要往out
里面寫東西,加了const
意味著out
不能修改。其次為了實現(xiàn)連續(xù)的輸出,返回值是ostream
類型的對象out
,因為此時出了作用域out
還在,所以可以用引用返回。
因為該運算符重載函數(shù)寫在全局,默認情況下,在該函數(shù)內(nèi)部是無法訪問到日期類的私有成員變量,為了解決這個問題,可以把該運算符重載函數(shù)設(shè)置成友元函數(shù),或者在類里面寫私有成員變量的Get
方法(Java常用)。
friend ostream& operator<< (ostream& out, Date& d);
友元函數(shù)只需要配合上friend
關(guān)鍵字,在日期類里面加上一條聲明即可,此時在該函數(shù)體就可以使用對象中的私有成員變量。該聲明不受類中訪問限定符的限制。
??重載>>
同理,>>
也應(yīng)該重載成全局的。
istream& operator>> (istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
注意:兩個形參in
和d
都不能用const
修飾,前者是因為in
本質(zhì)上是一個對象,在進行流插入的時候,會改變對象里面的一些狀態(tài)值,而后者是因為,我們就是希望通過流插入往d
里面寫入數(shù)據(jù),所以也不能加const
修飾。
小Tips:C++中的流插入和流提取可以完美的支持自定義類型的輸入輸出,而C語言的scanf
和printf
只能支持內(nèi)置類型,這就是C++相較于C語言的一個優(yōu)勢。
四、const成員
將const修飾的成員函數(shù)稱為const成員函數(shù),const修飾類的成員函數(shù),實際上修飾的是該成員函數(shù)隱含的*this
,表明該成員函數(shù)中不能修改調(diào)用該函數(shù)的對象中的任何成員。這樣一來,不僅普通對象可以調(diào)用該成員函數(shù)(權(quán)限的縮?。?,const
對象也能調(diào)用該成員函數(shù)(權(quán)限的平移)。經(jīng)過const
修飾的成員函數(shù),它的形參this
的類型就是:const T* const this
。
bool Date::operator<(const Date& d) const//用const修飾
{
if (_year < d._year)
{
return true;
}
else if (_year == d._year && _month < d._month)
{
return true;
}
else if (_year == d._year && _month < d._month && _day < d._day)
{
return true;
}
else
{
return false;
}
}
對于所有的關(guān)系運算符重載函數(shù),都應(yīng)該加const
修飾,因為它們不會改變對象本身。
??總結(jié):
并不是所有的成員函數(shù)都要加const
修飾,要修改對象成員變量的函數(shù),是不能加const
修飾的,例如:重載的+=
、-=
等,而成員函數(shù)中如果沒有修改對象的成員變量,可以考慮加上const
修飾,這樣不僅普通對象可以調(diào)用該成員函數(shù)(權(quán)限的縮?。?,const
對象也能調(diào)用該成員函數(shù)(權(quán)限的平移)。
五、取地址及const取地址操作符重載
Date* operator&()
{
cout << "Date* operator&()" << endl;
return this;
}
const Date* operator&() const
{
cout << "const Date* operator&() const" << endl;
return this;
}
int main()
{
Date d1(2023, 7, 22);
const Date d2(2023, 7, 22);
cout << &d1 << endl;
cout << "--------" << endl;
cout << &d2 << endl;
return 0;
}
這倆取地址運算符重載函數(shù),又構(gòu)成函數(shù)重載,因為它們的默認形參this
指針的類型不同,一個用const
修飾了,另一個沒有。const
對象會去調(diào)用const
修飾的取地址運算符重載函數(shù)。
小Tips:這兩個&
重載,屬于類的默認成員函數(shù),我們不寫編譯器會自動生成,所以這兩個運算符重載一般不需要重載,使用編譯器生成的默認取地址的重載即可,只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容。文章來源:http://www.zghlxwxcb.cn/news/detail-598277.html
??結(jié)語:
?今天的分享到這里就結(jié)束啦!如果覺得文章還不錯的話,可以三連支持一下,您的支持就是春人前進的動力!文章來源地址http://www.zghlxwxcb.cn/news/detail-598277.html
到了這里,關(guān)于【C++雜貨鋪】運算符重載的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!