后缀自增和前缀自增的概念是什么?

79

我不理解后缀和前缀的自增或自减概念。有没有人能给出更好的解释呢?


2
可能是What is the difference between ++i and i++的重复问题。 - TylerH
或许是在C++中递增-何时使用x++或++x?(或者,很可能还有其他10个问题)。 - underscore_d
如果你懂印地语 :), 这个视频非常清晰地解释了 https://www.youtube.com/watch?v=yRt-zYFJIvE - Deepak
14个回答

131

到目前为止,所有四个答案都不正确,因为它们断言了一种特定的事件顺序。

相信“城市传说”误导了许多初学者(和专业人士),即关于表达式中未定义行为的无尽问题。

所以。

对于内置的 C++ 前缀运算符,

++x

增加x的值,并将其作为左值返回

x++

x++ 操作会使 x 的值加 1,并将加 1 前的原始值作为表达式的结果返回。

特别地,x++ 并不意味着增量和返回原始值的时间顺序是确定的。编译器可以生成一些机器码,以产生 x 的原始值(例如可能存在于某个寄存器中),并延迟增量直到表达式结束(下一个序列点)。

一些错误地认为增量必须先发生的人往往得出某些表达式必须具有明确定义效果的结论,但实际上这些表达式具有未定义行为。


10
“lvalue”本质上是指向具有存储的对象的引用。这个术语源于早期的C语言,其中赋值需要在左侧使用lvalue。 - Cheers and hth. - Alf
5
你对于机制的理解是正确的,但我认为你在概念层面上说其他人是错的。机制只是尝试实现概念的实际细节。"Post" 的意思是“之后” - 在概念上,增量是“之后”完成的。'sequence' 的整个概念,比如 'sequence point',都暗示了一个顺序,而 UB (未定义行为) 是因为误解了序列点的工作方式,而不是误解了“pre”和“post”的含义。 - sje397
6
在C++03标准下,内置的&&运算符引入了一个序列点(C++03 §1.9/18)。在C++11标准下,内置的&&运算符中左侧操作数表达式先于右侧操作数表达式被计算(C++11 §5.14/2)。这意味着如果进行调用,它将使用i的值6。 - Cheers and hth. - Alf
3
@Cheers and hth. - Alf 另外一个问题,其他运算符呢?例如 x++ + y(x)?或者使用 ++x 的等效表达式呢?如果我现在有 c++ 环境的话,我可能会尝试一下,但不幸的是我没有... - AJMansfield
4
&&||这两个操作符在提供短路求值(因此也有序列点)方面是独特的。三元选择操作符也有点类似,因为它保证不选中的选择不被评估。但对于算术操作符,当您在同一表达式中修改并使用变量时,就会发生未定义行为。我认为,但不确定,所有其他操作符也是如此。无论如何,这是最好的假设,并且编写依赖于一些微妙的特殊情况的代码并不好,因为很少有程序员知道这些细节。;-) - Cheers and hth. - Alf
显示剩余10条评论

30
int i, x;

i = 2;
x = ++i;
// now i = 3, x = 3

i = 2;
x = i++; 
// now i = 3, x = 2

'Post' 的意思是后缀 - 即,在读取变量之后进行递增。'Pre' 的意思是前缀 - 因此,变量值先被递增,然后再用于表达式。


“变量读取后再进行增量操作”。我曾经认为自己理解了后缀和前缀增量,但现在你让我感到困惑了! - wilhelmtell
为什么会让人感到困惑呢?我们正在谈论“pre”和“post”递增的__概念__。这些前缀确实意味着__之前__和__之后__。 - sje397
7
“preincrement”和“postincrement”的“pre”和“post”指的是运算符相对于操作数的位置。它们并不暗示递增发生的时间顺序与变量读取的时间顺序之间的关系。 - James McNellis
2
@James:我理解你的观点,但为了举例说明,想象一下如果运算符的功能被颠倒会有多么混乱。我知道从技术上讲,没有时间顺序的暗示,但后置递增的概念映射肯定是“在使用之后递增”。 - sje397
@sje397,为什么根据你的例子,例如int a = 5; cout << a++;只打印5而不是6? - RaHuL

