【C++】日期类Date(详解)③

简介: 该文介绍了C++中直接相减法计算两个日期之间差值的方法,包括确定max和min、按年计算天数、日期矫正及计算差值。同时,文章讲解了const成员函数,用于不修改类成员的函数,并给出了`GetMonthDay`和`CheckDate`的const版本。此外,讨论了流插入和流提取的重载,需在类外部定义以符合内置类型输入输出习惯,并介绍了友元机制,允许非成员函数访问类的私有成员。全文旨在深化对运算符重载、const成员和流操作的理解。


直接相减法
这个过程就稍稍有些复杂,你可以先确定max(大日期对象)下一年的第一天为tmpmax和min(小日期对象)本年的第一天tmpmin,让它们的差值✖365,加到 n 上,同时遍历一遍这些年,找到一个闰年就让n+1。最后定义两个整型变量(tmp1,tmp2),让tmpmin逐次++(++使用的是之前重载的日期类++,最好用前置++,减少拷贝的消耗),同时用tmp1计数,直到和min相等;让max逐次++,同时tmp2计数,直到和tmpmax相等。这时让n减去tmp1和tmp2后,得到的就是两个日期之间的差值了。

以下是实现代码,亲测正确:

int Date::operator-(const Date& d)
{
Date max = this;
Date min = d;
int flag = 1;
int n = 0;
if (
this < d) {
max = d;
min = this;
flag = -1;
}
// 以上是比较日期确定max和min
n += (max._year - min._year + 1)
365;
for (int i = min._year; i <= max._year; i++)
if (i % 4 == 0 && i % 100 != 0 || i % 400 == 0)
++n;
// 以上是根据年计算间隔的天数
Date tmpmax(max._year + 1, 1, 1);int tmp1 = 0;
Date tmpmin(min._year, 1, 1);int tmp2 = 0;
while (tmpmax != max) {
++max;
++tmp1;
}
while (tmpmin != min) {
++tmpmin;
++tmp2;
}
// 以上是日期矫正,计算tmp1和tmp2
n -= tmp1;
n -= tmp2;
return n * flag;
}

对于日期相减,不止有这两种实现,大家想到一些别的方式也可以自己亲自试一试,还是很有意思的。

const成员
将const修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。

具体怎么修饰呢?写一个const在参数列表后面:

在()括号后放const是将隐式传递的this指针类型变成了const this指针类型,这种解决方案是为了应对*this无法被显示改变为const类型而产生的。

对于const类型的对象,和内置类型的const修饰规则非常相似,权限只能平移和缩小,而不能放大。看看代码案例:

const Date d1;
Date& d2 = d1;// 不支持,d2是Date类型,取d1的引用属权限放大
const Date& d3 = d1;//支持,权限平移

Date d4;
const Date& d5 = d4;// 支持,权限缩小

所以,对于一些不会改动Date对象数据的成员函数,尽量提供const类型的成员函数即可,如下:

// 获取某年某月的天数const版
int GetMonthDay(int year, int month)const
{
assert(month > 0 && month < 13);
int months[13] = { -1,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;
}
else return months[month];
}
GetMonthDay定义在类内部,故不需要限定命名空间。

// 检查日期是否合法const版
bool Date::CheckDate()const
{
if (_month < 1 || _month>12
|| _day < 1 || _day>GetMonthDay(_year, _month)) {
return false;
}
else return true;
}
基本上就是在函数()后加上const即可,需要这样调整的函数还包括比大小重载函数,日期相减函数,-day和+day,Print打印日期函数等等,这里就不一一列举了。

流插入和流提取重载
大家之前可能接触过C语言,这里就能体现出C语言输入输出的局限性了,它没办法支持对象类型的输入输出。在C++标准库(包含istream类和ostream类)中,内含了输入流对象cin和输出流对象cout,通过使用这两个对象,我们可以支持输出所有的内置类型变量。今天要讲的流插入和流提取重载,就是可以帮助大家直接用cin和cout支持内置类型的输入输出。

如果需要定义一个流插入的重载,你会如何定义呢?

void Date::operator<<(ostream& out)
{
cout << _year << "-" << _month << "-" <<_day <<endl;
}
是这样吗?那就大错特错了,按照运算符重载规则,

这种书写方式明显不符合重载时的参数顺序,所以如果想要调用上面这份重载,需要这样写:

但是,这种使用方式明显违背了像内置类型那样使用输入输出流的初衷。产生这种问题的主要原因还是无法改变调用类内部定义的成员函数时,第一个传过去的元素永远是this指针。

为解决这样的问题,需要我们把流插入和流提取的函数重载在类的外部,像下面这样:

ostream& operator<<(ostream& out, const Date& d)
{
out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
return out;
}
这里返回一个对象引用类型是为了贴合内置类型的使用规则,连续输出,如cout << a << b << c;如果没有此返回值,那么使用对象时,就只能用一次cout打印一个Date对象,如cout << d;而不是cout << d1 << d2 << d3;。

流提取也是同样的方法:

istream& operator>>(istream& in, Date& d)
{
cout << "请输入年月日,用空格分隔:";
in >> d._year >> d._month >> d._day;
if (!d.CheckDate()) {
cout << "日期非法\n" << endl;
}
return in;
}
返回一个对象的引用也是为了贴合内置类型使用规则。

