运算符重载
- 1.当运算符被用于类类型的对象时,C++语言允许我们通过运算符重载的形式指定新的含义。C++规定类类型对象使用运算符时,必须转换成调用对应运算符重载,若没有对应的运算符重载,则会编译报错。
-
- 运算符重载是具有特殊名字的函数,他的名字是由operator和后面要定义的运算符共同构成。和其他函数一样,它也具有其返回类型和参数列表以及函数体。
-
- 重载运算符函数的参数个数和该运算符作用的运算对象数量一样多。一元运算符有一个参数,二元运算符有两个参数,二元运算符的左侧运算对象传给第一个参数,右侧运算对象传给第二个参数。
- 4.如果一个重载运算符函数是成员函数,则它的第一个运算对象默认传给隐式的this指针,因此运算符重载作为成员函数时,参数比运算对象少一个。
- 5.运算符重载以后,其优先级和结合性与对应的内置类型运算符保持一致。
- 6.不能通过连接语法中没有的符号来创建新的操作符:比如operator@。7.
-
- .* :: sizeof ?: . 注意以上5个运算符不能重载。(选择题里面常考,大家要记一下)
- 8.重载操作符至少有一个类类型参数,不能通过运算符重载改变内置类型对象的含义,如:int operator+(int x, int y)
-
- 一个类需要重载哪些运算符,是看哪些运算符重载后有意义,比如Date类重载operator-就有意义,但是重载operator*就没有意义。
- 10.重载++运算符时,有前置++和后置++,运算符重载函数名都是operator++,无法很好的区分。C++规定,后置++重载时,增加一个int形参,跟前置++构成函数重载,方便区分。
- 11.重载<<和>>时,需要重载为全局函数,因为重载为成员函数,this指针默认抢占了第一个形参位置,第一个形参位置是左侧运算对象,调用时就变成了对象<<cout,不符合使用习惯和可读性。重载为全局函数把ostream/istream放到第一个形参位置就可以了,第二个形参位置当类类型对象。
如代码,自定义类型进行使用"=="操作符会产生报错:

由于自定义类型比较复杂,所以默认情况下自定义类型是无法进行运算的,但是呢,有的自定义类型进行相关运算却是有意义的,比如我们经常遇到的日期类,两个日期相减得到的就是两个日期之间相隔的天数,日期加上天数就能得到另一个日期,学习运算符重载,我们可以让自定义类型进行运算,这样会更加方便。
话不多说,我们直接来看代码吧:
先写好日期类的代码,这是我们这一小节经常需要使用的类:
cpp
//日期类
#include<iostream>
using namespace std;
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;
}
//在写运算符重载的算法中,我们不可避免的会用到类中的成员变量,所以需要将成员变量改为公有的,但其实我们一般不推荐这么做,在以后我们学习了友元函数就有更好地解决办法
//private:
int _year;
int _month;
int _day;
};
现在,我们要对"=="运算符进行重载,也就是比较两个日期是否相同,按照运算符重载的特点:
//重载运算符名字:由operator和后面要定义的运算符共同构成
//具有返回值,参数,函数体
bool operator==(Date x1 ,Date x2)
{
//将自定义类型的比较转化为成员变量中内置类型的比较
return ((x1._year == x2._year) && (x1._month == x2._month) && (x1._day == x2._day));
}
写完这个代码后,再看是否还会有语法错误:

