C++ —— 实现一个日期类

news/2024/10/23 10:16:03/

目录

一. 对日期类的介绍

二. 实现日期类

1. 运算符重载

2.日期类实现代码


一. 对日期类的介绍

通过对类和对象(这里链接是类和对象的介绍)的学习,类就是一种模型一样的东西,是对象一种抽象的描述。所以实现日期类,就是实现一个日期模型,里面有所对应的成员变量(属性:年/月/日),还有一些它的方法(成员函数:打印该日期、对日期加减等)。

二. 实现日期类

在实现日期类之前,我们还需要了解一下运算符重载,这是为了后续我们对日期的加、减天数以及日期减日期等做准备,因为当运算符被用于类类型的对象时,需要我们去重载运算符去指定新的含义。(C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。

//内置类型
int i = 1;
int j = 2;
int sum = 0;i = i + 1;
sum = i - j;//自定义类型对象若想用类似上面的就需要在类里面声明定义对应的运算符重载函数

1. 运算符重载

C++规定类类型对象使用运算符时,必须转换成对应运算符重载,若没有对应的运算符重载,则会编译报错。

也就是运算符被用于类类型的对象时,可以通过运算符重载的形式指定新的含义。

运算符重载是一个函数,它的名字是由关键字operator和运算符组成,也具有返回类型、参数列表、函数体。

class A
{
public:A(int a = 1, int b = 1):_a(a),_b(b){}//这里就是赋值运算符重载A& operator=(const A& a){_a = a._a;_b = a._b;return *this;}
private:int _a;int _b;
};int main()
{A a1;A a2(5, 2);a1 = a2;return 0;
}

我们来查看验证是否按照指定新的含义进行:

赋值之前:

赋值之后:

 可以看到a1里的成员确实被a2里的成员变量赋值了。

注意:

//上述代码中a1 = a2;
//其实详细写法是:
a1.operator=(a2);//但它们俩意义相同都是为了调用赋值运算符重载

一元运算符只有一个参数,二元运算符要有两个参数。(注意:二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数)

也就是说,重载运算符函数的参数个数和该运算符作用的运算对象数量一致的。

上面我们看到A类重载=运算符只写了一个函数参数,这是因为它是成员函数,第一个参数默认传了this指针(类和对象有介绍)。

注意:重载运算符必须至少有一个类类型的形参,不能通过运算符重载改变内置类型对象的含义。

若不是成员函数,就按照上面规则来:

class A
{
public:A(int a = 1, int b = 1):_a(a),_b(b){}A& operator=(const A& a){_a = a._a;_b = a._b;return *this;}int _a;int _b;
};//注意:operator + 必须至少有一个类类型的形参,否则会报错,就比如下面注释掉的情况
//int operator(int a,int n)
//{
//    return a-n;
//}int operator+(const A& a, int& n)
{return a._a + n;  //这里成员变量需要放公有,否则不能直接访问
}int main()
{A a;int n = 10;cout << a + n << endl;return 0;
}

 运算符重载以后,优先级和结合性与对应的内置类型运算符保持一致的。

不能重载的情况: 

  1. 不能用没有的符号创建新的操作符(运算符)
    //例如:
    operator@ //就不能创建

  2. 有五个运算符是不能重载的:.*    ::    sizeof     ?:   . 

 后四个是经常用到的,第一个非常用,下面单独拿出来解释

//函数指针较为特别,typedef类型需要这样写
typedef void (*PF)(); class A
{
public:void func(){cout << "A::func()" << endl;}
};//若typedef成员函数指针类型,就加个指定类域即可
typedef void(A::*PF)();// .*运算符就用于以下方式回调函数的场景(成员函数回调)
int main()
{//成员函数要加&才能取到函数指针PF pf = &A::func;      //这里相当于 void(A::*pf)() = &A::func;A a;(a.*pf)();return 0;
}

(深入理解指针) 

//这里是普通全局函数回调的形式
(*pf)();

所以这个符号的意义是对象调用成员函数指针时,成员函数的回调 (注意:考虑有隐含的this指针,不能显示写形参和传实参)

重载++运算符时,前置++与后置++的运算符重载函数名都是operator++,无法来区分。 C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。

 

class Date
{
public://构造函数Date(int year = 1900,int month =1,int day=1):_year(year),_month(month),_day(day){}//前置++Date& operator++(){cout << "这里是前置++" << endl;  //这里演示一下,先不实现return *this;}//后置++Date& operator++(int)  //这里的加不加形参名都可以,必须要有int (只要整型){Date tmp;cout << "这里是后置++" << endl;  return tmp;}private:int _year;int _month;int _day;
};int main()
{Date d1;Date d2;++d1;d2++;d2.operator++(10); //这里传不传值都可以,因为那个int一般不接收。 
}

注意:重载的++运算符也要与内置类型++运算符规则一致(前置++:先++,再返回++后的结果,不产生拷贝;后置++:进行++,++之后返回的是++前的结果,会产生拷贝。所以,一般开以选择前置++来减少拷贝)。

我们实现日期类,还想需要可以对这个日期cout和cin来方便输出和输入,所以<<和>>也是可以重载的,但是需要重载成全局函数,重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。否则会有隐含的this指针,导致调用时变成:对象<<cout ,不符合使用习惯和可读性(想要的是:cout<<对象)

2.日期类实现代码

我们可以声明定义分离来实现,分别创建Date.h头文件和Date.cpp用来定义头文件声明的函数。

//Date.h#include<iostream>
#include <assert.h>
using namespace std;
class Date
{//友元函数声明 --这两个全局函数就可以访问对象的私有成员friend ostream& operator<<(ostream& out, const Date& d);friend istream& operator>>(istream& in, Date& d);
public://构造函数,若没有传参 就是默认构造 全缺省函数Date(int year = 1900, int month = 1, int day = 1);//拷贝构造Date(const Date& d);void Print()const;  //打印日期bool CheckDate() const;  //检查日期输入是否正确//频繁调用的建议直接定义类里面,默认inline内联int GetMonthDay(int year, int month)const  {assert(month > 0 && month < 13);static int monthDay[13] = { -1,31,28,31,30,31,30,31,31,30,31,30,31 }; //因为没有0月,将0置-1空出来//用static修饰是因为这个数组会频繁调用,直接放在静态区if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))){return 29;}return monthDay[month];}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;//d1 = d2Date& operator=(const Date& d);Date operator+(int day) const;Date& operator+=(int day);Date operator-(int day) const;Date& operator-=(int day);// d1++;// d1.operator++(0);Date operator++(int);// ++d1;// d1.operator++();Date& operator++();// d1--;// d1.operator--(0);Date operator--(int);// --d1;// d1.operator--();Date& operator--();// d1 - d2int operator-(const Date& d) const;//void operator<<(ostream& out);Date* operator&() //取地址运算符重载 //普通对象返回类型Date*{return (Date*)0x2673FF40;  //返回假地址}const Date* operator&() const  //取地址运算符重载 //const对象要调用const成员函数要返回类型const Date*{return (Date*)0x2673FE30; //返回假地址	}
private:int _year;int _month;int _day;
};//重载流插入流提取
// ostream/istream类型对象不能传值只能传引用,否则会报错,因为c++不支持它们的拷贝构造。
//要写成全局函数 否则调用的时候需要这样写:d1<<cout    d1>>cin   用着会比较别扭
//因为如果写成了成员函数第一个参数有隐藏的this指针,并且不能修改
//cout是ostream类型的 (也可以用void返回类型但不建议)
//cin默认关联cout
//在cin进行i/o操作前会刷新cout的缓冲区//流插入
ostream& operator<<(ostream& out, const Date& d);//流提取
//这里第一个参数不能加const修饰了,因为提取的值要放日期类对象里
istream& operator>>(istream& in, Date& d);
//Date.cpp#include "Date.h"
bool Date::CheckDate() const//检查日期正确
{if (_month < 1 || _month > 12|| _day < 1 || _day > GetMonthDay(_year, _month)){return false;}else{return true;}
}Date::Date(int year, int month, int day)  //构造函数(全缺省,前面声明中已经给了缺省值,定义这里就不能再写出来了)
{_year = year;_month = month;_day = day;if (!CheckDate()){cout << "非法日期:";Print();}cout << endl;
}Date::Date(const Date& d)   //拷贝构造
{_year = d._year;_month = d._month;_day = d._day;
}void Date::Print()const  //打印日期
{cout << _year << "/" << _month << "/" << _day << endl;
}//d1 = d2
Date& Date::operator=(const Date& d)
{if (*this != d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}Date& Date::operator+=(int day)  //加等可以让自己改变    
{//先判断day是否是负数 ---- _day+(-day)==_day-dayif (day < 0){return *this -= (-day);}_day += day;while (_day > GetMonthDay(_year, _month)){_day -= GetMonthDay(_year, _month);++_month;if (_month == 13){_year++;_month = 1;}}return *this;
}Date Date::operator+(int day) const //只加不能改变自己  
//这里不能用传引用返回 因为这里用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-(-day)==_day+dayif (day < 0)         {return *this += (-day);}_day -= day;while (_day <= 0){--_month;if (_month == 0){_month = 12;--_year;}_day += GetMonthDay(_year, _month);}return *this;
}//d1<d2
bool Date::operator<(const Date& d) const  //这里传引用 d就是d2的别名 也就是d2的本身
{if (_year < d._year){return true;}else if (_year == d._year){if (_month < d._month){return true;}if (_month == d._month){return _day < d._day;}}return false;
}//d1<=d2
//这里就可以复用函数了
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);
}
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);
}// d1++;  后置++有拷贝
// d1.operator++(0);
//用的时候括号里只要是整数都可以
Date Date::operator++(int)  // 加不加形参名都可以,一般不接收
{Date tmp = *this;*this += 1;return tmp;
}// ++d1;  //前置++无拷贝
// d1.operator++();
Date& Date::operator++()
{*this += 1;return *this;
}//-- 同理++
// d1--;
// d1.operator--(0);
Date Date::operator--(int)
{Date tmp = *this;*this -= 1;return tmp;
}// --d1;
// d1.operator--();
Date& Date::operator--()
{*this -= 1;return *this;
}// d1 - d2
int Date::operator-(const Date& d) const
{Date max = *this;  //假设第一个大Date min = d;   //第二个小int flag = 1;   //等于1时表示第一个大第二个小if (*this < d){max = d;min = *this;flag = -1;       //第二个大第一个小}int n = 0;while (min != max){++min;++n;}return n *= flag;
}//流插入cout<<d1<<d2
ostream& operator<<(ostream& out, const Date& d)
{cout << d._year << "年" << d._month << "月" << d._day << "日" << endl;return out;
}//流提取cin>>d1>d2
istream& operator>>(istream& in, Date& d)
{cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;//检查日期是否非法if (!d.CheckDate()){cout << "非法日期: " << d << "请重新输入" << endl;while (1){cout << "请依次输入年月日:>";in >> d._year >> d._month >> d._day;if (!d.CheckDate()){cout << "输入日期非法:";d.Print();cout << "请重新输入!!!" << endl;}else{break;}}}return in;
}

 有些注意事项,在上面代码的实现注释中有一些解释。

