??樊梓慕:個(gè)人主頁
???個(gè)人專欄:《C語言》《數(shù)據(jù)結(jié)構(gòu)》《藍(lán)橋杯試題》《LeetCode刷題筆記》《實(shí)訓(xùn)項(xiàng)目》《C++》
??每一個(gè)不曾起舞的日子,都是對生命的辜負(fù)
前言
我們繼續(xù)學(xué)習(xí)默認(rèn)成員函數(shù),本篇文章博主帶來的是拷貝構(gòu)造函數(shù)與運(yùn)算符、操作符重載的講解,并且還有const成員所帶來的一系列問題,最后博主會(huì)給大家貼出利用前面所學(xué)知識寫出的日期類。
歡迎大家??收藏??以便未來做題時(shí)可以快速找到思路,巧妙的方法可以事半功倍。
=========================================================================
GITEE相關(guān)代碼:??fanfei_c的倉庫??
=========================================================================
1.拷貝構(gòu)造函數(shù)
1.1概念
在介紹拷貝構(gòu)造函數(shù)之前,我想先舉一個(gè)例子來告訴大家為什么會(huì)有拷貝構(gòu)造?
比如:
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
Date d2(d1);//這里是什么行為?
return 0;
}
請觀察我在代碼中標(biāo)識的部分,很明顯這里是將d1作為實(shí)參傳遞給構(gòu)造函數(shù)。
我們之前學(xué)習(xí)函數(shù)時(shí),曾說過,形參是實(shí)參的一份臨時(shí)拷貝。
那么好了,這就是拷貝構(gòu)造函數(shù)存在的其中一個(gè)意義。
拷貝構(gòu)造函數(shù):只有單個(gè)形參,該形參是對本類類型對象的引用(一般常用const修飾),在用已存在的類類型對象創(chuàng)建新對象時(shí)由編譯器自動(dòng)調(diào)用。
1.2特征
拷貝構(gòu)造函數(shù)也是特殊的成員函數(shù),其特征如下:
1.拷貝構(gòu)造函數(shù)是構(gòu)造函數(shù)的一個(gè)重載形式。
2.拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類類型對象的引用,使用傳值方式編譯器直接報(bào)錯(cuò),因?yàn)闀?huì)引發(fā)?無窮遞歸調(diào)用?。
??為什么這里會(huì)引發(fā)無窮遞歸調(diào)用??
前面對于拷貝構(gòu)造函數(shù)的定義是這樣說的:拷貝構(gòu)造函數(shù)在用已存在的類類型對象創(chuàng)建新對象時(shí)由編譯器自動(dòng)調(diào)用。
我們觀察下面的代碼:
Date(const Date date)//你會(huì)發(fā)現(xiàn)這里報(bào)錯(cuò)了??
{
_year = date._year;
_month = date._month;
_day = date._day;
int main()
{
Date d1;
Date d2(d1);
}
?當(dāng)d1作為參數(shù)傳遞給拷貝構(gòu)造函數(shù)時(shí),是不是會(huì)再次調(diào)用拷貝構(gòu)造函數(shù)??
那我們就找到為什么會(huì)引發(fā)無窮遞歸調(diào)用的答案了。
解決的方案就是特征所描述的那樣:拷貝構(gòu)造函數(shù)的參數(shù)只有一個(gè)且必須是類類型對象的引用。?
也就是這樣:
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
3.若未顯式定義,編譯器會(huì)生成默認(rèn)的拷貝構(gòu)造函數(shù)。 默認(rèn)的拷貝構(gòu)造函數(shù)對象按內(nèi)存存儲(chǔ)按字節(jié)序完成拷貝,這種拷貝叫做淺拷貝,或者值拷貝。
4.編譯器生成的默認(rèn)拷貝構(gòu)造函數(shù)(淺拷貝)已經(jīng)可以完成字節(jié)序的值拷貝了,還需要自己顯式實(shí)現(xiàn)嗎?當(dāng)然像日期類這樣的類是沒必要的。那么下面的類呢?驗(yàn)證一下試試?
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請空間失敗");
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(s1);
return 0;
}
程序報(bào)錯(cuò):
??那么對于日期類,他所做的拷貝就是淺拷貝,那淺拷貝會(huì)不會(huì)有風(fēng)險(xiǎn)存在呢??
對于日期類,淺拷貝沒有風(fēng)險(xiǎn)。
但是對于自己開了空間的自定義類型來講,淺拷貝可能會(huì)引發(fā)大麻煩。
?大家還記得之前學(xué)習(xí)的析構(gòu)函數(shù)么,通過前面的學(xué)習(xí)我們知道,析構(gòu)函數(shù)會(huì)在對象銷毀時(shí)時(shí)自動(dòng)調(diào)用。
那么如果對于自己開了空間的自定義類型來講,單純的進(jìn)行淺拷貝,就會(huì)有兩個(gè)對象的兩個(gè)指針指向相同的空間,那這兩個(gè)對象在銷毀時(shí),自動(dòng)調(diào)用析構(gòu)函數(shù),就會(huì)發(fā)生同一塊空間釋放兩次的情況,顯然程序會(huì)崩潰。
?那如何避免這樣的問題呢,很顯然我們需要像構(gòu)造函數(shù)那樣,自己設(shè)計(jì)一個(gè)函數(shù)用來進(jìn)行拷貝,這樣的拷貝被稱為深拷貝。
一般比如順序表、鏈表、二叉樹等開了空間的自定義類型都需要深拷貝。
那么我們可以得到結(jié)論:
- 內(nèi)置類型成員完成值拷貝;
- 自定義類型調(diào)用該成員的拷貝構(gòu)造。
那么都有什么樣的場景用到了拷貝構(gòu)造呢?
當(dāng)用一個(gè)已初始化過了的自定義類類型對象去初始化另一個(gè)新構(gòu)造的對象的時(shí)候,拷貝構(gòu)造函數(shù)就會(huì)被自動(dòng)調(diào)用。也就是說,當(dāng)類的對象需要拷貝時(shí),拷貝構(gòu)造函數(shù)將會(huì)被調(diào)用。
以下情況都會(huì)調(diào)用拷貝構(gòu)造函數(shù):
① 程序中需要新建立一個(gè)對象,并用另一個(gè)同類的對象對它初始化,如前面介紹的那樣。
② 當(dāng)函數(shù)的參數(shù)為類的對象時(shí)。在調(diào)用函數(shù)時(shí)需要將實(shí)參對象完整地傳遞給形參,也就是需要建立一個(gè)實(shí)參的拷貝,這就是按實(shí)參復(fù)制一個(gè)形參,系統(tǒng)是通過調(diào)用復(fù)制構(gòu)造函數(shù)來實(shí)現(xiàn)的,這樣能保證形參具有和實(shí)參完全相同的值。
③ 函數(shù)的返回值是類的對象。在函數(shù)調(diào)用完畢將返回值帶回函數(shù)調(diào)用處時(shí)。此時(shí)需要將函數(shù)中的對象復(fù)制一個(gè)臨時(shí)對象并傳給該函數(shù)的調(diào)用處。?
1.3補(bǔ)充的一些知識
首先再來給大家強(qiáng)化一下構(gòu)造和拷貝構(gòu)造的理解。
觀察下面的代碼:
int main()
{
A aa1(1); //構(gòu)造
// 一個(gè)已經(jīng)存在的對象拷貝初始化另一個(gè)要?jiǎng)?chuàng)建的對象,就是拷貝構(gòu)造
A aa2(aa1); // 拷貝構(gòu)造
A aa3 = aa1; // 拷貝構(gòu)造 or 賦值拷貝 -> 拷貝構(gòu)造
// 兩個(gè)已經(jīng)存在的對象拷貝是賦值拷貝
aa2 = aa3;
return 0;
}
?本質(zhì)上講,A aa2(aa1)和A aa2=aa1兩種寫法是一樣的,都屬于拷貝構(gòu)造。
?博主還會(huì)在接下來的文章中,介紹更多有關(guān)構(gòu)造和拷貝構(gòu)造的內(nèi)容,這里的內(nèi)容比較瑣碎,目前的知識支撐還不夠,所以我們還要往后學(xué)幾章。
2.運(yùn)算符重載
2.1運(yùn)算符重載
C++為了增強(qiáng)代碼的可讀性引入了運(yùn)算符重載,運(yùn)算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運(yùn)算符符號。
函數(shù)原型:返回值類型 operator操作符(參數(shù)列表)
注意:
- 不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@。
- 重載操作符必須有一個(gè)類類型參數(shù)。
- 用于內(nèi)置類型的運(yùn)算符,其含義不能改變,例如:內(nèi)置的整型+,不能改變其含義。
- 作為類成員函數(shù)重載時(shí),其形參看起來比操作數(shù)數(shù)目少1,因?yàn)槌蓡T函數(shù)的第一個(gè)參數(shù)為隱藏的this。
- .*? ::? sizeof? ?:(三目操作符)? .? 注意以上5個(gè)運(yùn)算符不能重載。這個(gè)經(jīng)常在筆試選擇題中出現(xiàn)。
我們來實(shí)現(xiàn)一個(gè)運(yùn)算符==的重載試試看:
大家同時(shí)思考一個(gè)問題:運(yùn)算符重載函數(shù)是放在類內(nèi)部,還是全局呢?
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//private: 這里是為了示例,一般成員變量為私有
int _year;
int _month;
int _day;
};
// 這里會(huì)發(fā)現(xiàn)運(yùn)算符重載成全局的就需要成員變量是公有的,那么問題來了,封裝性如何保證?
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test()
{
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
cout << (d1 == d2) << endl;
}
我們發(fā)現(xiàn)如果運(yùn)算符重載成全局的,一般來講類的成員變量都是私有的,那么如何訪問成員變量進(jìn)行運(yùn)算呢?
這里其實(shí)可以用我們后面學(xué)習(xí)的友元解決,或者干脆重載成成員函數(shù)。
由于友元我們還沒講,那就先試試看將運(yùn)算符重載函數(shù)放到類內(nèi)部。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// bool operator==(Date* this, const Date& d2)
// 這里需要注意的是,左操作數(shù)是this,指向調(diào)用函數(shù)的對象
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
還記得我們前面學(xué)習(xí)的this指針是隱藏的參數(shù)吧。
我們知道==運(yùn)算符需要兩個(gè)操作數(shù),所以運(yùn)算符重載函數(shù)只需要一個(gè)顯式參數(shù),另一個(gè)為隱藏參數(shù)*this。?
2.2賦值運(yùn)算符重載
1. 賦值運(yùn)算符重載格式
- 參數(shù)類型:const T&,傳遞引用可以提高傳參效率;
- 返回值類型:T&,返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值;
- 檢測是否自己給自己賦值;
- 返回*this :要符合連續(xù)賦值的含義。
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
Date& operator=(const Date& d)
{
if (this != &d)//檢測是否自己給自己賦值
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
private:
int _year;
int _month;
int _day;
};
2.賦值運(yùn)算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)(和普通運(yùn)算符的區(qū)別)
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int _year;
int _month;
int _day;
};
// 賦值運(yùn)算符重載成全局函數(shù),注意重載成全局函數(shù)時(shí)沒有this指針了,需要給兩個(gè)參數(shù)
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 =”必須是非靜態(tài)成員
原因:賦值運(yùn)算符如果不顯式實(shí)現(xiàn),編譯器會(huì)生成一個(gè)默認(rèn)的。此時(shí)用戶再在類外自己實(shí)現(xiàn)一個(gè)全局的賦值運(yùn)算符重載,就和編譯器在類中生成的默認(rèn)賦值運(yùn)算符重載沖突了,故賦值運(yùn)算符重載只能是類的成員函數(shù)。
3.用戶沒有顯式實(shí)現(xiàn)時(shí),編譯器會(huì)生成一個(gè)默認(rèn)賦值運(yùn)算符重載,以值的方式逐字節(jié)拷貝(淺拷貝)。
注意:內(nèi)置類型成員變量是直接賦值的,而自定義類型成員變量需要調(diào)用對應(yīng)類的賦值運(yùn)算符重載完成賦值。
class Time
{
public:
Time()
{
_hour = 1;
_minute = 1;
_second = 1;
}
Time& operator=(const Time& t)
{
if (this != &t)
{
_hour = t._hour;
_minute = t._minute;
_second = t._second;
}
return *this;
}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
private:
// 基本類型(內(nèi)置類型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定義類型
Time _t;
};
int main()
{
Date d1;
Date d2;
d1 = d2;//重載賦值運(yùn)算符
return 0;
}
有沒有發(fā)現(xiàn)這里和構(gòu)造函數(shù)仍然十分相似。
內(nèi)置類型執(zhí)行淺拷貝,自定義類型調(diào)用自己的重載賦值運(yùn)算符。?
為什么自定義類型不能簡單的進(jìn)行淺拷貝,而還要自己設(shè)計(jì)深拷貝?
相信前面講解的拷貝函數(shù)的二次析構(gòu)問題已經(jīng)給了你答案,?我們再在這里舉一個(gè)例子加深大家的印象。
// 這里會(huì)發(fā)現(xiàn)下面的程序會(huì)崩潰掉?這里就需要我們以后講的深拷貝去解決。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
_array = (DataType*)malloc(capacity * sizeof(DataType));
if (nullptr == _array)
{
perror("malloc申請空間失敗");
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;//這里是默認(rèn)的賦值運(yùn)算符,完成的是淺拷貝
return 0;
}
如果前面的二次析構(gòu)你理解了的話,這里不是問題。
分析:
總結(jié)一下:
- 如果類中未涉及到資源管理,賦值運(yùn)算符是否實(shí)現(xiàn)都可以;
- 一旦涉及到資源管理則必須要實(shí)現(xiàn)。
2.3流運(yùn)算符重載
流運(yùn)算符設(shè)計(jì)的初衷是為了解決自定義類型的輸入和輸出問題。
那么就一定會(huì)涉及到流運(yùn)算符重載的相關(guān)內(nèi)容。
但是流運(yùn)算符又不能和其他運(yùn)算符一樣重載到類內(nèi)部,相反,流運(yùn)算符必須實(shí)現(xiàn)到全局,這是為什么呢?
我們知道成員函數(shù)有一個(gè)隱藏的參數(shù)this,流運(yùn)算符也是一個(gè)雙目操作符,一般流運(yùn)算符我們寫為cout<<a;
但假設(shè)我們將流運(yùn)算符重載到類內(nèi)部作成員函數(shù)的話,第一個(gè)參數(shù)為this,所以好像我們這樣寫才是對的a<<cout;
也就是說我們只能也必須將流運(yùn)算符重載實(shí)現(xiàn)到全局,才能避免this作第一個(gè)參數(shù)。
我們試試看:
對啊,定義到全局,就訪問不了私有的成員變量了呀。
那這里一般我們會(huì)采用友元函數(shù)解決成員變量私有的問題。(友元函數(shù)會(huì)在后續(xù)介紹)
比如:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
// 友元函數(shù)
friend void operator<<(ostream& out, const Date& d);
private:
int _year;
int _month;
int _day;
};
void operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
這樣就可以解決類外部的函數(shù)無法訪問到私有成員變量的問題了。?
可是我們在使用時(shí)發(fā)現(xiàn),<<是需要返回值的,比如這種cout<<a<<b<<endl;
那我們修改一下函數(shù)實(shí)現(xiàn):
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1);
// 友元函數(shù)
friend ostream& operator<<(ostream& out, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
?完整的輸入輸出流重載函數(shù):
class Date
{
public:
Date(int year, int month, int day) // 構(gòu)造函數(shù)
{
_year = year;
_month = month;
_day = day;
}
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
cout << d._year << "/" << d._month << "/" << d._day;
return out;
}
istream& operator>>(istream& in, Date& d)
{
in >> d._year >> d._month >> d._day;
return in;
}
int main()
{
int a = 0;
int b = 1;
cout << a << " " << b << endl;
Date d1(2022, 11, 27);
Date d2(2022, 11, 28);
cout << d1 << " " << d2 << endl;
cin >> d2;
cout << d2;
return 0;
}
2.4前置++與后置++重載
前置++與后置++的區(qū)別是什么呢?
一個(gè)是先加后用,一個(gè)則是先用后加。
還有另一個(gè)問題,他們的符號都是++,那怎么區(qū)分呢?
C++規(guī)定:后置++重載時(shí)多增加一個(gè)int類型的參數(shù),但調(diào)用函數(shù)時(shí)該參數(shù)不用傳遞,編譯器自動(dòng)傳遞。
我們直接看實(shí)現(xiàn):
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+1之后的結(jié)果
// 注意:this指向的對象函數(shù)結(jié)束后不會(huì)銷毀,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// 注意:后置++是先使用后+1,因此需要返回+1之前的舊值,故需在實(shí)現(xiàn)時(shí)需要先將this保存一份,然后給this + 1
// 而temp是臨時(shí)對象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d;
Date d1(2022, 1, 13);
d = d1++; // d: 2022,1,13 d1:2022,1,14
d = ++d1; // d: 2022,1,15 d1:2022,1,15
return 0;
}
3.const成員函數(shù)
將const修飾的“成員函數(shù)”稱之為const成員函數(shù),const修飾類成員函數(shù),實(shí)際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對類的任何成員進(jìn)行修改。
比如這樣:
?觀察下面的代碼:
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();
}
請思考下面的幾個(gè)問題:
1. const對象可以調(diào)用非const成員函數(shù)嗎?
2. 非const對象可以調(diào)用const成員函數(shù)嗎?
3. const成員函數(shù)內(nèi)可以調(diào)用其它的非const成員函數(shù)嗎?
4. 非const成員函數(shù)內(nèi)可以調(diào)用其它的const成員函數(shù)嗎??
?之前在學(xué)習(xí)引用時(shí),我們曾經(jīng)討論過權(quán)限縮小和放大的問題??樊梓慕-引用
權(quán)限只能縮小,不能放大。
??解釋一下??
也就是說const的對象在引用時(shí),引用須加const,如果不加,此時(shí)引用的權(quán)限大于被引用的const對象,這就是所謂的權(quán)限不能放大。
但是不加const的對象在引用時(shí),引用可加可不加const,因?yàn)闄?quán)限是平級或是縮小了的,這就是所謂的權(quán)限只能縮小。
那搞清楚了這個(gè)概念,再看前面的題目有沒有思路了呢?
答案:
- ?權(quán)限被放大;
- ?權(quán)限被縮??;
- ?權(quán)限被放大;
- ?權(quán)限被縮小;
??????成員函數(shù)定義的原則??????
- 能定義成const的成員函數(shù)都應(yīng)該定義成const(這里的const是指參數(shù)列表后面的const,也就是修飾this的const),這樣const對象和非const對象(這里的const是指修飾對象的類型為const)都可以調(diào)用;比如下面圖片,屬于const對象調(diào)用非const成員函數(shù),我們剛做的小題已經(jīng)告訴我們這樣是權(quán)限放大的問題了,所以現(xiàn)在只需要將成員函數(shù)設(shè)計(jì)為const成員函數(shù),將this的類型改為const Date* const this,就可以解決這一問題。
- 要修改成員變量的成員函數(shù),不能定義成const。
4.取地址及const取地址操作符重載
這兩個(gè)默認(rèn)成員函數(shù)一般不用重新定義,編譯器默認(rèn)生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
這兩個(gè)運(yùn)算符一般不需要重載,使用編譯器生成的默認(rèn)取地址的重載即可,只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容!??
5.日期類的實(shí)現(xiàn)
class Date
{
// 獲取某年某月的天數(shù)
int GetMonthDay(int year, int month)
{
static int days[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int day = days[month];
if (month == 2
&& ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
day += 1;
}
return day;
}
// 全缺省的構(gòu)造函數(shù)
Date(int year = 1900, int month = 1, int day = 1)
{
if (year < 1900
|| month < 1 || month > 12
|| day < 1 || day > GetMonthDay(year, month))
{
cout << "非法日期" << endl;
}
_year = year;
_month = month;
_day = day;
}
// 拷貝構(gòu)造函數(shù)
// d2(d1)
Date(const Date& d)
{
this->_year = d._year;
_month = d._month;
_day = d._day;
}
// 賦值運(yùn)算符重載
// d2 = d3 -> d2.operator=(&d2, d3)
Date& operator=(const Date& d)
{
if (this != &d)
{
this->_year = d._year;
this->_month = d._month;
this->_day = d._day;
}
return *this;
}
// 析構(gòu)函數(shù)
~Date()
{
// 清理工作
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
// 日期+=天數(shù)
// d1 += 10
// d1 += -10
Date& operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
// 日期+天數(shù)
// d + 10
Date operator+(int day)
{
Date ret(*this);
ret += day;
return ret;
}
// 日期-天數(shù)
Date operator-(int day)
{
Date ret(*this);
ret -= day;
return ret;
}
// 日期-=天數(shù)
// d -= 100
// d -= -100
Date& operator-=(int day)
{
if (day < 0)
{
return *this += -day;
}
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 前置++
// ++d -> d.operator++(&d)
Date& operator++()
{
*this += 1;
return *this;
}
// 后置++
// d++ -> d.operator++(&d, 0)
Date operator++(int)
{
Date ret(*this);
*this += 1;
return ret;
}
// // 后置--
Date operator--(int)
{
Date ret(*this);
*this -= 1;
return ret;
}
// 前置--
Date& operator--()
{
*this -= 1;
return *this;
}
// d1 > d2
// >運(yùn)算符重載
bool operator>(const Date& d)
{
if (_year > d._year)
{
return true;
}
else if (_year == d._year)
{
if (_month > d._month)
{
return true;
}
else if (_month == d._month)
{
if (_day > d._day)
{
return true;
}
}
}
return false;
}
// ==運(yùn)算符重載
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// 下面復(fù)用上面兩個(gè)的實(shí)現(xiàn)
// >=運(yùn)算符重載
bool operator >= (const Date& d)
{
return *this > d || *this == d;
}
// <運(yùn)算符重載
bool operator < (const Date& d)
{
return !(*this >= d);
}
// <=運(yùn)算符重載
bool operator <= (const Date& d)
{
return !(*this > d);
}
// !=運(yùn)算符重載
bool operator != (const Date& d)
{
return !(*this == d);
}
// d1 - d2
// 日期-日期 返回天數(shù)
int operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
max = d;
min = *this;
flag = -1;
}
int day = 0;
while (min < max)
{
++(min);
++day;
}
return day * flag;
}
private:
int _year;
int _month;
int _day;
};
這里需要注意的點(diǎn)是:已經(jīng)實(shí)現(xiàn)了的運(yùn)算符重載可以復(fù)用,邏輯轉(zhuǎn)化就好了,沒必要每一個(gè)都要完全實(shí)現(xiàn)。
最近的內(nèi)容很碎,大家只需要跟著博主一起慢慢學(xué)習(xí)就好,內(nèi)容完全展開后就豁然開朗啦
=========================================================================
如果你對該系列文章有興趣的話,歡迎持續(xù)關(guān)注博主動(dòng)態(tài),博主會(huì)持續(xù)輸出優(yōu)質(zhì)內(nèi)容
??博主很需要大家的支持,你的支持是我創(chuàng)作的不竭動(dòng)力??
??~ 點(diǎn)贊收藏+關(guān)注 ~??文章來源:http://www.zghlxwxcb.cn/news/detail-742747.html
=========================================================================?文章來源地址http://www.zghlxwxcb.cn/news/detail-742747.html
到了這里,關(guān)于【C++】類和對象(中)之拷貝構(gòu)造與運(yùn)算符、操作符重載的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!