前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++修炼之路】4. 类和对象(中):日期类实现

【C++修炼之路】4. 类和对象(中):日期类实现

作者头像
每天都要进步呀
发布2023-03-28 12:33:49
7870
发布2023-03-28 12:33:49
举报
文章被收录于专栏:C++/Linux

C++之类和对象(中)后续

本节目标

本篇文章衔接类和对象(中),将剩余的部分进行讲解:

  • 1.日期类实现
  • 2.输入流、输出流
  • 3.const成员函数
  • 4.取地址及const取地址操作符重载

1. 日期类的实现

对于日期类来说,其成员变量包括:年(_year)、月(_month)、日(_day)三个成员变量。对于日期类的实现,通常执行的操作是:日期加天数、日期减天数,日期减日期来确定二者之间相差多少时间,但没有日期加日期,因为这个毫无意义。上述提到的日期运算,运算过程中的日期实际上就是日期类创建的对象。

我们知道,每个月的天数都不一定相同,而且还有闰年这个影响因素,因此我们在进行运算的时候需要考虑这些,并且将其封装成一个通过年和月就能确定这个年、月所对应的天数,所以我们可以构造下面的这个函数GetMonthDay

1.0 代码实现(不是最终代码)

为了便于后续需要,这里先展示代码(目的是展示其中函数的参数类型),不需要对内容进行了解,只需要在讲解时观察参数类型即可

Date.h

代码语言:javascript
复制
#pragma once
#include<iostream>
using namespace std;


class Date
{
public:
	int GetMonthDay(int year, int month)
	{
		static int monthDayArray[13] = { 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 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		// 检查日期是否合法
		if (!(year >= 1
			&& (month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}

	void Print() 
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	bool operator==(const Date& d);
	// d1 > d2
	bool operator>(const Date& d);
	// d1 >= d2
	bool operator>=(const Date& d);
	bool operator<=(const Date& d);
	bool operator<(const Date& d);
	bool operator!=(const Date& d);

	// d1 += 100
	Date& operator+=(int day);

	// d1 + 100
	Date operator+(int day);

	// d1 -= 100
	Date& operator-=(int day);

	// d1 - 100
	Date operator-(int day);

	// 前置
	Date& operator++();

	// 后置
	Date operator++(int);

	// 前置
	Date& operator--();

	// 后置
	Date operator--(int);

	//日期-日期
	int operator-(const Date& d);

	//int DayCount();

	//void operator<<(ostream& out);
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

bool Date::operator==(const Date& d)
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// d1 > d2
bool Date::operator>(const Date& d)
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year && _month > d._month)
	{
		return true;
	}
	else if (_year == d._year && _month == d._month && _day > d._day)
	{
		return true;
	}

	return false;
}

bool Date::operator>=(const Date& d)
{
	return *this > d || *this == d; //这里就复用了前两个函数
}

bool Date::operator<=(const Date& d)
{
	return !(*this > d);//复用
}

bool Date::operator<(const Date& d)
{
	return !(*this >= d);//复用
}

bool Date::operator!=(const Date& d)
{
	return !(*this == d);//复用
}

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		//return *this -= -day; 和下面的方式均可
		return *this -= abs(day); //复用下面的 -=
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month += 1;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day)
{
	Date ret(*this); //*this本身不改变,因此需要再拷贝一个对象,对其进行计算

	ret += day; //复用

	return ret;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		//return *this -= -day;
		return *this += abs(day);// 复用
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator-(int day)
{
	Date ret(*this);
	ret -= day;//复用

	return ret;
}

//前置++
Date& Date::operator++()
{
	*this += 1;//复用
	return *this;
}

//后置++ 多一个int参数主要是为了和前置区分,构成函数重载
Date Date::operator++(int)
{
	Date tmp(*this);
	
	*this += 1;//复用
	return tmp;
}

Date& Date::operator--()
{
	*this -= 1;//复用
	return *this;
}

// 后置
Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;//复用

	return tmp;
}

int Date::operator-(const Date& d)
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)//这里将在下面描述顺序问题
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;//计算相差的天数
	while (min != max)
	{
		++n;
		++min;
	}
	return n * flag;
}

1.1 GetMonthDay的实现

