num = (5 + 2) * (9 * 6);
如果我问你:上面的c程序在执行的时候到底是先算(5 + 2)还是(9 * 6)?你会怎么回答?
无非就两种:先算(5 + 2)或者先算(9 * 6)对吧?那么我来告诉你,这两种结果都不对,答案是不确定,你应该先问编译器。
在解释上面的概念之前,我们先来介绍两个专业术语:副作用、序列点
副作用(side effect)是指对数据对象或文件的修改。
例如:
states = 50;
它的副作用是将变量的值设置为50。
看起来,副作用更像是主要目的,其实并不是。从C语言的角度来看,上面的代码的主要目的是对表达式求值。
给出4 + 6,C会对齐求值得到10,同理,给出states = 50;C语言会对其进行求值得到50。
在C语言的眼里,上面的程序更像是这样的:
(states = 50);
类似的,printf函数显示的信息其实是它的副作用(printf的返回值是待显示字符的个数,也就是放入输出缓冲区字符的个数),不懂缓冲区的可以看我的这篇文章:你真的理解printf函数吗?
序列点(sequence point)是程序执行的点,在这个点上,所有的副作用都会在进入下一步之前发生。语句中的分号标记了一个序列点。
分号的意思是:在这个语句之前,赋值运算符、递增运算符和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。
C把先计算哪一个部分的决定权留给编译器的设计者,以便针对特定系统优化设计。
也就是说,对于开始的那个程序,哪一段表达式先被计算是不确定的。
但是,对于逻辑运算是个例外。
在C语言中,保证逻辑表达式的求值顺序是从左往右。&&和||都是序列点,所以一个程序在从一个运算对象执行到下一个运算对象之前,所有的副作用都会生效。
并且,重要的一点是,C一旦发现某个元素让表达式无效,便立即停止求值。
有了上面的规则,才能写出下面的代码:
while ((c = getchar()) != ' ' && c != '\n');
在上面的代码中,读取字符直到遇到第一个空格或者换行符停止,需要注意的是,如果没有上面的规则,c != '\n'
这个语句就有可能在c = getchar()
之前执行了,这样一来,就不能保证语义的正确性了。