Re: ゼロから学ぶ C++ 入門(八)类和对象·第五篇:時間计算器

◆ 博主名称: 晓此方-CSDN博客

大家好,欢迎来到晓此方的博客。

⭐️C++系列个人专栏:

Re:从零开始的C++_晓此方的博客-CSDN博客


目录

0.1概要&序論

一,整体框架搭建

1.1一笔带过定义类、头文件、预处理、构造、析构等各种准备

二,比较类运算符重载

2.1一笔带过重载"=="与"!="

2.2重载">"与">="

2.3重载"<"与"<="

三,加减运算符重载

3.1获取每月的天数

3.2日期+/+=天数

3.3日期-/-=天数

3.4前置和后置++

3.5前置和后置--

3.6日期-日期:时间间隔

四,流重载

4.1检查函数

4.2初步实现流插入

4.3实现流插入

4.4实现流提取


0.1概要&序論

这里是此方,久しぶりです! 今天,衔接上文的运算符重载 ,为大家带来時間计算器并补充更多的相关概念,其中就包括流绑定等重要内容 。希望本文能让你对运算符重载有更深的认识,「此方」です。让我们现在开始吧!

本项目由【测试文件test_of_Date.cpp,实现文件Date.cpp,和声明头文件date.h】三部分构成,以下主讲实现文件Date.cpp和Date.h部分,剩余的测试代码可由读者自行完成。

一,整体框架搭建

1.1一笔带过定义类、头文件、预处理、构造、析构等各种准备

cpp 复制代码
#pragma once
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<stdbool.h>
using namespace std;
class Date
{
public:
//构造函数
Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
// 析构函数
~Date()
{
	_year = 0;
	_month = 0;
	_day = 0;
}
// 拷贝构造函数
Date(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}
Date& operator=(const Date& d)
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
	return *this;
}
//..........
private:
	int _year;
	int _month;
	int _day;
};

二,比较类运算符重载

2.1一笔带过重载"=="与"!="

cpp 复制代码
bool operator==(const Date& d)	
{
	if (_year == d._year
		&& _month == d._month
		&& _day == d._day)
		return true;
	else
		return false;
}
bool operator != (const Date& d)
{
	if (*this == d)
	{
		return false;
	}
	else
	{
		return true;
	}
}

2.2重载">"与">="

cpp 复制代码
bool operator>(const Date& d)
{
	if (_year < d._year)
	{
		return false;
	}
	else if (_year == d._year)
	{
		if (_month < d._month)
		{
			return false;
		}
		else if (_month == d._month)
		{
			if (_day <= d._day)
			{
				return false;
			}
		}
	}
	return true;
}
  1. 比较两个Date对象的大小。
  2. 比较顺序为 年 → 月 → 日
  3. 只要发现当前对象在任一层级上不可能大于d,就立即返回false。
  4. 所有情况都排除后,最终返回true,表示当前日期更晚。
cpp 复制代码
bool operator >= (const Date& d)
{
	if (*this == d)
	{
		return true;
	}
	else if (*this > d)
	{
		return true;
	}
	else
	{
		return false;
	}
}
  1. 复用前者的逻辑,直接采用>号,>=就是>和=两种情况的合并。
  2. 否则返回false。

2.3重载"<"与"<="

cpp 复制代码
bool operator < (const Date& d)
{
	if (*this >= d)
	{
		return false;
	}
	else
	{
		return true;
	}
}
  1. 复用2.2的逻辑,直接判断
cpp 复制代码
bool operator <= (const Date& d)
{
	if (*this > d)
	{
		return false;
	}
	else
	{
		return true;
	}
}
  1. 复用2.2的逻辑,直接判断

三,加减运算符重载

3.1获取每月的天数

cpp 复制代码
int GetMonthDay(int year, int month)
{
	static int arrey[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 };
	if ( month == 2&&year % 4 == 0 && year % 100 != 0 )
	{
		return 29;
	}
	else
	{
		return arrey[month];
	}
}
  1. 如果直接采用case语句的话会大大增加代码量,所以采用数组+下标返回的方式如图。
  2. 小巧思:由于数组的下标从0开始,设置数组长度的时候多设置一个位置,让下标数字和月数一致便于理解
  3. 注意闰年的二月特殊处理
  • 小巧思1:arrey[]数组建议静态化。放在静态区。避免频繁调用反复开辟栈空间。
  • 小巧思2:在判断的时候把二月的判断放在前面,利用短路操作,如果不是二月不需要判断是不是闰年。
  • 小巧思3:因为getmonyhday是一个非常频繁调用的函数,把这个函数放在类里面,默认是内联函数。