代码语言:javascript
复制
int GetMonthDay(int year, int month)//获取日期对应的天数
	{
		static int monthDayArray[13] = { 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 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

通过GetMonthday函数我们就可以在求解天数的时候,将对应的年和月传进去,就可以得到对应的天数。(由于后续需要对返回值进行操作,因此不能传引用返回,拷贝返回值是必要的)

但还是可以说明一下,static修饰的变量不会随着函数的结束而销毁,因为其不是存在函数栈帧里面的,而是静态存储的。

image-20221010114434370
image-20221010114434370
image-20221010115457526
image-20221010115457526

在这里进行一下conststatic的区分:

对于C/C++来说:

  1. const就是只读的意思,只在声明中使用,意即其所修饰的对象为常量((immutable)),它不能被修改,并存放在常量区。除了被修改之外,他没有别的作用,因此不像static一样,const修饰的变量出了作用域仍然会被销毁。(上面的const修饰引用返回,虽然没警告,但是不代表没错误)
  2. static一般有两个作用,规定作用域和存储方式(静态存储)。对于局部变量,static规定其为静态存储(存放在静态区)方式每次调用的初始值为上一次调用后的值,调用结束后存储空间不释放;对于全局变量,如果以文件划分作用域的话,此变量只在当前文件可见,对于static函数也是如此。static修饰的变量如果没有初始化,则默认为0.

更详细的内容可以看这篇博客:const与static的区别

1.2 日期类的框架实现

代码语言:javascript
复制
class Date
{
public:
	int GetMonthDay(int year, int month)//获取日期对应的天数
	{
		static int monthDayArray[13] = { 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 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		//检查日期是否合法,公元前除外
		if (!(year >= 1
			&& (month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
			
	}
	void Print()
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

抛开运算之外,我们一个整体的日期类的框架就搭建好了,需要注意的是在自定义的构造函数中,我们给其缺省值并在内部判断日期是否合法,像除了闰年的年份不存在2月29号,类似于这样的非法日期一旦传入,其就会出现非法日期的提示。

1.3 日期类的运算函数

对于运算函数,如果都将全部内容放到类中,这样会使类的内容看起来很多,因此我们这样操作:对于常用的或者较短的函数我们可以放在类中当成内联,对于这些运算的函数采取声明和定义分离的形式展示(分离后,函数的定义采用类::限定符来确定是属于这个类中的函数),并且我们将这些运算能进行一定的复用从而减少代码量

框架搭建好之后,就需要利用我们上个文章中的运算符重载的内容将日期的加、减、等操作进行一系列的实现:

pass1:= 、==、>=、> 、<、<=的实现

上篇文章中,我们在运算符重载已经展示了这几种运算、这里直接展示:

代码语言:javascript
复制
bool Date::operator==(const Date& d2)
	{
		return _year == d2._year
			&& _month == d2._month
			&& _day == d2._day;
	}
bool Date::operator>(const Date& d)
	{
		if (_year > d._year)
		{
			return true;
		}
		else if (_year == d._year && _month > d._month)
		{
			return true;
		}
		else if (_year == d._year && _month == d._month && _day > d._day)
		{
			return true;
		}
		return false;
	}

	// d1 >= d2
bool Date::operator>=(const Date& d)//*this是d1,传的d就是d2的别名
	{
		return *this > d || *this == d;
	}
	//…… <<= !=

bool Date::operator<=(const Date& d)
{
	return !(*this > d);//复用
}

bool Date::operator<(const Date& d)
{
	return !(*this >= d);//复用
}

pass2: += 、+天的实现

(一般是加天数、日期加日期没有意义)

那么接下来展示+=和+这些运算符的计算:我们直接演示正确的方式:

代码语言:javascript
复制
Date& Date::operator+=(int 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)
{
    Date ret(*this);
    ret += day;//这里也是运算符重载,复用了+=

    return ret;
}

首先,对于+=,是改变了自身的值,因此我们需要对其本身进行操作,最后返回的时候由于出了作用域*this(上篇解释过为什么不会销毁)还在,并不会被销毁,因此在这里可以加上引用返回避免了拷贝的过程。 其次,对于+,不能改变自身的值,因此,我们需要重新拷贝一下this指针对应的对象,对拷贝后的对象进行操作,这样才不会改变原本的对象,。

将这两个运算符重载放入类之后那我们看看操作前和操作后值的变化:

  • 操作前:
image-20221009200133195
image-20221009200133195
  • 操作后:
image-20221009200148966
image-20221009200148966

这样就完成了+和+=的要求,当然,上篇提到过链式加,在这里同样是可以的,因为返回类型是Date

对于加天来说,事实上,不用运算符重载也是可以的,即,像这样的函数也是可以计算的:

代码语言:javascript
复制
Date AddDay(int day)//3.这样也可以加天,但是可读性不如operator
{}
Date JiaTian(int day)//这是啥,运算符重载的意义是可读性。
{}

但实际上,我们采用运算符重载,就是让代码的可读性更高,让其可以像普通内置类型一样的加减,而不是调用函数,调用函数也就是失去了运算符重载的意义。

pass3: -、-=天的实现

代码语言:javascript
复制
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		//return *this -= -day;
		return *this += abs(day);// 复用
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator-(int day)
{
	Date ret(*this);
	ret -= day;//复用

	return ret;
}

pass4:前置++和后置++的实现

对于此类,我们在运算符重载时不能区分,都是operator++()。因此,C++规定:将括号中带有int的规定为后置++,不带int的为前置++ 。(int后面可以加参数,也可以不加)

代码语言:javascript
复制
//前置++
Date& Date::operator++()
{
	*this += 1;//复用
	return *this;
}

//后置++ 多一个int参数主要是为了和前置区分,构成函数重载
Date Date::operator++(int)
{
	Date tmp(*this);
	
	*this += 1;//复用
	return tmp;
}
image-20221010144754007
image-20221010144754007

从这个可以看出,自定义类型的后置++的效率没有前置++的效率高,因为多了拷贝的过程。

pass5:前置–和后置–的实现

依旧是和++相同的规定:将括号中带有int的规定为后置–,不带int的为前置–

代码语言:javascript
复制
Date& Date::operator--()
{
	*this -= 1;//复用
	return *this;
}

// 后置
Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;//复用

	return tmp;
}
image-20221010145455560
image-20221010145455560

pass6:日期减日期

对于日期减日期,实际上就是两个对象相减的过程,在减之前,我们需要判断这两个对象的大小关系,然后用大日期减去小日期,小日期减去大日期的结果为负,但我们相减的目的是为了看相距的时间,因此小日期减去大日期没有意义。

日期间日期有很多的方法,最麻烦的就是逐年逐月逐日的去减,因为需要考虑对应的天数或者是不是闰年,因此这种方式是不可取的。此外呢,对于自己的思考来说,我想到一种求解的方法:假如我们以1年1月1日也就是公元第一天为基准,建立一个通过日期求天数的函数,当把这两个日期都求出来具体天数之后,再进行相减,就可以解决。此外,仍有一种方式,就是先通过运算符重载的比较函数比较日期大小,再拷贝小的日期为一个新的对象,判断这个对象与大的是否相等,不相等就++这个对象(运算符重载)

方法1:直接根据基准量求天数函数,再相减

那我们来看看代码:

代码语言:javascript
复制
int Date::DayCount()//求天数的函数
{
	Date tmp(*this);
	int day = 0;
	while (tmp._year > 0)
	{
		while (tmp._month > 0)
		{
			while (tmp._day > 0)
			{
				day += tmp._day;
				tmp._day = 0;
			}
            //要注意的是不能先--月,否则对应不上
			tmp._day = GetMonthDay(tmp._year, tmp._month--);
		}
		tmp._year--;
		tmp._month = 12;
	}

	return day;
}
//日期-日期
int Date::operator-(Date& d)
{
	int day = d.DayCount();
	int Day = DayCount();//实际上是this->DayCount() , this省略了
	
	return Day - day;
}
image-20221011191830440
image-20221011191830440

从这里看出,我们上面的函数是没问题的。

方法2:小日期拷贝++比较大日期

代码:

代码语言:javascript
复制
int Date::operator-(const Date& d) 
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)//这里将在下面描述顺序问题
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;//计算相差的天数
	while (min != max)
	{
		++n;
		++min;
	}
	return n*flag;
}

这样,同样是可以的。并且比上面的代码简洁并且思路简单,下面就以方法二的代码一一描述。

对于上面的日期类的实现,实际上我们已经将所有的功能完成,只需要进行整理即可成为标准的日期类,但是,我们在程序中发现,*this总是出现在左侧,因为我们知道,对于比较*this和d来说,也属于运算符重载,即*this < d可以看成(*this).operator<(d),那既然我们认为*this和d是同一个类型,那我们是否可以将其调换位置,从*this < d改成d > *this呢,即变成d.operator>(*this)呢?

image-20221011205230261
image-20221011205230261

我们发现,这是不行的,当然,我们知道operator左右有所区别,但是具体的区别是什么呢?后面将会进行详细的讲解。

此外,由于日期类整体的代码还可以进一步更新,因此整体代码将会在下面展示。(3.3)

2. 输入流、输出流

在这之前,我们知道对于流提取(cin)和流插入(cout)都是库中的函数,并且其能识别类型进行输入输出,那么为什么他能够识别任意类型呢?实际上,对于这两个函数,在库里面以函数重载的方式存在,因此,其才能够识别任意类型。

image-20221012104727609
image-20221012104727609

当然,任意类型所说的是内置类型,对于自定义的类类型,事实上其没有符合的重载函数,因此也是没办法进行识别的,因此想要以<<运算符识别类,就需要自己来写运算符重载函数。

image-20221012105002386
image-20221012105002386

对于流来说,我们有输入流和输出流,也就是IO流,我们可以在C++的头文件<iostream>这个库函数清晰的看到,那么对于流提取和流插入来说,其分别属于ostreamistream

说了这么多,那我们展示一下具体的步骤:(仍然以Date类为基础)

image-20221012110812866
image-20221012110812866

然而我们清晰的发现,这样定义是错误的,因为运算符重载展开之后是不符合逻辑的

image-20221012111012826
image-20221012111012826

修改成这样之后,就可以编译成功。

但是这样也就是失去了我们原本的初衷,本来是cout << d1,而现在却变成了d1<<cout,本来是人骑马,现在却变成了马骑人,虽然符合逻辑,但是却不符合伦理。造成这种现象的原因实际是隐藏的this指针不能修改位置,即this指针一定在运算符重载的左侧,因此也就造成了d1会在左侧。因此,为了避免这样的情况,我们需要将其修改成不用this指针的形式,进而推出,我们需要将这个运算符重载函数从类里面拿出来,进行单独定义。

image-20221012111914663
image-20221012111914663

当我们单拿出来之后,会发现,这样访问的变量是私有变量,由于这个函数不在类中,我们通过这个函数不能访问私有变量,因此也就是需要把private的限定解开,变成public,虽然这样可以,但却失去了对于类内部成员的保护。此外,还有第二个问题,需要我们把私有的变成公有才能进行演示。

2.1 编译链接产生的问题

继续接着上面讨论:

image-20221012113724298
image-20221012113724298

我们发现,这样变成公有之后仍然运行不成功,实际上还有着编译链接的问题存在:由于我们在Date.h中存在全局的operator<<()函数,因此在编译链接的过程中,其分别会在Date.cppTest.cpp重复展开,这就导致了在生成符号表时自己与自己产生冲突,因此运行失败。实际上,此问题对于所有的函数都会存在,因此这也是我们需要避免的。将其变成内联不生成符号表

因此我们把private变成公有,先讨论第二个解决的方法,事实上有三种方法可以解决

  • 声明和定义分离(声明不占用符号表)
  • Date.h的全局函数变成静态的,即static修饰函数
  • Date.h中变成内联(编译时自动展开,不生成符号表)

第一种方式我们知道,因此主要讲述后两种方式。先来看一下第二种方式的运行结果:

image-20221012114646915
image-20221012114646915

即这样就可以以人骑马的方式正确的运行。对于static修饰的函数来说,由于是静态的,因此并不会在别的的文件产生符号表,只会在Date.h中产生,这也就使得不会在Date.cpp产生符号表,因此也就不会产生冲突。

还要说明的一点是,由于cout也存在链式调用(在类和对象中描述过)因此,我们需要把void变成ostream&

image-20221012184112458
image-20221012184112458
image-20221012180458377
image-20221012180458377

  • 这里做一个补充:对于头文件中的pragma once是对头文件中的重复声明进行去重的,而不是对所有文件去重。

对于内联函数,直接在Date.h中同样不会产生问题

image-20221012181225013
image-20221012181225013

因此总结一下,static和inline修饰都不会产生符号表,但是原因不同,static是静态只在Date.h作用;inline是直接展开,不看做函数。

以上就是对编译链接这部分问题的分析过程。

2.2 解决私有的成员变量问题

最后,别忘了,这些都是基于私有变成公有的结果,我们真正需要的是用私有去解决这个问题,即成员变量必须是私有的。而2.1中的问题前提都是将私有变成公有之后才方便演示的。因此,在这里将解决私有成员变量的问题。

  • 方法:在类的任意位置对该函数进行友元声明
image-20221013104936985
image-20221013104936985

通过在类的内部的任意位置通过friend进行友元声明,这样就相当于给此全局函数开了绿灯,即可以访问私有成员变量(直接偷家)。

image-20221013105134001
image-20221013105134001

因此,这样才是解决此问题的最终方案。需要注意的是友元函数不易声明过多,因为这样会造成耦合度过高导致程序不易观察。

2.3 流的总结

上面的一共讨论了两个问题并得到了合理的解决。

  1. 编译链接的问题:利用static或者inline进行处理。
  2. 私有成员变量访问的问题:通过友元函数声明解决。

此外,上面我们详细解释了流提取的操作,相应的,还有流插入,流插入与流提取很类似,不同的地方就在于流提取是istream并且符号是>>,同样对流提取也进行inline和友元声明。同样的可以返回引用,因为cin也是全局的函数并不会被销毁。

image-20221013112552291
image-20221013112552291
image-20221013112405180
image-20221013112405180

即:通过自定义流的重载,就可以将我们自定义的类进行输入和输出,Print方法就可以忽略了。

3. const成员

在1.3的pass6的结尾,我们谈到了一个问题,在3.3进行分析。

在这之前我们需要了解const修饰变量的关系:

3.1 const 限定

对于const的·限定,实际上是C语言中我们就需要掌握的东西,但这里还是要重新讲解一下。:既然说到C语言中的const,就离不开指针最具代表的东西,先来看看这个:

代码语言:javascript
复制
int* const a;
const int* a;

我们知道,const限定代表不可修改,但对于指针来说,当我们用const修饰时,是指针本身不可修改还是指针指向的内容不可修改呢?这与具体的写法有关,就比如上面两行代码,在这里我们直接记这个结论:const 在* 的前面,就代表指向的内容不可修改;const在*的后面,就代表指针不可修改。

image-20221013143416276
image-20221013143416276

为了便于理解,我们可以这样思考,抛开const不说,我们在int*定义变量时,变量名一定是指针变量,因此如果*在const的后面,const的定义对象就是解引用的指针,解引用的指针就是常量,因此const int*pb 就代表这pb这个指针指向的内容不可修改,也就是b不可修改,但是pb却是可以修改的;反之,就是a可以修改,而pa不可以修改。

3.2 const 修饰权限

经过上面的复习之后,接下来我们开始正式解释const成员。(仍以Date类)

image-20221013145120796
image-20221013145120796

对于Date类,当我们将其const之后,用Print方法却显示错误。事实上,这个提示已经很清楚了,是因为调用时产生了权限的放大。

那我们就需要观察一下Print中的参数了:

image-20221013145336352
image-20221013145336352

我们发现,在Print中,只有唯一的参数,也就是this指针。通过上述日期类的实现我们可以清楚地明白:this指针是不可以修改的,但this指针指向的内容是可以修改的(比如++、+=等)因此通过3.1的描述,我们知道this指针的类型是Date* const this。 而对于上述我们所定义的d2,就是this指向的内容,也就是说,不可修改的d2传到Print中变成了可修改的类型,这就是权限的放大,因此这样会产生错误。 但是我们需要解决这样的问题,就需要将Print方法中的this指针的类型从Date* const this 变成 const Date* const this,但是this是内置不显示的,无法直接进行修改。这个时候,就增加了一个语法:在函数名的后面加上const就表示this指向的内容不可修改,同时也就等同于const Date* const this,即:

代码语言:javascript
复制
void Print() const 
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}
image-20221013150400015
image-20221013150400015

这就是我们在const成员中所讲的最核心的内容。

这样一来,我们就可以合理的解释上述pass6最后的原因,并能加以修改:

3.3 this指针的比较运算符重载

image-20221013150645085
image-20221013150645085

通过这一节的学习,我们明白pass6这里的错误也是权限放大的原因,即d > *this 会变成:d.operator>(*this),而d在这个函数中是const限定的不可修改的,传入>的运算符重载之后,就会使得权限放大,因此,我们也需要对这些比较运算符中的this指向的没人进行const的修饰,即将所有函数的后面加上const;而对于那些自己不改变的运算符重载也可以加上const修饰(+、-)

即日期类的代码的所有细节处理我们都已经优化完毕,下面就是Date类的最终的代码:

4. 日期(Date)类最终代码:

Date.h

代码语言:javascript
复制
#pragma once
#include<iostream>
using namespace std;


class Date
{
public:
	friend ostream& operator<<(ostream& out, const Date& d);
	friend istream& operator>>(istream& in, Date& d);

	int GetMonthDay(int year, int month)
	{
		static int monthDayArray[13] = { 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 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;

		// 检查日期是否合法
		if (!(year >= 1
			&& (month >= 1 && month <= 12)
			&& (day >= 1 && day <= GetMonthDay(year, month))))
		{
			cout << "非法日期" << endl;
		}
	}

	void Print() const 
	{
		cout << _year << "/" << _month << "/" << _day << endl;
	}

	bool operator==(const Date& d) const;
	// d1 > d2
	bool operator>(const Date& d) const;
	// d1 >= d2
	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 += 100
	Date& operator+=(int day);

	// d1 + 100
	Date operator+(int day) const;

	// d1 -= 100
	Date& operator-=(int day);

	// d1 - 100
	Date operator-(int day) const;

	// 前置
	Date& operator++();

	// 后置
	Date operator++(int);

	// 前置
	Date& operator--();

	// 后置
	Date operator--(int);

	//日期-日期
	int operator-(const Date& d) const;

	//int DayCount();

	//void operator<<(ostream& out);
private:
	int _year;
	int _month;
	int _day;
};

//inline ostream& operator<<(ostream& out, const Date& d)
//{
//	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
//	return out;
//}

inline ostream& operator<<(ostream& out, const Date& d)
{
	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
	return out;
}

// cin >> d1   operator(cin, d1)
inline istream& operator>>(istream& in,  Date& d)
{
	in >> d._year >> d._month >> d._day;
	return in;
}

Date.cpp

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include"Date.h"

bool Date::operator==(const Date& d) const
{
	return _year == d._year
		&& _month == d._month
		&& _day == d._day;
}

// d1 > d2
bool Date::operator>(const Date& d) const
{
	if (_year > d._year)
	{
		return true;
	}
	else if (_year == d._year && _month > d._month)
	{
		return true;
	}
	else if (_year == d._year && _month == d._month && _day > d._day)
	{
		return true;
	}

	return false;
}

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 !(*this == d);//复用
}

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		//return *this -= -day; 和下面的方式均可
		return *this -= abs(day); //复用下面的 -=
	}

	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		_month += 1;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}

	return *this;
}

