操作符重载就是指操作符会对不同类型的操作数表现出不同的行为。例如:(a) 按位左移操作符’«‘在操作流对象的时候就变成了插入操作;(b) *
操作符操作于两个数字的时候就是乘法操作,而作用于
地址的时候就是间接寻址操作。C++允许你再用户端扩展操作符重载。
操作符重载就像函数重载那样,同一个函数名可以因为参数不同而同时存在很多版本。
1. string 类中重载的操作符
C++的"string"类中重载了以下操作符来作用于"string"对象:
- 字符串比较操作(=,!=,>,<,>=,<=):例如,使用
str1 == str2
来比较两个"string"对象 - 流插入和取出操作(«,»):例如,你可以使用
cout << st1
和cin >> str2
来输出/输入"string"对象 - 字符串连接(+,+=):例如,
str1 + str2
,将两个"string"对象合并成一个新的"string"对象,str1 += str2
将str2
追加到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
,而且不能改变p1
和p2
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)
下面是一些示例,省略。
评论