可以看到,当再次使用这个运算符时,就不会产生报错了。
另外,我们可以想一下上面那个运算符重载的代码还有啥可以改进的地方。我们在前面一节介绍过了,自定义类型的传值传参会调用拷贝构造,但是传引用传参就不会调用拷贝构造,所以在C++中,为了提高程序的性能,我们要习惯去使用引用传参:
//引用传参不需要调用拷贝构造
bool operator==(const Date& x1, const Date& x2)
{
return ((x1._year == x2._year) && (x1._month == x2._month) && (x1._day == x2._day));
}
那我们应当如何使用重载后的运算符呢?
方法一:函数调用的形式
bool ret= operator==( d1 , d2 );
方法二:就像内置类型一样直接使用运算符,一般情况下我们推荐这种写法
d1==d2;
现在我们就来运用一下这个运算符:
cpp
#include<iostream>
using namespace std;
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 _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 main()
{
Date d1(2025, 8, 3);
Date d2(2025, 8, 8);
if (d1 == d2)
{
cout << "两个日期相同" << endl;
}
else
{
cout << "两个日期不同" << endl;
}
return 0;
}
另外,为了在运算符重载中能够使用类里面的成员变量,我们将成员变量改为了公有的,但其实这种方式不太好,一种解决方法就是将运算符重载函数写到类里面去,我们来试一下:
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
bool operator==(const Date& x1, const Date& x2)
{
return ((x1._year == x2._year) && (x1._month == x2._month) && (x1._day == x2._day));
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
//引用传参不需要调用拷贝构造
int main()
{
Date d1(2025, 8, 3);
Date d2(2025, 8, 8);
return 0;
}
但是编译器会报错:
为啥就只是把运算符重载函数改到类里面就报错了呢?
这就是this指针在装神弄鬼了。表面上我们把函数写到类里面去是传递了两个参数,其实第一个参数的位置还有一个隐含的this指针。所以其实你是传递了3个参数的,但"=="只能接受2个操作数,所以会报错。
还需注意的是,在 C++ 中,当运算符重载函数 作为成员函数 定义在类内部时,this
指针指向的是运算符左侧的操作数对象的地址。这是运算符重载的核心规则之一。(这也就是第4点的意思)
所以,我们在类里面规定运算符重载时,我们自己写的参数个数应该比运算符实际能接受的操作数个数少一,比如,上面的代码应该改成:
cpp
#include<iostream>
using namespace std;
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//编译器会把它处理为:operator(Date*this,const Date& x2)
bool operator==(const Date& x2)
{
return ((_year == x2._year) && (_month == _month) && (_day == x2._day));
}
void Print()
{
cout << _year << '/' << _month << '/' << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
//引用传参不需要调用拷贝构造
int main()
{
Date d1(2025, 8, 3);
Date d2(2025, 8, 8);
d1 == d2;
//等价于:d1.operator==(d2);
if (d1 == d2)
{
cout << "两个日期相同" << endl;
}
else
{
cout << "两个日期不同" << endl;
}
return 0;
}
.* :: sizeof ?: . 5个运算符不能重载,我们现在来简单讲一下.*运算符的用法(这个运算符的挺少用的,简单看一下就好),见以下代码和注释:
cpp
//成员函数指针的创建与访问:
#include<iostream>
using namespace std;
void func1()
{
cout << "void func1()" << endl;
}
class A
{
public:
void func2()
{
cout << "void func2()" << endl;
}
};
int main()
{
//普通函数指针的创建:
void (*pf1)() = func1;
//利用函数指针来调用函数:
(*pf1)();
//类的成员函数指针的创建:
void(A:: * pf2)() =& A::func2;
//为啥类的成员函数指针要这样写:
//在 C++ 中,类成员函数指针的声明和赋值需要特殊语法,这是由成员函数的本质特性决定的
//成员函数(非静态)与普通函数不同,它隐含一个 this 指针参数(用于访问对象实例的数据)
// 因此,成员函数指针的语法需要体现:所属类(A::),调用时的对象绑定(通过 .* 或 ->* 运算符)
//成员函数指针的调用
//想一下,利用成员函数的指针调用成员函数可以这么调用嘛:(*pf2)();
//不可以的:因为成员函数中是有隐含的this指针的,this指针接收的是调用函数的对象的地址,所以在调用成员函数
//时,还需要指定对象
A aa;
//利用成员函数指针调用函数:
(aa.*pf2)();//这就是.*运算符的用途
return 0;
}
再来讲解一下特点8是啥意思:
重载操作符至少有一个类类型参数 :意思是当你重载一个运算符(如 +
, ==
, <<
等)时,至少有一个参数必须是自定义的类(class)或结构体(struct)类型 ,而不能全部是基本类型(如 int
, double
, char
等)。
这是因为:
-
C++ 不允许你修改基本类型(如
int
,float
等)的运算符行为,否则会导致代码混乱。 -
运算符重载的目的是为了让自定义类型(如
Date
,String
,Vector
等)也能像内置类型一样使用运算符。
不能通过运算符重载改变内置类型对象的含义: 意思是 你不能改变基本类型(如 int
, float
, char
等)的运算符的原始行为。
这是因为:
-
如果允许修改基本类型的运算符行为,比如让
1 + 1
返回3
,那代码会变得极其混乱,无法维护。 -
C++ 只允许你为自定义类型(如
Date
,String
)定义新的运算符行为,而不能篡改内置类型的运算规则。
赋值运算符重载
赋值运算符重载是一个默认成员函数,用于完成两个已经存在的对象直接的拷贝赋值,这里要注意跟拷贝构造区分,拷贝构造用于一个对象拷贝初始化给另一个要创建的对象。
赋值运算符重载的特点 :
-
赋值运算符重载是一个运算符重载,规定必须重载为成员函数。赋值运算重载的参数建议写成const 当前类类型引用,否则会传值传参会有拷贝
-
有返回值,且建议写成当前类类型引用,引用返回可以提高效率,有返回值目的是为了支持连续赋值场景。
-
没有显式实现时,编译器会自动生成一个默认赋值运算符重载,默认赋值运算符重载行为跟默认拷贝构造函数类似,对内置类型成员变量会完成值拷贝/浅拷贝(一个字节一个字节的拷贝),对自定义类型成员变量会调用他的赋值重载函数。
-
像Date这样的类成员变量全是内置类型且没有指向什么资源,编译器自动生成的赋值运算符重载就可以完成需要的拷贝,所以不需要我们显示实现赋值运算符重载。像Stack这样的类,虽然也都是内置类型,但是_a指向了资源,编译器自动生成的赋值运算符重载完成的值拷贝/浅拷贝不符合我们的需求,所以需要我们自己实现深拷贝(对指向的资源也进行拷贝)。像MyQueue这样的类型内部主要是自定义类型Stack成员,编译器自动生成的赋值运算符重载会调用Stack的赋值运算符重载,也不需要我们显示实现MyQueue的赋值运算符重载。这里还有一个小技巧,如果一个类显示实现了析构并释放资源,那么他就需要显示写赋值运算符重载,否则就不需要。
写代码来理解这些特点:
cpp
#include<iostream>
using namespace std;
class Date
{
public:
//构造函数
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
//拷贝构造:
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
}
//赋值运算符重载:如果是传值传参,在调用赋值运算符重载时还会调用拷贝构造
//但这里是传引用传参,不会调用拷贝构造
void operator=(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;
};
//引用传参不需要调用拷贝构造
int main()
{
Date d1(2025, 8, 3);
//拷贝构造
Date d2(d1);
//赋值构造
Date d3(2025, 7, 8);
d3 = d1;
d1.Print();
d2.Print();
d3.Print();
return 0;
}
在调试过程中,确实调用了赋值运算符重载:

还需要区分一个点,如果在main函数中有这样一行代码:
Date d4 = d1;
请问这个代码是会调用拷贝构造还是赋值运算符重载?
答案是赋值运算符重载,可以看到d4这个对象是在创建的时候同时让对象d1对其进行初始化,这就是拷贝构造的另一种写法,可能容易与拷贝构造混淆,这一点在上一节我们也是讲到过的哦。
看到这里,就有一个注意的地方,我们上面写的赋值运算符重载是有一点小小的错误的哦,特点2已经告诉我们了,赋值运算符重载是有返回类型的哦,这样才能支持连续赋值。
那么我们可以想一下:如果有两个日期类对象d1和d3,我们要执行d1=d3,那么赋值运算符重载的返回值是什么呢?返回的应当是d1中的内容,因为是要把d3赋值给d1
cpp
//执行:d1=d3
//等价于: d1.operator(d3)
//形参部分:operator=(const Date& d)
//实际上,编译器会将代码转化为:operator=(Date* this , const Date& d)
//实参的传参部分:d1.operator(&d1 , d3 )
//函数体内部:
//{
// this-> _year = d._year;
// this-> _month = d._month;
// this->_day = d._day;
//}
//最后返回值返回的应该是d1这个对象,而在参数中,d1这个对象的地址实际上传给了this,所以可以通过*this获得d1
//最终代码:
Date& operator=(const Date& d)
{
_year =d._year;
_month = d._month;
_day = d._day;
return *this;
}
//为什么这里可以用引用返回
//原因一:因为*this并不是局部对象,*this得到的就是d1,他是在main函数中定义的,出了赋值运算符重载的作用域以后*this对应的空间并没有被销毁,所以这里可以传引用返回
//原因二:如果这里是传值返回,就要再调用拷贝构造函数,这样比较麻烦
看到这里,其实代码还可以再修改一下,想一下,假设有一种情况,有的小伙伴执行:d1=d1这种自己给自己赋值的代码(虽然这种代码无意义,但难免会有人真这么做),我们就可以把代码改成这样:
cpp
Date& operator=(const Date& d)
{
//当自己给自己复制时,this表示的是d1的地址,d是d1的别名,&d也就是&d1
//即this==&d1
if(this!=&d)
{
_year =d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
日期类的实现
上面讲了这么多,我们就一起来实践一下,来完成日期类的实现吧!!!
cpp
//Date.h
#pragma once
#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();
//日期的相关比较函数
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);
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);
//得到一月有多少天:
int GetDayOfMonth(int year,int month)
{
int arr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
if (month == 2 && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))
{
arr[month]++;
}
return arr[month];
}
private:
int _year;
int _month;
int _day;
};
//Date.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#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;
}
bool Date::operator==(const Date& d)
{
return ((_year == d._year) && (_month == d._month) && (_day == d._day));
}
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);
}
bool Date::operator< (const Date & d)
{
return (_year < d._year) ||
((_year == d._year) && (_month < d._month)) ||
((_year == d._year) && (_month == d._month) && (_day < d._day));
}
bool Date::operator> (const Date& d)
{
return (*this != d) && (!(*this < d));
}
Date& Date::operator+=(int day)
{
_day += day;
while (_day > GetDayOfMonth(_year, _month))
{
_day -= GetDayOfMonth(_year, _month);
_month++;
if (_month == 13)
{
_year++;
_month = 1;
}
}
return *this;
}
//不改变*this
Date Date::operator+(int day)
{
Date d1 = *this;
d1 += day;
return d1;
}
//
//// d1 -= 天数
Date& Date:: operator-=(int day)
{
if (_day > day)
{
_day -= day;
return *this;
}
while (_day <= day)
{
_month--;
if (_month == 0)
{
_year--;
_month = 12;
}
_day += GetDayOfMonth(_year, _month);
if (_day > day)
{
_day -= day;
break;
}
}
return *this;
}
Date Date::operator-(int day)
{
//Date d(*this);
Date d = *this;
d -= day;
return d;
}
////后置加加:有拷贝构造
Date Date::operator++(int)
{
//Date d(*this);
Date d = *this;
(*this) += 1;
return d;
}
//前置--
Date& Date:: operator--()
{
*this -= 1;
return *this;
}
Date Date::operator--(int)
{
//Date d(*this);
Date d = *this;
(*this) -= 1;
return d;
}
//前置++:没有拷贝构造
Date& Date::operator++()
{
*this += 1;
return *this;
}
////
//// d1 - d2
int Date::operator-(const Date& d)
{
//暴力搜索
Date d1 = d;
assert(*this > d);
int count = 0;
while (*this != d1)
{
d1++;
count++;
}
return count;
}
// d1 += 100
//Date& Date::operator+=(int day)
//{
// *this = *this + day;
// return *this;
//}
//
//// d1 + 100
//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;
// }
// }
//这是+和+=的另外一套写法,上面一种写法是让+去复用+=的逻辑,会经过2次复制拷贝
//这一种写法是让+=去复用+,会经过3次拷贝
//所以我们选用上一种方法:让+去复用+=
//test.cpp
#include"Date.h"
#include<iostream>
using namespace std;
int main()
{
Date d1(2025, 1, 1);
//Date d2(d1);
//Date d3 = d2 + 100;
//d3.Print();
//d1 -= 100;
//d1.Print();
//Date d2 = d1 - 100;
//d2.Print();
/*++d1;*/
//d1.Print();
//Date d2=d1++;
//d1.Print();
//d2.Print();
//Date d2=d1--;
//d1.Print();
//d2.Print();
//Date d2=--d1;
//d1.Print();
//d2.Print();
Date d2(2024, 9, 27);
Date d3 = d2 + 96;
d3.Print();
int gap = d1 - d2;
cout << gap << endl;
return 0;
}