Date Date::operator+(int day) const
{
	Date ret(*this); //*this本身不改变,因此需要再拷贝一个对象,对其进行计算

	ret += day; //复用

	return ret;
}

Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		//return *this -= -day;
		return *this += abs(day);// 复用
	}

	_day -= day;
	while (_day <= 0)
	{
		--_month;
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}

		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

Date Date::operator-(int day) const
{
	Date ret(*this);
	ret -= day;//复用

	return ret;
}

//前置++
Date& Date::operator++()
{
	*this += 1;//复用
	return *this;
}

//后置++ 多一个int参数主要是为了和前置区分,构成函数重载
Date Date::operator++(int)
{
	Date tmp(*this);
	
	*this += 1;//复用
	return tmp;
}

Date& Date::operator--()
{
	*this -= 1;//复用
	return *this;
}

// 后置
Date Date::operator--(int)
{
	Date tmp(*this);

	*this -= 1;//复用

	return tmp;
}

//int Date::DayCount()
//{
//	Date tmp(*this);
//	int day = 0;
//	while (tmp._year > 0)
//	{
//		while (tmp._month > 0)
//		{
//			while (tmp._day > 0)
//			{
//				day += tmp._day;
//				tmp._day = 0;
//			}
//			//tmp._month--;
//			tmp._day = GetMonthDay(tmp._year, tmp._month--);
//		}
//		tmp._year--;
//		tmp._month = 12;
//	}
//
//	return day;
//}
日期-日期
//int Date::operator-(Date& d)
//{
//	int day = d.DayCount();
//	int Day = DayCount();//实际上是this->DayCount() , this省略了
//	
//	return Day - day;
//}

