C++ 如何实现一个日期类

 更新时间:2024年10月23日 10:19:45   作者:FFDUST  
通过对类和对象的学习,理解了类是对象的抽象描述,实现日期类涉及定义年月日属性及成员函数如打印日期、日期加减,重点介绍了运算符重载的概念和作用,通过代码示例展示了如何实现一个日期类,包括头文件和源文件的分离编写

一. 对日期类的介绍

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

二. 实现日期类

在实现日期类之前,我们还需要了解一下运算符重载,这是为了后续我们对日期的加、减天数以及日期减日期等做准备,因为当运算符被用于类类型的对象时,需要我们去重载运算符去指定新的含义。(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);
//但它们俩意义相同都是为了调用赋值运算符重载//上述代码中
 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;
}

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

不能重载的情况: 

不能用没有的符号创建新的操作符(运算符)

//例如:
operator@ //就不能创建

有五个运算符是不能重载的:.*    ::    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 = d2
	Date& 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 - d2
	int 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-day
	if (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+day
	if (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;
}

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

到此这篇关于C++ 如何实现一个日期类的文章就介绍到这了,更多相关C++日期类内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • OpenCV利用K-means实现根据颜色进行图像分割

    OpenCV利用K-means实现根据颜色进行图像分割

    K-means是一种经典的无监督聚类算法---不需要人工干预。本文将通过K-means算法实现根据颜色进行图像分割的效果,感兴趣的小伙伴可以尝试一下
    2022-10-10
  • C++使用redis的实例详解

    C++使用redis的实例详解

    这篇文章主要介绍了C++使用redis的实例详解的相关资料,希望通过本文能帮助到大家,让大家理解掌握这部分内容,需要的朋友可以参考下
    2017-10-10
  • C语言小游戏之小熊跳板功能的实现

    C语言小游戏之小熊跳板功能的实现

    这篇文章主要介绍了C语言小游戏之小熊跳板功能的实现,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-12-12
  • 获取C语言中int类型的最大值的方法小结

    获取C语言中int类型的最大值的方法小结

    在C语言中,int 类型的大小通常是根据系统架构来决定的,在大多数现代系统上,int 通常是32位的,在C语言中,获取int类型的最大值有几种不同的方法,下面,我们将讨论两种方法:使用标准库函数和使用算法,需要的朋友可以参考下
    2024-06-06
  • C++任意线程通过hwnd实现将操作发送到UI线程执行

    C++任意线程通过hwnd实现将操作发送到UI线程执行

    做Windows界面开发时,经常需要在多线程环境中将操作抛到主线程执行,下面我们就来学习一下如何在不需要重新定义消息以及接收消息的情况下实现这一要求,感兴趣的可以了解下
    2024-03-03
  • C++中类的默认成员函数详解

    C++中类的默认成员函数详解

    大家好,本篇文章主要讲的是C++中类的默认成员函数详解,感兴趣的同学赶快来看一看吧,对你有帮助的话记得收藏一下
    2022-01-01
  • C++ boost thread库用法详细讲解

    C++ boost thread库用法详细讲解

    Boost是为C++语言标准库提供扩展的一些C++程序库的总称。Boost库是一个可移植、提供源代码的C++库,作为标准库的后备,是C++标准化进程的开发引擎之一,是为C++语言标准库提供扩展的一些C++程序库的总称
    2022-11-11
  • Qt实现自定义日志类的示例代码

    Qt实现自定义日志类的示例代码

    这篇文章主要为大家详细介绍了使用 qInstallMessageHandler() 实现一个简单的日志工具,文中的示例代码讲解详细,感兴趣的小伙伴可以学习一下
    2023-12-12
  • Microsoft Visual C++进行调试的方法实现

    Microsoft Visual C++进行调试的方法实现

    VS功能极其强大,使用极其便利,本文主要介绍了Microsoft Visual C++进行调试的方法实现,具有一定的参考价值,感兴趣的可以了解一下
    2024-06-06
  • C语言实现扫雷小游戏简单版

    C语言实现扫雷小游戏简单版

    这篇文章主要为大家详细介绍了C语言实现扫雷小游戏简单版,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-07-07

最新评论