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;
}

输出:
C++运算符重载(操作符重载)-LMLPHP
这样做就显得很繁琐,如果可以直接这么写就好了:

Point p3 = p1 + p2;

也就是直接让这两个对象进行相加操作,
但是默认情况下这么写报错
所以我们可以利用

1.1 运算符重载语法

接着刚才的问题,我们利用运算符重载为运算符增加一些新的功能,

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;
}

输出:
C++运算符重载(操作符重载)-LMLPHP

  • 本质上是调用了 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;
}

输出:
C++运算符重载(操作符重载)-LMLPHP
此时三个对象相加也可以了,所以还是比直接调用 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 &);
  • 有没有发现这种写法和的格式很类似呢?
// 拷贝构造函数
	Point(const Point &point) {
		m_x = point.m_x;
		m_y = point.m_y;
	}

我的理解是也是由于 ,它的接受范围更大

  • 另外,运算符重载也可以直接写在类里面,这样就可以直接访问类里面的成员,我们就省下了去写友元这一步
  • 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能保证我们的

// 运算重载符
	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.只需要在参数后面加个 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;
}

输出:可以正常打印对象了

C++运算符重载(操作符重载)-LMLPHP

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;
}

输出:
C++运算符重载(操作符重载)-LMLPHP
对象里的成员变量的值已经更改为键盘输入的值了

3.4 重载括号运算符(仿函数/函数对象)

括号运算符()也可以重载,对象名可以当成函数来使用(函数对象、仿函数)。

注意:

函数对象的用途:

4. 重载运算符注意事项

  1. 返回自定义数据类型的引用可以让多个运算符表达式串联起来。(不要返回局部变量的引用)
  2. 重载函数参数列表中的顺序决定了操作数的位置。
  3. 重载函数的参数列表中至少有一个是用户自定义的类型,防止程序员为内置数据类型重载运算符。
  4. 如果运算符重载既可以是成员函数也可以是全局函数,应该,这样更符合运算符重载的初衷。
  5. 重载函数。
  6. 不能创建新的运算符。
  7. 以下运算符不可重载:

暂时先写这么多,这部分的知识点还有很多,后面遇到再回来补充

  1. 以下运算符只能通过成员函数进行重载:
05-11 15:26