int Date::operator-(const Date& d) const
{
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)//这里将在下面描述顺序问题
	{
		max = d;
		min = *this;
		flag = -1;
	}
	int n = 0;//计算相差的天数
	while (min != max)
	{
		++n;
		++min;
	}
	return n * flag;
}

//ostream& operator<<(ostream& out, const Date& d)
//{
//	out << d._year << "年" << d._month << "月" << d._day << "日" << endl;
//	return out;
//}

Test.cpp

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS 1
#include "Date.h"

void TestDate1()
{
	Date d1(2022, 10, 8);
	Date d3(d1);
	Date d4(d1);

	d1 -= 10000;
	d1.Print();

	Date d2(d1);
	/*Date d3 = d2 - 10000;
	d3.Print();*/
	(d2 - 10000).Print();
	d2.Print();

	d3 -= -10000;
	d3.Print();

	d4 += -10000;
	d4.Print();
}

void TestDate2()
{
	
	//(++d1).Print(); // d1.operator++()
	//d1.Print();

	//(d2++).Print(); // d1.operator++(1)
	//d2.Print();
	Date d1(2022, 10, 8);
	Date d2(d1);
	Date d3(d1);
	Date d4(d1);

	(--d3).Print();
	d3.Print();
	(d4--).Print();
	d4.Print();
	int p = d3 - d4;
	cout << p << endl;

	/*cout <<d4.DayCount()<<endl;
	Date d;
	cout << d.DayCount()<<endl;*/
	Date d5(2022, 9, 8);

	cout << d5 - d1 << endl;

		
}

