使用成员函数重载运算符
本节阅读量:前面我们学习了如何使用友元函数和普通函数重载运算符。下面来学习如何使用成员函数重载运算符。
使用成员函数重载运算符与使用友元函数重载运算符非常相似。使用成员函数重载运算符时:
- 重载运算符是左操作数的成员函数。
- 左操作数成为隐式*this对象。
- 所有其他操作数都成为函数参数。
下面是使用友元函数重载operator+的方法:
|
|
将友元函数形式的重载运算符转换为成员函数形式很容易:
- 将重载运算符定义为成员函数,而不是友元函数(Cents::operator+,而不是友元operator+)。
- 移除左侧参数,因为它现在变成了隐式的*this对象。
- 在函数体内,移除对左侧参数的显式使用(例如,cents.m_cents变为m_cents,因为它已经通过*this访问)。
现在,使用成员函数方法重载相同的运算符:
|
|
注意,运算符的用法没有改变(两种情况下都是cents1+2),我们只是用不同方式定义了函数。双参数友元函数变成了单参数成员函数,友元版本中最左侧的参数(cents)成为成员函数版本中的隐式*this。
让我们仔细看看表达式cents1+2的计算方式。
在友元函数版本中,表达式cents1+2会变成函数调用operator+(cents1,2)。注意,这里有两个函数参数。这很简单。
在成员函数版本中,表达式cents1+2会变成函数调用cents1.operator+(2)。请注意,现在只有一个显式函数参数,并且cents1成为了对象前缀。编译器会隐式地将对象前缀转换为名为*this的最左侧隐藏参数。因此,实际效果上cents1.operator+(2)会变成operator+(¢s1,2),这几乎与友元版本相同。
这两种情况产生相同的结果,只是方式略有不同。
因此,如果某个运算符既可以重载为友元函数,也可以重载为成员函数,我们应该选择哪一种?要回答这个问题,还需要了解一些内容。
并非所有内容都可以作为友元函数重载
赋值(=)、下标([])、函数调用(())和成员选择(->)运算符必须重载为成员函数,因为语言要求它们是成员函数。
并非所有内容都可以作为成员函数重载
在前面重载I/O运算符时,我们使用友元函数方式为Point类重载了operator«。下面是对应示例:
|
|
然而,我们不能将运算符«作为成员函数重载。为什么呢?因为重载运算符必须作为左操作数的成员。在这种情况下,左操作数是std::ostream类型的对象,而std::ostream属于标准库。我们不能修改它的类声明,把重载添加为std::ostream的成员函数。
因此,需要将运算符«重载为普通函数(首选)或友元函数。
类似地,尽管我们可以将operator+(Cents,int)作为成员函数重载(正如上面所做的),但不能将operator+(int,Cents)作为成员函数重载,因为int不是可以添加成员的类。
通常,如果左操作数不是类(例如int),或者是无法修改的类(例如std::ostream),就无法使用成员函数重载。
何时使用普通、友元或成员函数重载
在大多数情况下,语言允许您自行决定使用普通/友元函数版本,还是成员函数版本。然而,其中一种通常会比另一种更合适。
当处理不修改左操作数的二元运算符(例如operator+)时,通常首选普通函数或友元函数版本,因为它适用于所有参数类型(即使左操作数不是类对象,或者是不可修改的类)。普通函数或友元函数版本还具有“对称性”的优势,因为所有操作数都会成为显式参数(而不是左操作数变成*this,右操作数才是显式参数)。
当处理会修改左操作数的二元运算符(例如operator+=)时,通常首选成员函数版本。在这些情况下,最左侧的操作数通常是类类型,直接在该对象上操作也更自然。因为右侧操作数会成为显式参数,所以不会混淆谁被修改、谁只是被读取。
一元运算符通常也作为成员函数重载,因为成员版本没有参数。
以下经验法则可以帮助您确定哪种形式最适合给定的情况:
- 如果重载赋值(=)、下标([])、函数调用(())或成员选择(->),请作为成员函数进行重载。
- 如果要重载一元运算符,请作为成员函数进行重载。
- 如果重载不修改其左操作数的二元运算符(例如,运算符+),请作为普通函数(首选)或友元函数进行重载。
- 如果重载修改其左操作数的二元运算符,但左侧操作数不允许添加成员函数(例如,运算符«,它具有ostream类型的左操作数)的类定义中,请作为普通函数(首选)或友元函数进行重载。
- 如果重载修改其左操作数的二元运算符(例如,运算符+=),并且可以修改左操作数的定义,请作为成员函数进行重载。