前言
本文将以日期类为基础,去探寻运算符重载的特性与使用方法,下面先给出日期类的基础定义:
class Date { public: Date::Date(int year, int month, int day) { if (month > 0 && month <= 12 && day > 0 && day <= GetDay(year, month)) { _year = year; _month = month; _day = day; } else { cout << "非法日期" << endl; assert(false); } } private: int _year;//年 int _month;//月 int _day;//日 };
备注:拷贝构造函数和析构函数,均可以不写,因为当前日期类的三个成员变量都是内置类型,没有动态申请空间,使用浅拷贝就可以。
一、运算符重载
📖如何比较两个日期的大小?
int main() { Date d1(2023, 7, 21); Date d2(2023, 6, 21); return 0; }
现如今,定义了两个日期类的对象d1和d2,该如何比较这两个对现象的大小呢?首先想到的是,写一个函数来比较他俩的大小,向下面这样:
//以小于比较为例 bool Less(const Date& x, const Date& y) { if (x._year > y._year) { return false; } else if (x._year == y._year && x._month > y._month) { return false; } else if (x._year == y._year && x._month == y._month && x._day > y._day) { return false; } else { return true; } }
存在的问题:首先这个函数是写在类外面的,意味着,日期类的成员变量如果是private私有的话,在类外面就无法访问,所以在这个函数里面是访问不到对象的年、月、日这三个成员变量,即x._year等都是非法的,要想实现该函数的功能,日期类的成员变量必须是public公有。
其次,在比较两个日期类对象大小的时候,需要写成Less(d1, d2),这和我们平时直接用<符号比较大小,比起来不够直观。
📖为什么日期类不能直接使用<
因为日期类是我们自己定义的,属于一种自定义类型,它的大小比较方式,只有定义它的人知道,而像int、double等内置类型,是祖师爷创造C++语言时就定好的,祖师爷当然知道该如何比较两个内置类型变量的大小,所以提前帮我们设置好了,我们可以直接用<去比较两个内置类型变量的大小,而至于祖师爷是怎么设置的,这里先埋一个伏笔。
📖运算符重载
为了解决上面Less函数存在的问题,C++引入了运算符重载,它可以让我们直接使用<来比较两个日期类的大小。
运算符重载是具有特殊函数名的函数,也具有返回值类型,函数名字、参数列表、返回值类型都和普通函数类似。
函数名字:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
bool operator<(const Date& x, const Date& y) { if (x._year > y._year) { return false; } else if (x._year == y._year && x._month > y._month) { return false; } else if (x._year == y._year && x._month == y._month && x._day > y._day) { return false; } else { return true; } }
上面就是对<运算符的一个重载,它的两个形参是Data类型的引用,此时两个日期类对象就可以直接用<来比较大小啦,d1 < d2本质上就是调用运算符重载函数,但是由于上面的运算符重载函数还是写在类外面,所以当日期类的成员变量是private私有的时候,该运算符重载函数还是用不了。
//下面两条语句是等价的本质都是调用运算符重载函数 d1 < d2; operator<(d1, d2);//d1 < d2的本质
📖将运算符重载函数写成成员函数
为了解决上面的私有成员变量在类外面无法访问的问题,可以把运算符重载函数写成类的成员函数或者友元,这样就能访问到私有的成员变量,但是友元一般不建议使用,因为友元会破坏封装。
bool 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; } else { return false; } }
上面就是把<运算符重载成类的成员函数,此时参数只有一个,因为<是一个双目运算符,类的非静态成员函数有一个隐藏的形参this指针,所以形参就只需要一个。
//它们俩是等价的 d1 < d2; d1.operator<(d2);//d1 < d2的本质
小Tips:一个双目运算符如果重载成类的成员函数,会把它的左操作数传给第一个形参,把右操作数传给第二个形参。以上面为例,this指针接收的是d1的地址,d接收的是d2。
📖注意事项:
不能通过连接其他符号来创建新的运算符:比如operator@。
重载操作符必须有一个类类型参数。
用于内置类型的运算符,其含义不能改变,例如:内置的+,不能改变其含义。
作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this。
.*、::、sizeof、? :、.这五个运算符不能重载。
二、赋值运算符重载
📖区分赋值运算符重载和拷贝构造
Date d1(2020, 5, 21); Date d2(2023, 6, 21); d1 = d2;//需要调用赋值运算符重载 Date d3 = d1;//这里是调用拷贝构造函数 //Date d3(d1);//和上一行等价调用拷贝构造
要区分赋值运算符重载和拷贝构造,前者是针对两个已存在的对象,将一个对象的值,赋值给另一个,而后者是用一个已存在的对象去初始化创建一个新对象。
赋值运算符重载格式:
参数类型:const T&(T是类型),传引用返回可以提高效率。
返回值类型:T&,返回引用可以提高效率,有返回值目的是为了支持连续赋值。
检测是否自己给自己赋值。
返回*this:要符合连续赋值的含义。
Date& operator=(const Data& d) { if (this != &d) { _year = d._year; _month = d._month; _day = d._day; } return *this;//出了作用域*this还在,所以可以用引用返回 }
📖只能是类的成员函数
上面的<运算符,最开始我们是在类外面把它重载成全局的,后来为了保证类的封装性,才把它重载成类的成员函数,而赋值运算符天生只能重载成类的成员函数,因为赋值运算符重载属于类的默认成员函数,我们不写,编译器会自动生成,所以,如果我们把赋值运算符重载写在类外面,就会和编译器生成的默认赋值运算符重载发生冲突。
📖编译器生成的干了些什么工作?
用户没有显式实现时,编译器生成的默认赋值运算符重载,对内置类型的成员变量是以值的方式逐字节进行拷贝(浅拷贝),对自定义类型的成员变量,调用其对应类的赋值运算符重载。
三、完善日期类
有了上面的基础,接下来完善一下日期类,重载其他的运算符。
3.1 重载关系运算符
关系运算符有<、>、==、<=、>=、!=,由于它们之间存在的逻辑关系,可以通过复用来实现,即:要想知道a是否大于b,可以通过判断a是否小于等于b来实现。因此,我们只要写一个<和==的比较逻辑,其他的直接复用即可。
📖重载<
bool 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; } else { return false; } }
📖重载==
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 || *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); }


