对已有的运算符重新进行定义,赋予其另一种功能,以适应不同的数据类型
1、对于+号运算符没有类 = 类 + 类,现在要给+号赋予对象可以相加的功能
(1)成员函数重载+号运算符
(2)全局函数重载+号运算符
(3)也可以把+号赋予为类 = 类 + 整形
总结:
(1)什么时候可以用运算符重载?参数是类、结构体或者枚举
(2)自定义数据类型可以改变运算符含义,具体怎么改变还原本质来看,比如operator+(Person &p, int i)就可以知道对+号运算符重载,具体实现怎么的加法还原本质:Person p3 = operator+(p1, 10); 可以知道p3 = p1 + 10;
(3)成员函数重载运算符只能有一个传参,具体是怎么定义+号的还原本质:Person p3 = p1.operator+(10); 可以知道p3 = p1 + 10;
2、<<运算符重载
cout << p; //直接输出对象,这样写错误,现在修改<<运算符让它这样写可以输出p对象内容
(1)成员函数重载<<运算符
void operator<<(cout) { }
本质是p.operator<<(cout),简化就是p << cout,反了,所以采用成员函数重载<<运算符行不通。
(2)全局函数重载<<运算符
如果cout << p1 << endl; 这样又报错了,因为这样写的本质是一个链式编程cout << p1得要有返回值才能继续<< endl; 所以做如下改进:
返回值ostream &加&的目的就是不管返回多少次一直是这个cout,cout << p1 << endl;先执行cout << p1返回cout,然后cout << endl;进行换行,如果不加&,第一次返回本身cout后会有个ostream类的对象接收cout,调用了拷贝构造,此时的cout不在是本身,这里设为cout’,即cout’ << endl;报错,因为cout是唯一的,只有cout本身才包含换行。
通常类中的变量都会设为私有,这样的话operator函数调用成员变量就报错了,这里可以引入全局函数做友元访问类中私有变量:
3、对前置++和后置++重定义
(1)重定义前置++
先实现可以直接打印对象p的值cout << p
实现前置++的函数不知道返回什么可以先设为void,内部实现对象p值的自增,但cout << ++p这样错误,现在只认cout << p,所以要保证重定义++函数能够返回一个对象,这样cout << ++p实际是cout << p(p是自增后的)
这是成员函数重载前置++的写法
(2)重定义后置++
后置++要知道一点,重定义函数中返回原来对象的值,然后原来对象值自增,这就需要借助中间对象tmp先接收对象p本身所有变量(拷贝构造),然后对象本身p值自增,最后返回原先的对象p这里返回tmp已经接收了原先p所有属性。
为什么不返回引用?
如果返回引用则一直针对的是对象tmp本身,而tmp对象是局部的在函数运行结束销毁,销毁后就无法根据tmp获取对象p值,所以直接返回对象,每次tmp都是不同的。我只要每次tmp能接收到对象p所有属性并能返回就行了,不需要每次都只针对同一个tmp。
注意:如果一个类中同时有前置++和后置++重定义函数
Person & operator++() {} //前置++重定义 Person operator++() {} //后置++重定义
这样会报错,除了返回值不同其他都一样,但返回值不同不能作为函数重载条件,这时候需要在后置++重定义函数中加个占位参数来和前置++进行函数重载予以区分前置和后置
Person operator++(int) {}
注意:后置++要有占位符
全局函数重定义后置++运算符:
可以看出如果同时存在前置++和后置++重定义函数,后置++需要有个占位参数来区分。
4、重定义=运算符
前面说到过C++编译器至少给一个类添加了三个默认函数,无参构造、拷贝构造(浅拷贝)和析构函数,学到这里还有一个默认函数实际是给一个类添加了四个默认函数,第四个为重定义=运算符函数(浅拷贝)。
有个注意的地方:
Person p1;
Person p2 = p1; //这里用到的是默认的拷贝构造,浅拷贝
p2 = p1; //这里用到的是默认的重定义=运算符函数,浅拷贝
以前一直以为p2 = p1也是调用默认拷贝构造,现在才知道是调用默认重定义=运算符函数。
在上面的例子中如果不手动写重定义=运算符函数,默认的则为:
void operator=(const Person& p) { age = p.age //两个对象指针指向同一块内存空间,造成多次释放同一块内存空间报错 }
这样就需要手动写重定义=运算符函数,在内部进行深拷贝。
我们注意到=号是可以连=的,即p2 = p1 = p; 这就需要operator=函数返回对象,如下图:
(应该return p才对,不是return * this )
p2 = p1 = p,加引用返回的一直是p对象本身,其实不加引用也没事只不过每次接收的p都是新的p,比较浪费内存空间,不如加引用一直针对一个对象本身。而且内部做了一个判断,为啥要先判断age是否在堆区?
Person p;
Person p1 = p(这里就当作用了深拷贝,这样p1在堆区有数据)
p1 = p; //这样做就相当于在重定义函数中又在堆区重新申请了内存进行深拷贝,那之前申请的内存空间要释放掉,不然会造成资源空间浪费,所以要先判断是否有属性在堆区,有就释放掉,然后重新申请内存进行深拷贝。这样的话C++提供的默认重定义函数如下:
Person& operator=(const Person& p) { age = p.age; return *this; }
5、自定义重载关系运算符
(1)自定义重载==运算符
6、函数调用运算符重载
函数调用运算符()可以重载,由于重载后使用方式非常像函数调用,因此称为仿函数,仿函数没有固定写法,非常灵活。
这里需要知道什么是匿名函数对象:
Person p = Person(); //这是第二种方法初始化对象 Person(); //这是匿名对象,用完就销毁 Person()(1, 2); //这是匿名对象和重载()运算符函数融合,称作匿名函数对象