操作符重载就是指操作符会对不同类型的操作数表现出不同的行为。例如:(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

示例代码:

#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

int main(int argc, char *argv[]) {
	string msg1("hello");
	string msg2("HELLO");
	string msg3("hello");

	cout << boolalpha;
	cout << (msg1 == msg2) << endl;
	cout << (msg1 == msg3) << endl;
	cout << (msg1 < msg2) << endl;

	string msg4 = msg1;
	cout << msg4 << endl;

	cout << (msg1 + " " + msg2) << endl;
	msg3 += msg2;

	cout << msg3 << endl;

	cout << msg1[1] << endl;
	cout << msg1[99] << endl;

	return 0;
}

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

2.1 “operator"函数

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

return-type operatorΔ(parameter-list)

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

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

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

Point.h

#ifndef POINT_H_
#define POINT_H_

class Point {
private:
	int x, y;
public:
	Point(int x = 0, int y = 0);
	int getX() const;
	int getY() const;
	void setX(int x);
	void setY(int y);
	void print() const;
	const Point opeator+(const Point &rhs) const;
};

#endif /* POINT_H_ */

说明:

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

Point.cpp

#include <iostream>
#include "Point.h"

using namespace std;

Point::Point(int x, int y) : x(x), y(y) {  }

int Point::getX() const { return x; }
int Point::getY() const { return y; }

void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }

void Point::print() const {
	cout << "(" << x << "," << y << ")" << endl;
}

const Point Point::operator+(const Point &rhs) const {
	return Point(x + rhs.x, y + rhs.y);
}

TestPoint.cpp

#include <iostream>
#include "Point.h"

using namespace std;

int main(int argc, char *argv[]) {
	Point p1(1, 2), p2(4, 5);
	Point p3 = p1 + p2;
	p1.print();
	p2.print();
	p3.print();

	Point p4 = p1.operator+(p2);
	p2.print();
	// Chaining
	Point p5 = p1 + p2 + p3 + p4;
	p5.print();
	return 0;
}

说明:

  • 你可以通过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

#ifndef POINT2_H_
#define POINT2_H_

#include <iostream>

class Point {
private:
	int x, y;
public:
	Point(int x = 0, int y = 0);
	int getX() const;
	int getY() const;
	void setX(int x);
	void setY(int y);

	friend std::ostream & operator<<(std::ostream &out, const Point &point);
    friend std::istream & operator>>(std::istream &in, Point &point);
};


#endif /* POINT2_H_ */

Point.cpp

#include <iostream>
#include "Point2.h"

using namespace std;

Point::Point(int x, int y) : x(x), y(y) {  }

int Point::getX() const { return x; }
int Point::getY() const { return y; }

void Point::setX(int x) { this->x = x; }
void Point::setY(int y) { this->y = y; }

ostream & operator<<(ostream & out, const Point & point) {
   out << "(" << point.x << "," << point.y << ")";  // access private data
   return out;
}

istream & operator>>(istream & in, Point & point) {
   cout << "Enter x and y coord: ";
   in >> point.x >> point.y;  // access private data
   return in;
}

TestPoint

#include <iostream>
#include "Point2.h"

using namespace std;

int main(int argc, char *argv[]) {
	Point p1(1, 2), p2;

	cout << p1 << endl;
	operator<<(cout, p1);
	cout << endl;

	cin >> p1;
	cout << p1 << endl;
	operator>>(cin, p1);
	cout << p1 << endl;

	cin >> p1 >> p2;
	cout << p1 << endl;
	cout << p2 << endl;

	return 0;
}

4. 重载二元操作符

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

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

成员函数:

class Point {
public:
	bool operator==(const Point &rhs) const;
	......
};

非成员函数:

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

4. 重载一元操作符

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

4.1 一元前缀操作符

使用友元函数实现

class Point {
	friend Point &operator++(Point &point);
	......
};

使用成员函数实现:

class Point {
public:
	Point &operator++();
	......
};

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

4.2 一元后缀操作符

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

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

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

下面是一些示例,省略。