C++ 日期类(运算符重载应用)

目录

判断日期合法

日期运算

+=

+运算

 - = && -

前置++/--与后置++/--

日期 - 日期

日期类流插入重载

日期类流提取重载

使用内联

const类问题

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

自定义取地址重载 

​编辑


我们用前面学的知识简单实现一下规范点的日期类吧,定义两个.cpp文件,用于测试和函数定义,一个.h文件用于头文件包含。

判断日期合法

从我们测试结果来看,输入了一个非法的日期却没有检测,所以我们要判断日期的合法性

之前我们写了一个获取天数的函数

int Date::GetMonthDay(int year, int month) const
{
	assert(month > 0 && month < 13);//合法
	int monthArray[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 monthArray[month];
	}
}

再完善构造函数

Date::Date(int year, int month, int day)
{	//assert
	if (month > 0 && month < 13 && day <= GetMonthDay(year, month) && day>0)//这里只对月日做判断
	{
		_year = year;
		_month = month;
		_day = day;
	}
	else
	{
		cout << "非法日期" << endl;
		exit(-1);
	}
}

日期运算

+=

理论来说,+=天数可以不用写 返回值,(this指针直接改变了它的值),

从运算符的特性来说(不支持d1 +=d2 += d3,但支持d1 = d2 + d3),要支持连续赋值,就得添加返回值

Date& Date:: operator+=(int x)//域名指定的是名字而不包含返回值
{
	_day += x;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		if (++_month > 12)
		{
			_year++;
			_month == 1;
		}
	}
	return *this;//连续赋值
}

+运算

+运算不能改变原对象,所以需要用到拷贝构造(赋值重载),而且之前函数重载那一篇文章提到过代码的复用,为我们减轻实现压力。

Date Date:: operator+(int x)
{
	Date tmp(*this);
	tmp += x;
	return tmp;
}

注意这里tmp是临时变量不能引用返回。有人说可以将tmp变成静态的,然后再用引用返回减少拷贝,但这样就陷入了另一个误区

通过调试发现,下次调用这个函数时,这个静态对象还保留着之前的值,就会出现这样的错误!

这里我们是用+来复用+=,那反过来怎么用+=来复用+呢?

Date Date::operator+(int day)
{
	Date tmp(*this);

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

	return tmp;
}
Date& Date:: operator+=(int x)
{
	*this = *this + x;//默认赋值重载
	return *this;
}

        大家觉得哪种方式更好呢?当然是第一种了。通过对比发现,第一种只在创建临时对象时调用了一次拷贝构造,第二种有三次拷贝,复用有两次,赋值又有一次拷贝构造,所以推荐用第一种方式。

 - = && -

与刚才类似,需要注意退位,如果天数不足,前面我们是减去多余的天数,那这次我们就加上上一月的天数。

Date& Date:: operator-=(int x)
{
	_day -= x;
	while(_day <= 0)
	{
		if (--_month < 1)
		{
			_month = 12;
			_year--;
		}
		_day += GetMonthDay(_year, _month);
	}
}
Date& Date:: operator-(int x)
{
	Date tmp(*this);
	tmp -= x;
	return tmp;
}

 题外话:如果我想+=一个负数怎么做呢,这种情况也是合理的啊?利用我们刚才写好的-=重载,我们只需在+=的后面加上一个判断并复用就可以了,注意我们这里输入的是负数,所以要加上负号才能正确使用。(负号不一定代表是负数)

if (x < 0)
	{
		*this -= -x;
		return *this;
	}

测试代码中发现一些小的问题,比如没有进入 -=说明是因为将*this -= -x写成了_day -= -x,造成不匹配,还有输出日期不对,是因为判断应该放在_day -= x这句代码之前执行,希望引以为鉴。

  

前置++/--与后置++/--

虽然+=和-=可以取代++,--的工作,但继承了c语言的特性,c++对自定义类型的前置后置++、--做了个良心的区分。

我们先来看看它们在类中的声明

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

前置很好理解,而后置函数体的int无实际含义,仅仅是为了占位,你可以理解为 a++0来区分它们。

