【C++初阶】类和对象实战 --- 日期类的实现

5 篇文章 0 订阅

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:课设
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


一、准备工作

为了方便管理,我们可以创建多个文件来实现

  • Test.cpp — 测试代码逻辑 (源文件)
  • Date.cpp — 逻辑实现 (源文件)
  • Date.h — 存放函数的声明 (头文件)
    在这里插入图片描述

二、实现内容(Date.h)

#include <iostream>
using namespace std;

class Date
{
public:
	// 友元函数
	friend istream& operator>>(istream& in, Date& x);
	friend ostream& operator<<(ostream& out, const Date& x);

	// 写构造函数
	// 全缺省(注意:声明给缺省值)
	Date(int year = 1, int month = 2, int day = 3);

	// 打印成员函数
	void Print() const
	{
		cout << this->Year << '-' << this->Month << '-' << this->Day << endl;
	}
	
	// 以下是运算符重载
	
	// == 
	bool operator==(const Date& x) const;

	// <
	bool operator<(const Date& x) const;

	// >=
	bool operator>=(const Date& x) const;

	// <=
	bool operator<=(const Date& x) const;

	// >
	bool operator>(const Date& x) const;

	// !=
	bool operator!=(const Date& x) const;

	// 获取某月的天数
	static int GetMonthDay(int Year, int Month);

	// 日期+=天数
	Date& operator+=(int day);

	// 日期-=天数
	Date& operator-=(int day);

	// 日期+day(返回的变量销毁了,不能用引用返回)
	Date operator+(int day) const;
	
	// 日期-day
	Date operator-(int day) const;
	
	// 前置++()
	Date& operator++();
	
	// 后置++
	Date operator++(int);

	// 前置--  
	Date& operator--();

	// 后置--
	Date operator--(int);

	// 日期-日期 = 天数(int)
	int operator-(const Date& x) const;
	
private:
	int Year; // 年
	int Month; // 月
	int Day; // 日
};

三、代码实现(Date.cpp)

3.1 三个没必要写的成员函数

  • 析构函数

成员类型都是内置类型,出了作用域,其变量自动销毁。因此可以不写

  • 拷贝构造函数

编辑器的默认拷贝构造函数对内置类型是直接拷贝的

  • 赋值运算符重载函数

编辑器的默认赋值操作符重载函数对内置类型也是直接拷贝的

3.2 构造函数

构造函数和以上三个默认成员函数不同,因为如果不自己定义构造函数,内置类型的默认构造函数默认是随机值

Date::Date(int year, int month, int day)
{
	// 防止构造出非法日期
	if (month >= 1 && month <= 12 && day > 0 && day <= Date::GetMonthDay(year, month))
	{
		Year = year;
		Month = month;
		Day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		assert(false);
	}
}

注意:

  • 当全缺省参数声明和定义分离的情况下,声明给缺省值
  • 类也可以让声明和定义分离。类声明放在.h文件中,成员函数定义放在.cpp文件中,同时需要使用作用域限定符::

3.3 打印成员函数

像短小且频繁调用的函数可以直接放进类里,编译器会将其看作内联函数。

void Print() const
{
	cout << this->Year << '-' << this->Month << '-' << this->Day << endl;
}

3.4 运算符重载之判断日期是否相等 (==)

// == 
bool Date::operator==(const Date& x) const
{
	return (Year == x.Year)
		&& (Month == x.Month)
		&& (Day == x.Day);
}

3.5 运算符重载之比较日期大小(<)

// <
bool Date::operator<(const Date& x) const
{
	// 年份大的则大
	if (Year < x.Year) 
		return true;
	// 月份大的则大(前提:年份相同)
	else if (Year == x.Year && Month < x.Month) 
		return true;
	// 日份大的则大(前提:年月份相同)
	else if (Year == x.Year && Month == x.Month && Day < x.Day) 
		return true;
	
	// 若不满足以上要求,则为假
	return false;
}

当以上<==写完后,以下代码都可以复用了(3.6 ~ 3.9)

3.6 运算符重载之比较日期大小 (>=)

bool Date::operator>=(const Date& x) const
{
	return !(*this < x);
}

3.7 运算符重载之比较日期大小 (<=)

bool Date::operator<=(const Date& x) const
{
	return (*this < x || *this == x);
}

3.8 运算符重载之比较日期大小 (>)

bool Date::operator>(const Date& x) const
{
	return !(*this <= x);
}

3.9 运算符重载之判断日期是否相当 (!=)

bool Date::operator!=(const Date& x) const
{
	return !(*this == x);
}

3.10 日期 + 天数

首先对象加上一个数,本对象是不会发生改变的,例如

int i = 0;
i + 1;
// i 本身还是0

因此,可以拷贝构造一个临时对象,让临时对象加天数,因此可以服用+=运算符,最后再返回即可

// 日期 + day
Date Date::operator+(int day) const
{
	// 下面是拷贝构造函数,不是赋值运算符重载
	Date tmp = *this; 
    tmp += day
	// 可以直接复用(最好)
	return tmp;
}

千万不能使用引用返回,因为当对象tmp生命周期结束,对象就不存在,可能会产生随机值。

3.11 日期 += 天数

