一、【C++】賦值運算符重載
1.1 運算符重載【引入】
- C++為了增強代碼的可讀性引入了運算符重載,運算符重載是具有特殊函數(shù)名的函數(shù),也具有其返回值類型,函數(shù)名字以及參數(shù)列表,其返回值類型與參數(shù)列表與普通的函數(shù)類似。
- 函數(shù)名字為:關(guān)鍵字operator后面接需要重載的運算符符號。
- 函數(shù)原型:返回值類型 operator操作符(參數(shù)列表)
注意:
-
不能通過連接其他符號來創(chuàng)建新的操作符:比如operator@
-
重載操作符必須有一個類類型參數(shù)【不能去重載運算符改變內(nèi)置類型的行為】
-
用于內(nèi)置類型的運算符,其含義不能改變,例如:內(nèi)置的整型+,不 能改變其含義
-
作為類成員函數(shù)重載時,其形參看起來比操作數(shù)數(shù)目少1個,因為成員函數(shù)的第一個參數(shù)為隱藏的this
-
.* :: sizeof ?: .
注意以上5個運算符不能重載。這里的是.*
,不是*
,這寫經(jīng)常在筆試選擇題中出現(xiàn)。
- 我們寫了一個日期類,有沒有可能要比較比較呢?
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;
};
- 我們寫了一個比較相等的函數(shù),如果是傳值的話沒有必要,我們形參直接寫成
&
,而且也不需要修改,所以再加上const
bool DateEquel(const Date& x, Date& y)
{
return x._year == y._year
&& x._month == y._month
&& x._day == y._day;
}
- 下面再實現(xiàn)一個函數(shù)【比較小于】
bool DateLess(const Date& x, Date& y)
{
if (x._year < y._year)
{
return true;
}
else if(x._year == y._year)
{
if (x._month < y._month)
{
return true;
}
else if(x._month == y._month)
{
if (x._day < y._day)
{
return true;
}
}
}
return false;
}
- 上面有一些不好的地方,取名字的問題,取得很亂就不知道這個函數(shù)是干什么的
下面我就要用一個新的符號
1.2 運算符重載
- 這里的運算符重載 和函數(shù)重載的重載不是一個意思
- 對運算符的行為重新定義
operator+運算符做函數(shù)名
- 剛剛上面寫的代碼就可以寫成下面這樣
bool operator==(const Date& x, Date& y)
{
return x._year == y._year
&& x._month == y._month
&& x._day == y._day;
}
bool operator<(const Date& x, Date& y)
{
if (x._year < y._year)
{
return true;
}
else if(x._year == y._year)
{
if (x._month < y._month)
{
return true;
}
else if(x._month == y._month)
{
if (x._day < y._day)
{
return true;
}
}
}
return false;
}
- 就可以這樣使用了
- 還可以這樣做
- 這里必須要加括號,因為流插入的優(yōu)先級很高
- 注意:參數(shù)不能反,左邊的操作數(shù)對應(yīng)的是左邊
- 我們再次回到上面,我們一開始是將內(nèi)置類型放開的
- 如果將他設(shè)置成私有,類外面就不能訪問了
- 我們這里有三種解決方法
- 第一種就是在類里面搞一個Get函數(shù),這樣獲取【Java就是這樣做的】
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
int GetYear()
{
return _year;
}
private:
int _year;
int _month;
int _day;
};
- 第二種方法就是將函數(shù)放到類里面
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& x, Date& y)
{
return x._year == y._year
&& x._month == y._month
&& x._day == y._day;
}
bool operator<(const Date& x, Date& y)
{
if (x._year < y._year)
{
return true;
}
else if (x._year == y._year)
{
if (x._month < y._month)
{
return true;
}
else if (x._month == y._month)
{
if (x._day < y._day)
{
return true;
}
}
}
return false;
}
private:
int _year;
int _month;
int _day;
};
- 然后我們又發(fā)現(xiàn),函數(shù)的參數(shù)太多了,因為成員函數(shù)有一個隱含的
this
指針
- 下面進行修改
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(Date& y)
{
return _year == y._year
&& _month == y._month
&& _day == y._day;
}
bool operator<(Date& y)
{
if (_year < y._year)
{
return true;
}
else if (_year == y._year)
{
if (_month < y._month)
{
return true;
}
else if (_month == y._month)
{
if (_day < y._day)
{
return true;
}
}
}
return false;
}
private:
int _year;
int _month;
int _day;
};
- 我們使用就可以這樣使用了
-
我們通過查看反匯編再來了解一下
-
首先看一下內(nèi)置類型的比較,是通過指令集的支持
- 再來看自定義類型
- 可以看到兩種寫法的匯編代碼是一樣的,編譯器會先轉(zhuǎn)化,會一步到位的
1.3 賦值運算符重載
- 我們接著寫日期類,下面寫一個賦值運算符
void operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
- 但是我們寫的這個又遇到新的問題了,就是連續(xù)賦值是無法完成的
- 其實連續(xù)賦值就是右操作的返回值,做操作的值再進行賦值
- 返回類型用傳引用返回,類型用引用,效率會提高
Date&
,返回的就是*this
,不是this
,這是個指針
Date& operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
return *this;
}
- 但是有可能有人這樣寫代碼:
d1 = d1
d1 = d1
- 這里我們就可以加上一個判斷,兩個相等的話直接返回即可
1.4 賦值
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
-
那么我們不寫這個賦值,編譯器不會不會自動生成一個呢?—>
會的!
-
因為這個是那6個默認成員函數(shù)之一
-
內(nèi)置類型會值拷貝,自定義類型會調(diào)用它的拷貝
總結(jié):
- 賦值運算符重載格式
- 參數(shù)類型:const T&,傳遞引用可以提高傳參效率
- 返回值類型:T&,返回引用可以提高返回的效率,有返回值目的是為了支持連續(xù)賦值
- 檢測是否自己給自己賦值
- 返回*this :要復(fù)合連續(xù)賦值的含義
- 賦值運算符只能重載成類的成員函數(shù)不能重載成全局函數(shù)
- 賦值運算符重載成全局函數(shù),注意重載成全局函數(shù)時沒有this指針了,需要給兩個參數(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;
}
原因:賦值運算符如果不顯式實現(xiàn),編譯器會生成一個默認的。此時用戶再在類外自己實現(xiàn)一個全局的賦值運算符重載,就和編譯器在類中生成的默認賦值運算符重載沖突了,故賦值運算符重載只能是類的成員函數(shù)。
二、日期類的實現(xiàn)
- 前面實現(xiàn)了一些邏輯,接下來我們就實現(xiàn)剩下的
- 所有代碼在最后~~
2.1 判斷小于
- 在上面已經(jīng)說明了,這里就直接寫出來了
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;
}
2.2 判斷等于
- 直接判斷相不相等
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
2.3 判斷小于等于
- 上面實現(xiàn)了小于,下面我們來實現(xiàn)一下小于等于
- 既然上面都寫了小于和等于了,我們這里就可以直接復(fù)用
bool operator<=(const Date& d)
{
return *this <= d || *this == d;
}
2.4 判斷大于
- 這里的大于不就是小于等于的取反
bool operator>(const Date& d)
{
return !(*this <= d);
}
2.5 判斷大于等于
- 大于等于就是小于取反
bool operator>=(const Date& d)
{
return !(*this < d);
}
2.6 判斷不等于
- 不等于就是等于的取反
bool operator!=(const Date& d)
{
return !(*this == d);
}
2.7 日期加等天數(shù)
2.8 獲取月份天數(shù)
- 在實現(xiàn)日期加天數(shù)的時候我們需要再先實現(xiàn)一個獲取月份天數(shù)
- 因為這里的獲取月份天數(shù)需要被頻繁調(diào)用,我們這里使用inline,可以不寫【在類內(nèi)聲明同時定義的成員函數(shù)自動轉(zhuǎn)化為內(nèi)聯(lián)函數(shù)】
- 我們還將數(shù)組使用static修飾,避免了頻繁開辟
- 然后再判斷閏年的時候我們把判斷月份放到了前面,首先判斷月份是否為2,為真繼續(xù)判斷下面的,否則每次都要執(zhí)行那么一長串的判斷閏年
inline int GetMonthDay(int year,int month)
{
assert(month < 13 && month > 0);
// 放到靜態(tài)區(qū)
static int MonthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
// 先判斷月份
if (month == 2 && (((year % 100 == 0) && (year % 4 == 0)) || (year % 400 != 0)))
return 29;
return MonthDays[month];
}
- 然后繼續(xù)來實現(xiàn)
- 首先加上天數(shù),然后判斷當前月的天數(shù)和加上的天數(shù)
- 然后進行減掉天數(shù),月份+1,如果月份等于了13,年就+1,月份賦值為1
Date& operator+=(int day)
{
// 這里就直接修改了
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
2.9 日期加天數(shù)
- 上面是實現(xiàn)了+=,+也就很好實現(xiàn)了,這里只需要另外開一塊空間,修改別的空間就不會影響我這里的值
- 這里不可以用引用返回,tmp是一個臨時對象,必須用傳值返回
Date operator+(int day)
{
Date tmp(*this);
tmp._day += day;
while (tmp._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
- 上面那個+和+=的代碼相似,我們也可以復(fù)用一下
Date operator+(int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
- 上面的代碼是+復(fù)用+=,那么我們也可以用+=復(fù)用+
Date& operator+=(int day)
{
*this = *this + day;
return *this;
}
- 那么這兩種寫法哪一種比較好呢?
- 這里左邊的更好,右邊的會復(fù)用+,而+里面會創(chuàng)建臨時對象
2.9.1 日期減等天數(shù)
- 經(jīng)過上面的思考,我們先實現(xiàn)減等
Date& operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
2.9.2 日期減天數(shù)
Date operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
三、前置++ && 后置++
- 前置++很簡單
Date& operator++()
{
*this += 1;
return *this;
}
- 后置++要返回++后的值【這里要注意,后置++的操作符是有一個**(int)區(qū)分的**】
- 后置++相比前置++的效率是低一些的
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
3.1 日期減日期【返回天數(shù)】
int operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
int flag = -1;
max = d;
min = *this;
}
// 相差天數(shù)
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
3.2 流插入
-
對于內(nèi)置類型可以流插入,那么內(nèi)置類型也可以流插入
-
對于流插入【cout】,對于庫里面全局的ostream類型的對象 -->點我
- 這里我們在使用的時候就反了,第一個被this占用了
void operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
- 作為成員函數(shù)重載,this指針占據(jù)第一個參數(shù),Date必須是在操作數(shù)
-
所以我們就必須讓
ostream
占據(jù)第一個位置 -
想要占據(jù)第一個參數(shù),就不能寫成成員函數(shù),就要寫成全局函數(shù)
-
當我們寫成全局函數(shù)的時候,·不能訪問類的私有了,這里也要后面的友元
void operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
}
- 這里想要改成多個插入,我們返回值就要改一下
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
3.3 流提取
- 流提取是
cin
- 這里就不能使用const了
istream& operator>>(istream& in, Date& d)
{
cout << "請依次輸入年月日:>";
in >> d._year >> d._month >> d._day;
return in;
}
3.4 檢查輸入日期是否合法
bool CheckInvalid()
{
if (_year <= 0
|| _month < 1
|| _month > 12
|| _day < 1
|| _day > GetMonthDay(_year,_month))
{
return false;
}
else
{
return true;
}
}
- 這個時候把可以改檢查的地方就檢查一下
四、日期類的實現(xiàn)【源碼】
#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:
// 構(gòu)造函數(shù)
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
if (!CheckInvalid())
{
cout << "構(gòu)造日期非法" << endl;
}
}
// 判斷等于
bool operator==(const Date& d)
{
return _year == d._year
&& _month == d._month
&& _day == d._day;
}
// 判斷小于
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;
}
// 判斷小于等于
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);
}
// 日期加等天數(shù)
Date& operator+=(int day)
{
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
++_month;
if (_month == 13)
{
++_year;
_month = 1;
}
}
return *this;
}
Date operator+(int day)
{
Date tmp(*this);
//Date tmp = *this;
tmp += day;
return tmp;
}
// 日期加天數(shù)
Date operator+(const Date& d)
{
Date tmp(*this);
tmp._day += d._day;
while (d._day > GetMonthDay(tmp._year, tmp._month))
{
tmp._day -= GetMonthDay(tmp._year, tmp._month);
++tmp._month;
if (tmp._month == 13)
{
++tmp._year;
tmp._month = 1;
}
}
return tmp;
}
// 日期-=天數(shù)
Date& operator-=(int day)
{
_day -= day;
while (_day <= 0)
{
--_month;
if (_month == 0)
{
--_year;
_month = 12;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 日期減天數(shù)
Date operator-(int day)
{
Date tmp = *this;
tmp -= day;
return tmp;
}
// 前置++
Date& operator++()
{
*this += 1;
return *this;
}
// 后置++
Date operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
// 日期-日期
int operator-(const Date& d)
{
int flag = 1;
Date max = *this;
Date min = d;
if (*this < d)
{
int flag = -1;
max = d;
min = *this;
}
int n = 0;
while (min != max)
{
++min;
++n;
}
return n * flag;
}
// 獲取月份天數(shù)[可以不寫inline,類里默認就是]
inline int GetMonthDay(int year, int month)
{
assert(month < 13 && month > 0);
// 放到靜態(tài)區(qū)
static int MonthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
// 先判斷月份
if (month == 2 && (((year % 100 == 0) && (year % 4 == 0)) || (year % 400 != 0)))
return 29;
return MonthDays[month];
}
// 拷貝構(gòu)造
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
bool CheckInvalid()
{
if (_year <= 0
|| _month < 1
|| _month > 12
|| _day < 1
|| _day > GetMonthDay(_year,_month))
{
return false;
}
else
{
return true;
}
}
void Print()
{
cout << _year << "/" << _month << "/" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
istream& operator>>(istream& in, Date& d)
{
while (1)
{
cout << "請依次輸入年月日:>";
in >> d._year >> d._month >> d._day;
if (!d.CheckInvalid())
{
cout << "輸入非法日期,請重新輸入" << endl;
}
else
{
break;
}
}
return in;
}
五、const修飾
5.1 const成員函數(shù)
- 將const修飾的“成員函數(shù)”稱之為const成員函數(shù),const修飾類成員函數(shù),實際修飾該成員函數(shù)隱含的this指針,表明在該成員函數(shù)中不能對類的任何成員進行修改。
- 我們看下面這段代碼
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()
{
cout << "Print()const" << endl;
cout << "year:" << _year << endl;
cout << "month:" << _month << endl;
cout << "day:" << _day << endl << endl;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
- 這里會出現(xiàn)一個權(quán)限放大的問題
-
那么在參數(shù)就需要改成
const Date*
-
為了解決這個問題,我們就要在函數(shù)后面的位置加上一個
const
-
修飾的是this指針指向的內(nèi)容
- 那么不是下面方法調(diào)用也是可以的,因為這個是權(quán)限的縮小
- const對象可以調(diào)用,非const對象也可以調(diào)用
- 那么全部函數(shù)都可以加上const嗎?—>不可以!
- 如果函數(shù)內(nèi)部要被修改,那肯定是不能加的
- 上面寫的日期類的部分函數(shù)也可以加上~~
5.2 小結(jié)一下:
-
成員函數(shù)如果是一個對成員變量只進行讀訪問的函數(shù),建議加上const,這樣const對象和非const對象都可以訪問
-
成員函數(shù)如果是一個對成員變量進行讀寫訪問的函數(shù),不可以加上const,否則不能修改成員變量
- const對象可以調(diào)用非const成員函數(shù)嗎? --> 不可以!【權(quán)限放大】
- 非const對象可以調(diào)用const成員函數(shù)嗎?–> 可以!【權(quán)限縮小】
- const成員函數(shù)內(nèi)可以調(diào)用其它的非const成員函數(shù)嗎?–> 不可以!【權(quán)限放大】
- 非const成員函數(shù)內(nèi)可以調(diào)用其它的const成員函數(shù)嗎?–> 可以!【權(quán)限縮小】
5.3 默認成員函數(shù)【取地址及const取地址操作符重載】
- 前面章節(jié)學(xué)習了4個默認成員函數(shù)了,剩下兩個再看一下
文章來源:http://www.zghlxwxcb.cn/news/detail-833145.html
- 這兩個默認成員函數(shù)一般不用重新定義 ,編譯器默認會生成。
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
- 比如說不想讓別人拿到我的地址,普通對象返回空,const對象返回假地址
- 就可以這樣寫:
Date* operator&()
{
return nullptr;
}
const Date* operator&()const
{
int i = 0;
return (const A*)&i;
}
- 這兩個運算符一般不需要重載,使用編譯器生成的默認取地址的重載即可,只有特殊情況,才需要重載,比如想讓別人獲取到指定的內(nèi)容!
最后本文主要學(xué)習了運算符重載這個知識點,并且在學(xué)習的時候?qū)崿F(xiàn)了一個日期類,望烙鐵們學(xué)有所成~~文章來源地址http://www.zghlxwxcb.cn/news/detail-833145.html
到了這里,關(guān)于【C++】中類的6個默認成員函數(shù) 取地址及const成員函數(shù) && 學(xué)習運算符重載 && 【實現(xiàn)一個日期類】的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!