我正在C中编写一个小型测试应用程序,并在我的Ubuntu 14.04上预装了GCC 4.8.4。我对表达式a=(b++);
与a=b++;
的行为方式相同这一事实感到困惑。使用以下简单代码:
#include <stdint.h>
#include <stdio.h>
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
。但是,我希望括号在其值分配给b1
之前增加a1
。
即,a1
应为11
而a2
等于10
。
有没有人对这个问题有所了解?
答案 0 :(得分:37)
但是,我希望括号在
的值分配之前增加a1
b1
你不应该期望:在增量表达式周围放置圆括号不会改变其副作用的应用。
副作用(在这种情况下,它意味着将11写入b1
)在检索++
的当前值后应用一段时间。这可能发生在完全评估完整赋值表达式之前或之后。这就是为什么后增量将保持后增量,包括或不包括括号。如果您想要预增量,请在变量之前放置a1 = ++b1;
:
<html>
<body>
<form method="post" action="b.php" enctype="multipart/form-data">
<input id="file" name="file" type="file">
<input value="upload" name="submit" type="submit">
</form>
</body>
</html>
答案 1 :(得分:32)
引自C99:6.5.2.4:
postfix ++运算符的结果是操作数的值。 获得结果后,操作数的值递增。 (即,将相应类型的值1添加到其中。)请参阅 关于加法算子和复合赋值的讨论 有关约束,类型和转换以及影响的信息 对指针的操作。更新存储值的副作用 操作数应在前一个和下一个序列之间发生 点。
您可以查看C99:附录C以了解有效序列点是什么。
在你的问题中,只是添加一个括号不会改变序列点,只有;
字符才会这样做。
或者换句话说,您可以查看它,就像b
的临时副本一样,副作用是原始b
递增的。但是,在达到序列点之前,所有评估都在b
的临时副本上完成。然后丢弃b
的临时副本,当达到序列点时,副作用即递增操作被提交到存储。
答案 2 :(得分:23)
圆括号可能很难想到。但他们不意味着,“确保首先发生所有内容”。
假设我们有
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++
这样的表达式会做两件事:它们取i
的值并向其添加1,然后将新值存储回{{1} }。但是回到i
的商店并不一定会马上发生,它可能会在以后发生。以及“商店何时回到i
确实发生了什么?”有点像“函数x何时被调用?”的问题。你无法真正告诉它,这取决于编译器,它通常无关紧要,它会因编译器而异,如果你真的在乎,你将不得不做其他事情来强制命令。
在任何情况下,请记住i
的定义是它将{em>旧值i++
输出到周围的表达式中。这是一个非常绝对的规则,只需添加一些括号就可以不进行更改!这不是括号所做的。
让我们回到上一个涉及函数x,y和z的例子。我注意到函数x首先被调用。假设我不想要那个,假设我想要首先调用函数y和z。我可以通过写
来实现这一目标i
我可以写出来,但它不会改变任何东西。请记住,括号并不意味着“先将所有内容”放在首位。它们确实会导致乘法在加法之前发生,但编译器无论如何都已经这样做了,基于乘法优先于加法的优先级。
上面我说,“如果你真的在乎,你将不得不做一些别的事情来强迫订单”。您通常需要做的是使用一些临时变量和一些额外的语句。 (技术术语是“插入一些序列点。”)例如,为了使y和z首先被调用,我可以写
x = z() + ((y() * z())?
在你的情况下,如果你想确保b的新值被分配给a,你可以写
c = y();
d = z();
b = x();
a = b + c * d;
但当然这很愚蠢 - 如果您想要做的就是增加b并将其新值分配给a,那就是前缀c = b++;
a = b;
的用途:
++
答案 3 :(得分:6)
你的期望完全没有根据。
括号对执行顺序没有直接影响。它们不会在表达式中引入序列点,因此它们不会强制任何副作用早于它们在没有括号的情况下实现。
此外,根据定义,增量后表达式b++
的计算结果为b
的原始值。无论您在b++
附近添加多少对括号,此要求都将保留。即使括号以某种方式“强制”即时增量,该语言仍然需要(((b++)))
来评估b
的旧值,这意味着a
仍然会保证收到b
的非递增值。
括号仅影响运算符及其操作数之间的语法分组。例如,在您的原始表达式a = b++
中,您可能会立即询问++
苹果到b
或a = b
的结果。在您的情况下,通过添加括号,您只需显式强制++
运算符应用于(与...分组)b
操作数。但是,根据语言语法(以及从中派生的运算符优先级和关联性),++
已经适用于b
,即一元++
的优先级高于二进制=
。你的括号没有改变任何东西,它只是重复了已经隐含的分组。因此行为没有变化。
答案 4 :(得分:5)
括号完全是句法。它们只是对表达式进行分组,如果要覆盖运算符的优先级或关联性,它们很有用。例如,如果您在此处使用括号:
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
。这是你观察到的,这是正确的行为。
答案 5 :(得分:3)
好的,简而言之:b++
是一元表达式,并且它周围的括号不会影响算术运算的优先级,因为++
增量运算符具有C中最高(如果不是 最高)优先级之一。在a * (b + c
)中,(b + c)
是二进制表达式(不要与二进制编号系统混淆!)因为变量b
及其加数c
。因此可以很容易地记住这样:括号放在二进制,三元,四元... + INF表达式几乎总是会影响优先级(*);围绕一元的括号绝不会 - 因为它们“足够强大”以“承受”括号分组。
(*)像往常一样,规则有一些例外,如果只有少数:e。 G。尽管是二元运算符,->
(访问结构上的指针成员)具有非常强的绑定。但是,C 初学者很可能需要很长时间才能在代码中编写->
,因为他们需要对指针和结构有深入的了解。 < / p>
答案 6 :(得分:3)
但是,我希望括号在将值分配给a1之前使b1递增。
您没有将b1
分配给a1
:您正在指定后增量表达式的结果。
考虑以下程序,该程序在执行赋值时打印b
的值:
#include <iostream>
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 " << a.x << endl;
return 0;
}
我怀疑这是未定义的行为,但是当使用ideone.com时,我得到如下所示的输出
b is 11 when operator= is executed
a is 10
答案 7 :(得分:2)
括号不会改变后增量行为本身。
A1 =(B1 ++); // B1 = 10
等于,
uint8_t mid_value = b1++; //10
a1 = (mid_value); //10
答案 8 :(得分:1)
将++
放在语句的末尾(称为后递增),意味着在语句之后完成增量。
即使将变量括在括号中也不会改变语句完成后递增的事实。
在后缀形式中,增量或减量发生在表达式求值中使用该值之后。
在前缀增量或减量操作中,增量或减量发生在表达式求值中使用该值之前。
这就是a = (b++)
和a = b++
在行为方面相同的原因。
在您的情况下,如果您想先增加b
,则应使用预增量,++b
代替b++
或(b++)
。
更改
a1 = (b1++);
到
a1 = ++b1; // b will be incremented before it is assigned to a.
答案 9 :(得分:1)
简而言之: 语句完成后,b ++会递增
但即使在那之后,b ++的结果也被放到了。
由于括号不会更改此处的值。
(如果查看操作的java字节码,可以看到这个)