C|副作用,顺序点,及逻辑、逗号运算符中的顺序点

对于以下语句:

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 <iostream>
#include <string>
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-

原文链接:,转发请注明来源!