C++面向对象(6) | 运算符的重载

本文是 C++ 面向对象学习笔记的一部分,索引见于 C++面向对象(0) | 写在前面和索引

本文是自主学习后根据学校课程《面向对象程序设计》和课本 Thinking in C++ (Second Edition) Volume One: Introduction to Standard C++ 进行的增补。

本文首发于 语雀。如有更改或增补,将优先在语雀完成。


1.9 运算符的重载

类似函数的重载,C++ 同样允许在一个作用域中对同一个运算符指定多个定义。这称为 运算符重载 。我们可以重载大部分 C++ 内置的运算符。

可重载的运算符

可重载的运算符包括:

  • 双目算术运算符 + - * / %
  • 关系运算符 == != < <= > >=
  • 逻辑运算符 || && !
  • 单目运算符 +(正) -(负) *(指针取值) &(取址)
  • 自增自减运算符 ++ –
  • 位运算符 | & ~ ^ << >>
  • 赋值运算符 = += -= *= /= %= |= &= ^= <<= >>=
  • 其他运算符 new new[] delete delete[] ()(函数调用) ->(成员访问)->*(成员指针访问) ,(逗号) [](下标)

不可重载的运算符包括:

  • .(成员访问) .*(成员指针访问) ::(范围解析) sizeof ?: #(预处理标记)


重载的运算符必须是本就存在的运算符,重载不改变其操作数数目和优先级。

成员函数形式的运算符重载

重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。与其他函数一样,重载运算符有一个返回类型和一个参数列表。例如:

class IntegerPair {
	int x = 0, y = 0;
public:
	IntegerPair() = default;
	IntegerPair(int x, int y) {
		this->x = x, this->y = y;
	}
	const IntegerPair operator- () {
		x = -x;
		y = -y;
		return IntegerPair(x, y);
	}
	const IntegerPair operator+ (const IntegerPair& integerPair) const {
		IntegerPair result;
		result.x = this->x + integerPair.x;
		result.y = this->y + integerPair.y;
		return result;
	}
	static void print(IntegerPair integerPair) {
		cout << integerPair.x << " " << integerPair.y << endl;
	}
};

int main() {
	IntegerPair p(19, 25);
	-p;
	IntegerPair::print(p);
	IntegerPair::print(p + p);
}

输出为:

-19 -25
-38 -50

在这个代码段中,我们在 IntegerPair 类中重载了单目运算符 - 和双目运算符 + ,分别起到 作相反数运算(注意,这里与正常的负号不同)、作相加运算的作用。单目运算符 - 影响了对象的值,双目运算符 + 不会影响,因此我们将 + 设置了 const。可以看到,单目运算符的参数列表为空,而双目运算符的参数列表中有 1 个参数。这是因为,运算符本身需要一个操作的对象。通常,单目运算符出现在它所操作的对象的左边,而双目运算符则出现在它所操作的对象的右边。

友元函数形式的运算符重载

除了这种将重载运算符设置为 成员函数 的形式以外,我们还可以将重载运算符设置为 全局函数 。由于重载运算符必须能够访问相应的类,因此全局函数形式会被定义成 非成员的友元函数 的形式。例如:

class IntegerPair {
	int x = 0, y = 0;
public:
	IntegerPair() = default;
	IntegerPair(int x, int y) {
		this->x = x, this->y = y;
	}
	friend const IntegerPair operator+(const IntegerPair &left, const IntegerPair &right);
	friend const IntegerPair operator-(const IntegerPair &right);
	static void print(IntegerPair integerPair) {
		cout << integerPair.x << " " << integerPair.y << endl;
	}
};

const IntegerPair operator+(const IntegerPair &left, const IntegerPair &right) {
	return IntegerPair(left.x + right.x, left.y + right.y);
}

const IntegerPair operator-(const IntegerPair &right) {
	return IntegerPair(-right.x, -right.y);
}

int main() {
	IntegerPair p(19, 25);
	p = -p;
	IntegerPair::print(p);
	IntegerPair::print(p + p);
}

输出与上例相同。需要注意的是这里的 - 表达的是正常的负号含义,与上例表示绝对值不同。因此此处 25 行的代码是 p = -p; 而不是上例的 -p
在友元函数形式下,单目运算符需要一个参数,双目运算符需要两个参数。实际上,成员函数形式下的参数可以视为在友元函数形式的基础上省略了第一个操作数。

