首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >C++ 运算符重载详解:赋值与取地址运算符及日期类实现

C++ 运算符重载详解:赋值与取地址运算符及日期类实现

作者头像
云泽808
发布2025-12-30 18:11:59
发布2025-12-30 18:11:59
1820
举报

前言

大家好啊,我是云泽Q,一名热爱计算机技术的在校大学生。近几年人工智能技术飞速发展,为了帮助大家更好地抓住这波浪潮,在开始正文之前,我想先为大家推荐一个非常优质的人工智能学习网站)。它提供了从基础到前沿的系列课程和实战项目,非常适合想要系统入门和提升AI技术的朋友,相信能对你的学习之路有所帮助。

一、赋值运算符重载

1.1 运算符重载

C++期望有更便捷,可读性更高的方式去使用运算符,这里有一个限制,内置类型可以尝试使用各种运算符,但是自定义类型不可以,编译器也不知道自定义类型如何使用,和拷贝构造是一样的,内置类型都是自己定义好的一些简单的类型(例如加减乘除),要拷贝时,底层有对应的指令支持,一个指令就拷贝了 。但是自定义类型是一个复合类型,其底层可能有各式各样的各种结构,程序员怎么运算,编译器不知道,编译器就无法支持。而且这个运算符到底有没有意义,也是另外一回事了,这是什么意思呢?

比如说,两个日期类相加就没有意义,两个日期类相减就可以算一个天数了。这个运算符具体在什么场景下到底有没有意义,怎么来算都要程序员来定,所以基于这样的原因,自定义类型是不支持使用各种运算符的

这里想要支持的话,C++之父也设置了一套机制

  • 当运算符被用于类类型(也就是自定义类型)的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应的运算符重载,若是没有对应的运算符重载,则会编译报错,也就说默认是不支持的,但是实现一个运算符重载的函数,就支持了。注意这里运算符重载和函数重载没什么关联
  • 运算符重载是具有特殊名字的函数,他的名字是由operator(关键字,本身就具有重载的意思)和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
  • 重载运算符的参数个数和该运算符作用的运算对象数量一样多。一元运算符(一个操作数)有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数(最好顺序不要乱,乘加等于就没有影响,减,除,比较就有影响了)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

但是没有显式调用的必要