Date Date:: operator++(int)//不推荐
{
	Date tmp(*this);
	*this += 1;
	return tmp;
}
Date& Date:: operator++()
{
	*this += 1;
	return *this;
}

注意后置++需要先创建临时变量,然后类自增1,最后返回临时变量,需要调用两次拷贝构造所以不推荐使用。

日期 - 日期

相比前面的日期+-一个数,日期减日期更为复杂,它的返回值是一个整形,且需要考虑闰年,闰月问题,这里给大家一种累加法求两个日期相差的天数。

在函数体内涉及一个大小问题,小的要累加直到和大的相同为止,可以设置一个flag开关,用于进行正负计算。

int Date:: operator-(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;
}

在运行中发现一个大意的问题揪了半天才揪出来。是这样的,刚开始小的日期计算没问题,但跨日期大了就报错了,而报错位置就是assert的月份出错了(如果不写aseert都检查不出来)。然后从GetMonthDay这个函数找,发现没问题,然后就去找调用了它的运算符重载,因为++是复用+=的,最终在+=函数里发现month的=写成了==,导致一遇到跨年的日期就会报错,而调试也要调试365次才能发现异样,希望大家能从我的这次粗心中学到一点经验,共勉。

日期类流插入重载

上面代码中我们都是调用Print来打印日期,再有的时候测试起来可能不太方便,在学习c++入门时我们简单介绍了cout是流插入函数,而<<是一个运算符重载,意为将右边的数据流入cout对象中,通过运算符重载概念的引入,现在大家对cout可以识别内置类型想必有了更清晰的认识。

int i;
double s;
cout << i;//cout.operator(i)
cout << s;//cout.operator(s)

具体原理就是识别不同类型,调用不同重载函数,这个过程就是 运算符重载 + 函数重载的过程 

对于内置类型,c++的库已经为我们提供iostream接口供我们使用,而对于自定义类型,需要我们自己来实现,所以c++为我们提供了流插入函数的声明使我们可以自己编写。

我们可以使用c++的ostream(输出流)来添加cout的自定义类型重载 

void Date::operator <<(ostream& out);//out == cout
{
	out << _year << "/" << _month << "/" <<_day << endl;
}

但是我们不能像 cout<<d1一样打印出d1,因为在类中运算符重载的左操作数是对象而不可能是cout,我们只能反过来调用或者通过d1.operator<<(ostream& cout)调用。但这样是有问题的,比如不支持链式调用。(d1<<d2<<cout? d1<<cout<<d2?)

在全局定义是不是就可以了呢?

我们可以将它放到全局,但全局是无法访问私有成员变量的,目前有两种方法可以访问成员变量,

1.变成公有(不推荐,破坏封装性)

2.类中实现相应的获取成员变量的函数

现在我们引入一种c++中新的方法:友元函数,使之能访问类中的private成员变量

为了实现运算符链式调用的功能,我们给其添加返回值,具体原理是从左向右依次读取cout和右操作数,然后返回ostream类型(cout),cout再作为左操作数与下一个操作数进行调用。

//类外
ostream& operator<<(ostream& out,const Date d)//out即cout
{
	out << _year << "/" << _month << "/" <<_day<<endl;
	return out;
}
friend ostream& operator<<(ostream& out, const Date d);//类里

这里传引用返回保证每个运算符都能访问前面运算符输出的流对象

通过这种类外自定义cout的方式 ,解决了c语言无法用printf进行输出自定义类型的问题

日期类流提取重载

既然支持流插入,那也应该支持流提取

//类里
friend istream& operator>>(istream& in,  Date& d);//无const
//类外
istream& operator>>(istream& in, Date& d)
{
	in >> d._year >> d._month >> d._day;
	//assert(d._month > 0 && d._month < 13 && d._day <= d.GetMonthDay(_year, _month) && d._day>0);
	return in;
}

注意这里不要输错天数,我本想使用assert检查,但我只对istream进行了友元声明,所以无法识别。

使用内联

像cout和cin这种体积小的函数,多次调用可能会浪费太多时间,我们可以使用内联来提高运行效率

我们在.h文件里直接声明和定义cout和cin函数,并加上inline的前缀,这样在编译过程展开直接就能call到函数的地址,无需再去符号表里查找并节约了函数调用的开销。

