🌟?各位看官好,我是egoist2023!
🌍?種一棵樹最好是十年前,其次是現在!
🚀?今天來學習C++類和對象的語法知識。注意:在本章節中,小編會以Date類舉例
👍?如果覺得這篇文章有幫助,歡迎您一鍵三連,分享給更多人哦
目錄
引入
運算符重載
賦值運算符重載
日期類實現
?日期計算
?日期判斷
流插入<<? 和? 流提取>>
const成員函數和取地址運算符重載
引入
//C++中
Date d1(2024, 12, 24);
Date d2(2024, 12, 24);
d1 = d2;//C語言中
d1._year = d2._year;
d1._month = d2._month;
d1._day = d2_day;
在上面一段代碼中,我們想把類類型的d2賦值給d1,如果在C語言版本中是需要把一個一個內置類型拷貝過去,未免過于麻煩。有什么方法能直接讓類對象d2賦值給d1呢?在C++中提出了運算符重載的概念,可以為類類型的對象指定新的含義。
運算符重載
- C++規定類類型對象使用運算符時,必須調用對應運算符重載,若沒有對應的運算符重載,則會編譯報錯。
- 運算符重載是一個函數,具有其返回類型和參數列表以及函數體,由operator和運算符共同構成。
//在成員函數中 bool operator=(const Date* d)
- 幾元運算符對應有幾個運算對象數量。如,一元運算符有一個運算對象,二元運算符有兩個運算對象,且規定二元運算符左側運算對象傳給第一個參數,右側傳給第二個參數。(如果沒有類類型形參則會報錯,在全局函數中 int operator+(int x, int y) )
- 重載<<和>>時,需要重載為全局函數(把ostream/istream放到第一個形參位置,第二個跟類類型對象)。? 因為重載為成員函數,this指針默認為第一個形參位置,是左側運算對象,調用時就變成了對象<<cout,用起來不習慣。
ostream& operator<<(ostream& out, const Date& d); istream& operator>>(istream& in, Date& d);
- 運算符重載不能連接語法中沒有的符號,如operator@。 同時注意?.*? ?::? ?sizeof? ??:? . 以上5個運算符不能重載。
- 一個類是否需要重載哪些運算符,是看重載后是否有意義,比如Date類重載operator-就有意義(可以算差值多少天)。
- 在C語言中有前置++和后置++,運算符重載函數名都是operator++,為了方便區分。C++規定,后置++重載時,增加?個int形參,跟前置++構成函數重載,方便區分。
Date& operator++();//前置++Date operator++(int);//后置++
賦值運算符重載
如下一行代碼是否是賦值運算符重載呢?并不是,是拷貝構造。
注意:拷貝構造是把一個已存在的對象拷貝初始化零一個要創建的對象;
而賦值運算符重載是?于完成兩個已經存在的對象直接的拷貝賦值。
Date d1=d2//d1和d2都是類類型對象
1. 賦值運算符重載 (跟拷貝構造類似)是?個運算符重載,必須重載為成員函數。賦值運算重載的參數建議 寫成const 當前類類型引用 (減少拷貝)。返回值時也建議攜程類類型引用(提高效率,支持返回值可提供連續賦值場景)。Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
2.沒有顯式實現時,編譯器會默認生成,對內置類型成員變量會淺拷貝(像Date類就可以不實現賦值賦值重載函數),對自定義類型成員變量會調用賦值重載函數(如 Stack ,_a指向了資源,默認生成的賦值重載函數不符合需求,需要 自己實現深拷貝 也對指向的資源也進行拷貝)。3.同樣,對于MyQueue這樣的類型內部主要是自定義Stack成員,編譯器默認生成賦值運算符重載會調用Stack的賦值運算符重載。
日期類實現
構造函數 | Date(int year = 1, int month = 1, int day = 1) |
獲取日期 | int GetMonthDay(int year, int month) const |
日期是否合法 | bool checkDate() const |
打印日期 | void Print() |
日期類構造函數不過多介紹。實現代碼如下代碼所示
//構造函數
Date::Date(int year, int month, int day)
{_year = year;_month = month;_day = day;if (!checkDate()){cout << "日期非法:<" << *this << endl;}
}
已知某年某月,如何知道是何日呢? 由于該函數需要頻繁調用,故設在類里面,類里默認為inline。
int GetMonthDay(int year, int month) const{static int MonthDayArray[13] = { -1,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 MonthDayArray[month];}
如果輸入的年月日?不符合實際上的年月日,需要一個函數檢查是否合法。
bool Date::checkDate() const
{//非法返回falseif (_month < 1 || _month>12 || _day > GetMonthDay(_year, _month) || _day < 1)return false;elsereturn true;
}
?日期計算
日期+天數 | ?? ?Date operator+(int day) const |
日期+=天數 | ?Date& operator+=(int day) |
日期-天數 | ?? ?Date& operator-=(int day) |
日期-=天數 | ?? ?Date operator-(int day) const |
日期-日期 | ?? ?int operator-(const Date& d) const |
前置/后置++ | ?? ?Date& operator++()? ?前置++ ?? ?Date operator++(int)? 后置++ |
前置/后置-- | ?? ?Date& operator--()? ? ?前置-- ?? ?Date operator--(int)? ? 后置-- |
?
?那是第一種復用第二種,還是第二種復用第一種呢?我們來剖析下函數的具體行為是什么。
第一種采用的是傳值返回,為什么不傳引用返回呢?因為返回的是tmp,tmp是建立在這個棧空間的,函數結束后棧會被銷毀,因此要用傳值返回。而C++規定類傳值返回會調用拷貝構造,會有一定的消耗。
第二種采用的是傳引用返回,由于類類型d并不是在此棧空間的,傳引用返回能減少拷貝,提高效率。
可以發現下圖中第二種復用第一種會有5次消耗,而第一種復用第二種只存在2次消耗。
?
同樣日期-天數和日期-=天數也可以采用復用的邏輯。但其中還有借月的邏輯,需要細細來剖析。?
Date& Date::operator-=(int day)
{if (day < 0){*this += (-day);}_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;
}
由于實現了日期加減天數的函數,前置和后置的加減都可以采用復用的邏輯。?(這里就不過多贅述)
//++d
Date& Date::operator++()
{//復用*this += 1;return *this;
}//d++
Date Date::operator++(int)
{Date tmp(*this);*this += 1;return tmp;
}//--d
Date& Date::operator--()
{*this -= 1;return *this;
}//d--
Date Date::operator--(int)
{Date tmp(*this);*this -= 1;return tmp;
}
接下來我們來看看日期-日期的實現方法。
當然,也可以通過一個變量來記錄2024年4月5日到2025年3月7日的天數,同樣采用復用的邏輯。
int Date::operator-(const Date& d) const
{int flag = 1;Date max = *this;Date min = d;if (*this < d){max = d;min = *this;flag = -1;}int count = 0;while (min != max){++min;++count;}return flag * count;
}
?日期判斷
d1>d2 | bool operator>(const Date& d) const |
d1>=d2 | bool operator>=(const Date& d) const |
d1<d2 | bool operator<(const Date& d) const |
d1<=d2 | bool operator<=(const Date& d) const |
d1==d2 | bool operator==(const Date& d) const |
d1!=d2 | bool operator!=(const Date& d) const |
bool Date::operator>(const Date& d) 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){return _day > d._day;}return true;
}bool Date::operator==(const Date& d) const
{return _year == d._year&& _month == d._month&& _day == d._day;
}
在實現日期判斷的時候,通常只要實現兩個一個>和==的函數,其他判斷只要復用這兩個函數就可以實現。如要實現d1>=d2,則只要滿足d1>d2或d1==d2;實現d1<d2,則滿足!(d1>=d2)。
流插入<<? 和? 流提取>>
流插入<< | ostream& operator<<(ostream& out, const Date& d) |
流提取>> | istream& operator>>(istream& in, Date& d) |
?前面我們說過需要把? 流插入<<? 和? 流提取>> 設置為全局變量,否則不符合我們的使用習慣。
需要注意的是,流插入時類類型d是可以+const,并不改變里面的值,而流提取不可以。
如果我們輸入的年月日是存在非法的情況的,因此需要判斷。
//流插入
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//流提取
istream& operator>>(istream& in, Date& d)
{//in >> d._year >> d._month >> d._day;while (1){in >> d._year >> d._month >> d._day;if (d.checkDate()){break;}else {cout << "輸入日期非法,請重新輸入:<" << endl;}}return in;
}
const成員函數和取地址運算符重載
?const修飾的成員函數稱之為const成員函數,放到成員函數參數列表的后面。void Print() const;
const修飾的是成員函數隱含的this指針,表明在成員函數中不能對類的任何成員進行修改。Date* const this 加const修飾 const Date* const this
取地址運算符重載分為普通取地址運算符重載和const取地址運算符重載,一般不需要去顯式實現,使用一些特殊情景。如不想讓別人取到當前類對象的地址,就可以自己實現,胡亂返回一個地址,調試的時候就會發現地址一直對不上。Date* operator&(){//return this;return (Date*)0x0123753f;}const Date* operator&() const{//return this;return nullptr;}
![]()