19

后缀递增运算符x++前缀递增运算符++x之间的区别在于它们如何评估其操作数。后缀递增运算符在概念上将操作数复制到内存中,递增原始操作数,最后生成副本的值。我认为通过代码实现运算符是最好的说明方式:

int operator ++ (int& n)  // postfix increment
{
    int tmp = n;
    n = n + 1;
    return tmp;
}

上述代码无法编译,因为你不能重新定义原始数据类型的运算符。编译器也无法判断我们定义的是一个后缀运算符而不是前缀运算符,但假设这是正确且合法的C++代码。你可以看到后缀运算符确实作用于它的操作数,但它返回的是自增之前的旧值。因此表达式x++的结果是自增之前的值。然而,x 的确被自增了。

前缀自增也将操作数自增,但它返回的是自增之后的操作数值:

int& operator ++ (int& n)
{
    n = n + 1;
    return n;
}

这意味着表达式++x的值是在加1之后 返回x值。

很容易认为表达式++x等同于赋值(x=x+1),但实际上不完全是这样的,因为增量是一种在不同上下文中可能具有不同含义的操作。对于一个简单的原始整数,确实++x可以替换(x=x+1)。但对于类类型,例如链表的迭代器,迭代器的前缀增量绝对不意味着"将对象加1"。


17
没有人回答这个问题:为什么这个概念容易混淆? 作为一个本科计算机专业的学生,我花了一些时间才理解这个问题,因为我读代码的方式有问题。 以下内容是不正确的!

x = y++

X 等于 y 后置自增。从逻辑上看,这似乎意味着 X 等于 Y 在自增操作完成之后的值。"Post" 意味着 "之后"。

或者

x = ++y
X 等于 y 的前置自增。从逻辑上看,这似乎意味着 X 等于在自增操作之前的值。"Pre" 意味着 "之前"。


它实际上的工作方式是相反的。这个概念很难理解是因为语言具有误导性。在这种情况下,我们不能使用单词来定义行为。
x=++y 实际上被解读为 X 等于自增之后的 Y 值。
x=y++ 实际上解读为 X 等于自增之前的 Y 值。

单词 "pre" 和 "post" 在语义上与英语相反。它们只表示++在 Y 的相对位置,没有其他的含义。

就我个人而言,如果可以选择,我会交换 ++y 和 y++ 的含义。这只是一个我必须学习的成语的例子。

如果有一种方法来理解这种疯狂的行为,我想以简单的方式了解。

谢谢阅读。


17
如果这种疯狂的方式有一种方法,我希望能用简单的语言了解。我的想法是这样的:对于PRE递增(y=++x):首先递增x,然后再赋值给y。对于POST递增(y=x++):先将x的值赋给y,然后再递增x。因此,将pre和post看作“x何时被递增”,而不是“y使用哪个版本的x”。这就是我的“方式”,在我看来非常合理。;-) - Graza
如果这种疯狂的方法有其规律可循,我想以简单的方式了解一下。当然,有的。前缀递增意味着“运算符在操作数之前”,因此递增在将操作数返回给调用者之前进行,所以他们得到的值包括递增。后缀递增意味着“运算符在操作数之后”,因此递增在(操作数的副本)返回给调用者之后进行,所以他们得到的值不包括递增。 - underscore_d
++y 是前缀自增,因为 ++ 用作前缀,y++ 是后缀自增,因为 ++ 用作后缀(或“后缀”)。这与英语语言并不矛盾。 - Pharap
“pre”和“post”这两个词在英语语义上是相反的。我不同意这种说法。“pre”和“post”是修饰“increment”的,准确地描述了增量概念的发生时间。“Pre-increment”在产生值之前概念上进行递增。“Post-increment”在产生值之后概念上进行递增。因此,使用pre-increment,您将获得递增后的值。而使用post-increment,则会获得原始值。 - Daniel Yankowsky