3.2日期+/+=天数

cpp 复制代码
// 日期+=天数
Date& operator+=(int day)
{
	_day += day;
	while ( _day> GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_month = 1;
			_year++;
		}
	}
	return *this;
}
  1. 日期加等于天数,首先让当下的这个day+上天数的数字。
  2. 然后让这个day循环和当月的天数进行比较。
  3. 如果day大于天数,那么该day就减去这个天数并让月数+1,如果day小于天数。那么就跳出循环进行结算。(返回*this)
  4. 注意月数也有可能溢出,在月数=13的时候重置月数=1。
cpp 复制代码
Date operator+(int day) 
{
    Date tmp(*this);   
    tmp += day;       
    return tmp;       
}
  1. 创建一个临时对象tmp并拷贝构造*this,+=day这个临时对象并返回
  2. 返回临时对象必须进行值返回,因为临时对象存放在栈区,离开operator作用域后销毁

3.3日期-/-=天数

cpp 复制代码
// 日期-=天数
Date operator-=(int day)
{
	_day -= day;
	while ( _day< 0)
	{
		if (_month == 1)
		{
			_year--;
			_month += 12;
		}
		_day += GetMonthDay(_year, _month-1);
		_month--;
	}
	return *this;
}
  1. 整体思路与 += 对称,先把"减多少天"这件事落到当前对象上。
  2. 先执行 _day -= day,可能会出现当前天数不够用的情况。
  3. 当 _day < 0 时,说明需要向前借月份,于是回退到上一个月。
  4. 借月份时,将上一个月的天数加到 _day,同时 _month--。
  5. 如果月份退到 0,说明跨年:月份重置为 12,年份减 1。
  6. 重复上述过程,直到 _day >= 0,日期稳定,返回 *this。
cpp 复制代码
Date operator-(int day) 
{
    Date tmp(*this);   
    tmp -= day;       
    return tmp;       
}
  • 基本同理

3.4前置和后置++

重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好地区分。C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

cpp 复制代码
// 前置++
Date& operator++()
{
	*this += 1;
	return *this;
}
// 后置++
Date operator++(int)
{
	Date ret(*this);
	*this += 1;
	return ret;
}
  1. 前置 ++ 先对当前对象本身进行加 1 操作。
  2. 具体实现中直接复用 += 1,避免重复编写日期进位逻辑。
  3. 修改完成后,返回修改后的 *this,符合前置运算符"先变再用"的语义。
  4. 后置 ++ 需要先保留自增前的状态,因此先用当前对象构造一个临时副本 ret。
  5. 再对当前对象执行 += 1,使日期向后推进一天。
  6. 最后返回保存的旧值 ret,体现后置运算符"先用再变"的行为

注意:在自定义类型中建议多用前置++,减少拷贝。在内置类型中无所谓

3.5前置和后置--

cpp 复制代码
// 后置--
Date operator--(int)
{
	Date ret(*this);
	*this -= 1;
	return ret;
}
// 前置--
Date& operator--()
{
	*this -= 1;
	return *this;
}
  • 同理,不多赘述。

3.6日期-日期:时间间隔

cpp 复制代码
// 日期-日期 返回天数
int operator-(const Date& d)
{
	int flag = 1;
	int count = 0;
	Date dmax = *this;
	Date dmin = d;
	if (dmax < dmin)
	{
		dmax = d;
		dmin = *this;
		flag = -1;
	}
	while (1)
	{
		count++;
		dmin++;
		if (dmin == dmax)
		{
			break;
		}
	}
	return flag * count;
}
  1. 该运算符用于计算两个日期之间相差的天数,返回一个 int 结果。
  2. 默认认为当前对象较大,用 dmax、dmin 分别保存较大和较小的日期,同时用 flag 记录结果的正负。
  3. 如果发现当前对象早于参数 d,则交换 dmax 与 dmin,并将 flag 设为 -1。
  4. 从较小日期 dmin 开始,每次递增一天,同时将计数器 count 加 1。
  5. 当 dmin 增加到与 dmax 相等时,结束循环。
  6. 最终返回 flag * count,数值表示天数差,符号表示先后关系。

四,流重载

最复杂的运算符重载,着重讲解