void TestDate3()
{
	Date d1(2022, 10, 11);
	Date d2(2023, 1, 20);

	cout << d2 - d1 << endl;
}

void TestDate4()
{
	Date d1, d2;

	//cin >> d1; //  流提取
	//cout << d1; // 流插入  编译器里面无法识别Date类,因此需要自己实现
	cin >> d1 >> d2;
	cout << d1 << d2 << endl;
}
void TestDate5()
{
	Date d1(2022, 10, 10);
	d1.Print();

	const Date d2(2022, 10, 10);
	d2.Print();
}
int main()
{
	TestDate5();
	int i = 1;
	double d = 1.11;
	// cout为什么能自动识别类型:实际上是函数重载
	/*cout << i;
	cout << d;*/
	return 0;
}

将这个与1.0中的代码对比会发现,最终代码对细节处理的效果以及涉及的知识储备远远高于1.0中的代码。

5. 取地址及const取地址操作符重载

这两个默认成员函数一般不用重新定义 ,编译器默认会生成。

代码语言:javascript
复制
class Date
{
public :
    Date* operator&()
    {
        return this;
    }
    const Date* operator&()const
    {
        return this;
    }
private :
    int _year ; // 年
    int _month ; // 月
    int _day ; // 日
};

这两个运算符一般不需要重载,使用编译器生成的默认取地址的重载即可,只有特殊情况,才需要重载,比如想让别人获取到指定的内容

