设为首页 加入收藏

TOP

4.5 递增和递减运算符
2013-10-07 16:29:14 来源: 作者: 【 】 浏览:106
Tags:4.5 递增 递减 运算

4.5  递增和递减运算符

递增运算符(++)和递减运算符(  )为对象的加1和减1操作提供了一种简洁的书写形式。这两个运算符还可应用于迭代器,因为很多迭代器本身不支持算术运算,所以此时递增和递减运算符除了书写简洁外还是必须的。

递增和递减运算符有两种形式:前置版本和后置版本。到目前为止,本书使用的都是前置版本,这种形式的运算符首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。后置版本也会将运算对象加1(或减1),但是求值结果是运算对象改变之前那个值的副本:
 

  1. int i = 0, j;  
  2.     j = ++i;        // j = 1i = 1: 前置版本得到递增之后的值  
  3.     j = i++;        // j = 1i = 2: 后置版本得到递增之前的值  

这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。

建议:除非必须,否则不用递增递减运算符的后置版本

C语言背景的读者可能对优先使用前置版本递增运算符有所疑问,其实原因非常简单:前置版本的递增运算符避免了不必要的工作,它把值加1后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。

对于整数和指针类型来说,编译器可能对这种额外的工作进行一定的优化;但是对于相对复杂的迭代器类型,这种额外的工作就消耗巨大了。建议养成使用前置版本的习惯,这样不仅不需要担心性能的问题,而且更重要的是写出的代码会更符合编程(www.cppentry.com)的初衷。

在一条语句中混用解引用和递增运算符

如果我们想在一条复合表达式中既将变量加1或减1又能使用它原来的值,这时就可以使用递增和递减运算符的后置版本。

举个例子,可以使用后置的递增运算符来控制循环输出一个vector对象内容直至遇到(但不包括)第一个负值为止:
 

  1. auto pbeg = v.begin();  
  2. // 输出元素直至遇到第一个负值为止  
  3. while (pbeg != v.end() && *beg >= 0)  
  4.     cout << *pbeg++ << endl; // 输出当前值并将pbeg向前移动一个元素  

对于刚接触C++(www.cppentry.com)和C的程序员来说,*pbeg++不太容易理解。其实这种写法非常普遍,所以程序员一定要理解其含义。

后置递增运算符的优先级高于解引用运算符,因此*pbeg++等价于*(pbeg++)。pbeg++把pbeg的值加1,然后返回pbeg的初始值的副本作为其求值结果,此时解引用运算符的运算对象是pbeg未增加之前的值。最终,这条语句输出pbeg开始时指向的那个元素,并将指针向前移动一个位置。

这种用法完全是基于一个事实,即后置递增运算符返回初始的未加1的值。如果返回的是加1之后的值,解引用该值将产生错误的结果。不但无法输出第一个元素,而且更糟糕的是如果序列中没有负值,程序将可能试图解引用一个根本不存在的元素。

建议:简洁可以成为一种美德

形如*pbeg++的表达式一开始可能不太容易理解,但其实这是一种被广泛使用的、有效的写法。当对这种形式熟悉之后,书写

  1. cout << *iter++ << endl;  

要比书写下面的等价语句更简洁、也更少出错

  1. cout << *iter << endl;  
  2. ++iter;  
  3. 不断研究这样的 

不断研究这样的例子直到对它们的含义一目了然。大多数C++(www.cppentry.com)程序追求简洁、摒弃冗长,因此C++(www.cppentry.com)程序员应该习惯于这种写法。而且,一旦熟练掌握了这种写法后,程序出错的可能性也会降低。

运算对象可按任意顺序求值

大多数运算符都没有规定运算对象的求值顺序(参见4.1.3节,第137页),这在一般情况下不会有什么影响。然而,如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值的话,运算对象的求值顺序就很关键了。因为递增运算符和递减运算符会改变运算对象的值,所以要提防在复合表达式中错用这两个运算符。

为了说明这一问题,我们将重写3.4.1节(第108页)的程序,该程序使用for循环将输入的第一个单词改成大写形式:

  1. for (auto it = s.begin(); it != s.end() && !isspace(*it); ++it)  
  2.     *it = toupper(*it);         // 将当前字符改成大写形式  

将产生未定义的行为。问题在于:赋值运算符左右两端的运算对象都用到了beg,并且右侧的运算对象还改变了beg的值,所以该赋值语句是未定义的。编译器可能按照下面的任意一种思路处理该表达式:
 

  1. *beg = toupper(*beg);           // 如果先求左侧的值  
  2. *(beg + 1) = toupper(*beg);         // 如果先求右侧的值 

也可能采取别的什么方式处理它。

4.5节练习

练习4.17:说明前置递增运算符和后置递增运算符的区别。

练习4.18:如果第148页那个输出vector对象元素的while循环使用前置递增运算符,将得到什么结果?

练习4.19:假设ptr的类型是指向int的指针、vec的类型是vector<int>、ival的类型是int,说明下面的表达式是何含义?如果有表达式不正确,为什么?应该如何修改?

(a) ptr != 0 && *ptr++    (b) ival++ && ival

(c) vec[ival++] <= vec[ival]
 

】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
分享到: 
上一篇4.4 赋值运算符(2) 下一篇4.6 成员访问运算符

评论

帐  号: 密码: (新用户注册)
验 证 码:
表  情:
内  容: