运算符重载
- 1. 运算符重载基础
- 1.1 运算符重载语法
- 1.2 运算符重载细节补充
- 1.3 更多的运算符重载
- 2. 重载单目运算符
- 3. 如何直接输入输出对象类型——重载运算符 << 和 >>
- 3.1 单个对象实现 cou <<
- 3.2 多个对象实现 cout<<
- 3.3 右移运算符 输入 cin >>
- 3.4 重载括号运算符(仿函数/函数对象)
- 4. 重载运算符注意事项
1. 运算符重载基础
C++将运算符重载扩展到自定义的数据类型,它可以让对象操作更美观。
例如字符串string用加号(+)拼接、cout用两个左尖括号(<<)输出。
有时候我们需要让对象之间进行运算, C++ 提供的“运算符重载”机制,赋予运算符新的功能,就能解决用+将两个复数对象相加这样的问题。
- 举个例子,比如说如下代码:
类Point
class Point {
friend Point add(Point, Point);
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << ", " << m_y << ")" << endl;
}
};
函数add()
Point add(Point p1, Point p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
然后在主函数里面我们定义两个对象,让两个点相加
int main() {
Point p1(10, 20);
Point p2(20, 30);
Point p3 = add(p1, p2);
p3.display();
getchar();
return 0;
}
输出:
这样做就显得很繁琐,如果可以直接这么写就好了:
Point p3 = p1 + p2;
也就是直接让这两个对象进行相加操作,
但是默认情况下这么写报错
所以我们可以利用 运算符重载(操作符重载):可以为运算符增加一些新的功能
1.1 运算符重载语法
返回值 operator运算符(参数列表);
接着刚才的问题,我们利用运算符重载为运算符增加一些新的功能,使得“+”可以允许两个Point类型进行相加操作。
Point operator+(Point p1, Point p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
不要忘记在Point类里面更新友元:
friend Point operator+(Point, Point);
完整代码:
#include <iostream>
using namespace std;
class Point {
friend Point operator+(Point, Point);
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << ", " << m_y << ")" << endl;
}
};
//Point add(Point p1, Point p2) {
// return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
//}
Point operator+(Point p1, Point p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
int main() {
Point p1(10, 20);
Point p2(20, 30);
Point p3 = p1 + p2;
p3.display();
getchar();
return 0;
}
输出:
- 本质上是调用了 operator+() 函数,并且返回了 Point 类型值
// 这两行代码等价
Point p3 = operator+(p1, p2);
Point p3 = p1 + p2;
- 利用运算符重载为运算符后,三个数相加也可以
例:
int main() {
Point p1(10, 20);
Point p2(20, 30);
Point p3(30, 40);
Point p4 = p1 + p2 + p3;
p4.display();
getchar();
return 0;
}
输出:
此时三个对象相加也可以了,所以还是比直接调用 add()函数 要方便的多
原理是这样的:
// 调用了两次operaator+
Point p4 = operator+(operator+(p1, p2), p3) ;
1.2 运算符重载细节补充
之前在 C++ 对象型参数和返回值 的帖子笔记中提到,最好不要在函数参数使用对象型类型,不然会产生中间变量。
Point operator+(Point p1, Point p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
修改成 引用(减少中间对象的产生) :
Point operator+(const Point &p1, const Point &p2) {
return Point(p1.m_x + p2.m_x, p1.m_y + p2.m_y);
}
前面用 const 修饰的原因是可以使加入进来的参数接收对象更广,既可以接受const对象,也可以接受非const对象。
记得更细友元:
friend Point operator+(const Point &, const Point &);
- 有没有发现这种写法和拷贝构造函数的格式很类似呢?
拷贝构造函数格式: 类名(const 类名& 对象名){…}
// 拷贝构造函数
Point(const Point &point) {
m_x = point.m_x;
m_y = point.m_y;
}
我的理解是也是由于 const 引用既可以接受const参数也可以接受非const参数的原因,它的接受范围更大
- 另外,运算符重载也可以直接写在类里面,这样就可以直接访问类里面的成员,我们就省下了去写友元这一步
- p.s: const 用来修饰成员函数,如果是在类外,全局函数就不要用const修饰了
#include <iostream>
using namespace std;
class Point {
friend Point operator+(Point, Point);
int m_x;
int m_y;
public:
Point(int x, int y) :m_x(x), m_y(y) {}
void display() {
cout << "(" << m_x << ", " << m_y << ")" << endl;
}
// 运算重载符
const Point operator+(const Point &point){
return Point(this->m_x + point.m_x, this->m_y + point.m_y); // this 指针可以省略
}
};
变成成员函数以后,则需要用对象去调用
p1.operator+(p2);
//等价于
p1 + p2;
那么成员函数中只接收一个参数就可以了
// 运算重载符
Point operator+(const Point &point){
return Point(this->m_x + point.m_x, this->m_y + point.m_y); // this 指针可以省略
}
还可以再完善以下,左边的这个const是防止返回值被赋值
右边的const能保证我们的返回值可以再次调用operator+()函数
// 运算重载符
const Point operator+(const Point &point)const{
return Point(this->m_x + point.m_x, this->m_y + point.m_y); // this 指针可以省略
}
- 结论:全局函数和成员函数都支持运算符重载
1.3 更多的运算符重载
除了 “ + ”,还有其他的运算符重载,如 “ - ”(减法运算)
const Point operator-(const Point &point)const{
return Point(m_x - point.m_x, m_y - point.m_y);
}
" += "
Point &operator+=(const Point &point) {
m_x += point.m_x;
m_y += point.m_y;
return *this; // 取出this指针所指向的东西
}
" == "
bool operator==(const Point &point) const {
// 1\0
if ((m_x == point.m_x) && (m_y == point.m_y)) {
return 1;
} else {
return 0;
}
// 或者以下写法
// return (m_x == point.m_x) && (m_y == point.m_y);
“ != ”
bool operator!=(const Point &point) const {
return (m_x != point.m_x) || (m_y != point.m_y);
}
" - " (负号)
const Point operator-() const {
return Point(-m_x, -m_y);
}
2. 重载单目运算符
可重载的一元运算符:
1)++ 自增 2)-- 自减 3)! 逻辑非 4)& 取地址
5)~ 二进制反码 6)* 解引用 7)+ 一元加 8) - 一元求反
一元运算符通常出现在它们所操作的对象的左边。
但是,自增运算符++和自减运算符–有前置和后置之分。
C++ 规定,重载++或–时,如果重载函数有一个int形参,编译器处理后置表达式时将调用这个重载函数。
- 区别:1.只需要在参数后面加个 int 就是后置++
// 前置++
Point &operator++() {
m_x++;
m_y++;
return *this;
}
// 后置++
const Point operator++(int) { // 返回const是由于 后置++ 是不能被赋值的(运算符后置++本身的特性)
Point old(m_x, m_y);
m_x++;
m_y++;
return old; // 返回的是临时的变量,因为前置++是先赋值再运算,也就是最后在进行++操作
}
- 2.前置++可以被赋值,后置++不可以
int main() {
int a = 10;
(++a) = 20; // ++放在前面可以赋值
(a++) = 20; // 会报错,因为相当于先赋值,a再自己+1
}
- 所以在写后置++的重载的时候,我们一个是要考虑它本身的不能被赋值的特性(前面用const修饰),一个是返回值应当返回+1之前的值
// 后置++
const Point operator++(int) { // 返回const是由于 后置++ 是不能被赋值的(运算符后置++本身的特性)
Point old(m_x, m_y);
m_x++;
m_y++;
return old; // 返回的是临时的变量,因为前置++是先赋值再运算,也就是最后在进行++操作
}
3. 如何直接输入输出对象类型——重载运算符 << 和 >>
3.1 单个对象实现 cou <<
- 我们能否能像输出其他类型一样,直接将对象进行 cout 呢?
可以通过对左移运算符进行重载
int main() {
Point p1(10, 20);
cout << p1 << endl;
getchar();
return 0;
}
这种情况重载就不能写在类里了,因为左移运算符左边是cout,而想要调用类里的成员函数首先要是对象才行,而cout显然不是对象,所以要写在类的外面
// output stream -> ostream 输出流
void operator<<(ostream& cout, const Point& point) {
cout << "(" << point.m_x << ", " << point.m_y << ")";
}
(ostream 是 cout 所在的类)
注意不要忘记在类里面放友元,不然无法访问m_x,m_y成员变量
friend void operator<<(ostream &, const Point &);
3.2 多个对象实现 cout<<
- 如果想实现多个数据的cout:
cout << p1 << p2 << endl;
其实类似如下过程:
cout << p1 << p2 << endl;
// 等价于
operator << (cout, p1) << p2 ;
其实就是得到一次返回值以后,再用这个返回值调用一次左移运算符,所以我们可以更新一下:
// output stream -> ostream 输出流
ostream& operator<<(ostream& cout, const Point& point) {
cout << "(" << point.m_x << ", " << point.m_y << ")";
return cout; // 返回cout本身,然后就可以继续打印
}
更新友元:
friend ostream& operator<<(ostream&, const Point&);
可以用以下代码实验
int main() {
Point p1(10, 20);
Point p2(20, 30);
cout << p1 << p2 << endl;
getchar();
return 0;
}
输出:可以正常打印对象了
3.3 右移运算符 输入 cin >>
- 如果我们想从键盘输入一些东西给到对象,该怎么做?
Point p1(10,20);
cin >> p1;
先在类里面更新友元:
friend istream& operator>>(istream&, Point&);
然后和cout类似,istream是cin所在的类,返回一个cin
// input stream -> istream
istream &operator>>(istream &cin, Point &point) {
cin >> point.m_x;
cin >> point.m_y;
return cin;
}
最后可以通过键盘输入查看对象中的值是否被修改了,键盘输入:40 50 60 70
int main() {
Point p1(10, 20);
Point p2(20, 30);
cin >> p1 >> p2;
cout << p1 << p2 << endl;
getchar();
return 0;
}
输出:
对象里的成员变量的值已经更改为键盘输入的值了
3.4 重载括号运算符(仿函数/函数对象)
括号运算符()也可以重载,对象名可以当成函数来使用(函数对象、仿函数)。
括号运算符重载函数的语法:返回值类型 operator()(参数列表);
注意:
- 括号运算符必须以成员函数的形式进行重载。
- 括号运算符重载函数具备普通函数全部的特征。
- 如果函数对象与全局函数同名,按作用域规则选择调用的函数。
函数对象的用途:
- 表面像函数,部分场景中可以代替函数,在STL中得到广泛的应用;
- 函数对象本质是类,可以用成员变量存放更多的信息;
- 函数对象有自己的数据类型;
- 可以提供继承体系。
4. 重载运算符注意事项
- 返回自定义数据类型的引用可以让多个运算符表达式串联起来。(不要返回局部变量的引用)
- 重载函数参数列表中的顺序决定了操作数的位置。
- 重载函数的参数列表中至少有一个是用户自定义的类型,防止程序员为内置数据类型重载运算符。
- 如果运算符重载既可以是成员函数也可以是全局函数,应该优先考虑成员函数,这样更符合运算符重载的初衷。
- 重载函数不能违背运算符原来的含义和优先级。
- 不能创建新的运算符。
- 以下运算符不可重载:
sizeof sizeof运算符
. 成员运算符
.* 成员指针运算符
:: 作用域解析运算符
?: 条件运算符
typeid 一个RTTI运算符
const_cast 强制类型转换运算符
dynamic_cast 强制类型转换运算符
reinterpret_cast 强制类型转换运算符
static_cast 强制类型转换运算符
暂时先写这么多,这部分的知识点还有很多,后面遇到再回来补充
- 以下运算符只能通过成员函数进行重载:
= 赋值运算符
() 函数调用运算符
[] 下标运算符
-> 通过指针访问类成员的运算符