目錄
運算符重載
運算符重載的特性
其他運算符重載的實現
默認成員函數——賦值運算符重載
默認成員函數——取地址操作符重載
const成員
附錄
運算符重載
C++為了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數名的函數
,也具有其返回值類型
,函數名字
以及參數列表
。
運算符重載實際上就如同函數重載,使操作符擁有新的功能。
結構:
返回值類型 operator操作符(參數列表)
例如
bool operator==(Date d1,Date d2);
定義一個Date類
class Date
{
public://構造函數Date(int year = 0, int month = 0, int day = 0){//判斷日期是否合法//GetMonthDay()獲取這個月的天數if (month > 0 && month < 13 &&(day > 0 && day <= GetMonthDay(year, month))){_year = year;_month = month;_day = day;}else{cout << "日期非法" << endl;}}
private:int _year;//年int _month;//月int _day;//日
};
我們都知道==
是用來比較的運算符,Date
類對象進行比較該怎么比較呢?我們可以規定,如果兩個對象的年、月、日
都相當則兩個對象相等,返回true
。
錯誤示例
class Date
{
public://構造函數Date(int year = 0, int month = 0, int day = 0){if (month > 0 && month < 13&& (day > 0 && day <= GetMonthDay(year, month))){_year = year;_month = month;_day = day;}}bool operator==(Date d1, Date d2){return (d1._year == d2._year) && (d1._month == d2._month) && (d1._day == d2._day);}
private:int _year;int _month;int _day;};
二元運算符
的重載函數的參數
有兩個,規定第一個參數
為左操作數
,第二個參數
為右操作數
。
還記得成員函數有什么特性嗎?成員函數有一個自帶的參數this
,類型為類類型
。因為我們不可能將this
指針刪掉,所以只能省略第一個參數
。
為減少拷貝引起的消耗,盡量使用引用的方式傳參
。
正確的做法
class Date
{
public://...bool operator==(const Date& d)//若不改變形參最好用const修飾{return (_year == d._year) && (_month == d._month) && (_day == d._day);}//...
};
運算符重載的特性
運算符重載有如下特性:
- 重載操作符必須有一個類類型參數;
- 不能通過連接其他符號來創建新的操作符:比如operator@、operator?等;
- 用于內置類型的運算符,其含義不能改變,例如:int類型的+,不能改變其含義;
- 作為類成員函數重載時,其形參看起來比操作數數目少1,因為成員函數的第一個參數為隱藏的this;
- .* :: sizeof ?: .注意以上5個運算符不能重載。這個經常在筆試選擇題中出現。
其他運算符重載的實現
有了上述的==
作為示例,我們還可以實現< > <= >= + - ++ --
等一系列操作符的重載。
<? ? >? ? ?<=? ? >=? ? ? != 重載
class Date
{
public://構造函數//...bool operator==(const Date& d){return (_year == d._year) && (_month == d._month) && (_day == d._day);}bool operator<(const Date& d) {return _year < d._year|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day);}bool operator<=(const Date& d) {//函數的復用return *this < d || *this == d;}bool operator>(const Date& d) {//函數的復用return !(*this <= d);}bool operator>=(const Date& d) {//函數的復用return !(*this < d);}bool operator!=(const Date& d) {//函數的復用return !(*this == d);}
//...
};
+=? ?-=? ? ?+? ? ?-
注意:下列四個運算符的右操作數都為天數
。
class Date
{
public://...//獲取當月的天數int GetMonthDay(int year, int month) {assert(month > 0 && month < 13);int monthArray[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;}else{return monthArray[month];}}//+= 返回自身的引用,減少拷貝Date& operator+=(int day){//判斷是否加了負數if (day < 0){//復用*this -= -day;return *this;}_day += day;while (_day > GetMonthDay(_year, _month));{_day -= GetMonthDay(_year, _month);//進位_month++;if (_month == 13){_year++;_month = 1;}}return *this;}//-= 返回自身的引用,減少拷貝Date& operator-=(int day){//判斷是否減了一個負數if (day < 0){//復用*this += -day;return *this;}_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;}Date operator+(int day) {//拷貝構造//因為加不改變自身的值,所以創建臨時對象Date tmp(*this);//復用tmp += day;return tmp;}Date operator-(int day){Date tmp(*this);tmp -= day;return tmp;}
//...
};
前置++ 與 后置++ 重載
前置+
+和后置++
都是一元運算符,為了讓前置++與后置++能形成正確重載,C++規定:
后置++
重載時多增加一個int類型的參數
,但調用函數時該參數不用傳遞
,編譯器自動傳遞
。
class Date
{
public://...//前置++Date& operator++(){*this += 1;return *this;}//后置++// 注意:后置++是先使用后+1,因此需要返回+1之前的舊值,// 故需在實現時需要先將this保存一份,然后給this + 1// 而temp是臨時對象,因此只能以值的方式返回,不能返回引用Date operator++(int){Date tmp(*this);*this += 1;return tmp;}//前置--Date& operator--(){*this -= 1;return *this;}//后置--Date operator--(int){Date tmp(*this);*this -= 1;return tmp;}
//...
};
日期 - 日期的實現
日期+日期
沒有意義,但是日期-日期
有意義,日期-日期
代表相距多少天
。
class Date
{
//...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 (min != max){++min;++n;}return n * flag;}//...
}
<<? ? >>? 重載
錯誤示例
class Date
{
//...//使用因為返回,為了適應連續輸入或輸出的情況ostream& operator<<(ostream& out){out << _year << "年" << _month << "月" << _day << "日" << endl;return out;}istream& operator>>(istream& in){in >>_year >>_month >>_day;return in;}//...
}
>> <<?
是二元操作符,上文中提到二元操作符第一個參數為左操作數,第二個參數為右操作數。此時這段代碼第一個參數為this
,也就意味著左操作數變成了對象
,右操作數變成了cout
。那么我們使用時只能這樣寫:
void Test2()
{Date d1(2023, 4, 1);d1 << cout;
}
雖然能滿足需求,但是用起來感覺怪怪的
。由于我們無法改變this
的位置,所以只能使用其它辦法來實現<< >>
的重載了。
這里我們只能將重載定義在類的外面才能避開this
的影響。但是類外的函數又訪問不了類的私有成員
。我們只能通過將重載函數設置為類友元函數
來實現了。
class Date
{
//...//申明友元函數friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);//...
}
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日";return out;
}istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
默認成員函數——賦值運算符重載
與之前講的構造函數與析構函數等默認成員函數相同,賦值運算符重載也屬于6個默認成員函數之一。
1. 作為與眾不同的默認成員函數,其有以下特性:
- 賦值運算符重載格式:
- 參數類型:const T&,傳遞引用可以提高傳參效率;
- 返回值類型:T&,返回引用可以提高返回的效率,有返回值目的是為了支持連續賦值檢測是否自己給自己賦值;
- 返回*this :要復合連續賦值的含義;
賦值重載
class Date
{
//...Date& operator=(const Date& d){if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;}//...
}
2. 賦值運算符只能重載成類的成員函數不能重載成全局函數
;
錯誤示例
class Date
{//...
};
// 賦值運算符重載成全局函數,注意重載成全局函數時沒有this指針了,需要給兩個參數
Date& operator=(Date& left, const Date& right)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
此種情況會出現編譯錯誤error C2801: “operator =”必須是非靜態成員。
- 出錯原因是:賦值運算符如果不顯式實現,編譯器會生成一個默認的。此時用戶再在類外自己實現一個全局的賦值運算符重載,就和編譯器在類中生成的默認賦值運算符重載沖突了,故賦值運算符重載只能是類的成員函數。
3. 用戶沒有顯式實現時,編譯器會生成一個默認賦值運算符重載,以值的方式逐字節拷貝。注意:內置類型成員變量是直接賦值的,而自定義類型成員變量需要調用對應類的賦值運算符重載完成賦值。
這里賦值重載與拷貝構造函數的特性非常相似。
?
默認成員函數——取地址操作符重載
6個默認成員函數只剩兩個——取地址重載與const取地址重載
。但是,這兩個函數實在沒有實現的必要,因為我們自己實現與編譯器自動實現出來的效果是一樣的。
class Date
{//...Date* operator&(){return this;}const Date* operator&()const{return this;}//...
};
const成員
將const修飾的成員函數稱之為const成員函數,const修飾類成員函數,實際修飾該成員函數隱含的this指針,表明在該成員函數中不能對類的任何成員進行修改。
什么情況下需要用const修飾?
我們可能暫時感受不到const修飾的作用,但是遇到如下情況,const修飾就非常有必要了。
class Date
{
public://...void print(){cout << _year << "年" << _month << "月" << _day << "日" << endl;}
private:int _year;int _month;int _day;
}void Test3()
{Date d1(2023, 4, 1);d1.print();const Date d2(2022, 3, 1);d2.print();
}
報錯內容為:“void Date::print(void)”: 不能將“this”指針從“const Date”轉換為“Date &”。
這里是典型的權限放大錯誤,我們不能將const Date* &d2傳遞給形參Date* this。
改正的辦法為同樣用const修飾this,但具體的寫法可不像我們想的那樣。
void print() const
{cout << _year << "年" << _month << "月" << _day << "日" << endl;
}
因為我們無法顯式的修改thi
s,所以C++規定在函數的后面加上const
即為修飾this
。
附錄
我們總結上文中的運算符重載,整理一下完整的日期類的實現。此處我們使用多文件的形式實現—>
Date.h
文件中進行頭文件包含
、命名空間展開
、類的聲明
、內聯函數定義
等;Date.cpp
文件中進行對類成員函數的定義。
#define _CRT_SECURE_NO_DEPRECATE 1
#include<iostream>
#include<assert.h>
using namespace std;// 類里面短小函數,適合做內聯的函數,直接是在類里面定義的
class Date
{// 友元函數聲明friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);public:Date(int year = 0, int month = 0, int day = 0);void Print() const;int GetMonthDay(int year, int month) const;bool operator==(const Date& d) const;bool operator!=(const Date& d) const;bool operator<(const Date& d) const;bool operator<=(const Date& d) const;bool operator>(const Date& d) const;bool operator>=(const Date& d) const;Date& operator+=(int day);Date operator+(int day) const;Date& operator-=(int day);Date operator-(int day) const;int operator-(const Date& d) const;Date& operator=(const Date& d);//前置++Date& operator++();// 后置++// int參數 僅僅是為了占位,跟前置重載區分Date operator++(int);// 前置--Date& operator--();// 后置--Date operator--(int);//取地址重載Date* operator&();const Date* operator&() const;private:int _year;int _month;int _day;
};inline ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日";return out;
}inline istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
#define _CRT_SECURE_NO_DEPRECATE 1
#include"Date.h"
//構造函數
Date::Date(int year, int month , int day)
{//判斷日期是否合法if (month > 0 && month < 13 &&(day > 0 && day <= GetMonthDay(year, month))){_year = year;_month = month;_day = day;}else{cout << "日期非法" << endl;}
}bool Date::operator==(const Date& d) const
{return (_year == d._year) && (_month == d._month) && (_day == d._day);
}bool Date::operator<(const Date& d) const
{return _year < d._year|| (_year == d._year && _month < d._month)|| (_year == d._year && _month == d._month && _day < d._day);
}bool Date::operator<=(const Date& d) const
{//函數的復用return *this < d || *this == d;
}bool Date::operator>(const Date& d) const
{//函數的復用return !(*this <= d);
}bool Date::operator>=(const Date& d) const
{//函數的復用return !(*this < d);
}bool Date::operator!=(const Date& d) const
{//函數的復用return !(*this == d);
}//獲取當月的天數
int Date::GetMonthDay(int year, int month) const
{assert(month > 0 && month < 13);int monthArray[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;}else{return monthArray[month];}
}//+= 返回自身的引用
Date& Date::operator+=(int day)
{//判斷是否加了負數if (day < 0){//復用*this -= -day;return *this;}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);//進位_month++;if (_month == 13){_year++;_month = 1;}}return *this;
}Date& Date::operator-=(int day)
{//判斷是否減了一個負數if (day < 0){//復用*this += -day;return *this;}_day -= day;while (_day <= 0){--_month;if (_month == 0){--_year;_month = 12;}_day += GetMonthDay(_year, _month);}return *this;
}Date Date::operator+(int day) const
{//拷貝構造//因為加不改變自身的值,所以創建臨時對象Date tmp(*this);//復用tmp += day;return tmp;
}Date Date::operator-(int day) const
{Date tmp(*this);tmp -= day;return tmp;
}//前置++
Date& Date::operator++()
{*this += 1;return *this;
}//后置++
// 注意:后置++是先使用后+1,因此需要返回+1之前的舊值,
// 故需在實現時需要先將this保存一份,然后給this + 1
// 而temp是臨時對象,因此只能以值的方式返回,不能返回引用
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}//前置--
Date& Date::operator--()
{*this -= 1;return *this;
}//后置--
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}int Date::operator-(const Date& d) const
{Date max = *this;Date min = d;int flag = 1;if (*this < d){max = d;min = *this;flag = -1;}int n = 0;while (min != max){++min;++n;}return n * flag;
}Date& Date::operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}Date* Date::operator&()
{return this;
}
const Date* Date::operator&() const
{return this;
}
?