当前位置: 首页 > news >正文

CD19.【C++ Dev】类和对象(10) 日期类对象的成员函数(日期+天数)

目录

日期+天数

需要考虑的几个问题

1.天数加在日上,有可能会溢出,需要进位

2.对月进位,也有可能导致月会溢出,需要进位

3.对年进位,需要考虑是否为闰年

代码设计

取得指定月的天数GetMonthDay函数

方法1:if判断或switch/case

方法2:查表

版本1

版本2

operator+

初始化临时对象

进位的处理

完整代码

测试代码

提问

operator+=

完整代码

测试代码

代码复用

1.+复用+=

2.+=复用+

提问

+复用+=调用拷贝构造函数次数如下:

+=复用+调用拷贝构造函数次数如下:

练习


日期+天数

日期+天数:显然需要重载"+",操作:?年?月?日+天数==?年?月?日

需要考虑的几个问题

1.天数加在日上,有可能会溢出,需要进位

2.对月进位,也有可能导致月会溢出,需要进位

3.对年进位,需要考虑是否为闰年

闰年判断参见15.【C语言】初识操作符 下文章

口诀:四年一闰,百年不闰,四百年又闰

if ((year%4==0 && year%100!=0) || (year%400==0))
{
    //do_something
}

代码设计

取得指定月的天数GetMonthDay函数

天数加在日上,有可能会溢出,需要进位则需要考虑每一个月最多多少天,而且二月份比较特殊(闰年2月有29天,非闰年2月有28天),可以写一个函数来取得每个月的天数

方法1:if判断或switch/case
if (month==1)
{
    //......
}
else if (month==2)
{
    //......
}
else if (month==3)
{
    //......
}
//.......
else
{
    //......
}

或者使用switch/case

switch (month)
{
    case 1:
    case 2:
    //......
    case 3:
}

无论使用if还是switch/case,代码冗长,可读性差

方法2:查表

这个思想其实是哈希表思想

int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//默认2月是28天,之后单独判断

(注:day[0]可以为任何数,仅起到占位的作用)

使用下标访问的方法:day[month]来取得指定月的天数

版本1
int GetMonthDay(int year,int month)
{
	int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//默认2月是28天,之后单独判断
	if (((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && month == 2)
	{
		return 29;
	}
	return day[month];
}

提问:版本1代码有没有可以优化的地方?

答:

优化1:day数组为局部数组,每次调用该函数,程序都会创建day数组,如果多次调用,该步骤会消耗大量时间,可以尝试使用全局数组或者将数组放到静态区上

优化2:if判断的改进

可以利用短路运算的特点,将第二个条件month==2前置,月份不为2就不判断是否为闰年,节省时间

版本2
int GetMonthDay(int year,int month)//可以不使用引用返回
{
    //数组放到静态区上,只创建一次
	static int day[] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };//默认2月是28天,之后单独判断
	if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
	{
		return 29;
	}
	return day[month];
}

operator+

算法:天满了向月进位,月满了向年进位.显然需要使用循环

要点:

1.先加到天上,再考虑进位的问题

2.operator+是不能改变原对象的,需要使用临时对象tmp,一旦使用临时对象tmp就不能引用返回,只能传值返回(避免传引用返回错误,牺牲一点时间)

3.区分operator+和operator+=

初始化临时对象

可以使用构造函数来初始化tmp,如下:

Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	//......
}
进位的处理
while (tmp._day > GetMonthDay(tmp._year,tmp._month))
{
	tmp._day -= GetMonthDay(tmp._year,tmp._month);
	tmp._month++;
	if (_month == 13)
	{
		tmp._year++;
		tmp._month = 1;
	}
}
完整代码
Date Date::operator+(int day)
{
	Date tmp(*this);
	tmp._day += day;
	while (tmp._day > GetMonthDay(tmp._year,tmp._month))
	{
		tmp._day -= GetMonthDay(tmp._year,tmp._month);
		tmp._month++;
		if (_month == 13)
		{
			tmp._year++;
			tmp._month = 1;
		}
	}
	return tmp;//注意返回的不是*this!
}
测试代码
#include "Date.h"
int main()
{
	Date d1(2025, 3, 25);
	Date d2=d1 + 100;
	d2.Print();
	return 0;
}

运行结果:

提问

Date d2=d1 + 100;是初始化还是赋值?

