More Effective C++ 条款6

原创 2001年09月27日 23:04:00

条款6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

很久以前(八十年代),没有办法区分++和--操作符的前缀与后缀调用。这个问题遭到程序员的报怨,于是C++语言得到了扩展,允许重载increment decrement操作符的两种形式。

 

然而有一个句法上的问题,重载函数间的区别决定于它们的参数类型上的差异,但是不论是incrementdecrement的前缀还是后缀都只有一个参数。为了解决这个语言问题,C++规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数:

 

class UPInt {                            // "unlimited precision int"

public:

  UPInt& operator++();                   // ++ 前缀

  const UPInt operator++(int);           // ++ 后缀

 

  UPInt& operator--();                   // -- 前缀

  const UPInt operator--(int);           // -- 后缀

 

  UPInt& operator+=(int);                // += 操作符,UPInts

                                         // ints 相运算

  ...

};

UPInt i;

++i;                                     // 调用 i.operator++();

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

--i;                                     // 调用 i.operator--();

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

 

这个规范有一些古怪,不过你会习惯的。而尤其要注意的是这些操作符前缀与后缀形式返回值类型是不同的。前缀形式返回一个引用,后缀形式返回一个const类型。下面我们将讨论++操作符的前缀与后缀形式,这些说明也同样使用与--操作符。

从你开始做C程序员那天开始,你就记住increment的前缀形式有时叫做“增加然后取回”,后缀形式叫做“取回然后增加”。这两句话非常重要,因为它们是increment前缀与后缀的形式上的规范。

 

// 前缀形式:增加然后取回值

UPInt& UPInt::operator++()

{

  *this += 1;                             // 增加

  return *this;                           // 取回值

}

// postfix form: fetch and increment

const UPInt UPInt::operator++(int)

{

  UPInt oldValue = *this;                 // 取回值

  ++(*this);        // 增加

return oldValue;                          // 返回被取回的值

}

 

后缀操作符函数没有使用它的参数。它的参数只是用来区分前缀与后缀函数调用。如果你没有在函数里使用参数,许多编译器会显示警告信息,很令人讨厌。为了避免这些警告信息,一种经常使用的方法时省略掉你不想使用的参数名称;如上所示。

 

很明显一个后缀increment必须返回一个对象(它返回的是增加前的值),但是为什么是const对象呢?假设不是const对象,下面的代码就是正确的:

 

UPInt i;

i++++;                                     // 两次increment后缀

                                           // 运算

 

这组代码与下面的代码相同:

 

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

 

很明显,第一个调用的operator++函数返回的对象调用了第二个operator++函数。

 

有两个理由导致我们应该厌恶上述这种做法,第一是与内置类型行为不一致。当设计一个类遇到问题时,一个好的准则是使该类的行为与int类型一致。而int类型不允许连续进行两次后缀increment

 

int i;

i++++;                                       // 错误!

第二个原因是使用两次后缀increment所产生的结果与调用者期望的不一致。如上所示,第二次调用operator++改变的值是第一次调用返回对象的值,而不是原始对象的值。因此如果:

 

i++++;

 

是合法的,i将仅仅增加了一次。这与人的直觉相违背,使人迷惑(对于int类型和UPInt都是一样),所以最好禁止这么做。

 

C++禁止int类型这么做,同时你也必须禁止你自己写的类有这样的行为。最容易的方法是让后缀increment 返回const对象。当编译器遇到这样的代码:

 

i++++;                                       // same as i.operator++(0).operator++(0);

 