代码语言:javascript
复制
int main()
{
	int i = 0, j = 1;//内置类型就转换成对应的指令
	bool ret = i == j;

如果这里不接收i==j的结果,比较没有意义,此时转到反汇编指令就会被优化

在这里插入图片描述
在这里插入图片描述

这里对应的指令是cmp,cmp就是compare,将i给eax,eax就是i的值,然后和j进行一个cmp比较,后面指令的意思就是取到比较的结果

在这里插入图片描述
在这里插入图片描述

这里也确实看到对于自定义类型要转换为调用对应的函数

这里的代码还有很多缺点,都知道传值传参是要调用拷贝构造的,对于内置类型,用传值传参和引用传参区别不大,第一内置类型都比较小,传值传参拷贝代价也不大,第二它也不涉及调用拷贝构造,都是指令直接完成。但是对于自定义类型,尤其是深拷贝的类型,用引用传参就非常的重要,而且这里函数不改变参数,加上const更好,这样普通对象,const对象,都可以调用

在这里插入图片描述
在这里插入图片描述

这样为了能比较将内置成员改为公有就太挫了,这里还有另外三种解决方式,其中一种我在类和对象下一篇来讲,我这篇写两种 有些地方会习惯去提供函数,这种方式Java很喜欢用

直接访问私有不行,但是可以提供一个公有的成员函数,对应位置就可以获取该成员,如图就可以提供Get Year,Get Month,Get Day

C++更习惯在类中实现运算,但是放到类中就语法报错了(运行报错operator==的参数太多了),这是因为类中的成员函数的参数列表还有一个隐含的this指针,所以参数还要进行调整

在这里插入图片描述
在这里插入图片描述

调用的时候编译器也很灵活,它看到运算符重载会找对应的函数,全局和类中它都会去找,严格来说先去类中找,找到之后就会转换为对应的调用,这里 d1 是调用这个函数的对象(相当于函数的第一个参数,即当前对象),d2 是传递给函数的参数(第二个操作数)。 这里还是建议使用operator传参,养成好习惯

  • 如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个
  • 运算符重载以后,其优先级和结合性与对应的内置类型运算保持一致(比如内置类型乘的优先级高,重载之后一个表达式内既有乘又有加,这肯定先运算乘在运算加)
  • 不能通过连接语法中内置类型没有的符号来创建新的操作符:比如operator@
  • .*(访问成员函数的指针) | ::(域作用限定符) | sizeof | ?:(三目选择) | . 注意以上5个运算符不能重载(这里选择题常见)下面理解以下.*这个运算符
在这里插入图片描述
在这里插入图片描述

之前C/C++是这样调用函数指针的

通过成员函数指针调用成员函数是下面这样调用的,之前那样调用就不可以了,有类域的限制

之所以这样写是因为成员函数的函数指针和普通函数的函数指针是不一样的,普通函数的func1是无参的,但是成员函数func2是有参的(隐含的this指针)

在这里插入图片描述
在这里插入图片描述
  • 重载操作符(和重载操作符是同一个意思)至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator + (int x,int y) 这里也就是说运算符重载的参数也可以是其他类型,还可以重载日期类的加,日期加天数就是有意义的
代码语言:javascript
复制
Date operator+(const Date& d, int x);
  • 一个类需要重载那些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator+(日期加日期)就没有意义。接下来建议直接跳到1.2看赋值运算符重载,碍于目录逻辑原因,有些地方的讲解是不能按目录逻辑来的
  • 重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分,C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分
  • 重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是在左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。

1.2 赋值运算符重载

赋值运算符重载(有些书上也叫赋值拷贝)是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象

在这里插入图片描述
在这里插入图片描述

这里调试就是完全没有问题的

赋值运算符重载的特点

  1. 赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算符重载的参数建议写成const当前类类型引用,否则会传值传参会有拷贝
  2. 有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
在这里插入图片描述
在这里插入图片描述

return也没有使用传值传参的必要,* this出了作用域还在,d3的生命周期在main中,换用引用传参就不会调用拷贝构造直接返回了

在这里插入图片描述
在这里插入图片描述

这里先d5赋值给d3,d3作为赋值运算符重载的返回值赋值给d1,这里有两个函数调用

赋值运算符重载还有一个很极端的场景

代码语言:javascript
复制
d1 = d1;//语法上是允许的,但逻辑就白白执行了

所以语法上可以给个判断,如果自己给自己赋值,就不再进行拷贝

代码语言:javascript
复制
	Date& operator=(const Date& d)//日期类的拷贝就是简单的值拷贝
	{
		//右边不是引用,是取地址
		if (this != &d)//如果左对象的地址和右对象的地址是一样的,就不进行拷贝
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
  1. 没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数
  2. 像Date这样的类成员变量全是内置类型且没有指向申明资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显式实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显式实现MyQueue的赋值运算符重载。这里还有一个小技巧,如果一个类显式实现了析构并释放资源,那么他就需要显式写赋值运算符重载,否则就不需要。

1.3 日期类实现

我这里实现一个阉割版的日期类,日期类加一个天数就有一个问题了,加个小天数还可以,加个大天数还需要进位

在这里插入图片描述
在这里插入图片描述

首先拿8月来举例,进位之前首先要获取8月的天数,8月31天,减掉之后,月数进一位,此时的日期还不合法,继续进位,获取9月天数并减掉,以此类推,直至日期合法

1.3.1 加等

代码实现 Date.h

代码语言:javascript
复制
#pragma once
#include<iostream>
#include<assert.h>
using namespace std;
//日期类的拷贝构造,赋值重载,析构都不需要实现,默认的够用
class Date
{
public:
	Date(int year = 1900, int month = 1, int day = 1);
	void Print();

	//高频调用的小函数最好搞成内联
	//这里在类里面定义的函数直接就是内联
	int GetMonthDay(int year, int month)
	{
		//不写成静态的话每次都要在栈帧创建数组
		static int monthDayArray[13] = { -1,31,28,31,30,31,30,
		31,31,30,31,30,31 };//-1的作用是对其月数与下标
		if (month == 2 && (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
		{
			//是闰年2月返回29天
			return 29;
		}
		else
		{
			return monthDayArray[month];
		}
	}

	bool operator<(const Date& d);
	bool operator<=(const Date& d);
	bool operator>(const Date& d);
	bool operator>=(const Date& d);
	bool operator==(const Date& d);
	bool operator!=(const Date& d);
	// d1 += 天数
	Date& operator+=(int day);
	// d1+ 天数
	Date operator+(int day);
	// d1 -= 天数
	Date& operator-=(int day);
	Date operator-(int day);

	// d1 - d2
	int operator-(const Date& d);
	//++d1 -> d1.operator++()
	Date& operator++();
	// d1++ -> d1.operator++(0)
	// 为了区分,构成重载,给后置++,强行增加一个int形参
	// 这里不需要写形参名,因为接受之是多少不重要,也不需要用
	// 这个参数仅仅是为了跟前置++构成重载区分
	Date operator++(int);
	Date& operator--();
	Date operator--(int);
private:
	int _year;
	int _month;
	int _day;
};

Date.cpp

代码语言:javascript
复制
#define _CRT_SECURE_NO_WARNINGS
#include"Date.h"
//缺省参数声明和定义不能同时给值
Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}
void Date::Print()
{
	cout << _year << "-" << _month << "-" << _day << endl;
}
// d1 += 天数
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;
}

test.cpp

在这里插入图片描述
在这里插入图片描述

这里d1也改变了,是一个加等的逻辑,下面的逻辑是d1不变,只返回d1+100后的值的逻辑

1.3.2 加

Date.cpp

代码语言:javascript
复制
// d1 + 天数
Date Date::operator+(int day)
{
	//*this是d1,d1不能改变,所以用d1拷贝了tmp,改变tmp,返回tmp即可
	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;
		}
	}
	//d3 + 100 生成一个 “新的日期对象”(100 天后的日期),而 d3 本身保持不变
	//所以使用值返回而不是引用返回
	return tmp;
}
在这里插入图片描述
在这里插入图片描述

