在《使用友元函数重载算术运算符》中,你已学习了如何使用友元函数重载算术运算符,也了解到运算符可以按普通函数的方式重载。事实上,许多运算符还有第三种重载方式:以成员函数的形式实现。
一、成员函数重载的基本思路
使用成员函数重载运算符与使用友元函数非常相似,但需满足以下要求:
- 被重载的运算符必须作为“左操作数”所属类的成员函数;
- 左操作数成为隐含的
*this
对象; - 其余操作数均作为函数的显式形参。
二、友元版本回顾
回忆一下我们曾用友元函数重载 operator+
的写法:
#include <iostream>
class Cents
{
private:
int m_cents {};
public:
Cents(int cents)
: m_cents { cents } { }
// 重载 Cents + int
friend Cents operator+(const Cents& cents, int value);
int getCents() const { return m_cents; }
};
// 注意:此函数并非成员函数!
Cents operator+(const Cents& cents, int value)
{
return Cents(cents.m_cents + value);
}
int main()
{
const Cents cents1 { 6 };
const Cents cents2 { cents1 + 2 };
std::cout << "I have " << cents2.getCents() << " cents.\n";
return 0;
}
三、改写为成员函数
将友元版本转换为成员版本十分直接:
- 将运算符定义为类的成员(
Cents::operator+
),而非友元; - 去掉左侧形参,因为它现在由隐含的
*this
提供; - 函数体内凡引用左侧形参之处,均改为直接访问数据成员(例如
cents.m_cents
改为m_cents
,隐含地通过*this
)。
#include <iostream>
class Cents
{
private:
int m_cents {};
public:
Cents(int cents)
: m_cents { cents } { }
// 重载 Cents + int
Cents operator+(int value) const;
int getCents() const { return m_cents; }
};
// 注意:此函数是成员函数!
// 友元版本中的 cents 形参现在由隐含的 *this 提供
Cents Cents::operator+(int value) const
{
return Cents { m_cents + value };
}
int main()
{
const Cents cents1 { 6 };
const Cents cents2 { cents1 + 2 };
std::cout << "I have " << cents2.getCents() << " cents.\n";
return 0;
}
四、表达式求值方式对比
表达式 cents1 + 2
在不同版本中的求值过程如下:
- 友元版本:表达式转换为函数调用
operator+(cents1, 2)
,有两个显式实参,直观明了。 - 成员版本:表达式转换为
cents1.operator+(2)
,仅有一个显式实参,cents1
作为对象前缀。根据,编译器会将对象前缀隐式转换为隐藏的最左形参 *this
,因此实际调用为operator+(¢s1, 2)
,与友元版本几乎一致。
两种实现最终产生相同结果,只是路径略有差异。
五、友元与成员函数的选择
若运算符既可声明为友元又可声明为成员,究竟该如何取舍?需先了解以下限制:
1. 某些运算符必须以成员函数形式重载
赋值(=
)、下标([]
)、函数调用(()
)、成员访问(->
)运算符,语言强制要求它们必须是成员函数。
2. 某些运算符无法以成员函数形式重载
在 Point
类重载了 operator<<
。回顾如下:
#include <iostream>
class Point
{
private:
double m_x {};
double m_y {};
double m_z {};
public:
Point(double x = 0.0, double y = 0.0, double z = 0.0)
: m_x { x }, m_y { y }, m_z { z }
{
}
friend std::ostream& operator<< (std::ostream& out, const Point& point);
};
std::ostream& operator<< (std::ostream& out, const Point& point)
{
// 因 operator<< 是 Point 的友元,可直接访问其成员
out << "Point(" << point.m_x << ", " << point.m_y << ", " << point.m_z << ")";
return out;
}
int main()
{
Point point1 { 2.0, 3.0, 4.0 };
std::cout << point1;
return 0;
}
我们无法将 operator<<
实现为成员函数,原因在于:
- 重载运算符必须作为“左操作数”所属类的成员;
- 此处左操作数为
std::ostream
类型,而std::ostream
属于标准库,无法修改其定义以添加成员。
因此,operator<<
只能以普通函数(首选)或友元函数形式实现。
同理,虽然 operator+(Cents, int)
可以写成成员函数,但 operator+(int, Cents)
却无法写成成员函数,因为 int
并非可添加成员的类。
六、何时使用普通函数、友元函数或成员函数
语言在大多数情况下允许自由选择,但通常其中一种形式更为合适。经验法则如下:
- 若重载赋值(
=
)、下标([]
)、函数调用(()
)或成员访问(->
)运算符,务必使用成员函数。 - 若重载一元运算符,通常使用成员函数(成员版本无形参)。
- 若重载不修改左操作数的二元运算符(如
operator+
),优先使用普通函数或友元函数,因其对所有参数类型均适用(即使左操作数不是类对象或无法修改),且所有操作数均为显式形参,具有“对称性”。 - 若重载会修改左操作数的二元运算符,但无法修改左操作数所属类的定义(例如
operator<<
,其左操作数为ostream
),则使用普通函数(首选)或友元函数。 - 若重载会修改左操作数的二元运算符(如
operator+=
),且能够修改左操作数所属类的定义,则使用成员函数。