它发现从第一个operator++函数返回的const对象又调用operator++函数,然而这个函数是一个nonconst成员函数,所以const对象不能调用这个函数。如果你原来想过让一个函数返回const对象没有任何意义,现在你就知道有时还是有用的,后缀incrementdecrement就是例子。(更多的例子参见Effective C++ 条款21

 

如果你很关心效率问题,当你第一次看到后缀increment函数时, 你可能觉得有些问题。这个函数必须建立一个临时对象以做为它的返回值,(参见条款19),上述实现代码建立了一个显示的临时对象(oldValue),这个临时对象必须被构造并在最后被结构。前缀increment函数没有这样的临时对象。由此得出一个令人惊讶的结论,如果仅为了提高代码效率,UPInt的调用者应该尽量使用前缀increment,少用后缀increment,除非确实需要使用后缀increment。让我们明确一下,当处理用户定义的类型时,尽可能地使用前缀increment,因为它的效率较高。

 

我们再观察一下后缀与前缀increment 操作符。它们除了返回值不同外,所完成的功能是一样的,即值加一。简而言之,它们被认为功能一样。那么你如何确保后缀increment和前缀increment的行为一致呢?当不同的程序员去维护和升级代码时,有什么能保证它们不会产生差异?除非你遵守上述代码里的原则,这才能得到确保。这个原则是后缀incrementdecrement应该根据它们的前缀形式来实现。你仅仅需要维护前缀版本,因为后缀形式自动与前缀形式的行为一致。

 

正如你所看到的,掌握前缀和后缀incrementdecrement是容易的。一旦了解了他们正确的返回值类型以及后缀操作符应该以前缀操作符为基础来实现的规则,就足够了。

《Effective C++》学习笔记——条款31

《Effective C++》学习笔记——条款31:将文件间的编译依存关系降至最低
  • lx417147512
  • lx417147512
  • 2015年06月15日 13:51
  • 1364

《More Effective C++》条款27:如何让类对象只在栈(堆)上分配空间?

昨天一个同学去网易面试C++研发,问到了这么一个问题:如何限制一个类对象只在栈(堆)上分配空间? 一般情况下,编写一个类,是可以在栈或者堆分配空间。但有些时候,你想编写一个只能在栈或者只能在堆上面分...
  • hxz_qlh
  • hxz_qlh
  • 2013年10月26日 21:27
  • 6045

《Effective C++》学习笔记——条款25

《Effective C++》学习笔记——条款25:考虑写出一个不抛异常的 swap 函数
  • lx417147512
  • lx417147512
  • 2014年12月30日 22:32
  • 836

《Effective C++》:条款41-条款42

条款41了解隐式接口和编译期多态 条款42了解typename的双重意义条款
  • KangRoger
  • KangRoger
  • 2015年03月10日 22:13
  • 1201

《Effective C++》:条款28-条款29

条款28避免返回handles指向对象内部成分:指的是不能返回对象内部数据/函数的引用、指针等。 条款29为异常安全而努力是值得的:指的是要有异常处理机制,避免发生异常时造成资源泄露等问题。...
  • KangRoger
  • KangRoger
  • 2015年02月19日 19:47
  • 1370

《Effective C++》让自己习惯C++:条款1-条款4

《Effective C++》条款1到条款4。基本是总结C++的一些特点,尤其是不同于C语言的特点。...
  • KangRoger
  • KangRoger
  • 2014年12月13日 19:26
  • 2345

《Effective C++》资源管理:条款13-条款15

在系统中,资源是有限的,一旦用完必须归还给系统,否则可能会造成资源耗尽或其他问题。例如,动态分配的内存如果用完不释放会造成内存泄漏。 这里说的资源不仅仅是指内存,还包括其他,例如文件描述符、网络连接、...
  • KangRoger
  • KangRoger
  • 2015年01月14日 21:46
  • 1282

《Effective C++》:条款44-条款45

条款44将与参数无关的代码抽离templates 条款45运用成员函数模板接受所有兼容类型...
  • KangRoger
  • KangRoger
  • 2015年03月12日 22:01
  • 1482

Effective Modern C++ 条款28 理解引用折叠

Effective Modern C++ 条款28
  • big_yellow_duck
  • big_yellow_duck
  • 2016年09月04日 19:34
  • 1985

《Effective C++》设计与声明:条款18-条款19

这两个条款讲的是:接口的设计和类的设计。其中接口的设计原则是让接口容易被正确使用,不容易被误用;后面有一系列的做法。类的设计,讲的是类设计犹如新类型type的设计。在设计类时要考虑的一系列问题。...
  • KangRoger
  • KangRoger
  • 2015年01月21日 21:43
  • 1322
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:More Effective C++ 条款6
举报原因:
原因补充:

(最多只允许输入30个字)