注意,这里d3赋值给d4是拷贝构造,用 d3 + 100 的结果创建并初始化 d4 但是当前的函数依旧实现的不好,他们逻辑是类似的,下面将他们复用了

在这里插入图片描述
在这里插入图片描述

在 C++ 中,tmp += day; 能够间接调用 operator+=,是因为 += 是可以被重载的运算符,并且对于用户自定义的类(如这里的 Date 类),当我们为该类重载了 operator+= 运算符后,就可以像使用内置类型(如 int、double 等)的 += 操作一样,对该类的对象使用 += 操作,此时就会调用我们重载的 operator+= 函数。所以当执行 tmp += day; 时,编译器会识别到这是对 Date 对象的 += 操作,进而调用我们重载的 operator+= 函数,实现对 tmp 这个 Date 对象增加 day 天的功能

在这里插入图片描述
在这里插入图片描述

效果是一样的

反向也可以复用,+由自己实现,+=复用+

在这里插入图片描述
在这里插入图片描述

*this + day 会调用 operator+,返回一个临时的 Date 对象(tmp的拷贝),*this = … 是将这个临时对象赋值给当前对象(*this),编译器会调用 Date 类的默认赋值运算符重载(operator=)

在这里插入图片描述
在这里插入图片描述

这两种复用方式前者更好一些,比如说有个d1对象,调用+100,有个d2对象,调用一下+=100,分别调用两种复用

d2调用+=100,+=内部无拷贝,d2传给this,一系列操作后传引用返回

在这里插入图片描述
在这里插入图片描述

+内部有拷贝,拷贝构造就是一次,传值返回是一次拷贝,复用+=无拷贝,第一种复用一共两种拷贝

第二种复用,+有一次拷贝构造,传值返回有一次拷贝。+=中复用+有两次拷贝,*this = *this + day;再调一次赋值也是一次拷贝,第二种复用方式有5次拷贝

所以让拷贝多的去复用拷贝少的最好

1.3.2.1 前置++与后置++

这里再说一下前置++和后置++,二者返回值不同,后置是++之前的,前置是++之后的。由于无论是哪种++都是单操作数的,单操作数并没有规定操作数是在左边还是右边,只传给一个参数,不像两个参数有顺序,二者的函数名都是operator++,都是一个参数。二者最大的问题是如何区分

两个函数名相同的函数想要同时存在必须构成重载,构成函数重载就要求函数名相同,参数不同,由于前置++用的相对多一些(前置无拷贝,效率高),所以编译器做了特殊处理,不接收返回值的情况下,前置++效率高一些, 所以默认情况下前置++不加参数,(后置++还有两个拷贝)后置++为了与之区分加了一个额外的参数,这个参数无实际意义,传什么都行()所以也不用写形参名字,只是为了与前置++构成重载

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

虽然这里写了赋值符号,但依旧是拷贝构造

1.3.3 减等

前面可以看出+=之中因为没有拷贝构造效率更高,所以复合的时候也是+里复合使用+=,接下来继续实现-和-=,依旧在-中复合使用-=,这里先减掉50天,+是进位,-就是借位,下面的借位是问8月借,9月的8天已经减完了,8月是31天,然后以此类推,借到合法为止

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

接下来写-

1.3.4 减
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

上面程序还有一个小bug,参数int可以传正也可以传负,在内部还要进行一个简单的处理,+和-复用的是+=和-=

1.3.5 优化bug
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
1.3.6 前置与后置减减

无论是前置还是后置,编译器编译之后都会转化为d1.operator–();

在这里插入图片描述
在这里插入图片描述

但是二者返回值是不一样的,减减d1返回的是减减之后的值,d1减减返回的是减减之前的值

在这里插入图片描述
在这里插入图片描述

调用的时候区分方式和前置与后置++区分方式一样,这增加的一个int类型仅仅是为了构成重载

在这里插入图片描述
在这里插入图片描述

对于类优先建议调用前置,前置效率更高,后置会多两次拷贝

在这里插入图片描述
在这里插入图片描述

显式调用

