【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;流插入和流提取重载:由于无法改变传参顺序而定义到类的外部等等。这篇博客也算是对之前学到类和对象内容的一个阶段性的应用和总结吧。

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

相关文章
|
4月前
|
人工智能 机器人 编译器
c++模板初阶----函数模板与类模板
class 类模板名private://类内成员声明class Apublic:A(T val):a(val){}private:T a;return 0;运行结果:注意:类模板中的成员函数若是放在类外定义时,需要加模板参数列表。return 0;
93 0
|
4月前
|
存储 编译器 程序员
c++的类(附含explicit关键字,友元,内部类)
本文介绍了C++中类的核心概念与用法,涵盖封装、继承、多态三大特性。重点讲解了类的定义(`class`与`struct`)、访问限定符(`private`、`public`、`protected`)、类的作用域及成员函数的声明与定义分离。同时深入探讨了类的大小计算、`this`指针、默认成员函数(构造函数、析构函数、拷贝构造、赋值重载)以及运算符重载等内容。 文章还详细分析了`explicit`关键字的作用、静态成员(变量与函数)、友元(友元函数与友元类)的概念及其使用场景,并简要介绍了内部类的特性。
169 0
|
6月前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
180 12
|
7月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
130 16
|
7月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
7月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
7月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
8月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
8月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
7月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
354 6