// 获取某月的天数
int Date::GetMonthDay(int Year, int Month)
{
	// 因为GetMonthDay函数会掉用很多次,每次调用都会为数组开辟空间
	// 因此使用static修饰的变量就不会再每次调用函数的时候开辟变量空间,减少损耗
	static int GetDay[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30,31,30,31 };
	// 可能存在在闰年的情况
	if (Month == 2 && Year % 400 == 0 || Year % 4 == 0 && Year % 100 != 0)
	{
		return 29;
	}
	return GetDay[Month];
}

// 日期+=天数
//d1 += 2
Date& Date::operator+=(int day)
{
	// 可能出现输入的天数是负数
	// 那么倒退即可
	if (day < 0)
	{
		return *this -= -day;
	}
	
	this->Day += day;
	// 可能会超过月份天数的最大值
	while (this->Day > GetMonthDay(this->Year, this->Month))
	{
		// 超过就减当月的最大值
		this->Day -= GetMonthDay(this->Year, this->Month);
		// 然后月份进位
		this->Month++;
		// 如果月份进位超过12
		if (this->Month > 12)
		{
			// 月份轮回
			this->Month -= 12;
			// 年份进位
			this->Year++;
		}
	}
	return *this;
}

日期+=天数返回的结果是给到对象本身。当生命周期结束后,*this(对象)不会被销毁,因此可以使用引用返回,提高效率。

3.12 日期 - 天数

Date Date::operator-(int day) const
{
	Date tmp = *this;
	tmp -= day
	return tmp;
}

3.13 日期 -= 天数

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		return *this += -day;
	}
	
	this->Day -= day;
	// 减去的日期可能存在负数日期
	while (this->Day <= 0)
	{
		// 月份减一(借位)
		this->Month--;
		// 可能存在月份借完的情况
		if (this->Month <= 0)
		{
			this->Month += 12;
			// 年份借位
			this->Year--;
		}
		this->Day += GetMonthDay(this->Year, this->Month);
	}
	return *this;
}

3.14 前置++

Date& Date::operator++()
{
	*this += 1;
	return *this;
}

3.15 后置++

// 后置++
Date Date::operator++(int)
{
	Date tmp = *this;
	*this += 1;
	return tmp;
}

前置++·后置++会发生一个问题:函数名会相同。因此,C++规定:后置(++/--)重载时多增加一个int类型的参数,但调用函数时该参数不用显示传递。

3.16 前置- -

Date& Date::operator--()
{
	*this -= 1;
	return *this;
}

3.17 后置- -

Date Date::operator--(int)
{
	Date tmp = *this;
	*this -= 1;
	return tmp;
}

3.18 日期 - 日期

// d1 - d2
// 思路:循环计数的方法
int Date::operator-(const Date& x) const
{
	// 假设一开始日期d2, 也就是*this比日期大
	Date Max = *this;
	Date Min = x;
	int flag = 1;
	
	// 特判存在日期2比日期1大
	if (Min > Max)
	{
		Max = x;
		Min = *this;
		flag = -1;
	}
	int cnt = 0;
	while (Min != Max)
	{
		Min++;
		cnt++;
	}	
	return cnt * flag;
}

四、流提取>>

那能否输入日期呢?其实是可以的。那么能否用cin或者printf函数呢?答案当然不可以。因为printf只能根据格式化输出来打印的,例如%d%f等内置类型,cout它是能自动识别类型的,同样的只能识别内置类型。(如下图)

在这里插入图片描述

为了能让自定义类型也能使用到cin,因此,可以通过重载>>运算符来实现输入的操作:

>>是一个双操作数:一个cin对象(cin是类对象,istream的类对象),另一个是是日期类对象。注意:cin是类对象。
在这里插入图片描述

istream& operator>>(istream& in, Date& x)
{
	int year, month, day;
	in >> year >> month >> day;
	// 可能存在输入非法日期
	if (month > 0 && month < 13 && day > 0 && day <= Date::GetMonthDay(year, month))
	{
		x.Year = year;
		x.Month = month;
		x.Day = day;
	}
	else
	{
		cout << "非法日期" << endl;
	}
	return in;
}
  • >>运算符重载不能成为成员函数
    成员函数的第一个参数默认是this指针,它是指向当前对象的指针,而>>的第一个参数是cin,因此不能变成成员函数,那就只能定义在全局了。但是这里有个问题,那就是全局的函数就不能访问类中私有的成员变量了,这里就用 友元函数 来解决此问题,即在函数声明前加上friend放在类中(详情见Date.h)
  • 返回值是istream&而不是void的原因:
    如果返回值是void,那它就不能连续输入。(cin >> d1 >> d2)因为流插入的顺序是从左向右的,cin >> d1后,返回的是void类型,再cin>>d2就会产生类型冲突,因此我们可以返回d1的类型来解决此问题,而cin的类型就是istream
  • 两个形参能否用const修饰:
    in>>d1in是每次都键盘上输入不同的内容,因此不能用const修饰;而d1在没获取数据之前可能是随机值,然后再通过键盘获取数据,也就改变了数据内容,因此不能用const修饰。

五、流插入<<

<<有是一个双操作数:一个是cout对象(cout是类对象,是ostream的类对象),另一个是日期类对象。
在这里插入图片描述

ostream& operator<<(ostream& out, const Date& x)
{
	out << x.Year << "年" << x.Month << "月" << x.Day << "日" << endl;

	return out;  
}
  • out不能用const修饰的原因:
    out相当于终端控制台上,而每次数据输出在控制台都不一样,因此out不能用const修饰
  • 更多详细细节参考流提取>>

五、源码地址

本篇博客代码地址:点击跳转

  • 9
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值