5.3.2 ++”之惑
在前面for语句的例句中,i++和i = i + 1所起到的效果是完全相同的,但实际上这两个表达式真正的含义却并不相同。由于++很容易给初学者带来困惑,所以有必要在此详细剖析一下。
1. 有几种“++”?
一共有两种++运算符。一种++运算符是写在运算对象的后面,如前面小节代码中的i++,另一种++运算符是写在运算对象的前面,例如++i。
写在运算对象后面的++叫后缀加运算符(Postfix increment operator) ;写在运算对象前面的叫前缀加运算符(Prefix increment operators),是个一元运算符。这两种运算符一模一样,但是它们的优先级和结合性却完全不同。前者的优先级是16(最高级别,和函数运算同级),结合性从左到右;而后者的优先级是15(和所有一元运算符相同),结合性从右到左。
这情形和“-”既可以作为减法运算符也可以作为求负值运算符一样。在“5-3”这个表达式中,“-”是减法运算符,优先级为12;而在“-3”这个表达式中,“-”是求负值运算符,优先级为15。编译器可以通过“-”所处的上下文判断出它究竟是哪种运算。代码的编写者更应该清楚两者之间的差别。
在这一点上,很多初学C语言的人都很容易轻率地认为两者是同一个东西。但是,从以上介绍来看,它们两者之间真的是相去甚远。
不过,无论是后缀加还是前缀加,其运算对象都必须是左值表达式(严格地说是可修改的左值表达式)。而所谓的左值表达式,到目前为止我们只见过一种,就是单个变量名构成的初级表达式(也可以在变量名外面加上(),这种写法十分罕见,但含义一样)。
2. 两种“++”运算符的区别
上面说的是++运算符具有两种不同的意义,那么在实际操作中,两种运算符究竟又有什么不同呢?下面以int类型变量i为例进行介绍。
对于一个int类型变量i,++i的确切含义是求i的值,此外这个运算还有一个“副效应”(Side effects),即是在求i值之前,先将i赋值为i+1。而i++的确切含义同样是求i的值,这个运算也有一个副效应,即是求完i值之后,将i赋值为i+1。
现在核心的问题就出现了。上文中的“求i值之前”和“求完i值之后”,这只是一个时间范围,而并不是一个确切的时间点,因此我们还是不知道到底在何时i会被赋值为i+1。总不能在程序一开始运行的时候就加1吧,要知道那时候变量可能还不存在或者还没定义呢;也总不能等程序运行结束之后再加1吧,那样的副效应还有什么意义呢?
此时,必须借助“序点”(Sequence Point)这个概念才能把这个问题说清。所谓序点,简单地说,序点就是程序执行时一些虚拟的“节点”,这些“节点”对应着代码中一些特定的位置。在这些“节点”之前代码所要求计算机执行的所有动作(包括副效应)必须在这个“节点”处必须完成。由此可见,序点是C语言用于规定或说明运算次序的一个基本概念。就目前为止,我们见过的序点有语句的结束标志“;”或”}”,“?” (问号),“,” (逗号),if语句、while语句、do-while语句及for语句中的()的“)”,for语句“()”中的“;”,此外main()函数的“{”实际上也是一个序点。
因此,不难理解,表达式“i++”的真正含义即是,在求得i值之后,下一序点之前,完成i=i+1这个副效应操作(表达式永远是用来求值的,其余的都是副效应)。而表达式“++i”的真正含义是在前一序点之后,求i值之前,完成i=i+1这个副效应操作。
同理,“--”运算符也有两个不同的意义,即后缀减运算符和一元前缀减运算符,除了表示在求变量值之后(之前)变量的值需要被减去1之外,其性质和对应的“++”运算符是一致的。
此外要说明的是,“++”、“--”(无论是前缀还是后缀)的运算对象可以是任何整数类型、实浮点类型或指针类型。