首先我们要知道,流插入和流提取能够支持多种类型是因为其底层采用了大量的函数重载。

4.1检查函数

首先我们要写一个检查函数,后面要用

cpp 复制代码
//检查
bool check_Date()
{
	if (_day== GetMonthDay(_year,_month))
	{
		return false;
	}
	if (_month > 12)
	{
		return false;
	}
	return true;
}
  1. 检查当前日期是否合法。
  2. 若日期已达到当月最大天数,或月份超过 12,则判定为非法并返回 false;
  3. 否则说明日期处于合理范围内,返回 true。

4.2初步实现流插入

cpp 复制代码
void Date::operator<<(ostream& out)
{
    out << _year << "年" << _month << "月" << _day << "日" << endl;
}
  1. 首先流插入合流提取是ostream和istream类型的。

  2. istream和ostream类型对象都不支持拷贝构造,所以不支持传值传递。

  3. 该代码确实实现了流插入,但是实现逻辑和使用方式上存在很大的问题

  4. 如果我们要使用这个流插入,那么调用方式应该是:this<<cout,这和我们的使用习惯上由很大的出入。

  5. **根本原因是什么?**是我们无法改变this指针的位置,this指针所在始终是运算符的左参数。

  6. **解决办法:**把函数放在类外面。

  7. **那么问题又又来了,怎么访问年月日?**难道要使用get一个一个访问吗?

  8. 实际上太麻烦了,这里简单引入一个概念,后面会深入讲:"友元函数":我是你的朋友,我可以访问你的私有空间

  9. 在类外面实现,并在类中设置一个友元声明

cpp 复制代码
friend ostream& operator<<(ostream& out, const Date d);
friend istream& operator>>(istream& in, Date& d);

好了,那么这就大功告成了吗?还没有,这个流掺入函数还不能实现连续输出。我们加一个返回值out,返回值实现连续输出的原理:

流插入操作符具有从左向右的结合性。cout<<d1的返回值作为<<d2的左操作数。以此类推。

最终,代码如下:

4.3实现流插入

cpp 复制代码
//重载<<
ostream& operator<<(ostream& out, const Date d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

4.4实现流提取

  1. 流提取运算符的本质是:从 istream 中读取数据,并填充到对象中,因此第一个参数必须是 istream&。
  2. istream 不支持拷贝构造,所以只能以引用方式传递,返回值也必须是引用,才能支持连续输入。
  3. 该函数定义在类外,是为了符合使用习惯:cin >> d,而不是 d >> cin。
  4. 在函数内部直接读取 year、month、day。
  5. 通过 while 循环配合 check_Date,实现输入即校验:非法就提示并重新输入,合法才退出。
  6. 由于需要访问私有成员,该函数通常被声明为友元函数。
  7. 最终返回 in,以保证 cin >> d1 >> d2 这种链式写法成立。
cpp 复制代码
//重载>>
istream& operator>>(istream& in, Date& d)
{
	cout << "请输入" << endl;
	while (1)
	{
		in >> d._year >> d._month >> d._day;
		if (d.check_Date() == false)
		{
			cout << "日期非法,请重新输入" << endl;
		}
		else if (d.check_Date() == true)
		{
			break;
		}
	}
	return in;
}

好了本期内容到此结束,如果对你有帮助不要忘了一键三联哦,我是此方,我们下期再见

相关推荐
无限进步_2 小时前
C++ Vector 全解析:从使用到深入理解
开发语言·c++·ide·windows·git·github·visual studio
秋邱2 小时前
Java数组与二维数组:创建、初始化、遍历与实操案例全解析
java·开发语言
Dream it possible!2 小时前
LeetCode 面试经典 150_分治_将有序数组转换为二叉搜索树(105_108_C++_简单)(递归)
c++·leetcode·面试
Q741_1472 小时前
C++ 栈 模拟 力扣 227. 基本计算器 II 题解 每日一题
c++·算法·leetcode·模拟
徐新帅2 小时前
CSP 二进制与小数进制转换专题及答案解析
c++·算法
wangxingps2 小时前
phpmyadmin版本对应的各php版本
服务器·开发语言·php
独自破碎E2 小时前
消息队列如何处理重复消息?
java·开发语言·rocketmq
im_AMBER2 小时前
Leetcode 88 K 和数对的最大数目
数据结构·c++·笔记·学习·算法·leetcode
兵哥工控2 小时前
MFC分组平均法数据平滑曲线实例
c++·mfc