赋值运算符重载
运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其
返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
- 不能通过连接其他符号来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。
// 全局operator==
bool operator==(const Date& d1, const Date&d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
int main()
{
Date d1(2023, 2, 8);
Date d2(2023, 5, 5);
// d1 == d2 - 转换成调用函数operator==(d1, d2);
cout << (d1 == d2) << endl;// 注意运算符的优先级
return 0;
}
// 成员函数的operator==, d1 == d2 等价于 d1.operator==(d2)
// bool operator==(Date* this, const Date& d2)
// 这里需要注意的是,左操作数是this,指向调用函数的对象
bool operator==(const Date& d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
在日期类中利用已经写好的==与<函数,即可以实现日期的不同比较运算符重载,:
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 && _month < d._month)
return true;
else if (_year == d._year && _month == d._month && _day < d._day)
return true;
else
return false;
//可以将代码简化为:
//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);
}
赋值运算符重载
1、赋值运算符重载格式
- 参数类型:const T&,传递引用可以提高传参效率
- 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
- 检测是否自己给自己赋值
- 返回*this :要复合连续赋值的含义
// 需要支持连续赋值,因此需要有Date类型的返回值
Date& operator=(const Date& d)// 可以不添加&,赋值引用不会产生无限递归,传参会调用拷贝构造然后进行赋值操作;但是最好将&添加上。
{
if (this != &d)// 避免出现 d1 = d1 的现象
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
2、赋值运算符只能重载成类的成员函数不能重载成全局函数
// 赋值运算符重载成全局函数,注意重载成全局函数时没有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、用户没有显式实现时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。注
意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。
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:
// 基本类型(内置类型)
int _year = 1970;
int _month = 1;
int _day = 1;
// 自定义类型
Time _t;
};
int main()
{
Date d1;
Date d2;
d1 = d2;
return 0;
}
在上述的代码例子中编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝,但这只是浅拷贝。如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必
须要实现(例如前面文章中提及的Stack类)。
+、+=、-、-=、前置++与后置++
首先,在日期中的加法运算可以令日期+天数、日期-日期、日期-天数:
+、+=
Date 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._month = 1;
++tmp._year;
}
}
return tmp;
}
使用+实现+=
//Date& Date::operator+=(int day)
//{
// *this = *this + day;
// return *this;
//}
// 这种好,因为+是有消耗的,而+=没有拷贝行为
// 有时需要编写的 d1 = d2 += d3,所以需要返回值。
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)
{
_month = 1;
++_year;
}
}
return *this;
}
使用+=来实现+
//Date Date::operator+(int day)
//{
// Date tmp(*this);
// tmp += day;
// return tmp;
//}
当实现了+=或者+之后可以使用已经编写好的函数进行复用,在上面代码中的两种复用方式中,先构建+=函数,再使用+=来构建+更加的好,因为+函数调用拷贝构造函数有更多的消耗。
-、-=
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)
{
Date tmp(*this);
tmp -= day;
return tmp;
}
// 日期-日期
int Date::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;
}
在实现+=和-=的过程中会遇到这样的问题,在编写函数的时候都是以输入正的天数为例子,当输入的天数为负数时,可以对其进行一个判断来使+=函数调用-=函数使+= -100 -> -= 100。
另外在编写日期-日期的函数的时候可以转换思路,将两个日期都转化为当前年份的天数,然后对两个天数进行处理获得差值,这样的方法比较简便。
++
// 前置++:返回+1之后的结果
// 注意:this指向的对象函数结束后不会销毁,故以引用方式返回提高效率
Date& operator++()
{
_day += 1;
return *this;
}
// 后置++:
// 前置++和后置++都是一元运算符,为了让前置++与后置++形成能正确重载
// C++规定:后置++重载时多增加一个int类型的参数,但调用函数时该参数不用传递,编译器自动传递
// 注意:后置++是先使用后+1,因此需要返回+1之前的旧值,故需在实现时需要先将this保存一份,然后给this + 1
// 而temp是临时对象,因此只能以值的方式返回,不能返回引用
Date operator++(int)
{
Date temp(*this);
_day += 1;
return temp;
}
--
// --d;
// 前置--
Date& Date::operator--()
{
*this -= 1;
return *this;
}
// d--;
// 后置--
Date Date::operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
流插入、流提取
当在成员函数中编写流插入函数时会有这样的问题:
ostream& Date::operator<<(ostream& out)
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
return out;
}
void TestDate5()
{
// 流插入
Date d1(2023, 2, 8);
cout << d1; // err
Date d2(2024, 2, 8);
d1.operator<<(cout);
d1 << cout;
//operator<<(cout, d1);
//cout << d1 << d2;
}
因为运算符重载规定第一个参数为左操作数,第二个参数为右操作数,成员函数有固定的做操作数*this,因此只能通过d1.operator<<(cout);的方式调用,或者d1 << cout;的方式。
使用全局函数的方式:
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;
return in;
}
void TestDate6()
{
// 流提取
Date d1;
cin >> d1;
cout << d1;
}
为了能够连续赋值使用ostream&作为返回值,同时为了访问到Date类中的私有成员需要在Date中是这个全局函数成为友元函数。
const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数
隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。‘’
先来简单地看一下下面的这个类,会发现aa.Print()会报错。
class A
{
public:
void Print()
{
cout << _a << endl;
}
private:
int _a;
};
int main()
{
const A aa;
aa.Print(); // err 权限的放大
return 0;
}
我们不能直接的改变this,只能通过间接的方式:
// const 修饰 *this
// this 的类型变成 const A*
void Print() const
{
cout << _a << endl;
}
添加const之后普通对象不会,会有权限的缩小,可以使用。
取地址及const取地址操作符重载
class Date
{
public:
Date* operator&()
{
return this;
}
const Date* operator&()const
{
return this;
}
private:
int _year; // 年
int _month; // 月
int _day; // 日
};
这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需
要重载,比如想让别人获取到指定的内容!