对于以下语句:
y = (4 + x++) + (6 + x++);
编译器无法定义正确的行为。为什么?需了解C语言定义的副作用、顺序点。
1 副作用和顺序点
副作用(side effect)指的是在计算表达式时对某些东西(如存储在变量中的值)进行了修改。
顺序点(sequence point)是程序执行过程中的一个点,且要求在进入下一步之前将确保对所有的副作用都进行了评估。
在C中,语句中的分号就是一个顺序点,这意味着程序处理下一条语句之前,赋值运算符、递增运算符和递减运算符执行的所有修改都必须完成。任何完整的表达式末尾也都有一个顺序点,如用作while循环中检测条件的表达式:
while(guests++ <10) //表达式guests++ <10就是一个完整表达式
cout << guests <<endl;
回过头来再看下面的语句:
y = (4 + x++) + (6 + x++);
表达式4+x++不是一个完整表达式,因此,C不保证x的值在计算子表达式4+x++后立即增加1。在这个例子中,整条赋值语句是一个完整表达式,而分号标示了顺序点,因此C只保证程序执行到下一条语句之前,x的值将被递增两次。C没有规定是在计算每个子表达式之后将x的值递增,还是在整个表达式计算完毕后才将x的值递增,有鉴于此,您应避免使用这样的表达式。
2 逗号运算符是一个顺序点
语句块(两个大括号{}括住的语句)允许把两条或更多条语句放到按C句法只能放一条语句的地方。逗号运算符对表达式完成同样的任务,允许将两个表达式放到C句法只允许放在一个表达式的地方。
++j, --i // tow expressions count as one for syntax purposes
如下示例:
#include
#include
int main()
{
using namespace std;
cout << enter a word: string word cin>> word;
char tmp;
int i, j;
for(j=0, i=word.size()-1; j<i; --i,++j)
{
tmp = word[i];
word[i] = word[j];
word[j] = tmp;
}
cout << word <<endl;
return 0;
}
逗号并不总是逗号运算符。例如,下面这个声明中的逗号将变量列表中相邻的名称分开:
int i, j; // comma is a separator here, not an operator
逗号运算符最常见的用途是将两个或更多的表达式放到一个for循环表达式中。不过C还为这个运算符提供了另外两个特性。首先,它确保先计算第一个表达式,然后计算第二个表达式(换句话说,逗号运算符是一个顺序点),如下所示的表达式是最安全的:
i = 20, j = 2 * i; // i set to 20, then j set to 40
其次,C规定,逗号表达式的值是第二部分的值。例如,上述表达式的值为40,因为j = 2*i的值为40。
在所有运算符中,逗号运算符的优先级是最低的。例如,下面的语句:
cats = 17,24;
被解释为:
(cats = 17),240;
也就是说,将cats设置为17,240不起作用。然而,由于括号的优先级最高,下面的表达式将把cats设置为240---逗号右侧的表达式值:
cats = (17,240);
3 逻辑运算符中的顺序点
C规定,||运算符是个顺序点。也就是说,先修改左侧的值,再对右侧进行判定。
i++ < 6 || i ==j
假设i原来的值为10,则 在对i和j进行比较时,i的值将会为11。另外,如果左侧的表达式为true,则C将不会去判断右侧的表达式,因为只要一个表达式为true,则整个逻辑表达式为true。
和||运算符一样,&&运算符也是顺序点,因此将首先判断左侧,并且在右侧被判断之前产生所有的副作用。如果左侧为false,则整个逻辑表达式必定为false,在这种情况下,C++将不会再对右侧进行判定。
4 循环语句控制结构部分的顺序点
对于while或for循环,控制结构写在小括号内,并不需要以分号结尾。其后紧跟循环体,循环体可以是一条语句,或以两个大括号括住的语句块。如果在控制结构后以分号结束语句,则循环体只是一个空语句,其后的语句块可能并不是预料中的执行逻辑:
int i = 0;
while(name[i] != '\0'); // problem semicolon
{
cout << name[i] << endl;
i++;
}
cout << "Done!\n";
如以上的代码,就会陷入到一个死循环。
-End-