您的位置:首页 > 其它

区别increment/decrement操作符的前置(prefix)和后置(postfix)形式

2016-03-02 00:24 701 查看
  最近在很多地方看到了这个问题,但是网上都没有很好的解释,很多面试书中解释的也不详尽,今晚翻阅《More Effective C++》的时候偶尔看到了这个解释,恍然大悟,心情大好,这里记录下来。

背景

  很久很久以前(大约20世纪80年代后期),在一个遥远的语言(我是指当时的C++)中,没有什么办法可以区分++和–操作符的前置式(prefix)和后置式(postfix)。

  但程序员毕竟是程序员,他们动不动就对此情况发个牢骚,于是C++决定扩充,允许 ++ 和 – 操作符的两种形式(前置式和后置式)拥有重载能力。

  这时候出现了一个语法上的问题:重载函数是以其参数类型来区分彼此,然而不论increment或decrement操作符的前置式或后置式,都没有参数。为了填平这个语言学上的漏洞,只好让后置式有一个int自变量,并且在它被调用时,编译器默默地为该int指定一个0值。

实现规则

class UPInt {
UPInt& operator++();
const UPInt operator++(int);

UPInt& operator--();
const UPInt operator--(int);

UPInt &operator+=();
...
};

UPInt i;
++i;    //调用i.operator++();
i++;    //调用i.operator++(0);
--i;    //调用i.operator--();
i--;    //调用i.operator--(0);


  这样的规则或许有点怪异,但你很快就会习惯。重要的是,那些操作符的前置式和后置式返回不同的类型,前置式返回一个reference,后置式返回一个const对象。以下我集中讨论++操作符的前置式和后置式,至于–操作符,故事一样。

  从C的时代回忆起,你或许还记得所谓increment操作符的前置式意义“increment and fetch”(累加然后取出),后置式意义“fetch and increment”(取出然后累加)。这两个词组值得记下来,因为他们几乎成为前置式和后置式increment操作符应该如何实现的正式规范:

//前置式:累加然后取出(increment and fetch)
UPInt& UPInt::operator++() {
*this += i;      //累加(increment)
return *this;    //取出(fetch)
}


//后置式:取出然后累加(fetch and increment)
const UPInt UPInt::operator(int) {
UPInt oldValue = *this;  //取出(fetch)
++(*this);               //累加(increment)
return oldValue;         //返回先前被取出的值
}


  请注意前置式操作符并未动用其参数。是的,其参数的唯一目的只是为了区别前置式和后置式而已。如果你在函数体内没有使用函数的命名参数,许多编译器会对此发出警告,可能会让你觉得厌烦。为了避免这类警告,一种常见的策略就是故意略去你不打算使用的参数的名称,这正式以上代码实行的策略。

  那为什么后置式increment操作符必须返回一个对象(代表旧值),原因很清楚了。但为什么是个const对象呢?

想象一下,如果不是这样,以下动作是合法的:

UPInt i;
i++++;    //实施“后置式increment操作符”两次


  这和以下动作相同:

i.operator++(0).operator++(0);


  这就拨云见日了:operator++的第二个调用动作施行于第一个调用动作的返回对象身上。

  两个理解使我们不欢迎这样的情况。

  第一,它和内建类型的行为不一致。设计classes的一条无上宝典就是:一旦有疑虑,试看ints行为如何并遵循之。我们知道,ints并不允许连续两次使用后置式increment操作符:

int i;
i++++;   //错误!(译注:++++i则合法)


  第二个理由是,即使能够两次施行后置式increment操作符,其行为也非你所预期。一如上述所示,第二个operator++所改变的对象是第一个operator++返回的对象,而不是原对象。因此即使下式合法:

i++++;


  i也只被累加一次而已。这是违反直觉的,也容易引起混淆(不论是对ints或UPInts),所以最好的办法就是禁止它合法化。

  C++针对ints禁止了上述行为,而你则必须针对你所设计的classes自行动手加以禁止。最简单的做法就是让后置式increment操作符返回一个const对象。于是当编译器看到:

i++++;    //视同i.operator++(0).operator++(0);


  它便认知到,第一次调用operator++所返回的const对象,将被用来进行operator的第二次调用。然而operator++是个non-const member function,所以const对象(亦即本例的后置式operator++返回值)无法调用之。但是,不执行这项限制的编译器也时有所闻。如果你层困惑“令函数返回const对象是否合理”,现在你就知道了:有时候的确需要如此,后置式increment和decrement操作符就是个例子。

执行效率

  如果你很担心效率问题,当你初次看到后置式increment函数,或许会头冒冷汗。该函数必须产生一个临时对象,作为返回值之用。上述实现代码也的确产生了一个明显的临时对象(oldValue),需要构造也需要析构。前置式increment函数就没有如此的临时对象。这导致一个令人吃惊的结论,但以效率因素而言,UPInt的用户应该更喜欢前置式increment多过后置式increment,除非他们真的需要后置式increment的行为。让我们把话说清楚,处理用户定制类型时,应尽可能使用前置式increment,因为它天生体质较佳。

  现在让我们对increment操作符的前置式和后置式做更一步观察。除了返回值之外,他们做相同的事情:将某值累加。那么你如何确定后置式increment与前置式increment的行为一致?你如何保证他们的实现代码不会因时间而分道扬镳?说不定不同的程序员对它们做了不同的维护与强化。除非遵照上述代码所表现的设计原则,否则你将毫无保障。那个原则是:后置式increment和decrement操作符的实现应以其前置式兄弟为基础。如此一来你就只需要维护前置式版本,因为后置式版本会自动调整为一致的行为。

总结

  如你所见,掌握increment和decrement操作符的前置式和后置式是很容易的。一旦你知道它们应该返回什么类型,以及后置式操作符应以前置式操作符为实现基础,就几乎没有什么更高阶的知识需要学习了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息