到这里,你不免会问,如果将重载函数定义在了类的外部,那么该如何使用类内部定义的私有成员变量,如_year,_month,_day呢?别急,接下来补一下之前挖的坑,友元。

友元(friend)
在C++中,友元(Friend)是一个特殊的机制,它允许一个非成员函数或者一个类(或类的成员函数)访问另一个类的私有(private)或保护(protected)成员。友元不是类的成员,但它可以访问类的所有成员,包括私有和保护成员。这种访问权限的赋予是通过在类的定义中使用friend关键字来实现的。只需要在类的内部添加上类外定义的函数的声明,并在声明前加上关键字friend即可,一般这种友元函数允许写在类内部的任意地方,一般来说会把它放在整个类的开头。当一个函数成为一个类的友元,那么这个函数内部就可以随意使用类中的私有(private)或保护(protected)成员了。

class Date
{
// 友元
friend ostream& operator<<(ostream& out, const Date& d);
friend istream& operator>>(istream& in, Date& d);
public:
//。。。
private:
//。。。
}

结语
本篇博客实现了日期类Date,加深对运算符重载的运用,讲了前置++和后置++:传一个改变成后置++样式的int;还讲到const成员:由于无法改变*this类型而添加的const;流插入和流提取重载:由于无法改变传参顺序而定义到类的外部等等。这篇博客也算是对之前学到类和对象内容的一个阶段性的应用和总结吧。

博主后续还会产出更多有意思的内容,感谢大家的支持!♥

相关文章
|
2天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。
|
2天前
|
存储 编译器 C++
【C++】类和对象④(再谈构造函数:初始化列表,隐式类型转换,缺省值
C++中的隐式类型转换在变量赋值和函数调用中常见,如`double`转`int`。取引用时,须用`const`以防修改临时变量,如`const int& b = a;`。类可以有隐式单参构造,使`A aa2 = 1;`合法,但`explicit`关键字可阻止这种转换。C++11起,成员变量可设默认值,如`int _b1 = 1;`。博客探讨构造函数、初始化列表及编译器优化,关注更多C++特性。
|
2天前
|
编译器 C++
【C++】类和对象④(类的默认成员函数:取地址及const取地址重载 )
本文探讨了C++中类的成员函数,特别是取地址及const取地址操作符重载,通常无需重载,但展示了如何自定义以适应特定需求。接着讨论了构造函数的重要性,尤其是使用初始化列表来高效地初始化类的成员,包括对象成员、引用和const成员。初始化列表确保在对象创建时正确赋值,并遵循特定的执行顺序。
|
2天前
|
定位技术 C语言 C++
C++】日期类Date(详解)①
这篇教程讲解了如何使用C++实现一个日期类`Date`,涵盖操作符重载、拷贝构造、赋值运算符及友元函数。类包含年、月、日私有成员,提供合法性检查、获取某月天数、日期加减运算、比较运算符等功能。示例代码包括`GetMonthDay`、`CheckDate`、构造函数、拷贝构造函数、赋值运算符和相关运算符重载的实现。
|
5天前
|
存储 编译器 C语言
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
【C++航海王:追寻罗杰的编程之路】类与对象你学会了吗?(上)
10 2
|
4天前
|
C++
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
7 0
C++职工管理系统(类继承、文件、指针操作、中文乱码解决)
|
5天前
|
存储 编译器 C++
【C++ 初阶路】--- 类和对象(下)
【C++ 初阶路】--- 类和对象(下)
7 1
|
5天前
|
存储 编译器 C语言
【C++初阶路】--- 类和对象(中)
【C++初阶路】--- 类和对象(中)
10 1
|
2天前
|
编译器 C++
【C++】类和对象③(类的默认成员函数:赋值运算符重载)
在C++中,运算符重载允许为用户定义的类型扩展运算符功能,但不能创建新运算符如`operator@`。重载的运算符必须至少有一个类类型参数,且不能改变内置类型运算符的含义。`.*::sizeof?`不可重载。赋值运算符`=`通常作为成员函数重载,确保封装性,如`Date`类的`operator==`。赋值运算符应返回引用并检查自我赋值。当未显式重载时,编译器提供默认实现,但这可能不足以处理资源管理。拷贝构造和赋值运算符在对象复制中有不同用途,需根据类需求定制实现。正确实现它们对避免数据错误和内存问题至关重要。接下来将探讨更多操作符重载和默认成员函数。
|
2天前
|
存储 编译器 C++
【C++】类和对象③(类的默认成员函数:拷贝构造函数)
本文探讨了C++中拷贝构造函数和赋值运算符重载的重要性。拷贝构造函数用于创建与已有对象相同的新对象,尤其在类涉及资源管理时需谨慎处理,以防止浅拷贝导致的问题。默认拷贝构造函数进行字节级复制,可能导致资源重复释放。例子展示了未正确实现拷贝构造函数时可能导致的无限递归。此外,文章提到了拷贝构造函数的常见应用场景,如函数参数、返回值和对象初始化,并指出类对象在赋值或作为函数参数时会隐式调用拷贝构造。