一、运算符重载
1、运算符重载的概念
对于C++的内置类型,我们有许多运算符可以使用,但是这些运算符却无法对自定义类型进行使用,我们只能写一个与运算符功能类似的函数,让自定义类型去调用。
例如:
#include<iostream> using namespace std; class Date { public: Date(int year = 1, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //给一个给日期加num天,不修改原始值 Date Add(int num) { //...... } //给一个日期加num天,并修改原始值 Date AddEqual(int num) { //..... } private: int _year; int _month; int _day; }; int main() { int a = 10; a + 10; a += 10; Date d1; d1.Add(10); //d1+10; //想写成这样,这样更直观方便,可是编译器不允许啊啊啊 d1.AddEqual(10); //d1+=10; //想写成这样,这样更直观方便,可是编译器不允许啊啊啊 return 0; }
C++为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。
函数名字为:关键字operator后面接需要重载的运算符符号。
函数原型:返回值类型 operator操作符(参数列表)
只看定义不太好理解运算符重载,我们还是直接先看代码,结合代码边分析边理解定义与注意要点。
// 全局的operator== #include<iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } //private: int _year; int _month; int _day; }; // 这里会发现:运算符重载成全局的 就需要成员变量是公有的,不然下面的函数无法访问到成员变量, // 那么问题来了,封装性如何保证? //这点我们现在还没有办法解决,所以我们先暂时将成员设定为公有。 //还有一种办法就是把它写进类中,变成成员函数。(暂时不用此种方法) //判断两个对象是否相同 bool operator==(const Date& d1, const Date& d2) { return d1._year == d2._year && d1._month == d2._month && d1._day == d2._day; } void Test() { Date d1(2023, 2, 12); Date d2(2023, 2, 12); cout << operator==(d1, d2) << endl;//判断两个对象是否相同,第一种使用方法,直接调用函数 cout << (d1 == d2) << endl;//判断两个对象是否相同,第二种使用方法,使用重载后的运算符 //此处必须加括号,运算符优先级:<< 大于 == }
相信仔细看完这个代码后你已经对运算符重载有了一定的了解,就是大概相当于自定义类型的运算符其实是一个我们手动写函数,但是呢经过运算符重载以后我们可以像使用内置类型运算符那样去使用函数。
2、运算符重载的注意事项
- 不能通过连接其他符号(C++中不存在的运算符)来创建新的操作符:比如operator@
- 重载操作符必须有一个类类型参数(因为运算符重载主要是为了让自定义类型能够像内置类型那样去使用运算符)
- 用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不 能改变其含义(因为运算符重载主要是为了让自定义类型能够像内置类型那样去使用运算符,内置类型不需要运算符重载)
- 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐 藏的
this
- .* :: sizeof ?: . 注意以上5个运算符不能重载。这个经常在笔试选择题中出现。
二、运算符重载的特例
1、前置++和后置++类型
经过上面的运算符重载的讲解相信你对于Date类中的一些其他运算符也能写出它的运算符重载如:
class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } Date& operator+(int num) { //...... } Date& operator-(Date& d) { //...... } Date& operator+=(int num) { //...... } //...... //其实这么多运算符重载我们可以一一实现,也可以实现一到两个,然后让其他运算符重载 private: int _year; int _month; int _day; };
但是我们在学习C语言时学习过 前置++,后置++,前置- -,后置- -, 对于这种运算符重载我们应该怎么做呢?
我们先写一下函数外部的参数列表:
//前置++ Date& operator++() { } //后置++ Date operator++() { }
我们发现它们的函数外部参数列表一模一样,这样根本无法构成函数重载,我们也只能实现 前置++,后置++中的一个。
为了解决这个问题,我们C++给这种前置++,后置++,前置- -,后置- -,这种运算符重载时进行了特殊化处理,规则是:
- 前置++正常实现
- 后置++在运算符重载时多传一个int型参数(此参数只是为了占位,实现函数重载,不起其他作用,也不必真的传递参数)
(- -与以上规则类似)
所以正确实现我们应该这样实现:
//前置++ Date& operator++() { *this += 1;//假设 +=我们已经运算符重载过了 return this; } //后置++ Date operator++(int a)//形参名可以不写,直接写成 int,调用时也不需要传递参数 { Date tmp(this); //会去调用拷贝构造 *this += 1; //假设 +=我们已经运算符重载过了 return tmp; }
2、流插入<< 流提取>>运算符
在前面讲解运算符重载时我们说过,<<其实是移位运算符,但是呢在C++中被重载为了流插入运算符,那具体是怎么做的呢?我们现在学习了运算符重载已经可以去讨论这个问题了。
首先我们经常使用的cout
和cin
,其实分别是一个ostream
类型对象,一个是istream
类型的对象,这两个类型又分别在<ostream>
和<istream>
两个C++标准库的std
标准命名空间内,然后我们经常使用的C++标准库<iostream>
又包含了<istream>
和<ostream>
头文件,所以我们使用cin
或cout
的时即要包含头文件<iostream>
又要使用using namespeace std;
将标准命名空间里面的内容展开到全局中。
我们再来看看流提取<<
运算符重载
可以看到在C++对流提取<<
运算符进行了许多运算符重载+函数重载,我们对于内置类型可以随意使用<<
,但是自定义类型我们却没有办法使用,因为自定义类型是我们自定义的,C++无法提前预知我们要写什么自定义类型,然后给我们的自定义类型进行运算符重载,所以我们想让我们的自定义类型也能用流提取<<
,必须我们手动实现自定义类型的<<
的运算符重载。
我们先来看看第一种写法
//<< 运算符重载 #include<iostream> using namespace std; class Date { public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } ostream& operator<< (ostream&out) { out << _year << "年" << _month << "月" << _day << "日" << endl; return out; } private: int _year; int _month; int _day; }; int main() { Date d1; int a = 10; cout << a << endl;//C++对自定义类型已经实现了 << 的运算符重载 cout << d1; return 0; }
编译失败了,我们仔细检查检查,会发现是67行我们写反了!应该写成
d1 << cout; //第一种写法 d1.operator<<(cout); //第二种写法
因为运算符重载时,第一个参数是左操作数,第二个操作数是右操作数(注意,第一个参数是隐藏的this
指针)
写正确后我们运行一下:
没有问题,但是这样写也太变态了,违法我们的使用直觉使用啊,直觉告诉我们,我们应该这样使用:
cout << d1;
那么我们就应该把这个流插入<<
重载定义到函数外面,因为定义在类的内部默认传递的第一个参数就是this
指针,我们永远达不到目的。
于是我们定义在外面:
#include<iostream> using namespace std; class Date { friend ostream& operator<<(ostream& out, Date& d);//友元,允许我们在类外部使用成员变量。 public: Date(int year = 1900, int month = 1, int day = 1) { _year = year; _month = month; _day = day; } private: int _year; int _month; int _day; }; ostream& operator<<(ostream& out, Date& d) { out << d._year << "年" << d._month << "月" << d._day << "日" << endl; return out; } int main() { Date d1; int a = 10; cout << a << endl;//C++对自定义类型已经实现了 << 的运算符重载 cout << d1; return 0; }
还又一个问题我们写<<
运算符重载时为什么要用引用呢?
注意:<<
是左结合性!
返回值用引用了我们就可以实现连续多个自定义类型打印了!
//假如d1 d2 d3 都是Date类型 cout << d1 << d2 << d3 << endl;