本文已参与「新人创作礼」活动,一起开启掘金创作之路。
一、任务清单
- 两个日期对象的大小比较:< 、 >、 =、 ==、 >=、 <=
- 日期对象加减天数:++(前置和后置) 、--(前置和后置)、 +=、 -=、+、 -
- 两个日期对象之间相隔天数:-
- 流提取与流插入的重载
二、基本原则
- ① 采用传值返回一定是对的,就是需要拷贝增加额外开销。能不能用使引用返回取决于出函数后引用的对象还在不在
- ② 尽可能的实现各个函数之间的复用
- ③ 长度较短的函数推荐使用内联函数的形式。注意内联函数的定义与声明不能分离,否则在编译的时候内联函数不会加入到符号表中产生链接错误。所以最好还是写入到类中,因为类中的函数默认是内联的,同时可以轻松访问成员变量
- ④ 在复用函数的时候,应该尽可能减少拷贝的次数来提高效率
三、功能实现
① 构造函数
class Date
{
public:
Date(int year, int month, int day)
:_year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
②两个对象间比大小
我们只实现 > 和 == 的重载,其他的都可以以此来复用(没有特殊说明都是写在类里面的,为了减少篇幅,类就不写出来了)
bool operator== (const Date& d) const
{
return _year == d._year && _month == d._month && _day == d._day;
}
bool operator> (const Date& d) const
{
if (_year > d._year ||
_year == d._year && _month > d._month ||
_year == d._year && _month == d._month && _day > d._day)
return true;
else
return false;
}
【评注】
- ① 使用引用传值来减少拷贝,最好加上const保护
- ② 对象的成员变量不需要修改时,最好给this指针加上 const 保护,这样通对象和const对象都可以调用该成员函数 参考文章
bool operator>= (const Date& d) const
{
return operator>(d) || operator==(d);
}
bool operator< (const Date& d) const
{
return !operator>=(d);
}
bool operator <= (const Date& d) const
{
return !operator>(d);
}
③ 日期对象加减天数
1.加减运算符的重载
int Getmonthday(int year, int month) const // 之后反复用到,不再赘述
{
static int day[13] = {0, 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;
else
return day[month];
}
Date operator+ (const int day) const
{
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 operator- (const int day) const
{
Date tmp(*this);
if (day < 0)
{
tmp = tmp + (-day);
return tmp;
}
tmp._day -= day;
while (tmp._day < 0)
{
tmp._month--;
if (tmp._month == 0)
{
tmp._month = 12;
tmp._year--;
}
tmp._day += Getmonthday(tmp._year, tmp._month);
}
return tmp;
}
【评注】
- 进行加减运算时,本身是不变的,所以需要先拷贝临时对象,最后将临时对象返回
- 因为临时对象出作用域后会被销毁,所以不能采用引用返回
- 上述代码各发生两次拷贝——创建临时变量、返回临时对象
2.+= -= 运算符的重载
Date& operator+= (const int day)
{
_day += day;
while (_day > Getmonthday(_year, _month))
{
_day -= Getmonthday(_year, _month);
_month++;
if (_month == 13)
{
_month = 1;
_year++;
}
}
return *this;
}
Date& operator-= (int day)
{
if (day < 0)
{
*this = *this + (-day);
return *this;
}
_day -= day;
while (_day < 0)
{
_month--;
if (_month == 0)
{
_month = 12;
_year--;
}
_day += Getmonthday(_year, _month);
}
return *this;
}
【评注】
- 基本思想与+-的实现是非常相似的
- 区别在于+=、-=对象本身是会改变的,所以不需要额外的拷贝,直接在原对象上操作
- 出函数后原对象并没有被销毁,所以可以使用引用返回减少拷贝
- +=、 -=的实现过程中没有用到拷贝构造
3.+- 与+= -=之间的复用
// +复用+=
Date operator+ (int day)
{
Date tmp(*this);
tmp += day;
return tmp;
}
// +=复用+
Date& operator += (int day)
{
*this = *this + day;
return *this;
}
根据上面代码的比较我们可以得出以下结论:
- +复用+=的时候,+的重载需要拷贝2次,而+=的重载本身不需要拷贝
- +=复用+的时候,+的重载需要拷贝2次,而+=的重载也需要拷贝两次
- 所以综上+复用+=的效果最佳
4.前置++与后置加加
// 前置减减后置减减同理,就不再这里赘述了
Date& operator++ () // 前置加加
{
*this += 1;
return *this;
}
Date operator++ (int) // 后置加加
{
Date tmp(*this);
*this += 1;
return tmp;
}
【评注】
- 为了区别前置加加和后置加加,规定后置加加带一个int型参数,前置加加不带参。注意带的参数类型必须是int类型,形参名可写可不写。
- 前置加加先加加后使用,所以将自增后的对象本身返回;后置加加先使用后加加,所以将自增前的对象本身返回
④两个对象之间的日期差
int operator- (const Date& d) const
{
Date max = *this;
Date min = d;
if (max < min)
{
max = d;
min = *this;
}
int cnt = 0;
while (max != min)
{
min++;
cnt++;
}
return cnt;
}
【评注】
- 计算两个天数差的方法有很多,这是最简单的方法
- 还可以计算两个到同一天的时间然后作差 + 1
⑤流插入与流提取运算符的重载
【问题一】为什么C++中的 cin 和 cout 会自动识别类型? 【答】因为流提取运算符和流插入运算符将所有常见的内置类型都给重载了
【问题二】cout 和 cin 是什么? 【答】cout 是 ostream 的全局对象;cin 是 istream 的全局对象
【问题三】流插入运算符和流提取运算符的重载可以写在类里面吗? 【答】不可以。因为类里面的函数第一次参数默认为 this 指针,所以在使用的时候不是 cout << xxx,而变成了xxx << cout
【问题四】写在函数外面如何访问成员变量 【答】使用友元函数,注意在类里面声明的时候前面需要加上 const,但是在定义的时候不需要加上 const。 友元函数的特性:
- 友元函数可访问类的私有和保护成员,但==不是==类的成员函数,因此也没有this指针
- 友元函数不能用const修饰(因为没有this指针)
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
【问题五】定义与声明分离的时候 【答】要在头文件声明,源文件定义,否则会出现链接错误。而类里面定义的函数默认是内联的,不会添加到符号表中,就不会出现问题。类里面长的函数也具有这样内联的属性