0. 上篇
1. 运算符重载
1.1 运算符重载
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
注意:
-
不能通过连接其他符号来创建新的操作符:比如operator@
-
重载操作符必须有一个类类型参数
-
用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其含义
-
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
-
.* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
// 全局的operator==
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;
};
// 这里会发现运算符重载成全局的就需要成员变量是公有的,那么问题来了,封装性如何保证?
// 这里其实可以用我们后面学习的友元解决,或者干脆重载成成员函数。
bool operator==(const Date& d1, const Date& d2)
{
return d1._year == d2._year
&& d1._month == d2._month
&& d1._day == d2._day;
}
void Test()
{
// 比如想要比较date对象是否大象相等
Date d1(2018, 9, 26);
Date d2(2018, 9, 27);
d1 == d2; // 编译器会转换成 operator==(d1,d2);
// 但是我们一般不会这么写,因为这样可读性不好:
operator==(d1,d2);
}
在上面的代码中我们会发现如果把运算符重载函数定义在外面,就要求类成员变量是公开的,这样肯定是不行的,于是我们有一个解决办法,将重载函数定义在类里面,变成类的成员函数:
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)
// 这里需要注意的是,不能定义成以前那种(d1,d2),
// 因为类成员函数的参数会自动包含一个 this 指针
// 所以此处相当于 bool operator==(Date* this, const Date & d2)
bool operator==(const Date & d2)
{
return _year == d2._year
&& _month == d2._month
&& _day == d2._day;
}
private:
int _year;
int _month;
int _day;
};
1.2 赋值运算符重载
1. 赋值运算符重载格式
-
参数类型:const T&,传递引用可以提高传参效率
-
返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
-
检测是否自己给自己赋值
-
返回*this :要符合连续赋值的含义
// 赋值运算符重载
// d3 = d1
// 运算符的重载是为了让自定义类性可以像内置类型一样去使用运算符
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
当我们不实现时,编译器也会自动生成 operator= 函数,此时的赋值运算符重载函数就和默认的拷贝构造函数一样,会完成按字节的值拷贝(浅拷贝),也就是说有些类,我们是不需要去实现拷贝构造和 operator= 的,因为编译器默认生成就可以用,比如上文中的 Date 类。但有些类,比如数据结构栈的类,就必须自己实现深拷贝的 operator= 函数,不然会出现和前文拷贝构造函数一样的浅拷贝问题:
注意:如果类中未涉及到资源管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要实现。
1.3 前置++和后置++重载
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
// 前置++:返回+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;
}
private:
int _year;
int _month;
int _day;
};
2. 实现一个完善的日期类
通过之前文章的学习内容和上述内容,我们可以完成一个较为完善的日期类:
// 实现一个完善的日期类
class Date
{
public:
int GetMonthDay(int year, int month)
{
int monthDays[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
// 闰年2月
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
return 29;
return monthDays[month];
}
Date(int year = 0, int month = 1, int day = 1)
{
// 如果日期合法
if (year >= 0 && month >= 1 && month <= 12 && day >= 1 && day <= GetMonthDay(year,month))
{
_year = year;
_month = month;
_day = day;
}
else
{
cout << "非法日期" << endl;
}
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._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;
return false;
}
bool operator<=(const Date& d)
{
// 函数原型为 operator<=(Date* this, const Date& d)
// 函数调用时相当于传参 operator<=(&d1, d2)
// 此处 *this = *(&d1),即d1
return *this < d || *this == d;
// 所以上面的代码等价于
// return d1 < d2 || d1 == d2;
}
// 通过 *this ,我们可以很轻松实现代码的复用
// 代码的复用有很多好处,当未来加入什么“时分秒”时不需要修改全部函数
// 只需要修改一开始的少数函数就可以达成修改全部函数的目的
// 复用是高内聚,写代码的提倡的就是低耦合高内聚
bool operator>(const Date& d)
{
return !(*this <= d);
}
bool operator>=(const Date& d)
{
return !(*this < d);
}
bool operator!=(const Date& d)
{
return !(*this == d);
}
// +
//Date operator+(int day)
//{
// Date ret(*this); // 拷贝构造一个ret
// ret._day += day;
// while (ret._day > GetMonthDay(ret._year, ret._month))
// {
// // 日期大于天数上限,往月进位
// ret._day -= GetMonthDay(ret._year, ret._month);
// ret._month++;
// // 检查月份合法性
// if (ret._month > 12)
// {
// ret._month = 1;
// ret._year++;
// }
// }
// return ret;
//}
// +
// 复用实现
Date operator+(int day)
{
Date ret(*this); // 拷贝构造一个ret
ret += day; // ret.operator+=(day)
return ret;
}
// +=
// 类对象本身在函数外,所以可以传引用返回
Date& operator+=(int day)
{
if (day < 0)
{
return *this -= -day;
}
_day += day;
while (_day > GetMonthDay(_year, _month))
{
_day -= GetMonthDay(_year, _month);
_month++;
if (_month > 12)
{
_year++;
_month = 1;
}
}
return *this;
}
// -
/*Date operator-(int day)
{
Date ret(*this);
ret._day -= day;
while (ret._day <= 0)
{
ret._month--;
if (ret._month <= 0)
{
ret._year--;
ret._month = 12;
}
ret._day += GetMonthDay(ret._year, ret._month);
}
return ret;
}*/
// -
// 复用实现
Date operator-(int day)
{
Date ret(*this);
ret -= day; // operator-=(day)
return ret;
}
// -=
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;
}
// ++(前置++)
Date& operator++()
{
*this += 1;
return *this;
}
// ++(后置++)
// 为了构成函数重载,函数参数加了一个int,但实际不用传参数
// 这个int仅仅是编译器为了构成函数重载加上的
Date operator++(int)
{
Date tmp(*this);
*this += 1;
return tmp;
}
// --(前置--)
Date& operator--()
{
*this -= 1;
return *this;
}
//--(后置--)
// 为了构成函数重载,函数参数加了一个int,但实际不用传参数
// 这个int仅仅是编译器为了构成函数重载加上的
Date operator--(int)
{
Date tmp(*this);
*this -= 1;
return tmp;
}
// d1 - d2
int operator-(const Date& d)
{
int flag = 1;
Date max = *this; // 拷贝构造
Date min = d;
if (*this < d)
{
max = d; // operator=
min = *this;
flag = -1;
}
int n = 0;
while (min != max)
{
++min; // 参考前置++与后置++的实现,自定义类型能用前置++就不要用后置
++n;
}
return n * flag;
}
// 赋值运算符重载
// d3 = d1
// 运算符的重载是为了让自定义类性可以像内置类型一样去使用运算符
Date& operator=(const Date& d)
{
if (this != &d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};