采用哪种形式?

  • 运算符 = () [] -> ->* 必须是成员;
  • 所有一元运算符和除 = 外的赋值运算符(+= -= *= /= %= |= &= ^= <<= >>=)建议使用成员;
  • 所有其他二元运算符建议使用非成员。

返回值优化

关注到本节两个例子中对 + 的重载有所不同:

/* -1- */
	const IntegerPair operator+ (const IntegerPair& integerPair) const {
		IntegerPair result;
		result.x = this->x + integerPair.x;
		result.y = this->y + integerPair.y;
		return result;
	}	
/* -2- */
    const IntegerPair operator+(const IntegerPair &left, const IntegerPair &right) {
        return IntegerPair(left.x + right.x, left.y + right.y);
    }

-1- 中,函数调用 IntegerPair 类的构造函数创建了一个 result 对象,对其操作后,用拷贝构造函数将返回值 result 拷贝到使用 + 符号的地方,最后在函数结尾处调用析构函数。
而 -2- 中的形式是一种“返回临时对象”的语法,编译器看到这样的返回值时意识到,我们对这个临时的对象并没有其他需求,只是返回它。因此编译器会直接在使用 + 符号的地方调用构造函数,不需要拷贝构造函数和析构函数。这是一种高效率的方式,常被称为 返回值优化 (return value optimization)

自增和自减运算符的重载

需要特别关注的是自增和自减运算符 (++, --)。这两个运算符既可以出现在操作数前 (prefix),又可以出现在操作数后 (postfix)。为了区别这两种情况,编译器会在 postfix 时传递一个参数 (int) 0 作为标记。例如:

class IntegerPair {
	int x = 0, y = 0;
public:
	IntegerPair() = default;
	IntegerPair(int x, int y) {
		this->x = x, this->y = y;
	}
	friend const IntegerPair operator+(const IntegerPair &left, const IntegerPair &right);
	friend const IntegerPair& operator++(IntegerPair &right);		/* prefix */
	friend const IntegerPair operator++(IntegerPair &left, int);	/* postfix */
	static void print(IntegerPair integerPair) {
		cout << integerPair.x << " " << integerPair.y << endl;
	}
};

const IntegerPair operator+(const IntegerPair &left, const IntegerPair &right) {
	return IntegerPair(left.x + right.x, left.y + right.y);
}

const IntegerPair& operator++(IntegerPair &right) {
	right.x++, right.y++;
	return right;
}

const IntegerPair operator++(IntegerPair &left, int) {
	left.x++, left.y++;
	return IntegerPair(left.x - 1, left.y - 1);
}

int main() {
	IntegerPair p(2, 2);
	IntegerPair::print(p);
	IntegerPair::print(p++);
	IntegerPair::print(++p);
}

输出为:

2 2
2 2
4 4

operator=

在 1.5 拷贝构造函数 一节中,我们提到过类似这样的代码段:

	IntegerPair a;
	IntegerPair b = a;
	b = a;

我们知道,第 1 行调用了构造函数,第 2 行调用了拷贝构造函数,而对于第 3 行,我们不需要对一个已经存在的对象调用拷贝构造函数。这种情况下,对 b 调用的是 IntegerPair::operator=,把出现在右侧的任何东西作为参数(即可以有多种参数表不同的函数定义,即重载)。
像构造函数一样,每个类都有一个缺省的 opertor= 定义。如果想禁用等号赋值,可以显式地将其定义在 private 中。
赋值会与拷贝构造函数遇到同样的指针相关的问题,解决方法也类似。具体参见 1.5 拷贝构造函数。

输入输出流

通过重载 << 和 >> 运算符可以实现对流的插入和提取,可以应用在 cout 等处。

class Fraction {
private:
	int top, bottom;
public:
    string toString() const;
    friend ostream& operator<<(ostream& os, const Fraction& right);
	friend istream& operator>>(istream& is, Fraction& right);
}

string Fraction::toString() const {
	string str = to_string(this->top);
	str += "/";
	str += to_string(this->bottom);
	return str;
}
ostream& operator<<(ostream& os, const Fraction& right) {
	return os << right.toString();
}
istream& operator>>(istream& is, Fraction& right) {
	char op;
	is >> right.top >> op >> right.bottom;
	right.fracReduction();
	return is;
}

上面代码段中 Fraction 类定义的是一个分数。对 << 和 >> 的重载分别返回输出流和输入流。<< 运算符将表示分数 right 的字符串插入到输出流 os 中并返回,>> 运算符从输入流中提取分数的信息存入分数 right 后将剩余的输入流返回。

你可能感兴趣的:(#,C++,学习,c++,指针,编程语言)