为什么a =(b ++)与a = b ++具有相同的行为?
我在C中编写了一个小型测试应用程序,并在我的Ubuntu 14.04上预装了GCC 4.8.4。 而我对表达式a=(b++);
的事实感到困惑a=(b++);
行为方式与a=b++;
确实。 使用以下简单代码:
#include #include int main(int argc, char* argv[]){ uint8_t a1, a2, b1=10, b2=10; a1=(b1++); a2=b2++; printf("a1=%u, a2=%u, b1=%u, b2=%u.\n", a1, a2, b1, b2); }
gcc编译后的结果是a1=a2=10
,而b1=b2=11
。 但是,我希望括号在其值分配给a1
之前使b1
递增。
即, a1
应为11
而a2
等于10
。
有没有人对这个问题有所了解?
引自C99:6.5.2.4:
postfix ++运算符的结果是操作数的值。 获得结果后,操作数的值递增。 (即,将相应类型的值1添加到其中。)有关约束,类型和转换以及操作对指针的影响的信息,请参阅加法运算符和复合赋值的讨论。 更新操作数的存储值的副作用应发生在前一个和下一个序列点之间。
您可以查看C99:附录C以了解有效序列点是什么。
在你的问题中,只是添加一个括号不会改变序列点,只有;
性格做到了。
或者换句话说,您可以像b
的临时副本一样查看它,副作用是原始b
递增。 但是,在达到序列点之前,所有评估都在b
的临时副本上完成。 然后丢弃b
的临时副本,当到达序列点时,副作用即增量操作被提交给存储。
但是,我希望括号在其值分配给
a1
之前使b1
递增
您不应该期望:在增量表达式周围放置括号不会改变其副作用的应用。
副作用(在这种情况下,它意味着将11写入b1
)在检索b1
的当前值后应用一段时间。 这可能发生在完全评估完整赋值表达式之前或之后。 这就是为什么后增量将保持后增量,包括或不包括括号。 如果你想要一个预增量,那就把++
放在变量之前:
a1 = ++b1;
圆括号可能很难想到。 但他们并不是说,“确保内部的一切都先发生”。
假设我们有
a = b + c * d;
乘法优先于加法的优先级告诉我们编译器将安排将c乘以d,然后将结果添加到b。 如果我们想要其他解释,我们可以使用括号:
a = (b + c) * d;
但是假设我们将一些函数调用抛入混合中。 也就是说,假设我们写作
a = x() + y() * z();
现在,虽然很明显y()的返回值将乘以z()的返回值,我们可以说一下x(),y()和z()的调用顺序吗? 答案是, 不,我们绝对不能! 如果您完全不确定,我邀请您尝试使用x,y和z函数,如下所示:
int x() { printf("this is x()\n"); return 2; } int y() { printf("this is y()\n"); return 3; } int z() { printf("this is z()\n"); return 4; }
我第一次尝试这个,使用我前面的编译器,我发现函数x()首先被调用,即使最后需要它的结果。 当我将调用代码更改为
a = (x() + y()) * z();
对x,y和z的调用顺序保持完全相同,编译器只是安排以不同的方式组合它们的结果。
最后,重要的是要意识到像i++
这样的表达式会做两件事:它们取值并为其加1,然后将新值存储回i
。 但是回到i
的商店并不一定会马上发生,它可能会在以后发生。 还有一个问题是“ i
的商店什么时候回来了?” 有点像“函数x什么时候被调用?”的问题。 你无法真正告诉它,它取决于编译器,它通常无关紧要,它将不同于编译器,如果你真的在乎,你将不得不做其他事情来强制命令。
在任何情况下,请记住i++
的定义是它将i
的旧值赋予周围的表达式。 这是一个非常绝对的规则,只是通过添加一些括号不能改变它! 这不是括号的作用。
让我们回到上一个涉及函数x,y和z的例子。 我注意到函数x首先被调用。 假设我不想要那个,假设我希望首先调用函数y和z。 我可以通过写作来实现这一目标
x = z() + ((y() * z())?
我可以写,但它不会改变任何东西。 请记住,括号并不意味着“先做所有事情 ”。 它们确实会导致乘法在加法之前发生,但编译器无论如何都已经这样做了,基于乘法相对于加法的更高优先级。
上面我说,“如果你真的在乎,你将不得不做其他事情来强迫订单”。 您通常要做的是使用一些临时变量和一些额外的语句。 (技术术语是“插入一些序列点 。”)例如,为了使y和z首先被调用,我可以写
c = y(); d = z(); b = x(); a = b + c * d;
在您的情况下,如果您想确保将b的新值分配给a,您可以编写
c = b++; a = b;
但当然这很愚蠢 – 如果您只想增加b并将其新值分配给a,那就是++
的前缀:
a = ++b;
你的期望是完全没有根据的。
括号对执行顺序没有直接影响。 它们不会在表达式中引入序列点,因此它们不会强制实现任何副作用,而不是在没有括号的情况下实现。
此外,根据定义,后增量表达式b++
评估为b++
的原始值。 无论您在b++
周围添加多少对括号,此要求都将保留。 即使括号以某种方式“强制”即时增量,语言仍然需要(((b++)))
来评估b
的旧值,这意味着仍然可以保证a
将接收b
的非递增值。
括号仅影响运算符与其操作数之间的语法分组。 例如,在你的原始表达式中, a = b++
可能会立即询问++
apple是单独b
还是a = b
的结果。 在您的情况下,通过添加括号,您只需显式强制++
运算符应用于(分组) b
操作数。 但是,根据语言语法(以及从中派生的运算符优先级和关联性), ++
已经应用于b
,即一元++
优先级高于二元组=
。 你的括号没有改变任何东西,它只是重复了已经隐含的分组。 因此,行为没有变化。
括号完全是句法的 。 它们只是对表达式进行分组,如果要覆盖运算符的优先级或关联性,它们很有用。 例如,如果您在此处使用括号:
a = 2*(b+1);
你的意思是b+1
的结果应该加倍,而如果你省略括号:
a = 2*b+1;
你的意思是只需要加倍b
然后结果应该递增。 这些分配的两个语法树是:
= = / \ / \ a * a + / \ / \ 2 + * 1 / \ / \ b 1 2 b a = 2*(b+1); a = 2*b+1;
通过使用括号,您可以因此更改与您的程序对应的语法树(当然)不同的语法可能对应于不同的语义。
另一方面,在你的程序中:
a1 = (b1++); a2 = b2++;
括号是多余的,因为赋值运算符的优先级低于后缀增量( ++
)。 这两项任务是等价的; 在这两种情况下,相应的语法树如下:
= / \ a ++ (postfix) | b
现在我们已经完成了语法,让我们去语义 。 此语句表示:评估b++
并将结果分配给a
。 评估b++
返回b++
的当前值(在程序中为10),作为副作用,增加b
(现在变为11)。 返回值(即10)分配给a
。 这是你观察到的,这是正确的行为。
但是,我希望括号在其值分配给a1之前使b1递增。
您没有将b1
分配给a1
:您正在分配postincrement表达式的结果。
考虑以下程序,它在执行赋值时打印b
的值:
#include using namespace std; int b; struct verbose { int x; void operator=(int y) { cout << "b is " << b << " when operator= is executed" << endl; x = y; } }; int main() { // your code goes here verbose a; b = 10; a = b++; cout << "a is " << ax << endl; return 0; }
我怀疑这是未定义的行为,但是当使用ideone.com时,我得到如下所示的输出
b is 11 when operator= is executed a is 10
好吧,简而言之: b++
是一元表达式,并且它周围的括号不会影响算术运算的优先级,因为++
增量运算符在C中具有最高(如果不是最高)优先级之一。在a * (b + c
)中, (b + c)
是二进制表达式(不要与二进制编号系统混淆!),因为变量b
及其加数c
。 因此可以很容易地记住这样:圆括号放在二进制,三元,四元… + INF表达式几乎总是会影响优先级(*); 围绕一元的括号绝不会 – 因为它们“足够强大”以“承受”括号分组。
(*)像往常一样,规则有一些例外,如果只有少数例子:例如->
(访问结构上的指针成员)尽管是二元运算符但具有非常强的绑定。 但是,C 初学者很可能需要花费很长时间才能在代码中编写->
,因为他们需要对指针和结构有深入的了解。
括号不会改变后增量行为本身。
A1 =(B1 ++); // B1 = 10
它等于,
uint8_t mid_value = b1++; //10 a1 = (mid_value); //10
将++
放在语句的末尾(称为后增量)意味着在语句之后完成增量。
即使将变量括在括号中也不会改变语句完成后递增的事实。
来自learn.geekinterview.com :
在后缀forms中,增量或减量发生在表达式求值中使用该值之后。
在前缀增量或减量操作中,增量或减量发生在表达式求值中使用该值之前。
这就是为什么a = (b++)
和a = b++
在行为方面是相同的。
在你的情况下,如果你想先增加b
,你应该使用预增量, ++b
而不是b++
或(b++)
。
更改
a1 = (b1++);
至
a1 = ++b1; // b will be incremented before it is assigned to a.
简而言之:语句完成后,b ++会增加
但即使在那之后,b ++的结果也被放到了。
因为括号不会改变这里的值。
(如果查看操作的java字节码,可以看到这个)