C++的算符重载
当前位置:以往代写 > C/C++ 教程 >C++的算符重载
2019-06-13

C++的算符重载

C++的算符重载

副标题#e#

算符重载的浸染是什么?它答允你为类的用户提供一个直觉的接口。 算符重载答允C/C++的运算符在用户界说范例(类)上拥有一个用户界说的意义。重载的算符是函数挪用的语法修饰:  

class Fred {
  public:    // …
};
#if 0           // 没有算符重载:
Fred add(Fred, Fred);
Fred mul(Fred, Fred);
Fred f(Fred a, Fred b, Fred c)
{
  return add(add(mul(a,b), mul(b,c)), mul(c,a)); // 哈哈,多好笑…
}
#else   // 有算符重载:
Fred operator+ (Fred, Fred);
Fred operator* (Fred, Fred);
Fred f(Fred a, Fred b, Fred c)
{
  return a*b + b*c + c*a;
}
#endif

算符重载的长处是什么?

通过重载类上的尺度算符,你可以掘客类的用户的直觉。使得用户措施所用的语言是面向问题的,而不是面向呆板的。 最终方针是低落进修曲线并淘汰错误率。

有什么算符重载的实例?这里有一些算符重载的实例:

myString + yourString 可以毗连两个 std::string 工具

myDate++ 可以增加一个 Date 工具

a * b 可以将两个 Number 工具相乘

a[i] 可以会见 Array 工具的某个元素

x = *p 可以反引用一个实际“指向”一个磁盘记录的 "smart pointer" —— 它实际上在磁盘上定位到 p 所指向的记录并返回给x。

可是算符重载使得我的类很丑恶;莫非它不是应该使我的类更清晰吗?算符重载使得类的用户的事情更浅易,而不是为类的开拓者处事的! 思量一下如下的例子:

class Array {
  public:
int& operator[] (unsigned i);
};
inline
int& Array::operator[] (unsigned i)
{   // …
}

有些人不喜欢operator要害字或类体内的有些离奇的语法。可是算符重载语法不是被期望用来使得类的开拓者的事情更浅易。它被期望用来使得类的用户的事情更浅易:  

int main()
{
  Array a;
  a[3] = 4; // 用户代码应该明明并且易懂…
}

记着:在一个面向重用的世界中,利用你的类的人有许多,而制作它的人只有一个(你本身);因此你做任何事都应该照顾大都而不是少数。

什么算符能/不能被重载?大大都都可以被重载。C的算符中只有 . 和 ? :(以及sizeof,技能上可以看作一个算符)。C++增加了一些本身的算符,除了::和.*,大大都都可以被重载。 这是一个下标算符的示例(它返回一个引用)。先没有算符重载:

class Array {
public:
int& elem(unsigned i) {  if (I > 99) error(); return data[i]; }
  private:
  int data[100];
};
int main()
{
  Array a;
  a.elem(10) = 42;
  a.elem(12) += a.elem(13);
}
   此刻用算符重载给出同样的逻辑:
class Array {
public:
int& operator[] (unsigned i)  {  if (I > 99) error(); return data[i]; }
  private:
  int data[100];
};
int main()
{
  Array a;
  a[10] = 42;
  a[12] += a[13];
}

我能重载 operator== 以便较量两个 char[] 来举办字符串较量吗?不可:被重载的算符,至少一个操纵数必需是用户界说范例(大大都时候是类)。 但纵然C++答允,也不要这样做。因为在此处你应该利用雷同 std::string的类而不是字符数组,因为数组是有害的。因此无论如何你都不会想那样做的。

我能为“幂”运算建设一个 operator** 吗?不可。 运算符的名称、优先级、团结性以及元数都是由语言牢靠的。在C++中没有operator**,因此你不能为类范例建设它。

假如尚有疑问,思量一下x ** y与x * (*y)等同(换句话说,编译器假定 y 是一个指针)。另外,算符重载只不外是函数挪用的语法修饰。固然这种非凡的语法修饰很是美好,但它没有增加任何本质的对象。我发起你重载pow(base,exponent)(双精度版本在中)。

顺便提一下,operator^可以成为幂运算,只是优先级和团结性是错误的。

如作甚Matrix(矩阵)类建设下标运算符? [Recently changed so it uses new-style headers and the std:: syntax (on 7/00). Click here to go to the next FAQ in the "chain" of recent changes.]


#p#副标题#e#

用 operator()而不是operator[]。

当有多个下标时,最清晰的方法是利用operator()而不是operator[]。原因是operator[]老是带一个参数,而operator()可以带任何数目标参数(在矩形的矩阵环境下,需要两个参数)。

