C++進一步認識類與對象
賦值操作符重載函數
1.運算符重載
C++為瞭增強代碼的可讀性引入瞭運算符重載,運算符重載是具有特殊函數名的函數,也具有其返回值類型,函數名字以及參數列表,其返回值類型與參數列表與普通的函數類似。
其函數名為: operator + 需要重載的運算符符號(參數列表)。
需要註意的是:
(1)不能通過連接其他符號來創建新的操作符,比如operator$.也就是說,operator隻能重載已有的操作符,其他的符號不能通過該函數來創造。
(2)重載操作符必須有一個類類型或枚舉類型的操作數。
(3)用於內置類型的操作符,其含義不能改變。比如,內置整型的==,不能改變其類型。
(4)運算符重載函數的參數個數為操作符的操作數個數,比如對於==操作符,其操作數由兩個,那麼重載該操作符的函數參數應為2個,即operator==(int x,int y);
(5)對於作為類成員的操作符重載函數,其參數看起來要比操作符的操作數個數少一個,這是因為函數隱含瞭一個形參this,並且this指針被限定為第一個形參。
(6)語法規定,有五個操作符不能被重載,即.*(成員中指針解引用)、::(作用域限定符)、?:(三目操作符)、.(成員(對象)選擇)、sizeof(長度運算符)。這裡需要註意的是.(解引用操作符)可以被重載,不能被重載的操作符為s1.*ptr中的.*操作符,即訪問類中指針成員並解引用。
用我們熟悉的日期類來舉例,比如我們要實現==這個操作符的重載函數:
//頭文件Date.h中 class Date { public: //獲取某年某月的天數 int GetDay(int year, int month) { assert(month > 0 && month < 13); int Day[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 Day[month]; } //全缺省的構造函數 Date(int year = 0, int month = 1, int day = 1) { _year = year; _month = month; _day = day; //判斷初始化的日期是否合理 if (_month >= 13 || _day > GetDay(_year, _month) || _month <= 0 || _day <= 0 || _year < 0) { cout << _year << "/" << _month << "/" << _day << "->"; cout << "非法日期" << endl; } } void Print() { cout << _year << "/" << _month << "/" << _day << endl; } //拷貝構造函數,d1(d2) Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day; } //==運算符重載 bool operator==(const Date& d) const; private: int _year; int _month; int _day; }; //源文件Date.cpp中 //需要註意,左操作數為this指針指向的調用函數的對象, //即函數等價於bool operator==(Date* this, const Date d); bool Date::operator==(const Date& d) const { return this->_year == d._year && this->_month == d._month && this->_day == d._day; }
由於==操作符的結果為真或假,因此函數的返回值設為bool類型。
2.賦值運算符重載
我們知道賦值運算符為=,那麼重載賦值運算符的函數應為:
//Date.cpp中 Date& Date::operator=(const Date& d) { if (this != &d)//排除兩個操作數相同的情況 { _year = d._year; _month = d._month; _day = d._day; } return *this; }
需要註意的是:
(1)首先,為瞭減少形參拷貝導致的開銷,我們用引用作為形參類型;其次,由於函數並不會修改原操作數,因此加上const可以保證代碼的安全性。
(2)這裡的*this即this所指向的對象出瞭這個賦值重載函數並不會銷毀,因此可以用引用返回,返回類型為類名加引用。
(3)如果操作數為兩個相同的對象,回導致賦值操作多餘,因此需要檢查是否出現自己給自己賦值的情況。
3.默認的賦值操作符重載函數
一個類如果沒有顯式定義賦值運算符重載,編譯器也會生成一個,完成對象按字節序的淺拷貝(值拷貝)。
以上面的Date類為例,在我們不實現賦值操作符重載函數的情況下:
int main() { Date d1(2021,10,15); Date d2; //這裡d2回調用編譯器自己生成的operator=函數完成拷貝 //即d2.operator=(d1); d2 = d1; return 0; }
和拷貝構造函數一樣,編譯器生成的默認賦值重載函數已經可以完成字節序的值拷貝瞭,對於Date類這樣的我們無需自己定義,但是對於Stack這樣會向內存申請空間的類,不能直接調用編譯器的默認函數,會對已經釋放的空間重復釋放。
4.賦值重載函數與拷貝構造函數的對比
賦值重載函數與拷貝構造函數的作用都是實現字節序的拷貝,那麼二者之間有什麼區別呢?
首先,拷貝構造函數是構造函數的一個函數重載形式,其函數名為類名,無返回值,而賦值重載函數為=這個操作符的重載,其函數名為operator=,返回值為類名,需要註意的是函數重載與操作符重載二者之間沒有任何關聯。
其次,一個對象在初始化時調用的時拷貝構造函數,而對象初始化完成後再調用即為賦值重載函數,比如:
int main() { Date d1(2021,10,15); //調用拷貝構造函數,相對於Date d2(d1); Date d2 = d1; Date d3(2021,10,15); Date d4; //調用賦值重載函數,即d4.operator=(d3); d4 = d3; return 0; }
最後,由於=可以連續使用,因此賦值重載函數可以連續調用,比如:
int main() { Date d1(2021,10,15); Date d2; Date d3; //連續調用,相當於,d3 = (d2 = d1); 而d2 = d1有一個返回值 //該返回值再作為操作數賦值給d3 //也就相當於d3.operator(d2.operator(d1)); d3 = d2 = d1; return 0; }
日期類的實現
學習完操作符重載,我們可以實現操作符==、+=、-=、>、<等等。
//賦值運算符重載,d2=d3 -> d2.operater=(d3) Date& operator=(const Date& d);//由於在類域中this所指向的對象並不會銷毀,因此可以用引用返回 //日期+=天數 Date& operator+=(int day); //日期+天數 Date operator+(int day) const; //日期-=天數 Date& operator-=(int day); //日期-天數 Date operator-(int day) const; //前置++ Date& operator++(); //後置++ Date operator++(int); //前置-- Date& operator--(); //後置-- Date operator--(int); //==運算符重載 bool operator==(const Date& d) const; //!=運算符重載 inline bool operator!=(const Date& d) const; //>運算符重載 bool operator>(const Date& d); //<運算符重載 inline bool operator<(const Date& d); //>=運算符重載 inline bool operator>=(const Date& d); //<=運算符重載 inline bool operator<=(const Date& d); //日期 - 日期,返回天數 int operator-(const Date& d) const;
const成員
我們先來看看下面這個代碼:
//>運算符重載 bool operator>(const Date& d); void Test6() { const Date d1(2021, 10, 16); Date d2 = d1 + 100; cout << (d1 > d2) << endl; }
實際運行過程中會發現d1 > d2
這句代碼編譯不過去,這是因為隱含的this指針默認的類型為不加const修飾的指針類型,而d1的類型為const修飾的變量,因此傳參給this後放大瞭修改的權限,導致出現錯誤。但是this指針的類型我們是無法修改的,那麼要怎麼解決這種情況呢?這就需要用到我們接下來要介紹的const修飾類的成員函數瞭。
1.const修飾類的成員函數
將const修飾的類成員函數稱之為const成員函數,const修飾類成員函數,實際修飾該成員函數隱含的this指針,表明在該成員函數中不能對類的任何成員進行修改。
比如說上述代碼我們可以修改為:
//>運算符重載,其相當於bool operator>(const Date* this, const Date& d); bool operator>(const Date& d) const;
2.小結
1.成員函數加const,變成const成員函數,這樣既可以讓const對象調用,也可以讓非const對象調用。
2.不是所有的成員函數都要加const,因為有的函數需要用this指針修改成員變量。
3.一個成員函數是否要加const應看其功能,若為修改型,比如operator+=();Push();等不需要加const;而對於隻讀型,Print();operator+();等就最好加上const。
綜上,如果要修改成員就不加const,若不修改則最好加上const。
取地址及const取地址操作符重載函數
類的最後兩個默認成員函數為操作符&的重載及其加上const修飾的函數。
class Date { public : Date* operator&() { return this ; } const Date* operator&()const { return this ; } private : int _year ; // 年 int _month ; // 月 int _day ; // 日 };
這兩個運算符一般不需要重載,使用編譯器生成的默認取地址的重載即可,隻有特殊情況,才需要重載,比如想讓別人獲取到指定的內容!但在實際過程中應用不多。
總結
本篇文章就到這裡瞭,希望能夠給你帶來幫助,也希望您能夠多多關註WalkonNet的更多內容!