操作符重载就是指操作符会对不同类型的操作数表现出不同的行为。例如:(a) 按位左移操作符’«‘在操作流对象的时候就变成了插入操作;(b) *操作符操作于两个数字的时候就是乘法操作,而作用于 地址的时候就是间接寻址操作。C++允许你再用户端扩展操作符重载。

操作符重载就像函数重载那样,同一个函数名可以因为参数不同而同时存在很多版本。

1. string 类中重载的操作符

C++的"string"类中重载了以下操作符来作用于"string"对象:

  • 字符串比较操作(=,!=,>,<,>=,<=):例如,使用str1 == str2来比较两个"string"对象
  • 流插入和取出操作(«,»):例如,你可以使用cout << st1cin >> str2来输出/输入"string"对象
  • 字符串连接(+,+=):例如,str1 + str2,将两个"string"对象合并成一个新的"string"对象,str1 += str2str2追加到str1
  • 字符索引或下标([]):例如,你可以使用str[n]来获取下标为 n 的字符;或者str[n] = c来修改下标为 n 处的字符。需要注意的是’[]‘操作符不会 做边界检测,也就是说,你需要自己保证下标不会超出边界,你可以使用"string"类的at()函数来做边界检测
  • 赋值(=):例如,str1 = str2,将str2赋值给str1

示例代码:

 1#include <iostream>
 2#include <iomanip>
 3#include <string>
 4
 5using namespace std;
 6
 7int main(int argc, char *argv[]) {
 8	string msg1("hello");
 9	string msg2("HELLO");
10	string msg3("hello");
11
12	cout << boolalpha;
13	cout << (msg1 == msg2) << endl;
14	cout << (msg1 == msg3) << endl;
15	cout << (msg1 < msg2) << endl;
16
17	string msg4 = msg1;
18	cout << msg4 << endl;
19
20	cout << (msg1 + " " + msg2) << endl;
21	msg3 += msg2;
22
23	cout << msg3 << endl;
24
25	cout << msg1[1] << endl;
26	cout << msg1[99] << endl;
27
28	return 0;
29}

2. 用户自定义操作符重载

2.1 operator"函数

为了实现操作符重载,我们需要使用一种特殊的函数形式,叫做操作符函数。操作符函数形如:“operator Δ()",Δ 就是将要被重载的操作符。

1return-type operatorΔ(parameter-list)

例如,operator+()重载了”+“操作符;operator<<()重载了”«“操作符。需要注意的是 Δ 必须是 C++中已经存在的操作符,你不能凭空创造一个新的操作符。

2.2 示例:重载+操作符,使其成为Point类的成员函数

在这个例子中,我们将要重载’+‘操作符,使其能够支持两个’Point’对象的加法操作。也就是说,我们可以写成p3 = p1 + p2,而p1,p2,p3都是’Point’对象,就像 普通的数字运算一样,我们需要构造一个新的’Point’类的实例p3,而且不能改变p1p2

Point.h

 1#ifndef POINT_H_
 2#define POINT_H_
 3
 4class Point {
 5private:
 6	int x, y;
 7public:
 8	Point(int x = 0, int y = 0);
 9	int getX() const;
10	int getY() const;
11	void setX(int x);
12	void setY(int y);
13	void print() const;
14	const Point opeator+(const Point &rhs) const;
15};
16
17#endif /* POINT_H_ */

说明:

  • 我们通过成员函数operator+()来实现’+‘操作符重载,重载后的操作符会将左边操作数和右边操作数相加,然后构造一个新的对象来存放相加后的结果, 最后将新构造的对象返回。注意这里返回的是新对象的值,而不是引用,因为新对象是在函数体内构造的,如果返回的是引用,在函数退出的时候,临时变量 都会给销毁
  • rhs操作数出于性能考虑,采用按引用传递的形式
  • 成员函数被声明为 const,说明它不能修改数据成员
  • 返回值被声明为 const 是为了防止它被用作 lvalue。例如,(p1 + p2) = p3,这种写法是没有意义的

Point.cpp

 1#include <iostream>
 2#include "Point.h"
 3
 4using namespace std;
 5
 6Point::Point(int x, int y) : x(x), y(y) {  }
 7
 8int Point::getX() const { return x; }
 9int Point::getY() const { return y; }
10
11void Point::setX(int x) { this->x = x; }
12void Point::setY(int y) { this->y = y; }
13
14void Point::print() const {
15	cout << "(" << x << "," << y << ")" << endl;
16}
17
18const Point Point::operator+(const Point &rhs) const {
19	return Point(x + rhs.x, y + rhs.y);
20}

TestPoint.cpp

 1#include <iostream>
 2#include "Point.h"
 3
 4using namespace std;
 5
 6int main(int argc, char *argv[]) {
 7	Point p1(1, 2), p2(4, 5);
 8	Point p3 = p1 + p2;
 9	p1.print();
10	p2.print();
11	p3.print();
12
13	Point p4 = p1.operator+(p2);
14	p2.print();
15	// Chaining
16	Point p5 = p1 + p2 + p3 + p4;
17	p5.print();
18	return 0;
19}

说明:

  • 你可以通过p1 + p2的形式来调用重载的操作符,它会被转换成点操作符调用的形式:p1.operator+(p2)
  • ‘+‘操作符支持链式调用,因为p1 + p2返回的是一个’Point’对象

