使用成员函数重载运算符
在前面使用友元函数重载算术运算符,您学习了如何使用友元函数重载算术运算符。您还了解到可以将运算符重载为正常函数。许多运算符可以以不同的方式重载,比如作为成员函数。
使用成员函数重载运算符与使用友元函数重载运算符非常相似。使用成员函数重载运算符时:
必须将重载运算符添加为左操作数的成员函数。
左操作数成为隐含的此对象
所有其他操作数都成为函数参数。
提醒一下,这是我们如何使用友元函数重载operator +:
#include <iostream>
class Cents
{
private:
int m_cents;
public:
Cents(int cents) { m_cents = cents; }
// 重载 Cents + int
friend Cents operator+(const Cents ¢s, int value);
int getCents() { return m_cents; }
};
//注意: 这不是成员函数
Cents operator+(const Cents ¢s, int value)
{
return Cents(cents.m_cents + value);
}
int main()
{
Cents cents1(6);
Cents cents2 = cents1 + 2;
std::cout << "I have " << cents2.getCents() << " cents.\n";
return 0;
}
将友元函数重载运算符转换为成员重载运算符很容易:
重载的运算符被定义为成员而不是友元(Cents :: operator +而不是friend operator +)
删除左参数,因为该参数现在变为隐式此对象。
在函数体内部,可以删除对左参数的所有引用(例如,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);
int getCents() { return m_cents; }
};
// 注意: 这不是成员函数
// 友元版本中的cents参数现在是隐式*此参数
Cents Cents::operator+(int value)
{
return Cents(m_cents + value);
}
int main()
{
Cents cents1(6);
Cents cents2 = cents1 + 2;
std::cout << "I have " << cents2.getCents() << " cents.\n";
return 0;
}
请注意,运算符的使用不会改变(在这两种情况下cents1 + 2),我们只是简单地定义了函数。我们的双参数友元函数成为单参数成员函数,友元版本中最左边的参数(cents)成为成员函数版本中的隐含* this参数。
让我们仔细看看表达式如何调用cents1 + 2。
在友元函数版本中,表达式cents1 + 2变为函数调用operator +(cents1,2)。请注意,有两个函数参数。这很简单。
在成员函数版本中,表达式cents1 + 2变为函数调用cents1.operator +(2)。请注意,现在只有一个显式函数参数,并且cents1已成为对象前缀。但是,隐藏的“this”指针中,编译器隐式地将对象前缀转换为名为* this的隐藏的最左侧参数。所以实际上,cents1.operator +(2)变成operator +(&cents1,2),这几乎与友元函数版本相同。
两种情况都会产生相同的结果,只是略有不同。
因此,如果我们可以将操作员作为友元函数或成员函数重载,我们应该使用哪个?为了回答这个问题,还有一些事情你需要知道。
并非一切都可以作为友元函数重载
赋值(=),下标([]),函数调用(())和成员选择( - >)运算符必须作为成员函数重载,因为语言需要它们。
并非所有内容都可以作为成员函数重载
在重载I / O运算符中,我们使用friend函数方法为我们的Point类重载了operator <<。这里提醒我们如何做到这一点:
#include <iostream>
class Point
{
private:
double m_x, m_y, 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类的朋友,我们可以直接访问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作为标准库的一部分被引用。我们不能修改类声明来将重载添加为std :: ostream的成员函数。
这需要operator<<作为友元重载。
类似地,虽然我们可以将operator +(Cents,int)重载为成员函数(如上所述),但我们不能将operator +(int,Cents)重载为成员函数,因为int不是我们可以添加成员的类至。
通常,如果左操作数不是类(例如int),或者它是我们无法修改的类(例如std :: ostream),我们将无法使用成员重载。
何时使用普通,友元函数或成员函数重载?
在大多数情况下,语言会由您决定是否要使用重载的普通/友元或成员函数版本。但是,两者中的一个通常是比另一个更好的选择。
处理不修改左操作数的二元运算符(例如operator +)时,通常首选普通函数或友元函数版本,因为它适用于所有参数类型(即使左操作数不是类对象,或者是一个不可修改的类)。普通函数或友元函数版本具有“对称性”的附加好处,因为所有操作数都变为显式参数(而不是左操作数变为* this且右操作数变为显式参数)。
当处理修改左操作数的二元运算符(例如operator + =)时,通常首选成员函数版本。在这些情况下,最左边的操作数将始终是类类型,并且正在修改的对象成为*this指向的对象,这很自然。因为最右边的操作数变成了一个显式参数,所以不会混淆谁被修改以及谁被调用。
一元运算符通常也会作为成员函数重载,因为成员版本没有参数。
以下经验法则可帮助您确定哪种形式最适合给定情况:
如果您正在重载赋值(=),下标([]),函数调用(())或成员选择( - >),请将其作为成员函数执行。
如果您正在重载一元运算符,请将其作为成员函数执行。
如果你正在重载修改其左操作数的二元运算符(例如operator + =),那么如果可以的话,就这样做。
如果您正在重载不修改其左操作数的二元运算符(例如operator +),请将其作为普通函数或朋友函数执行。