在这里插入图片描述
在这里插入图片描述
1.3.7 日期比较大小
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里的书写顺序就建议

  1. 等于
  2. 小于
  3. 小于等于(复用小于和等于)
  4. 大于(小于等于取反)
  5. 大于等于(小于取反)
  6. 不等于(等于取反)

这样的写法有两种好处,减少了代码量,更加美观。在日后如果要增加功能的时候,例如说加时分秒,只用要在最初的等于和小于把时分秒的逻辑放进来,其他的代码都是前两个函数的复用,不用改,达到了降本增效的目的。

这也是日常处理业务的一个小技巧,尽可能代码复用性更高,就可以大大的增加可维护性

1.3.8 日期减日期

这里提供两种思路

第一种思路,让两个日期都去计算离当年的一月一日差多少天

在这里插入图片描述
在这里插入图片描述

算出两个天数x和y之后,再让2025 1 1与2020 1 1相减,得出他们之间差了5年(z),再加上2020年到2025年中间经过了几个闰年,闰年的天数是366,例如这里2020和2024年是闰年,再加上(x-y) 例如:5年×365+2+(x-y)

还有一个更简单的思路,这个相对于就比较暴力了,两个日期相减必定有一个日期大,另一个日期小,可以先比较一下,算出小的那个日期,然后让小的日期循环向大的日期++,每次加一天,直到和大的日期相等,加了多少次,就相差多少天

如果两个日期差的很多,比如差了几百年的情况,第一种思路效率更高,因为年的跨度一把就算出来了,第二种复用度更高 我这里只展示第二种思路,这其中也有很多小技巧

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里flag是一个纠正的作用,如果第一个日期大,第二个日期小,那么加出来的结果就是正的,加的天数×1就没问题。但是如果第一个小,第二个大,就要×负一变为负的天数

1.3.9 流插入(输出)(友元的初次使用)

补充一下,对于这种类定义的自定义类型想要用运算符是需要自己重载的,而且日期类默认是不能进行流插入流提取的输入输出的

在这里插入图片描述
在这里插入图片描述

因为这个日期类是自己定义的,以什么样的格式输出编译器是不知道的,所以这里想输出就要重载运算符<<(流插入)

这里重载为成员函数的对象一个为日期类对象d1,还有一个对象是cout,cout是一个ostream类型的对象,ostream是库里面实现的一个类

点击ostream

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

内置类型(int,char…)可以直接使用的原因是库里面已经重载了它们对应的实现,ostream特殊一点,它的输出输入复杂一些,不能转换为对应的指令,没有对应的指令,所以会转换为调用对应的函数,转换为调用库里面的operator<<,cout能自动识别类型源于这样的函数重载

代码语言:javascript
复制
	int i = 1;
	double d = 1.1;
	//函数重载
	cout << i;//这里能自动识别类型相当于转换为cout.operator<<(i)这样的函数调用
	cout << d;//这里能自动识别类型相当于转换为cout.operator<<(d)这样的函数调用
	return 0;

下面就重载一下对应的运算符,使得自定义类型能够支持这样输出的流插入 一个是日期类对象,一个是ostream类型的对象,这里先直接写到Date.h类里面

在这里插入图片描述
在这里插入图片描述

cout传给了ostream,out是cout的别名,日期类传给了隐含的this

而且这样的多个函数调用实际效率并不会低,如果是个小函数,高频调用的东西,C++会将其搞成内联,内联会将调用的地方展开,就没有函数调用的开销了,当然能否真的称为内联由编译器决定

另一方面现在的计算机运行计算速度已经很快了,这种多个函数调用也不会有太大的性能影响

在这里插入图片描述
在这里插入图片描述

但是这样这样输出的写法就看的很奇怪

代码语言:javascript
复制
cou << i

流插入输出的运算符表达的意思是一个对象或变量流向控制台去,图片中的写法就变为流对象流向到日期类对象里面去

虽然代码可以运行,但是不符合可读性,这里就想办法让cout占据第一个参数,一个方法就是像库里面一样重载,库里面是写为ostream这个类的成员函数,如果能向ostream类里面增加一个成员函数,这个函数的第一个形参是ostream的this指针,就可以让cout占据第一个参数,但是库是无法随便更改的

在这里插入图片描述
在这里插入图片描述

另一个方法就是重载运算符的这个函数不把它放在类中,这样其就不是一个成员函数,就没有隐含的this指针了,将其放为成员函数的核心原因还是因为想访问私有的成员变量

为了可读性,流插入输出运算符的重载都是写为全局的函数 接下来为了访问私有成员有几种解决方案,第一种就是让日期类提供一个成员函数间接访问(GetYear,GetMonth,GetDay),还有一种方式更简单,就是友元,就在类中的函数前新加一个关键字friend我在下一篇文章详细去写

Date.h

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Date.cpp

在这里插入图片描述
在这里插入图片描述

