C++基础教程面向对象(学习笔记(22))

使用成员函数重载运算符

在前面使用友元函数重载算术运算符,您学习了如何使用友元函数重载算术运算符。您还了解到可以将运算符重载为正常函数。许多运算符可以以不同的方式重载,比如作为成员函数。

使用成员函数重载运算符与使用友元函数重载运算符非常相似。使用成员函数重载运算符时:

必须将重载运算符添加为左操作数的成员函数。
左操作数成为隐含的​​此对象
所有其他操作数都成为函数参数。

提醒一下,这是我们如何使用友元函数重载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() { return m_cents; }
};
 
//注意: 这不是成员函数
Cents operator+(const Cents &cents, 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 +),请将其作为普通函数或朋友函数执行。

猜你喜欢

转载自blog.csdn.net/qq_41879485/article/details/83067839