类中声明和定义的函数可能会被编译器默认为内联函数(短小精悍的函数),所以我们可以将这样的函数直接在类里面展开

const类问题

先给大家普及一下被const类型修饰的类型的特性——需要在定义的时候初始化

我们先来举个例子

class A
{
public:
	void Print()
	{
		printf("%d", _a);
	}
	A(int a)
	{
		_a = a;
	}
private:
	int _a ;//缺省
};
int main()
{
    const int* a;//int const* a
    const int b;
    int* const c;
    const A aa;
    return 0;
}

上述四个对象哪些是不初始化可以被编译的,哪些必须初始化?

答案是除了第一个,都要初始化对象,不是说被const修饰的类型都要初始化吗,那是因为作为指向常量的指针,它所储存的是一个具体的内存地址而不是一个数,所以可以不用初始化。第四个比较特殊,如果你在类声明中定义了构造函数或者给所有成员变量给了缺省值,就算成功实例化。

题外话:引用在声明时必须指向一个对象,这里不做考虑。

弄清楚这个以后,我们都知道类中成员函数都有隐含的this指针,如果我们执行下面的操作就会编译出错。

const A *paa = &aa;
//传递过程,不支持显式写
aa.Print(&aa);
aa.Print(paa);

而在函数体中的this指针为

A* this;

 根据规定,在传递参数这个过程中是不允许显式写出其地址和this指针的,为了解决这一权限放大问题,规定在函数体外书写const的形式来修饰this指针,使其变成常指针

这种场景一般适用与将传递const类型的类对象

//类里
void Print() const//cosnt A * this
	{
		printf("%d", _a);
	}
//类外
void Fun(const A& a)
{
	a.Print();
}

所以对照日期类,我们可以给内部不改变成员变量的成员函数加上const,使代码更加安全(友元函数没有this指针所以不用在外加const)

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

前面我们讲了四大成员函数,作为最后的两个成员函数,一般情况下不需要自己写,它们很好理解

题外话:六大成员函数如果自己手动写,必须在类里声明,运算符重载可以在类里可以在类外,函数同样如此,不能因为学了类就固化了思维

自定义取地址重载 

 可以看到可以通过这种方式改变了地址,但其实只是改变了输出结果,通过调试还是能看到真实地址。注意这里const取地址重载后要加const构成重载

  • 7
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
C++中的时间类运算符重载可以通过重载类的成员函数或者友元函数来实现。下面是一个示例代码,展示了如何重载时间类的加法运算符(+)和流插入运算符(<<): ```cpp #include <iostream> class Time { private: int hours; int minutes; public: Time(int h = 0, int m = 0) { hours = h; minutes = m; } Time operator+(const Time& t) const { Time sum; sum.minutes = minutes + t.minutes; sum.hours = hours + t.hours + sum.minutes / 60; sum.minutes %= 60; return sum; } friend std::ostream& operator<<(std::ostream& os, const Time& t) { os << t.hours << " hours, " << t.minutes << " minutes"; return os; } }; int main() { Time t1(2, 30); Time t2(1, 45); Time t3 = t1 + t2; std::cout << "Time 1: " << t1 << std::endl; std::cout << "Time 2: " << t2 << std::endl; std::cout << "Time 1 + Time 2: " << t3 << std::endl; return 0; } ``` 在上面的代码中,我们定义了一个名为Time的时间类。该类具有hours和minutes两个私有成员变量,并且定义了一个构造函数和两个运算符重载函数。 在运算符重载函数中,我们重载了+运算符来实现两个时间对象的相加操作。在重载函数中,我们通过创建一个新的Time对象来保存两个时间对象的和,并确保小时与分钟的进位被正确处理。 另外,我们还通过友元函数重载了流插入运算符(<<),以便能够直接使用std::cout输出Time对象。 在主函数中,我们创建了两个Time对象t1和t2,并将它们相加得到t3。然后,我们使用std::cout输出了t1、t2和t3的值。 这只是一个示例,你可以根据实际需求重载其他的运算符,如减法运算符(-)、赋值运算符(=)等等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小C您好

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值