即这两个操作符重载,基本上是不会用到的,因此这里只需要知道有这个东西即可。

6. 总结

这一篇是类和对象中的后续,可见类和对象中的内容的重要性,此后续不仅讲解了大体上日期类的实现,还在实现的过程中解决了一系列的问题:运算函数、流、编译链接、权限。最终完成了日期类的代码实现。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-10-13,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • C++之类和对象(中)后续
  • 本节目标
  • 1. 日期类的实现
    • 1.0 代码实现(不是最终代码)
      • 1.1 GetMonthDay的实现
        • 1.2 日期类的框架实现
          • 1.3 日期类的运算函数
            • pass1:= 、==、>=、> 、<、<=的实现
            • pass2: += 、+天的实现
            • pass3: -、-=天的实现
            • pass4:前置++和后置++的实现
            • pass5:前置–和后置–的实现
            • pass6:日期减日期
        • 2. 输入流、输出流
          • 2.1 编译链接产生的问题
            • 2.2 解决私有的成员变量问题
              • 2.3 流的总结
              • 3. const成员
                • 3.1 const 限定
                  • 3.2 const 修饰权限
                    • 3.3 this指针的比较运算符重载
                    • 4. 日期(Date)类最终代码:
                      • Date.h
                        • Date.cpp
                          • Test.cpp
                          • 5. 取地址及const取地址操作符重载
                          • 6. 总结
                          领券
                          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档