想让类外面的一个函数,用日期类的对象访问私有成员变量,就将这个函数定义为该类的友元,友元声明放到哪里都可以(公有,保护),友元函数并不是这个类正真的成员,不存在私有或要用访问限定符的概念,它只是一个声明,并不是类里定义的东西,但一般放在开始居多。 可以理解为告诉这个类,我是你的朋友,我可以访问你的私有

在这里插入图片描述
在这里插入图片描述

但是当前的代码是不支持连续的输出的

在这里插入图片描述
在这里插入图片描述

连续输出是从左到右,cout<<d1就是一个函数调用,调用流插入函数,所以需要一个返回值作为下一次输出调用的左操作数,返回值就需要一个cout

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

前面在.h文件里的两个声明也要加

1.3.10 流提取(输入)

也是同样的道理,cin是istream类型的对象

在这里插入图片描述
在这里插入图片描述

内置类型实现流提取也已经实现好了

在这里插入图片描述
在这里插入图片描述

用的时候就调用库里面对应的函数来达到一个自动识别的效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

具体的流插入流提取的内部的细节是根据自己的需求来写的,这里日期类就单纯的输入输出比较简单

这里说一下为什么 ostream 的 operator<<第二个参数是 const,而 istream 的 operator>> 第二个参数没有 const

  1. operator<<的作用是把Date对象的内容 “输出显示”(比如打印到控制台),这个过程不需要修改Date对象本身。 为了保证对象在输出时不会被意外修改,所以第二个参数用const Date&(const 引用),强制约束 “只能读、不能写”。
  2. operator>>的作用是把外部输入的数据(比如从键盘输入)存入Date对象(比如修改d._year、d._month、d._day的值)。 这个过程必须修改Date对象的成员,因此第二个参数需要用非 const 的引用(Date&),这样才能允许对Date对象的成员进行赋值修改。
1.3.11 日期类的检查

可以看出当前的程序还有一些缺陷,如果输入了一些非法的日期,也是能正常输出的和拿去运算的

在这里插入图片描述
在这里插入图片描述

再写一个日期类的检查

也可以在输入的地方进行一个更严格的检查,直接不让输入非法日期

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

二、取地址运算符重载

代码语言:javascript
复制
	Date d1(2025, 9, 8);
	d1.Print();

之前这样是能正常打印的,但是如果在Date前加一个const就不行了

在这里插入图片描述
在这里插入图片描述

这里就涉及一个权限的放大,对象调用成员函数时,会把对象的地址传递给隐含的this指针,this指针默认的类型是Date* ,但是这里对d1取地址类型是const Date* ,指向的内容不能修改的指针,很多情况下编译器的报错时的描述是不太准确的

虽然this指针有一个const,但是它的const在星的右边(Date* const this),星的右边是修饰this指针本身不能被改变,成员函数里面不能去修改this指针。这里要让Print函数的this指针也变为const Date* ,

这里的难点就是this指针是隐含的形参,他是编译器自动生成的形参,基于这样的问题就有了接下来的const成员函数

2.1 const成员函数

  • 将const修饰的成员函数称为const成员函数,const修饰成员函数放到成员函数参数列表的后面
  • const实际修饰该成员函数隐含的this指针,表明在该成员函数中不能对类的任何成员进行修改。const修饰Date类的Print成员函数,Print隐含的this指针由Date* const this变为const Date* const this
在这里插入图片描述
在这里插入图片描述

注意,声明和定义分离的话两边都要加

当在函数的参数列表右边加const,就修饰的是this指针指向的内容了,对象d1被const修饰,自己不能改变,传递给this指针,这个this指针无论是本身还是其指向的内容都不能被改变

在这里插入图片描述
在这里插入图片描述

用const对象调用Print之后,后面用普通对象也可以调用Print了,这就是权限不能放大,但是权限可以平移和缩小

在这里插入图片描述
在这里插入图片描述

要修改成员变量的成员函数不能加const,但是不修改成员变量的成员函数都应该加上const

虽然函数在初始化和析构的时候调用构造函数的时候传了一个this指针过去,但构造和析构函数肯定是没有const的,要特殊说明一下,所有const修饰的对象在初始化和析构的时候都做了一个特殊处理,在初始化和析构时没有const属性的,否则const对象无法初始化和析构了

代码语言:javascript
复制
const int i = 0;

上面代码如果在初始化的时候就有const属性,就无法初始化了,在初始化之后就有const属性了

而且不修改成员变量的函数加上const之后,普通对象和const对象都可以调用这个成员函数

2.2 取地址运算符重载

取地址运算符重载分为普通取地址运算符重载const取地址运算符重载,一般这两个函数编译器自动生成的就够用了,不需要去显式实现,除非一些很特殊的场景,比如我不想让别人取到当前类对象的地址,就可以自己实现一份,胡乱返回一个地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这里之所以还有一个普通取地址运算符重载的原因是,仅管无论普通对象还是const都可以调用取地址运算符函数,但是使用取地址运算符的时候,如果是个const对象,其返回值是const Date* ,但是普通对象返回值应该是Date* ,二者同时写返回值更加匹配,而且构成函数重载