制作不易,若有不足之处或出问题的地方,请各位大佬批评指正 ,感谢大家的阅读支持!!!


http://www.ppmy.cn/news/1541297.html

相关文章

如何做设备管理系统?六大亮点功能与八大关键指标?

在当今企业运营中&#xff0c;设备管理面临着诸多挑战。 领导不重视管理&#xff0c;使得设备管理缺乏战略层面的支持和资源投入&#xff1b;设备超负荷运转&#xff0c;不仅影响设备寿命&#xff0c;还增加了故障风险&#xff1b;管理方式陈旧&#xff0c;无法适应现代企业高…

JAVA接口,继承,和抽象类的使用

特点&#xff1a; 接口是行为的契约&#xff0c;定义了类必须实现的能力。可以用于多继承。抽象类是具有部分实现的类&#xff0c;需要通过继承来具体化某些行为。继承用于复用父类的实现&#xff0c;增强代码的扩展性和可维护性。 在Java中&#xff0c;接口、继承和抽象类都…

马拉车算法(C/C++)

#1024程序员节 | 征文# 马拉车算法&#xff08;Manachers Algorithm&#xff09;是一种用于在字符串中查找最长回文子串的线性时间复杂度算法。该算法由Udi Manacher在1980年代提出&#xff0c;因此得名。它的核心思想是利用已知的回文信息来减少不必要的比较&#xff0c;从而提…