如:

class Matrix {
  public:
  Matrix(unsigned rows, unsigned cols);
  double& operator() (unsigned row, unsigned col);
  double operator() (unsigned row, unsigned col) const; // …
  Matrix(); // 析构函数
  Matrix(const Matrix& m); // 拷贝结构函数
  Matrix& operator= (const Matrix& m); // 赋值算符   // …
  private:
  unsigned rows_, cols_;
  double* data_;
};
inline
Matrix::Matrix(unsigned rows, unsigned cols)
: rows_ (rows),
cols_ (cols),
data_ (new double[rows * cols])
{
  if (rows == 0 || cols == 0)
  throw BadIndex("Matrix constructor has 0 size");
}
inline
Matrix::~Matrix()
{
  delete[] data_;
}
inline
double& Matrix::operator() (unsigned row, unsigned col)
{
  if (row >= rows_ || col >= cols_)
  throw BadIndex("Matrix subscript out of bounds");
  return data_[cols_*row + col];
}
inline
double Matrix::operator() (unsigned row, unsigned col) const
{
  if (row >= rows_ || col >= cols_)
  throw BadIndex("const Matrix subscript out of bounds");
  return data_[cols_*row + col];
}
   然后,你可以利用m(I,j)来会见Matrix m 的元素,而不是m[i][j]:
int main()
{
  Matrix m(10,10);
  m(5,8) = 106.15;
  std::cout << m(5,8);  // …
}

#p#分页标题#e#

为什么Matrix(矩阵)类的接口不该该象数组的数组?本FAQ其实是关于:某些人成立的Matrix 类,带有一个返回 Array 工具的引用的operator[]。而该Array 工具也带有一个 operator[] ,它返回Matrix的一个元素(譬喻,一个double的引用)。因此,他们利用雷同m[i][j]的语法来会见矩阵的元素,而不是象m(I,j)的语法。

数组的数组方案显然可以事情,但相对付operator()要领来说,缺乏机动性。尤其是,用[][]要领很难表示的时候,用operator()要领可以很简朴的完成,因此[][]要领很大概导致差劲的表示,至少某些环境细是这样的。

譬喻,实现[][]要领的最简朴途径就是利用作为麋集矩阵的,以以行为主的形式生存(或以列为主,我记不清了)的物理机关。相反,operator() 要领完全埋没了矩阵的物理机关,在这种环境下,它大概带来更好的表示。

可以这么认为:operator()要领永远不比[][]要领差,有时更好。

operator() 永远不差,是因为用operator()要领实现以行为主的麋集矩阵的物理机关很是容易。因此,当从机能概念出发,那样的布局正好是最佳机关时,operator()要领也和[][]要领一样简朴(也许operator()要领更容易一点点,但我不想过甚其辞)。 Operator() 要领有时更好,是因为当对付给定的应用,有其它比以行为主的麋集矩阵更好的机关时,用 operator() 要领比[][]要领实现会容易得多。 作为一个物理机关使得实现坚苦的例子,最近的项目产生在以列会见矩阵元素(也就是,算法会见一列中的所有元素,然后是另一列等),假如物理机关是以行为主的,对矩阵的会见大概会“cache失效”。譬喻,假如行的巨细险些和处理惩罚器的cache巨细相当,那么对每个元素的会见,城市产生“cache不掷中”。在这个非凡的项目中,我们通过将映射从逻辑机关(行,列)变为物理机关(列,行),机能获得了20%的晋升。

虽然,尚有许多这类工作的例子,而稀疏矩阵在这个问题中则是又一类例子。凡是,利用operator()要领实现一个稀疏矩阵或互换行/列顺序更容易,operator()要领不会损失什么,而大概得到一些对象——它不会更差,却大概更好。

#p#副标题#e#

利用 operator() 要领。

该从外(接口优先)照旧从内(数据优先)设计类?从外部! 精采的接口提供了一个简化的,以用户词汇表达的视图。在面向工具软件的环境下,接口凡是是单个类或一组细密团结的类的public要领的荟萃. 首先思量工具的逻辑特征是什么,而不是规划如何建设它。譬喻,假设要建设一个Stack(栈)类,其包括一个 LinkedList:  

class Stack {
  public:  // …
  private:
  LinkedList list_;
};

Stack是否应该有一个返回LinkedList的get()要领?可能一个带有LinkedList的set()要领?可能一个带有LinkedList的结构函数?显然,谜底是“不”,因为应该从外向里设计接口。也就是说,Stack工具的用户并不体贴 LinkedList;他们只体贴 pushing 和 popping。

此刻看另一个更微妙的例子。假设 LinkedList类利用Node工具的链表来建设,每一个Node工具有一个指向下一个Node的指针:  

class Node { /*…*/ };
class LinkedList {
  public:  // …
  private:
  Node* first_;
};

#p#分页标题#e#

LinkedList类是否应该有一个让用户会见第一个Node的get()要领?Node 工具是否应该有一个让用户会见链中下一个 Node 的 get()要领?换句话说,从外部看,LinkedList应该是什么样的?LinkedList 是否实际上就是一个 Node 工具的链?可能这些只是实现的细节?假如只是实现的细节,LinkedList 将如何让用户在某时刻会见 LinkedList 中的每一个元素?

或人的答复:LinkedList 不是的 Node 链。它大概简直是用 Node 建设的,但这不是本质。它的本质是元素的序列。因此,LinkedList 象应该提供一个“LinkedListIterator”,而且“LinkedListIterator”应该有一个operator++ 来会见下一个元素,而且有一对get()/set()来会见存储于Node 的值(Node 元素中的值只由LinkedList用户认真,因此有一对get()/set()以答允用户自由地维护该值)。

从用户的概念出发,我们大概但愿 LinkedList类支持看上去雷同利用指针算法会见数组的算符:  

void userCode(LinkedList& a)
{
  for (LinkedListIterator p = a.begin(); p != a.end(); ++p)
  std::cout << *p << '\n';
}

实现这个接口,LinkedList需要一个begin()要领和end()要领。它们返回一个“LinkedListIterator”工具。该“LinkedListIterator”需要一个前进的要领,++p ;会见当前元素的要领,*p;和一个较量算符,p != a.end()。

如下的代码,要害在于 LinkedList 类没有任何让用户会见 Node 的要领。Node 作为实现技能被完全地埋没了。LinkedList内部大概用双重链表代替,甚至是一个数组,区别仅仅在于一些诸如

prepend(elem) 和 append(elem)要领的机能上。

#include // Poor man's exception handling
class LinkedListIterator;
class LinkedList;
class Node {   // No public members; this is a "private class"
  friend LinkedListIterator; // 友员类
  friend LinkedList;
  Node* next_;
  int elem_;
};
class LinkedListIterator {
public:
bool operator== (LinkedListIterator i) const;
bool operator!= (LinkedListIterator i) const;
void operator++ (); // Go to the next element
int& operator* (); // Access the current element
private:
LinkedListIterator(Node* p);
Node* p_;
friend LinkedList; // so LinkedList can construct a LinkedListIterator
};
class LinkedList {
  public:
  void append(int elem); // Adds elem after the end
  void prepend(int elem); // Adds elem before the beginning  // …
  LinkedListIterator begin();
  LinkedListIterator end();  // …
  private:
  Node* first_;
};

这些是显然可以内联的要领(大概在同一个头文件中):  

inline bool LinkedListIterator::operator== (LinkedListIterator i) const
{
  return p_ == i.p_;
}
vinline bool LinkedListIterator::operator!= (LinkedListIterator i) const
{
  return p_ != i.p_;
}
  inline void LinkedListIterator::operator++()
{
  assert(p_ != NULL); // or if (p_==NULL) throw …
  p_ = p_->next_;
}
inline int& LinkedListIterator::operator*()
{
  assert(p_ != NULL); // or if (p_==NULL) throw …
  return p_->elem_;
}
inline LinkedListIterator::LinkedListIterator(Node* p)
: p_(p)
{ }
inline LinkedListIterator LinkedList::begin()
{
  return first_;
}
inline LinkedListIterator LinkedList::end()
{
  return NULL;
}

结论:链表有两种差异的数据。存储于链表中的元素的值由链表的用户认真(而且只有用户认真,链表自己不阻止用户将第三个元素酿成第五个),而链表底层布局的数据(如 next 指针等)值由链表认真(而且只有链表认真,也就是说链表不让用户改变(甚至看到!)可变的next 指针)。

因此 get()/set() 要领只获取和配置链表的元素,而不是链表的底层布局。由于链表埋没了底层的指针等布局,因此它可以或许作很是严格的理睬(譬喻,假如它是双重链表,它可以担保每一个后向指针都被下一个 Node 的前向指针匹配)。

我们看了这个例子,类的一些数据的值由用户认真(这种环境下需要有针对数据的get()/set()要领),但对付类所节制的数据则不必有get()/set()要领。

留意:这个例子的目标不是为了汇报你如何写一个链表类。实际上不要本身做链表类,而应该利用编译器所提供的“容器类”的一种。理论上来说,要利用尺度容器类之一,如:std::list 模板。

    关键字:

在线提交作业