三、日期类完整源码

Date.h

代码语言:javascript
复制
#pragma once
#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);
	void Print() const;
	int GetMonthDay(int year, int month) const
	{
		static int monthDayArray[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 monthDayArray[month];
		}
	}

	bool CheckDate() const
	{
		if (_month < 1 || _month>12)
			return false;
		if (_day<1 || _day>GetMonthDay(_year, _month))
			return false;
		return true;
	}

	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 += 天数
	Date& operator+=(int day);
	//d1 + 天数
	Date operator+(int day) const;
	//d1 -= 天数
	Date& operator-=(int day);
	//d1 - 天数
	Date operator-(int day) const;
	//++d1 -> d1.operator++()
	Date& operator++();
	//d1++ -> d1.operator++(0)
	//为了区分,构成重载,给后置++,强行增加一个int形参
	//这里不需要写形参名,因为接受值是多少不重要,也不需要用
	//这个参数仅仅是为了根前置++构成重载区分
	Date operator++(int);//(int i)
	//--d1 -> d1.operator--()
	Date& operator--();
	//d1-- -> d1.operator--(0)
	Date operator--(int);
	// d1 - d2
	int operator-(const Date& d) const;
	//d1.operator<<(cout);
	//void operator<<(ostream& out)
	//{
	//	//后面都属于内置类型
	//	out << _year << "/" << _month << "/" << _day << '\n';
	//	//out << _year内部的调用是有返回值的,也是一个ostream类型的对象,也就是把cout返回了,这里是把cout的别名返回了
	//	//所以这里可以理解为多个函数调用,和连续赋值一样的道理,只是这里是从左到右
	//	//out << _year又作为out << _year << "/"的左操作数去调用
	//	//out << _year << "/" 作为out << _year << "/" << _month的左操作数再去调用
	//	//这里就是多个流插入调用
	//}
	Date* operator&()
	{
		return this;
		//return nullptr;
	}
	const Date* operator&()const
	{
		return this;
		//return nullptr;
	}
private:
	int _year;
	int _month;
	int _day;
};

ostream& operator<<(ostream& out, const Date& d);
istream& operator>>(istream& in, Date& d);

Date.cpp

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

Date::Date(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
	if (!CheckDate())
	{
		cout << "非法日期:>" << *this;
	}
}

void Date::Print() const
{
	cout << _year << "-" << _month << "-" << _day << endl;
}

Date& Date::operator+=(int day)
{
	if (day < 0)
	{
		// d1 += -100,直接转换成去调用减等(d1 -= 100)
		return *this -= -day;
	}
	_day += day;
	while (_day > GetMonthDay(_year, _month))
	{
		_day -= GetMonthDay(_year, _month);
		++_month;
		if (_month == 13)
		{
			++_year;
			_month = 1;
		}
	}
	return *this;
}

//// d1 + 天数
//Date Date::operator+(int day)
//{
//	//*this是d1,d1不能改变,所以用d1拷贝了tmp,改变tmp,返回tmp即可
//	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;
//		}
//	}
//	//d3 + 100 生成一个 “新的日期对象”(100 天后的日期),而 d3 本身保持不变
//	//所以使用值返回而不是引用返回
//	return tmp;
//}

//d1 + 天数
Date Date::operator+(int day) const
{
	Date tmp(*this);
	tmp += day;
	return tmp;
}
// d1 -= 100
Date& Date::operator-=(int day)
{
	if (day < 0)
	{
		// d1 -= -100
		return *this += -day;
	}
	_day -= day;
	//>0才合法
	while (_day <= 0)
	{
		--_month;
		//借上个月的,但是要防止1月减减变0月
		if (_month == 0)
		{
			--_year;
			_month = 12;
		}
		_day += GetMonthDay(_year, _month);
	}
	return *this;
}

// d1 - 100
Date Date::operator-(int day) const
{
	Date tmp = *this; //拷贝构造
	tmp -= day;
	return tmp; //局部对象
}

//++d1 -> 编译器转换为调用d1.operator++();
Date& Date::operator++()
{
	*this += 1;//调用+=赋值运算符重载
	return *this;//出了作用域d1还在,用引用返回
}
//d1++ -> 编译器转换为调用d1.operator++(0);括号内数字任意
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;//拷贝
}

