💓 博客主页:倔强的石头的CSDN主页
📝Gitee主页:倔强的石头的gitee主页
⏩ 文章专栏:《C++项目实战》
期待您的关注
目录
引言
介绍
一、类的设计
二、成员函数的实现
🍃构造函数、析构函数、拷贝构造函数和赋值运算符重载
🍃友元函数:重载>>和<<
🍃日期合法性检查函数
🍃获取某年某月的天数
🍃比较运算符重载
🍃 日期加减操作
🍃自增自减操作
🍃日期差计算
结语
引言
在编程的世界里,类和对象是面向对象编程(OOP)的核心概念。它们为我们提供了一种组织和封装数据及其相关操作的强大机制。类可以被视为一种蓝图或模板,它定义了对象的属性和方法。而对象则是根据这些类创建的具体实例,它们具有自己的状态(通过属性表示)和行为(通过方法实现)。
日期处理是编程中常见且重要的一个领域。无论是在日志记录、事件管理,还是在日程安排中,日期都扮演着至关重要的角色。通过实现一个日期类,我们不仅可以深入理解类和对象的概念,还能将这些理论知识应用于解决实际问题。
介绍
本实践将围绕实现一个日期类展开,旨在通过这一具体案例来教授类和对象的基础知识。
我们将从定义类的基本结构开始,逐步添加属性和方法,以构建一个功能完备的日期类。
首先,我们需要明确日期类应该具备哪些基本属性。显然,一个日期应该包含年、月和日这三个关键信息。这些属性将用于表示日期的状态。
接下来,我们将为日期类定义一系列方法。这些方法将实现日期的各种操作,如设置日期、获取日期、计算两个日期之间的天数差、判断一个日期是否是闰年等。通过这些方法,日期类将具有自己的行为,能够根据需要进行各种计算和操作。
在实现日期类的过程中,我们还将涉及到一些面向对象编程的高级概念,如封装、继承和多态。封装将帮助我们隐藏类的内部实现细节,只暴露必要的接口给外部使用。虽然在这个简单的日期类示例中可能不会直接用到继承和多态,但了解这些概念将有助于我们更深入地理解面向对象编程的精髓。
一、类的设计
首先,我们需要设计一个日期类Date
,包含年、月、日三个私有成员变量,并定义多个成员函数来实现日期的各种操作。
以下是类的声明部分:
#include<iostream>
#include<stdbool.h>
using namespace std; class Date {
public: // 友元函数声明 friend ostream& operator<<(ostream& out, const Date& d); friend istream& operator>>(istream& in, Date& d); // 检查日期合法性 bool CheckDate() const; // 获取某年某月的天数 int GetMonthDay(int year, int month) const; // 构造函数 Date(int year = 1900, int month = 1, int day = 1); // 拷贝构造函数 Date(const Date& d); // 赋值运算符重载 Date& operator=(const Date& d); // 析构函数 ~Date(); // 日期+=天数 Date& operator+=(int day); // 日期+天数 Date operator+(int day) const; // 日期-天数 Date operator-(int day) const; // 日期-=天数 Date& operator-=(int day); // 前置++ Date& operator++(); // 后置++ Date operator++(int); // 后置-- Date operator--(int); // 前置-- Date& operator--(); // >运算符重载 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; // 日期-日期 返回天数 int operator-(const Date& d) const; private: int _year; int _month; int _day;
};
二、成员函数的实现
🍃构造函数、析构函数、拷贝构造函数和赋值运算符重载
Date::Date(int year, int month, int day) { _year = year; _month = month; _day = day;
} Date::Date(const Date& d) { _year = d._year; _month = d._month; _day = d._day;
} Date& Date::operator=(const Date& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this;
} Date::~Date() {}
解析:
- 这四个成员函数都属于类的默认成员函数,是实现类的第一步。
- 日期类中除了构造函数需要自己单独实现之外,其他三个可使用编译器实现的默认成员函数即可。
- 示例代码中在声明中给出了全缺省构造函数(全缺省构造函数是最稳妥的选择,可以适应各种情况),要注意带缺省参数声明和定义分离时,只在声明处给出缺省值
- 更多默认成员函数知识可参考类和对象系列文章
- C++指南_倔强的石头_的博客-CSDN博客
🍃友元函数:重载>>和<<
//运算符重载
ostream& operator<<(ostream& out,const Date& d)
{out << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;//方便连续使用
}//运算符重载
istream& operator>>(istream& in, Date& d)
{cout << "请输入年 月 日" << endl;while (1){in >> d._year >> d._month >> d._day;if (d.CheckDate())break;cout << "输入的日期非法,请重新输入!!!" << endl;}return in;
}
为什么要重载成友元函数(全局函数)而不是成员函数?
void Date:: operator<<(ostream& out)//错误示范{out << this->_year << "-" << this->_month << "-" << this->_day;
}
如果我们重载成成员函数,运算符重载默认第一个参数为隐藏的this指针指向对象,第二个参数才是cout对象。那么想要使用cout打印对象内容时,就变成了下面这样的形式,这明显不符合我们的要求
Date() << cout ;
只有改变两个对象的位置,让std::ostream
对象的引用(std::ostream&
)作为第一个参数才能解决,但this指针是隐藏的,我们无法修改。所有该问题只有通过重载成全局函数才能解决
为什么要有返回值?
对于输出运算符(<<
),重载函数通常返回一个对std::ostream
对象的引用(std::ostream&
)。这样做允许我们进行链式操作,即将多个输出操作连接在一起,而不需要在每个操作后都调用std::endl
或类似的分隔符。例如,std::cout << obj1 << obj2;
将首先输出obj1
的状态,然后紧接着输出obj2
的状态
🍃日期合法性检查函数
bool Date::CheckDate()const
{if (_day > GetMonthDay(this->_year, this->_month) || _month > 12)return false;return true;
}
数据合法性检查是十分必要的
🍃获取某年某月的天数
int Date:: GetMonthDay(int year, int month)const
{int getdays[] = { 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 getdays[month] + 1;//闰年二月天数+1}return getdays[month];
}
- 考虑到天数的复杂性,每个月的天数都不一样,并且还存在闰年的2月份有29,逻辑复杂必须封装成函数实现
- 将每个月的天数写在数组里,并空出第一个位置来,这样数组下标就能与月份一一对应
- 获取月份的天数之后,再判断是否是2月以及是否是闰年再做进一步处理
- 注意要先判断是否是2月,再判断是否是闰年,如果左侧不成立右侧就不会计算
🍃比较运算符重载
// >运算符重载
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;//上述条件都没有执行,就返回false
}// ==运算符重载
bool Date::operator==(const Date& d)const
{return _year == d._year && _month == d._month && _day == d._day;
}// >=运算符重载
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 == d);
}
-
这个函数通过逐步比较年份、月份和日期,实现了两个
Date
对象之间的“大于”比较。如果当前对象在任何一级比较中大于参数对象,就返回true
;否则,返回false
。 -
比较运算符中只需要实现一个大于或者小于运算符,和一个==运算符,其他的函数皆可复用代码
🍃 日期加减操作
// 日期+=天数
Date& Date::operator+=(int day)
{_day += day;int tmp = GetMonthDay(this->_year, this->_month);while (_day > tmp)//日期大于本月天数{_day -= tmp;//就减去本月天数++_month;//月份+1if (_month > 12)//月份大于12{++_year;//年份+1,月份置为1_month = 1;}tmp = GetMonthDay(this->_year, this->_month);}return *this;
}// 日期+天数
Date Date::operator+(int day)const
{Date tmp = *this;//复用+=代码tmp += day;return tmp;
}// 日期-天数
Date Date::operator-(int day)const
{Date tmp = *this;tmp -= day;return tmp;
}// 日期-=天数
Date& Date::operator-=(int day)
{_day -= day;while (_day < 1){--_month;//向前借位if (_month == 0){--_year;_month = 12;}_day+= GetMonthDay(this->_year, this->_month);}return *this;
}
+=实现思路:
- 增加天数:将传入的天数
day
加到当前日期的天数_day
上。 - 检查天数是否超出本月:使用
GetMonthDay
函数获取当前年份和月份的天数tmp
。如果增加后的天数_day
大于tmp
,说明天数超出了当前月份的天数。 - 调整日期:如果天数超出,则减去当前月份的天数,并将月份
_month
加1。如果月份超过12(即一年结束),则将年份_year
加1,并将月份重置为1。 - 重复检查:重复上述步骤,直到
_day
不大于当前月份的天数为止。 - 返回当前对象:返回对当前对象的引用,以支持链式操作。
+可以直接复用+=的代码
-=实现思路:
- 减少天数:将传入的天数
day
从当前日期的天数_day
中减去。 - 检查天数是否小于1:如果减少后的天数
_day
小于1,说明天数不足以满足当前月份,需要向前借位。 - 调整日期:如果天数小于1,则月份
_month
减1。如果月份减到0(即一年开始之前),则将年份_year
减1,并将月份重置为12。然后,使用GetMonthDay
函数获取新的月份的天数,并加到_day
上。 - 重复检查:重复上述步骤,直到
_day
不小于1为止。 - 返回当前对象:返回对当前对象的引用,以支持链式操作。
-可以直接复用-=的代码
🍃自增自减操作
可以直接复用+=和-=的代码完成++和--函数
Date& Date::operator++() { return *this += 1;
} Date Date::operator++(int) { Date temp = *this; ++(*this); return temp;
} Date Date::operator--(int) { Date temp = *this; --(*this); return temp;
} Date& Date::operator--() { return *this -= 1;
}
🍃日期差计算
// 日期-日期 返回天数
int Date::operator-(const Date& d)const
{int flag = 1;int count = 0;Date max = *this;//假设this大,d小Date min = d;if (max < min)//假设不对,则更改{max = d;min = *this;flag = -1;}while (min != max)//使用计数器计算{++min;++count;}return flag * count;
}
思路解析:
- 变量初始化:
flag
:用于标记日期的先后顺序,以便确定最终结果的符号(正或负)。count
:用于累加两个日期之间的天数差。max
和min
:分别用于存储较大的日期和较小的日期,以便后续计算天数差。
- 假设与赋值:
- 假设当前对象(
*this
)表示的日期大于传入的日期d
,因此将当前对象赋值给max
,将d
赋值给min
。 - 如果假设不成立(即
*this
表示的日期小于d
),则交换max
和min
的赋值,并将flag
设置为-1
,表示最终的天数差应为负数。
- 假设当前对象(
- 计算天数差:
- 使用一个循环,每次循环将
min
表示的日期加1天,并累加count
的值。 - 循环继续,直到
min
和max
表示的日期相等为止。此时,count
的值即为两个日期之间的天数差。
- 使用一个循环,每次循环将
- 返回结果:
- 根据
flag
的值,返回count
或-count
作为两个日期之间的天数差。如果flag
为1(即初始假设成立),则返回count
;如果flag
为-1(即初始假设不成立),则返回-count
。
- 根据
- 这种实现方式在日期相差较大时可能效率较低,因为它通过逐天累加来计算天数差。
- 但相对来说逻辑是比较简单的,直接计算的方法都比较复杂,涉及到不同的月份天数和闰年,因为天数的计算相对来说数据是比较小的,这些计算量在CPU面前还是小意思的
日期+日期没有实际意义,所有这里不进行运算符重载
结语
通过实现一个日期类,我们不仅能够掌握类和对象的基础知识,还能将这些知识应用于解决实际问题。同时,这一实践还将为我们后续学习更复杂的面向对象编程概念打下坚实的基础。