Java | Leetcode Java题解之第502题IPO

题目&#xff1a; 题解&#xff1a; class Solution {public int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {int n profits.length;int curr 0;int[][] arr new int[n][2];for (int i 0; i < n; i) {arr[i][0] capital[i];arr[i][1] profi…

Android14 和android12 在锁屏界面Keyguard输错5次密码后倒计时30秒时重启手机不显示倒计时

参考如下修改&#xff1a;Android9.0在锁屏界面Keyguard输错5次密码后倒计时30秒时重启手机不显示倒计时_android 锁屏密码输错5次-CSDN博客 android 14 修改如下&#xff1a; androidap/SYSTEM/frameworks/base$ git status Refresh index: 100% (47218/47218), done. HEAD d…

自定义组件使用v-model 实现双向数据绑定

在 Vue.js 中&#xff0c;如果你想在一个自定义组件中使用 v-model 来实现双向数据绑定&#xff0c;你需要遵循一些特定的步骤。v-model 实际上是以下两个属性的语法糖&#xff1a; 一个名为 value 的 prop&#xff0c;用于接收父组件传递的数据。一个名为 input 的事件&#…

Python流程控制专题:while、break与continue

在Python编程中,流程控制是至关重要的一个环节,能够让程序根据条件的不同而采取不同的执行路径。这篇博文将深入探讨Python中的三种主要流程控制结构:while循环,以及break和continue语句。我们将通过详细的解释、示例代码及应用场景,让你全面了解如何有效地使用这些控制结…

持续深化信创布局,途普科技与统信软件完成产品兼容性互认证

近日&#xff0c;由北京途普科技有限公司&#xff08;以下简称“途普科技”&#xff09;自主研发的TopGraph图数据库及知识图谱构建平台已成功完成统信服务器操作系统V20的兼容性互认证&#xff0c;标志着途普科技在国产自控技术上又迈出了坚实的一步。 在各项严格的测试环节中…