2.3 操作符重载的局限

  • 重载操作符必须是已经存在的合法的操作符,你不能创造你自己的操作符
  • 某些 C++操作符不能被重载,例如"sizeof”,点(..*),范围解析(::)和(?:)
  • 重载操作符至少要有一个操作数是用户自定义类型,你不能重载一个操作符使其作用于基本数据类型
  • 你不能改变语法规则(例如结合性,优先级以及参数个数)

3. 通过"friend"关键字修饰的非成员函数重载操作符

3.1 为什么我们不能总使用成员函数来重载操作符?

成员函数’operatorΔ()‘只能被对象通过点操作符调用,例如:‘p1.operatorΔ(p2)’,显然左边的操作数p1必须是一个特定类的对象。 假设我们想重载一个二元操作符,例如*来将一个对象p1和一个’int’字面量作乘法,p1 * 5能够被转成p1.operator*(5),但是5 * p1就不能 被成员函数表示。一种解决办法就是不允许用户做5 * p1这样的操作,只能是p1 * 5,但是这样很不友好,也破坏了乘法的交换律。另一种解决办法 是使用非成员函数,这样它的调用就不是通过对象和点操作符,而是通过函数和参数的形式。例如,5 * p1会被转换成operator*(5, p1)

总之,当你的左边操作数不是该类对象的时候,你就不能使用成员函数来重载操作符。

3.2 friend"函数

普通的非成员函数不能直接访问其参数中对象的私有属性。然而有一种特殊被称为友元的函数,能够访问到对象的私有属性。

一个类的友元函数是用关键字"friend"修饰的定义在类之外的函数,它的该类型参数能够没有限制的访问该类的所有成员。 友元函数能够提高性能,因为它不需要调用 public 成员函数去访问私有数据成员。

3.3 示例:使用友元函数重载操作类Point的\<\<和\>\>操作符

Point.h

 1#ifndef POINT2_H_
 2#define POINT2_H_
 3
 4#include <iostream>
 5
 6class Point {
 7private:
 8	int x, y;
 9public:
10	Point(int x = 0, int y = 0);
11	int getX() const;
12	int getY() const;
13	void setX(int x);
14	void setY(int y);
15
16	friend std::ostream & operator<<(std::ostream &out, const Point &point);
17    friend std::istream & operator>>(std::istream &in, Point &point);
18};
19
20
21#endif /* POINT2_H_ */

Point.cpp

 1#include <iostream>
 2#include "Point2.h"
 3
 4using namespace std;
 5
 6Point::Point(int x, int y) : x(x), y(y) {  }
 7
 8int Point::getX() const { return x; }
 9int Point::getY() const { return y; }
10
11void Point::setX(int x) { this->x = x; }
12void Point::setY(int y) { this->y = y; }
13
14ostream & operator<<(ostream & out, const Point & point) {
15   out << "(" << point.x << "," << point.y << ")";  // access private data
16   return out;
17}
18
19istream & operator>>(istream & in, Point & point) {
20   cout << "Enter x and y coord: ";
21   in >> point.x >> point.y;  // access private data
22   return in;
23}

TestPoint

 1#include <iostream>
 2#include "Point2.h"
 3
 4using namespace std;
 5
 6int main(int argc, char *argv[]) {
 7	Point p1(1, 2), p2;
 8
 9	cout << p1 << endl;
10	operator<<(cout, p1);
11	cout << endl;
12
13	cin >> p1;
14	cout << p1 << endl;
15	operator>>(cin, p1);
16	cout << p1 << endl;
17
18	cin >> p1 >> p2;
19	cout << p1 << endl;
20	cout << p2 << endl;
21
22	return 0;
23}

4. 重载二元操作符

C++中除了不能被重载的三元操作符(? :),其余的不是二元操作符,就是一元操作符。

假设我们想重载”=“操作符来比较两个"Point"对象,我们将会使用成员函数和非成员函数的方式来实现。

成员函数:

1class Point {
2public:
3	bool operator==(const Point &rhs) const;
4	......
5};

非成员函数:

1class Point {
2	friend bool operator==(const Point &lhs, const Point &rhs);
3	......
4};

4. 重载一元操作符

大多数一元操作符都是前缀操作符,例如!x, -x。但是,一元操作符自增和自减却有两种形式:前缀(++x, --x)和后缀(x++, x--)。 我们通过一些机制来区别这两种形式。

4.1 一元前缀操作符

使用友元函数实现

1class Point {
2	friend Point &operator++(Point &point);
3	......
4};

使用成员函数实现:

1class Point {
2public:
3	Point &operator++();
4	......
5};

你既可以使用非成员函数,也可以使用成员函数来实现,只要它的唯一操作数是该类的对象就可以。

4.2 一元后缀操作符

自增和自减操作符都有两种形式。重载后缀形式需要一些技巧。因为要跟前缀形式做区分,所以我们使用一个额外的参数来区别于前缀形式:

1class Point {
2	friend const Point operator++(Point &point, int dummy);
3};
1class Point {
2public:
3	const Point operator++(int dummy);
4};

编译器将会把pt++转换成pt.operator++(0)

下面是一些示例,省略。