回顾CD17.【C++ Dev】类和对象(8):赋值运算符文章的知识点:

1.赋值运算符重载:已经存在的两个对象之间的拷贝(如d3 = d1)

2.构造函数:用一个已经存在的对象去初始化另一个对象

显然Date d2=d1 + 100是构造函数执行的,因此为初始化

也可以手动打印测试信息:

修改构造函数和拷贝构造函数:

Date::Date(int year , int month, int day)
{
	cout << this <<" Date::Date(int year , int month, int day)" << endl;
	_year = year;
	_month = month;
	_day = day;
}

Date::Date(const Date& x)
{
	cout << this<< " Date::Date(const Date& x)" << endl;
	_year = x._year;
	_month = x._month;
	_day = x._day;
}

调用哪个函数就打印哪个函数的信息

测试以下代码:

#include "Date.h"
int main()
{
	Date d1(2025, 3, 25);
	cout << "d1 address:"<< & d1 << endl;
	Date d2 = d1 + 100;
	cout <<"d2 address:"<< &d2 << endl;
	return 0;
}

运行结果:

初始化d2时调用的是拷贝构造函数,因为拷贝构造也是构造!

operator+=

可以在operator+的基础上进行代码复用(代码复用的思想参见CD18.【C++ Dev】类和对象(9)(声明和定义分离的写法以及代码复用)文章)

完整代码

按照之前在CD17.【C++ Dev】类和对象(8):赋值运算符文章中讲过复制运算符的格式,注意要返回*this,返回类型为Date&

Date& Date::operator+=(int day)
{
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}
测试代码
#include "Date.h"
int main()
{
	Date d1(2025, 3, 25);
	d1 += 100;
	d1.Print();
	return 0;
}

运行结果是正确的:

提问

operator+=的代码真的没有问题吗?

答:其实仍然有问题.

使用下面这个测试代码:

#include "Date.h"
int main()
{
	Date d1(2025, 3, 25);
	d1 += -100;
	d1.Print();
	return 0;
}

 运行结果:日是负数,错误

修复

需要添加判断:加一个负数等于减这个数的相反数,需要调用operator-=,会在CD20.【C++ Dev】类和对象(11) 日期类对象的成员函数(++、--、日期-日期)文章实现

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		*this -= -day;
		return *this;
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month++;
		if (_month == 13)
		{
			_year++;
			_month = 1;
		}
	}
	return *this;
}

代码复用

+和+=的代码有些重复,代码如下:

1.+复用+=

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

2.+=复用+

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

提问

对比以上两段代码,哪个写法更高效?

修改函数,打印测试信息:

Date::Date(int year , int month, int day)
{
	cout <<"构造函数:Date::Date(int year , int month, int day)" << endl;
	_year = year;
	_month = month;
	_day = day;
}

Date::Date(const Date& x)
{
	cout << "拷贝构造函数:Date::Date(const Date& x)" << endl;
	_year = x._year;
	_month = x._month;
	_day = x._day;
}

测试代码(在没有优化的情况下,问一共拷贝对象几次?)

#include "Date.h"
int main()
{
    cout << "d1和d2初始化:" << endl;
    Date d1(2025, 3, 25);
    Date d2(1, 1, 1);
    cout << endl;
    cout << "d2=d1 + 100如下:" << endl;
    d2=d1 + 100;
    cout << endl;
    cout << "d1 += 100如下:" << endl;
    d1 += 100;
    return 0;
}
+复用+=调用拷贝构造函数次数如下:

可画过程图分析:

(注:Date tmp(*this)传的是*this,为Date::Date(const Date& x)需要的参数) 

结果:拷贝对象一共两次 

测试代码执行结果:

(VS显示只调用拷贝对象一次的原因:编译器做了优化)

 如果在MSDOS下的老编译器Tubro C++上测试:

#include <iostream.h>
#include <conio.h>
class Date
{
public:
	int GetMonthDay(int year,int month)
	{
		static int day[] = { 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;
		}
		return day[month];
	}
	Date (int year , int month, int day)
	{
		cout << this <<"Date (int year , int month, int day)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
 
	Date (const Date& x)
	{
		cout << "Date(const Date& x)" << endl;
		_year = x._year;
		_month = x._month;
		_day = x._day;
	}
	
	Date operator+(int day)
	{
		Date tmp(*this);
		tmp += day;
		return tmp;
	}
	
	Date& operator+=(int day)
	{
		_day += day;
		while (_day > GetMonthDay(_year, _month))
		{
			_day -= GetMonthDay(_year, _month);
			_month++;
			if (_month == 13)
			{
				_year++;
				_month = 1;
			}
		}
		return *this;
	}
	
	int _year;
	int _month;
	int _day;
};

int main()
{
    cout << "Initializing d1 and d2......" << endl;
    Date d1(2025, 3, 25);
    Date d2(1, 1, 1);
    cout << endl;
    cout << "d2=d1 + 100:" << endl;
    d2=d1 + 100;
    cout << endl;
    cout << "d1 += 100:" << endl;
    d1 += 100;
    getch();
    return 0;
}

(旧编译器下头文件是.h结尾的,最后的getch()是为了让运行临时暂停,好查看结果)  

运行结果:

(没有优化,一共拷贝对象2次)  

+=复用+调用拷贝构造函数次数如下:

可画过程图分析:

结果:拷贝对象一共四次

测试代码执行结果:

(VS显示只调用拷贝对象两次的原因:编译器做了优化)  

如果在MSDOS下的老编译器Tubro C++上测试:

#include <iostream.h>
#include <conio.h>
class Date
{
public:
	int GetMonthDay(int year,int month)
	{
		static int day[] = { 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;
		}
		return day[month];
	}
	Date (int year , int month, int day)
	{
		cout << this <<"Date (int year , int month, int day)" << endl;
		_year = year;
		_month = month;
		_day = day;
	}
 
	Date (const Date& x)
	{
		cout << "Date(const Date& x)" << endl;
		_year = x._year;
		_month = x._month;
		_day = x._day;
	}
	
	Date operator+(int day)
	{
		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._year++;
				tmp._month = 1;
			}
		}
		return tmp;
	}
	
	Date& operator+=(int day)
	{
		*this = *this + day;
		return *this;
	}
	
	int _year;
	int _month;
	int _day;
};

int main()
{
    cout << "Initializing d1 and d2......" << endl;
    Date d1(2025, 3, 25);
    Date d2(1, 1, 1);
    cout << endl;
    cout << "d2=d1 + 100:" << endl;
    d2=d1 + 100;
    cout << endl;
    cout << "d1 += 100:" << endl;
    d1 += 100;
    getch();
    return 0;
}

运行结果是这样的: 

(没有优化,一共拷贝对象4次)

练习

日期-日期问题:

https://leetcode.cn/problems/number-of-days-between-two-dates/

下篇博客将分析此题

相关文章:

  • django orm的优缺点
  • 数据结构与算法——顺序表的实现以及增、插、删、查、印、毁
  • MySQL-- 多表查询的分类,SQL92与SQL99,7种JOIN的实现,SQL99语法的新特性
  • Postman 全局 Header 如何设置?全局设置了解一下
  • 接口用例设计原则
  • 旋转变换原理
  • 养老更安心!智绅科技“智慧”养老系统,智在何处?
  • A SAM-guided Two-stream Lightweight Model for AnomalyDetection
  • springBoot统一响应类型3.3版本
  • 4、网工软考—VLAN配置—hybird配置
  • 以科技赋能,炫我云渲染受邀参加中关村文化科技融合影视精品创作研讨会!
  • 《白帽子讲 Web 安全》之跨站请求伪造
  • 剑指Offer44 -- 思维
  • Java Synchronized底层原理:Monitor机制、锁膨胀、自旋优化与偏向锁细节解密
  • vcpkg安装指定版本的库
  • 重磅推出稳联技术Profinet转CANopen网关智能工厂解决方案!
  • 磷酸铁锂电池自动分选机:新能源产业的智能新宠
  • 深入理解机器学习之TF-IDF:文本特征提取的核心技术
  • STM32 时钟树配置(debug)
  • Citus源码(1)分布式表行为测试
  • CBA官方对孙铭徽罚款3万、广厦投资人楼明停赛2场罚款5万
  • 去年上海全市博物馆接待观众约4087万人次,同比增31.9%
  • “GoFun出行”订单时隔7年扣费后续:平台将退费,双方已和解
  • 北方将现今年首场大范围高温天气,山西河南山东陕西局地可超40℃
  • 中拉互联网发展与合作论坛在西安开幕
  • 商务部:中方将适时发布中美经贸磋商相关消息