//日期比较小于
bool Date::operator<(const Date& d) const
{
	if (_year < d._year)
	{
		return true;
	}
	else if (_year == d._year)
	{
		if (_month < d._month)
		{
			return true;
		}
		else if (_month == d._month)
			return _day < d._day;
	}
	//为真的情况都走完了,剩下的就是false
	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 - d2
int Date::operator-(const Date& d) const
{
	//假设第一个日期大
	Date max = *this;
	Date min = d;
	int flag = 1;
	if (*this < d)
	{
		min = *this;
		max = d;
		flag = -1;
	}
	int day = 0;
	//还可以用小于,不等于的比较逻辑更简单一点
	while (min != max)
	{
		//尽可能用前置++,前置无拷贝
		++min;
		++day;
	}
	return day * flag;
}

ostream& operator<<(ostream& out, const Date& d) 
{
	out << d._year << "/" << d._month << "/" << d._day << '\n';
	//出了作用域out还在,用引用返回
	return out;
}
istream& operator>>(istream& in, Date& d)
{
	while (1)
	{
		cout << "请依次输入年月日:>";
		in >> d._year >> d._month >> d._day;
		if (d.CheckDate())
		{
			break;
		}
		else {
			cout << "输入日期非法,请重新输入" << endl;
		}
	}
	return in;
}

Test.cpp

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

//class Stack
//{
//public:
//	Stack(int n)
//	{}
//};
//
//class Myqueue
//{
//public:
//	// 初始化列表,后面讲
//	Myqueue()
//		:_pushst(4)
//		,_popst(4)
//	{}
//private:
//	Stack _pushst;
//	Stack _popst;
//};
//
//int main()
//{
//	Myqueue q;
//
//	return 0;
//}

//class Date
//{
//public:
//	Date(int year = 1, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	// Date d4(d3);
//	//Date(const Date& d)
//	//{
//	//	_year = d._year;
//	//	_month = d._month;
//	//	_day = d._day;
//	//}
//
//	void Print()
//	{
//		cout << _year << "-" << _month << "-" << _day << endl;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
//// 自定义类型,传值传参要调用拷贝构造
//// void Func(const Date& d)
//void Func(Date d)
//{}
//
//typedef int STDataType;
//class Stack
//{
//public:
//	Stack(int n = 4)
//	{
//		_a = (STDataType*)malloc(sizeof(STDataType) * n);
//		if (nullptr == _a)
//		{
//			perror("malloc申请空间失败");
//			return;
//		}
//		_capacity = n;
//		_top = 0;
//	}
//
//	// Stack st2(st1);
//	/*Stack(const Stack& s)
//	{
//		_a = s._a;
//		_capacity = s._capacity;
//		_top = s._top;
//	}*/
//
//	Stack(const Stack& s)
//	{
//		_a = (STDataType*)malloc(sizeof(STDataType) * s._capacity);
//		if (_a == NULL)
//		{
//			perror("realloc fail");
//			return;
//		}
//
//		memcpy(_a, s._a, s._top * sizeof(STDataType));
//
//		_capacity = s._capacity;
//		_top = s._top;
//	}
//
//	void Push(STDataType x)
//	{
//		if (_top == _capacity)
//		{
//			int newcapacity = _capacity * 2;
//			STDataType* tmp = (STDataType*)realloc(_a, newcapacity *
//				sizeof(STDataType));
//			if (tmp == NULL)
//			{
//				perror("realloc fail");
//				return;
//			}
//			_a = tmp;
//			_capacity = newcapacity;
//		}
//		_a[_top++] = x;
//	}
//
//	~Stack()
//	{
//		cout << "~Stack()" << endl;
//		free(_a);
//		_a = nullptr;
//		_top = _capacity = 0;
//	}
//private:
//	STDataType* _a;
//	size_t _capacity;
//	size_t _top;
//};
//
//class MyQueue
//{
//private:
//	Stack _pushst;
//	Stack _popst;
//};

//int main()
//{
//	Date d1(2025, 8, 1);
//	// 拷贝构造
//	Date d2(d1);
//
//	const Date d3(2025, 8, 1);
//	Date d4(d3);
//
//	Func(d1);
//
//	////////////////////////////////////
//	Stack st1;
//	st1.Push(1);
//	st1.Push(2);
//	st1.Push(3);
//
//	Stack st2(st1);
//
//	MyQueue q1;
//	MyQueue q2(q1);
//
//	return 0;
//}

//int& func2()
//{
//	int x = 1;
//	return x;
//}
//
//Stack& Func3()
//{
//	Stack st;
//	return st;
//}
//
//// 10:30
//int main()
//{
//	int ret1 = func2();
//	cout << ret1 << endl;
//
//	Stack ret2 = Func3();
//	// Stack ret2(Func3());
//
//	Stack st3;
//	// 以下都是调用拷贝构造
//	Stack st4(st3);
//	Stack st5 = st3;
//
//	return 0;
//}

//////////////////////////////////////////////////////////////////////////////////
// 运算符重载

//class Date
//{
//public:
//	Date(int year = 1, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	void Print()
//	{
//		cout << _year << "-" << _month << "-" << _day << endl;
//	}
//
//	int GetYear()
//	{
//		return _year;
//	}
//
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
//bool operator==(const Date& x1, const Date& x2)
//{
//	return x1._year == x2._year
//		&& x1._month == x2._month
//		&& x1._day == x2._day;
//}
//
//int operator-(const Date& x1, const Date& x2)
//{
//	return 0;
//}
//
//int main()
//{
//	int i = 0, j = 1;
//	Date d1(2025, 8, 1);
//	Date d2(2025, 10, 1);
//	bool ret = i == j;
//	cout << (d1 == d2) << endl;
//	operator==(d1, d2);
//
//	d1 - d2;
//	operator-(d1, d2);
//
//	return 0;
//}

//class Date
//{
//public:
//	Date(int year = 1, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	void Print()
//	{
//		cout << _year << "-" << _month << "-" << _day << endl;
//	}
//
//	bool operator==(const Date& d)
//	{
//		return _year == d._year
//			&& _month == d._month
//			&& _day == d._day;
//	}
//
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
//int main()
//{
//
//	Date d1(2025, 8, 1);
//	Date d2(2025, 10, 1);
//
//	cout << (d1 == d2) << endl;
//	d1.operator==(d2);
//
//	return 0;
//}

//void func1()
//{
//	cout << "void func()" << endl;
//}
//
//class A
//{
//public:
//	void func2()
//	{
//		cout << "A::func()" << endl;
//	}
//};

//int main()
//{
//	// 普通函数指针
//	void(*pf1)() = func1;
//	(*pf1)();
//
//	// A类型成员函数的指针
//	void(A::*pf2)() = &A::func2;
//	A aa;
//	(aa.*pf2)();
//
//	return 0;
//}

// Date operator+(const Date& d, int x);
//int main()
//{
//
//
//	return 0;
//}

//////////////////////////////////////////////////////////////////////
// 赋值运算符重载
//class Date
//{
//public:
//	Date(int year = 1, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	// Date d4(d3);
//	Date(const Date& d)
//	{
//		_year = d._year;
//		_month = d._month;
//		_day = d._day;
//	}
//
//	// 11:40
//	// d1 = d3 = d5
//	// d1 = d1
//	/*Date& operator=(const Date& d)
//	{
//		if (this != &d)
//		{
//			_year = d._year;
//			_month = d._month;
//			_day = d._day;
//		}
//
//		return *this;
//	}*/
//
//	void Print()
//	{
//		cout << _year << "-" << _month << "-" << _day << endl;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
//int main()
//{
//	Date d1(2025, 8, 1);
//	Date d2(d1);
//	// 一定注意,这个是拷贝构造
//	Date d4 = d1;
//
//	Date d3(2025, 10, 1);
//	d1 = d3;
//	Date d5(2025, 9, 1);
//
//	d1 = d3 = d5;
//
//	d1 = d1;
//
//	return 0;
//}

///////////////////////////////////////////////////////
//#include"Date.h"
//
//int main()
//{
//	/*Date d1(2025, 8, 1);
//	Date d2 = d1 += 100;
//	d1.Print();
//	d2.Print();
//
//	Date d3(2025, 8, 1);
//	Date d4 = d3 + 100;
//	d3.Print();
//	d4.Print();*/
//
//	Date d1(2025, 8, 1);
//	Date ret1 = d1++;
//	// Date ret1 = d1.operator++(10); // 显示调用,实参只要是整形就可以
//
//	ret1.Print();
//	d1.Print();
//
//	Date d2(2025, 8, 1);
//	Date ret2 = ++d2;
//	//Date ret2 = d2.operator++();
//	ret2.Print();
//	d2.Print();
//
//	return 0;
//}

void TestDate4()
{
	const Date d1(2025, 9, 8);
	d1.Print();
	Date d3 = d1 + 100;
	
	Date d2(2025, 9, 8);
	d2.Print();
	d2 += 10;

	bool ret = d1 > d2;

	Date* p1 = &d2;
	const Date* p2 = &d1;
	cout << p1 << " " << p2 << endl;
}

int main()
{
	TestDate4();
	return 0;
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-10-15,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、赋值运算符重载
    • 1.1 运算符重载
    • 1.2 赋值运算符重载
    • 1.3 日期类实现
      • 1.3.1 加等
      • 1.3.2 加
      • 1.3.3 减等
      • 1.3.4 减
      • 1.3.5 优化bug
      • 1.3.6 前置与后置减减
      • 1.3.7 日期比较大小
      • 1.3.8 日期减日期
      • 1.3.9 流插入(输出)(友元的初次使用)
      • 1.3.10 流提取(输入)
      • 1.3.11 日期类的检查
  • 二、取地址运算符重载
    • 2.1 const成员函数
    • 2.2 取地址运算符重载
  • 三、日期类完整源码
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档