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

1,问题引入

最近在用C++语言编程时,遇到如下问题:判断下列程序片段的输出内容。
这里写图片描述
下面是其标准输出:
这里写图片描述
很明显,以上问题的根本在于运算符(解引用和递增运算符)作用的优先级,只有弄清楚了相关运算符的优先级顺序,这个问题的解决自然水到渠成。

2,递增运算符基础

在这是解释文章后序问题之前,我们要知道非常关键的一点——递增运算符的运算规则。下面是运算符解释的相关引用:
引用:递增(++)和递减运算符(–)为对象的加1和减1提供了一种简洁的书写形式。这两个运算符还可以用于迭代器,因为很多迭代器本身不支持算术运算,所以此时递增和递减运算符除了书写简洁外还是必须的。
递增和递减运算符有两种形式:前置版本和后置版本(或前缀或后缀)。前置版本的运算符首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。后置版本也会将运算符对象加1(或减1),但求值结果是运算对象改变之前那个值得副本。
以上引用我们需要注意两点:第一,递增和递减运算符在某些迭代器中不仅简洁而且是必须的;第二,后置版本的递增或递减运算符返回未改变之前的值作为运算结果。知道这两点后我们就可以看看本文开头的问题。

3,“混用”的结果,Why?

这里用了‘混用’一词,我想只是为了说明问题所在,而不是表示一个贬义或不提倡,后文对这一点会做出合理的建议!
引入的问题是,对于指向数组arr的指针ptr,*ptr++的输出到底是什么?这里应该知道后置递增运算符的优先级是高于解引用运算符的。(不熟悉可以参考博文:https://blog.csdn.net/yk_ee/article/details/51638566所以,此式子会首先执行ptr++运算,这导致的问题有两个:其一,ptr这个指针确实指向了数组的下一个元素;其二,ptr++运算后的返回值是ptr未改变前的副本(由文章第二部分易知)。然后执行*ptr运算,此时的ptr是未经递增运算的副本即arr[0],所以第一个输出是元素1。之后再对ptr解引用输出,自然是已经递增过的指针,则应该输出arr[1]即2。
知道了上面表达式的运算过程,我们可以看看下面几条语句的输出:
这里写图片描述
读者可以自己梳理,然后上机调试出了,轻松应对这些繁琐的变化!

4,迭代器中一个有趣的问题

我们该讲点正式的东西了。首先我们还是看一段实现一个简单功能的程序片段:
这里写图片描述
以上代码,主体是用迭代器对向量容器的一次遍历,但是我们要研究的问题不会停留在这里。下面我们要把他改动成我们本次所研究的问题——混用解引用与递增运算符的版本,代码段如下:
这里写图片描述
两个程序段的打印结果如下:
这里写图片描述
没错,这两段代码的打印结果都是上图中的这一个!我可没说他们真的是一样的 !为了方便前后代码的比较,我在改进后的版本中注释掉了原版的部分语句。改进后的版本加入了我们本文中的“运算符混用”策略。为什么说他们不是真的一样?下面我们把程序做一个小小的改动,再来看看运行结果:
这里写图片描述
我们的改动是,插入的元素不再是原来的奇数,而是原来奇数的100倍,这样做只是为了区分那个才是我们真正插入的元素!从打印结果中可以看到,他们真的不一样,新插入的元素一个是在原来的奇数前,另一个是在原来的奇数后面。先前我们感觉‘一样’是因为我们插入了与原来一样的数据,导致我们产生了误判。那么到底为何?
其本因还是在于混用导致我们提前改变了迭代器iter,在原来的版本中,我们插入元素后,直接让迭代器前进两个元素,跳过了我们新插入的元素,这没什么,如我们所料。但是在改进的版本中,插入函数的第二个参数解引用之后,iter就已经改变了,而恰好我们在插入函数的第一个参数中使用了他,尽管我们会用insert的返回值重新更新iter,但是为时已晚,我们刚好插入到了已知奇数的后面,弄拙成巧!所以看到了打印出完全不同的结构。
从这里我们应该尤其注意一点,在混用解引用与递增(或递减)运算符的时候,无论指针还是迭代器都是立刻改变的,这就会出现一个“隐患”,我们可能会立刻用这个指针或者运算符,但是如果我们依旧认为他还是原来的指向就会出现严重的程序设计逻辑错误,并且这种错误很难被发现!

5,建议!

程序设计中,上述的逻辑错误是很难被发现的,也许会浪费我们大量的时间和精力,甚至导致项目搁浅!
避免这些的有效途径除了掌握扎实的语法外,还应该在平时编程的时候养成良好的习惯。下面是摘自一些大师的忠告:
(1)除非必须,否则不用递增递减运算符的后置版本
前置版本的运算符避免了不必要的工作,他把值加1后直接返回改变了的运算对象。与之相比,后置版本需要将原始值存储下来以便于返回这个未修改的内容。如果我们不需要修改前的值,那么后置版本的操作就是一种浪费。
(2)简洁可以成为一种美德
cout<<iter++ << endl;要比书写下面的等价语句更简洁、也更少出错:cout<<*iter << endl;++iter; 不断研究这样的例子,直到对他们的含义一目了然。大多数C++程序追求简洁,摒弃冗长。因此C++程序员应该习惯这种写法。而且一旦熟练掌握了这种写法后,程序出错的可能性也会降低。*
(3)大多数运算符都没有规定运算对象的求值顺序,这在一般情况下不会有什么影响。然而如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值得话,运算对象的求值顺序就很关键了。因为递增运算符和递减运算符会改变运算对象的值,所以要提防在复合表达式中错用这两个运算符。

6,参考资料

书籍:《C++primer(第五版)》
博文:https://blog.csdn.net/yk_ee/article/details/51638566
http://blog.csdn.net/misayaaaaa/article/details/56834852

猜你喜欢

转载自blog.csdn.net/weixin_37818081/article/details/79324394