AutoCAD 3DMAX C语言 Pro/E UG JAVA编程 PHP编程 Maya动画 Matlab应用 Android
Photoshop Word Excel flash VB编程 VC编程 Coreldraw SolidWorks A Designer Unity3D
 首页 > C++

C++的算符重载

51自学网 http://www.51zixue.net



  为什么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()方法不会损失什么,而可能获得一些东西——它不会更差,却可能更好。

使用 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_;
};

  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 模板。

 
 

上一篇:计算机编程的24条法则  下一篇:Access&nbsp;Violations(访问冲突)