7

很简单。两者都会增加变量的值。以下两行代码是等价的:

x++;
++x;

区别在于你是否使用变量的值进行递增:

x = y++;
x = ++y;

这里,两行代码都将y的值加一。然而,第一行将增量之前的y的值分配给x,而第二行将增量后的y的值分配给x。

因此,只有在增量也被用作表达式时才有区别。后置增量在返回值后增加。前置增量在之前增加。


4
这两行完全不相等。-1 - wilhelmtell
6
威廉·泰尔:感谢您对讨论的毫无贡献。做得好。 - Jonathan Wood
1
@wilhelmtell:好的,是的。即使有一致的运算符重载,当您执行后加操作时,仍然会复制该对象。 - James McNellis
1
@JamesMcNellis 你将会首先调用两个不同的函数。 - wilhelmtell
4
如果重载函数是一致的(应该是这样的),那么 x++;++x; 这两个语句的效果是相同的。是的,你调用了不同的函数,但它们应该做相同的事情。这就是我试图表达的意思。 - James McNellis
显示剩余3条评论

4
int i = 1;
int j = 1;

int k = i++; // post increment
int l = ++j; // pre increment

std::cout << k; // prints 1
std::cout << l; // prints 2

后增量意味着在将值赋给k之后,值i会被增加。然而,前增量意味着在将值赋给l之前,值j会被增加。

递减也是同样的道理。


赞一个,因为用例子解释很清楚。 - Rajesh

2

后自增:

int x, y, z;

x = 1;
y = x++; //this means: y is assigned the x value first, then increase the value of x by 1. Thus y is 1;
z = x; //the value of x in this line and the rest is 2 because it was increased by 1 in the above line. Thus z is 2.

前缀递增:

int x, y, z;

x = 1;
y = ++x; //this means: increase the value of x by 1 first, then assign the value of x to y. The value of x in this line and the rest is 2. Thus y is 2.
z = x; //the value of x in this line is 2 as stated above. Thus z is 2.

1

这里已经有很好的答案了,但通常似乎缺乏一些普遍的清晰度,只是简单地记住它们的工作方式。我想这是因为语义上解决命名并不完全直观。例如,您可能知道“pre-”表示“之前”。但是,pre-increment ++i 是否返回增量之前的 i 的值,还是在返回值之前增加 i?

我发现从左到右视觉上跟随表达式要容易得多:

        ++                    i 
-------------------------------------------------->
    Increment i   Then supply the value of i


             i                      ++
-------------------------------------------------->
    Supply the value of i    Then increment i

当然,正如Alf在被接受的答案中指出的那样,这可能不反映“真实i”何时更新,但这是一种方便的思考表达式所需提供的内容的方式。

你的回答可以通过提供更多支持信息来改进。请编辑以添加进一步的细节,例如引用或文档,以便他人可以确认你的答案是正确的。您可以在帮助中心找到有关如何编写良好答案的更多信息。 - Community

1

现在我们有内联JavaScript代码片段,我可以添加一个关于前置和后置增量的交互式示例。虽然不是C++,但概念相同。

let A = 1;
let B = 1;

console.log('A++ === 2', A++ === 2);
console.log('++B === 2', ++B === 2);


0

后置递增(a++)

如果 int b = a++,那么这意味着

int b = a;

a = a+1;

这里我们将值加1。在增量进行之前,返回该值,

例如 a = 1; b = a++;

那么 b=1,a=2

预增量 (++a)

如果 int b = ++a; 那么这意味着

a=a+1;

int b=a ;

前缀自增:这将在主值上加1。增量完成后,该值将被返回。例如a = 1; b = ++a; 那么b = 2,a = 2。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接