实现函数:
1、基本函数
- 构造函数
- 拷贝构造
- 析构函数
- 打印函数
2、运算符重载
- 小于 小于等于
- 大于 大于等于
- 判断相等
- 赋值运算
- 加等于 减等于 (加减等于 一个天数)(改变本身)
- 加减 (加减 一个天数)(不改变本身)
- 两个日期之间相隔天数
- 前置++ 和 后置++(重点)
- 前置-- 和 后置–
3、流插入 << 和 流提取 >>
- 流插入:cout 输出 <<
- 流提取:cin 输入 >>
注意:我们日期类各个函数的实现是声明和定义分离:声明写在头文件,定义写在.cpp文件
4、实现总代码:Date.h 、Date.cpp
1、基本函数 与 类的框架
//Date.h
class Date
{
public:
// 构造函数
Date(int year, int month, int day);
// 拷贝构造函数
Date(const Date& d);
// 析构函数:用默认的
// ~Date(){}
// 打印函数
void Print() {
cout << _year << "/" << _month << "/" << _day;
}
private:
int _year = 1970;
int _month = 1;
int _day = 1;
};
//Date.cpp
// 构造函数
Date::Date(int year, int month, int day)
:_year(2024)
, _month(1)
, _day(1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
Date::Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
// 打印函数
void Date::Print() {
cout << _year << "/" << _month << "/" << _day;
}
2、运算符重载
我们先实现 小于 和 判断相等 两个重载函数,其他的函数都可以复用这两个函数实现
2.1 小于
// 赋值运算符重载
bool Date::operator<(const Date& d) const
{
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 Date::operator==(const Date& d) const
{
return ((*this)._year == d._year &&
(*this)._month == d._month &&
(*this)._day == d._day);
}
2.3 大于、大于等于 、小于等于、不等于
(1)大于
bool Date::operator>(const Date& d) const
{
return !((*this) < d) && !((*this) == d);
}
(2)大于等于
bool Date::operator>=(const Date& d) const
{
return !((*this) < d);
}
(3)小于等于
bool Date::operator<=(const Date& d) const
{
return ((*this) < d || (*this) == d);
}
(4)不等于
bool Date::operator!=(const Date& d) const
{
return !((*this) == d);
}
2.4 赋值运算
// 日期类都是没有指向资源的,只有内置类型:使用默认的赋值运算符重载函数也够了,这里我们手动实现
void Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
2.5 加等于 / 减等于 (加减等于 一个天数)(改变本身)
注意:我们这个函数需要考虑天数是 负数的情况
- d += -10 相当于 d -= 10
- d -= -10 相当于 d += 10
注意:+= 和 -= 都是改变日期本身,因此直接修改 *this,同时返回该修改后的日期
为什么要返回该日期?
举例:
int t = 10;
t += 1;
本质上 这个计算也需要返回 t,即返回左操作数,相当于更新操作数
// += 和 -= 要考虑 days 是负数的情况
Date& Date::operator+=(int days)
{
if (days < 0) {
(*this) -= (-days);
return *this;
}
_day += days;
while (_day > GetMonthDay(_year, _month)) {
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13) {
_month = 1;
_year++;
}
}
return *this;
}
Date& Date::operator-=(int days)
{
if (days < 0) {
(*this) += (-days);
return *this;
}
_day -= days;
while (_day < 0) {
_month--;
if (_month == 0) {
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
// 获取该月的天数
// 因为这个函数会频繁的调用,因此写在类里面(其实类里面的函数都是内联函数)
int GetMonthDay(int year, int month) const {
assert(month > 0 && month < 13);
static int Month[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// 细节优化技巧
// 这里将 Month写成静态的非常有意义:因为这个函数会频繁的调用,若 Month 开在栈上,则会频繁的销毁创建
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)) return 29;
else return Month[month];
}
实现思路:这里解释 += 函数的思路,-= 差不多
通过 GetMonthDay函数获取本月的天数
先_day += days;
然后while循环,不断 _day -= GetMonthDay(_year, _month); 一直减到天数无法凑满当月的天数
其中,不断进位,月进位,年进位
2.6 加减 (加减 一个天数)(不改变本身)
你可以发现,大部分函数都是复用前面实现的一小部分函数(这也是开发技巧,不要盲目的作重复的事,要懂得复用已经实现的东西)
Date& Date::operator+(int days)
{
Date tmp = *this;
tmp += days;
return *this;
}
Date& Date::operator-(int days)
{
Date tmp = *this;
tmp -= days;
return *this;
}
因为 加减是不改变本身的,但是本身会存在执行的加减操作后的数,所有要返回一个加减后的数
如
int t = 10;
t + 10; // 这个操作不会改变 t
t = t + 10; // 虽然没有改变 t,但是 t + 10 这个表达式是有结果的
2.7 两个日期之间相隔天数
思路:以一个日期为基础,while 循环 +1 或 -1 不断靠近另一日期,最终相等,过程中累计的天数就是 两个日期之间相隔天数
这个方法看起来有些耗时,但实际上现在的计算机一秒可以计算两亿多次,所以影响不会很大(若有更好的方法,欢迎大佬在评论区分享)
int Date::operator-(const Date& d)
{
int flag = 1, cnt = 0;
if ((*this) > d) flag = -1;
Date tmp = (*this);
while (tmp != d)
{
tmp += flag;
cnt++;
}
return flag * cnt;
}
2.8 前置++ 和 后置++(重点)
// 前置++
Date& Date::operator++()
{
return (*this) += 1;
}
// 后置++
Date& Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
有没有发现这两个函数名相同:没错这里就是 函数重载
函数重载和运算符重载没有任何关系,各论各的
多个 运算符重载函数 可以构成 函数重载:即同名运算符重载函数
为什么 第二个函数有个 int 形参?
仅仅是为了区分,构成函数重载,给后置++,强行增加一个 int 形参(不用写形参名)
int 取什么值都行,这里仅仅只是祖师爷老本为了区分加上去的语法,没有其他的含义
后置++ 设计的思路:
后置++返回+1之前的值,同时本身已经+1
这里也就明白了后置++ 底层的运行逻辑:本身已经+1,只不过需要返回的值是之前的值
同时注意:若需要自定义类型++ 最好是前置++,后置++还要拷贝一次
2.9 前置-- 和 后置–
原理参考:前置++ 和 后置++
Date& Date::operator--()
{
return *this -= 1;
}
Date Date::operator--(int)
{
Date tmp = *this;
*this -= 1;
return tmp;
}
3、流插入 << 和 流提取 >>
为什么要写这个重载函数?
为了使用 cout 和 cin 直接 输入输出 自定义类型
3.1 概念和原理实现
“<<” 是 流插入运算符
“>>” 是 流提取运算符
查看一下C++文档:
我们可以直接 cout << 10; 打印一个 int 等其他内置类型的数据到控制台上
左边的是第一个操作数 cout 、 中间的是 流插入运算符<< 、 右边的是第二个操作数 int 类型的 10
对应重载函数为 :ostream& operator<<(int val);
我们要想 cout 一个自定义类型
设计的重载函数是:ostream& operator<<(ostream & out);
使用格式为:至于为什么要传一个 ostream & 类型的 cout 参数过去,这里不讨论
// 调用函数的写法
d1.operator<<(cout);
// 直接运用运算符的写法:<<
d1 << cout;
你可以发现,根据现有的函数写法,我们只能这样写:d1 << cout;
明显不正常
作为类的重载函数,函数本身会隐藏一个 this指针变量在 函数参数的一号位
即上面的 d1 :一号位参数放在左边
若想把 d1 放在 右边,cout 放在左边(符合常规格式)
就要想办法将 this指针 放置 二号位
但是又因为:函数是成员函数时,this 永远霸占第一个位置
因此,要想达到目的,就不能将该函数写成 成员函数了,需要变成全局函数,可以无限制的,自己调整函数参数的位置
全局函数:ostream& operator<<(ostream& out, Date& d)
就可以这样写了:cout << d1;
可读性极强
⭐问题又来了!为什么连续输出不行 cout << d1 << d2;
?
想想之前的连续赋值的实现原理:
a = b = c = 10;
原理:每次赋值后,返回左操作数,作为新的赋值式的 右操作数
同理,要想实现连续 cout << d1 << d2;
需要返回 ostream& out
右操作符(因为本身你函数的顺序就调过来了)
因为出了函数作用域,out 还在,可以写成引用
out 是 cout 的别名,cout 是全局对象
3.1 函数实现
// 流插入函数
ostream& operator<<(ostream& out, Date& d) {
out << d._year << "/" << d._month << "/" << d._day;
return out;
}
// 流提取函数
istream& operator>>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
if (!d.IsVaild()) {
cout << "日期不合法\n";
}
return in;
}
bool Date::IsVaild()
{
if (_year < 0 || _month < 0 || _month > 12 || _day < 0 || _day > GetMonthDay(_year, _month)) return false;
return true;
}
因为这两个全局函数都需要使用到 对象的私有成员
无法直接访问
则将这两个函数写成友元函数(在本篇博客中有讲解)
4、总代码
Date.h
#pragma once
#include<iostream>
using namespace std;
class Date
{
friend ostream& operator<<(ostream& out, Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
// 构造函数
Date(int year, int month, int day);
// 拷贝构造函数
Date(const Date& d);
// 析构函数:用默认的
// ~Date(){}
// 重载
bool operator<(const Date& d) const;
bool operator<=(const Date& d) const;
bool operator>(const Date& d) const;
bool operator>=(const Date& d) const;
bool operator==(const Date& d) const;
bool operator!=(const Date& d) const;
// 赋值运算符重载
void operator=(const Date& d);
// 日期 加减 等于 天数
// 因为对象定义在 main函数,这里就可以返回 引用
Date& operator+=(int days);
Date& operator-=(int days);
// 日期 加减 天数
// 因为对象定义在 main函数,这里就可以返回 引用
Date& operator+(int days);
Date& operator-(int days);
// 日期 减 日期
int operator-(const Date& d);
// 前置++ 和 后置++
Date& operator++();
Date& operator++(int);
// 获取日期
int GetMonthDay(int year, int month);
// 打印函数
void Print();
// 判断日期是否合法
bool IsVaild();
private:
int _year = 1970;
int _month = 1;
int _day = 1;
};
Date.cpp
#include "Date.h"
// 构造函数
Date::Date(int year, int month, int day)
:_year(2024)
, _month(1)
, _day(1)
{
_year = year;
_month = month;
_day = day;
}
// 拷贝构造函数
Date::Date(const Date& d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
// 赋值运算符重载
bool Date::operator<(const Date& d) const
{
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 Date::operator<=(const Date& d) const
{
return ((*this) < d || (*this) == d);
}
bool Date::operator>(const Date& d) const
{
return !((*this) < d) && !((*this) == d);
}
bool Date::operator>=(const Date& d) const
{
return !((*this) < d);
}
bool Date::operator==(const Date& d) const
{
return ((*this)._year == d._year &&
(*this)._month == d._month &&
(*this)._day == d._day);
}
bool Date::operator!=(const Date& d) const
{
return !((*this) == d);
}
// 日期类都是没有控制资源的 内置类型:使用默认的赋值运算符重载函数也够了
void Date::operator=(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
// += 和 -= 要考虑 days 是负数的情况
Date& Date::operator+=(int days)
{
if (days < 0) {
(*this) -= (-days);
return *this;
}
_day += days;
while (_day > GetMonthDay(_year, _month)) {
_day -= GetMonthDay(_year, _month);
_month++;
if (_month == 13) {
_month = 1;
_year++;
}
}
return *this;
}
Date& Date::operator-=(int days)
{
if (days < 0) {
(*this) += (-days);
return *this;
}
_day -= days;
while (_day < 0) {
_month--;
if (_month == 0) {
_month = 12;
_year--;
}
_day += GetMonthDay(_year, _month);
}
return *this;
}
Date& Date::operator+(int days)
{
Date tmp = *this;
tmp += days;
return *this;
}
Date& Date::operator-(int days)
{
Date tmp = *this;
tmp -= days;
return *this;
}
int Date::operator-(const Date& d)
{
int flag = 1, cnt = 0;
if ((*this) > d) flag = -1;
Date tmp = (*this);
while (tmp != d)
{
tmp += flag;
cnt++;
}
return flag * cnt;
}
Date& Date::operator++()
{
return (*this) += 1;
}
Date& Date::operator++(int)
{
Date tmp = *this;
*this += 1;
return tmp;
}
int Date::GetMonthDay(int year, int month)
{
int Month[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)) Month[2] += 1;
return Month[month];
}
void Date::Print() {
cout << _year << "/" << _month << "/" << _day;
}
bool Date::IsVaild()
{
if (_year < 0 || _month < 0 || _month > 12 || _day < 0 || _day > GetMonthDay(_year, _month)) return false;
return true;
}
// 流插入函数
ostream& operator<<(ostream& out, Date& d) {
out << d._year << "/" << d._month << "/" << d._day;
return out;
}
// 流提取函数
istream& operator>>(istream& in, Date& d) {
in >> d._year >> d._month >> d._day;
if (!d.IsVaild()) {
cout << "日期不合法\n";
}
return in;
}