您经常需要重载的三个重要运算符
探讨三个你经常需要重载的重要运算符。
引言
在我们学习的大部分 C++ 书籍中,都提到了关于运算符重载的几个非常重要的观点。不幸的是,我们大多数人要么忽略了这些观点,要么在编写代码时认为这些观点不够重要。根据我的亲身经验,我将提交关于“运算符重载”的五个重要方面。希望大家觉得这篇文章很有趣。
使用代码
第 1 点 ==> 将运算符重载为全局函数或成员函数都可以,只要“左侧”操作数确实是你要“操作”的类的对象。
有些人争辩说,因为在某些情况下我们必须需要将运算符重载为全局函数,所以我们应该使用通用的编程结构,即,将所有运算符都重载为全局函数。这完全是程序员自己的选择。从来没有强制要求这样做。只要运算符左侧的操作数确实是“待操作”类的对象,你就可以将该运算符重载为成员函数。而且,这确实是一个好习惯,唯一的区别是它需要更少的参数(如果你将运算符重载定义为全局函数,你需要传递的第一个参数是对运算符将要作用的类的引用)。
也就是说,如果你尝试将一元运算符重载为成员函数,你根本不需要传递参数;对于二元运算符,只传递一个参数就足够了。
考虑
#include <iostream>
class complex{
private:
double re,im;
public:
complex(double=0.0,double=0.0);
complex operator+(complex);
complex operator*(complex);
};
在这里,我们的目标是重载两个二元运算符:加法 (+) 和乘法 (*)。隐式的“this”指针足以满足我们对“左侧”操作数的需求。你只需要将右侧操作数作为参数传递即可。
第 2 点 ==> 当“左侧”操作数超出你的控制范围时,你必须将运算符重载为全局函数,但友元函数呢?
这个的经典例子是什么?是的,你没猜错,是流插入 (<<) 和流提取 (>>) 运算符。为了在我们设计的类中重载这些运算符(这样我们就可以像使用原始类型一样使用我们自己类的对象与 cout 和/或 cin 进行交互),我们首先需要了解这些运算符实际上是如何工作的。考虑以下内容。
#include <iostream>
int main()
{
int var=10;
std::cout<<var;
return 0;
}
在这里,左侧操作数是 ostream&
类型,例如 std::cout <<var;
。而右侧操作数是原始类型。因此,如果我们想为我们自己的类做同样的事情,左侧操作数将保持不变,即 ostream&
,而右侧操作数将是我们自己类的对象。
现在,只有当左侧操作数是我们的对象时,我们才能将运算符重载为成员函数。因此,要将 <<
运算符重载为成员函数,它必须是 ostream
类的成员。现在想想,这可能吗?我们允许修改 C++ STL 提供的标准吗?简单的答案是“绝不”。
同样的逻辑也适用于“流提取” (>>
) 运算符,唯一的区别是这里的左侧操作数是“istream&
”。
所以,到目前为止一切都很清楚。我们同意使用全局函数来重载“流插入”和“流提取”运算符。自然,这个函数不是类的一部分,因此这个函数将无法访问类的私有成员。这太荒谬了。如果该函数无法访问类的私有成员,那么这个运算符有什么用?没用。
感谢 Bjarne Stroustrup 先生为我们提供了绝妙的解决方案:友元函数。是的,如果我们只是将该函数声明为 friend
,我们就万事俱备了。现在,我们仍然使用全局函数,但因为它是一个类的“友元”,所以类会很乐意授予它访问其私有成员的权限。所以,请始终使用以下结构。
#include <iostream> using std::cout; using std::cin; using std::ostream; using std::istream; class complex{ friend ostream& operator<<(ostream&, const complex&); friend istream& operator>>(istream&, complex&); private: double re,im; public: complex(double=0.0,double=0.0); complex operator+(complex); complex operator*(complex); }; ostream& operator<<(ostream& mystream, const complex& cmp){ mystream<<cmp.re; mystream<<cmp.im; return mystream; } istream& operator>>(istream& mystream, complex& cmp) { mystream>>cmp.re; mystream>>cmp.im; return mystream; }
你真的、必须在这里使用全局函数吗?现在,你有选择。如果你的类设计得当,并且在其公共接口中为你正在查找的所有成员提供了 getter 方法(访问器),你可以在全局函数中通过对象/对象引用来使用这些方法。你不需要将你的运算符重载函数声明为该类的友元。
第 3 点 ==> 你是否计划重载下标运算符 ([ ])?如果是,请牢记“可修改的左值”和“常量右值”这两种情况。
假设你计划编写一个高级数组类,例如具有新的“边界检查”功能(默认数组没有此功能)。你需要重载的一个最常见的运算符是下标运算符([ ]
)。尽管它看起来很简单,但在不集中注意力的情况下重载此运算符会导致一个非常著名且流行的错误:猜猜是哪个。
下标运算符有两种用法。
- 如果它用在赋值(=)的左侧,我们打算使用该索引位置的“存储桶”,并打算将新值放入该占位符。明白了吗?因此,无论如何,我们需要对该索引位置拥有完全的写入访问权限。用 C++ 的术语来说,你必须拥有该“存储桶”位置的“引用访问权限”。也就是说,我们需要拥有可修改的“
lvalue
”访问权限。 - 如果它用在赋值(=)的右侧,或者作为独立使用(例如在
cout
语句中),我们打算使用一个 const 的“rvalue
”。
所以,在重载此运算符时,我们必须提供两个不同的版本。
// Version one, returning modifiable l-vaue int& Array::operator[](int index) { if(index<0 || index>=size) { cout<<"Bad Index...........exiting..."<<endl; exit(1); } else return ptr[index]; } //version two, returning constant r-value int Array::operator[](int index) const { if(index<0 || index>=size) { cout<<"Bad Index...........existing..."<<endl; exit(1); } else return ptr[index]; }
参考文献
- C++ 程序设计语言:Bjarne Stroustrup
- C++ 程序设计