i++ 和 ++i 有啥区别?

Posted

技术标签:

【中文标题】i++ 和 ++i 有啥区别?【英文标题】:What is the difference between i++ and ++i?i++ 和 ++i 有什么区别? 【发布时间】:2011-03-21 18:15:03 【问题描述】:

我已经看到它们都在许多 C# 代码中使用,我想知道何时使用 i++++ii 是一个数字变量,如 int、@987654330 @、double 等)。有谁知道吗?

【问题讨论】:

除非它没有任何区别,否则你永远不应该使用任何一个,因为它只会导致人们问你现在问的同样的问题。 “不要让我思考”适用于代码和设计。 Soooo 很多重复:***.com/questions/467322/…、***.com/questions/484462/…、***.com/questions/1678519/difference-between-i-and-i 等 @Dlaor:你有没有阅读我评论中的链接?第一个是关于 C# 的,第二个是与语言无关的,有一个公认的答案,专注于 C#。 @gnovice,第一个询问性能差异,而我询问实际代码方面的差异,第二个是关于循环中的差异,而我询问一般差异,第三个是关于 C++。 我相信这不是 Difference between i++ and ++i in a loop? 的副本 - 正如 Dloar 在 his comment above 中所说,另一个问题专门询问了循环内的使用情况。 【参考方案1】:

不幸的是,这个问题的典型答案已经在这里发布了,一个是在剩余操作“之前”进行增量,另一个是在剩余操作“之后”进行增量。 虽然直观地理解了这个想法,但从表面上看,这种说法是完全错误的事件的时间顺序在 C# 中定义得非常好,并且强调不是前缀 (++var) 和后缀 (var++) 版本的情况++ 相对于其他操作以不同的顺序执行操作。

对于这个问题,您会看到很多错误的答案,这不足为奇。许多“自学 C#”的书也弄错了。此外,C# 的执行方式与 C 的执行方式不同。许多人认为 C# 和 C 是同一种语言。他们不是。我认为C#中递增和递减运算符的设计避免了这些运算符在C中的设计缺陷。

要确定前缀和后缀 ++ 在 C# 中的具体操作是什么,必须回答两个问题。第一个问题是结果是什么?第二个问题是增量的副作用什么时候发生?

这两个问题的答案并不明显,但是一旦你看到它,它实际上是非常简单的。让我准确地为您说明 x++ 和 ++x 对变量 x 的作用。

对于前缀形式(++x):

    x 被评估以产生变量 变量的值被复制到一个临时位置 临时值递增以产生新值(不会覆盖临时值!) 新值存储在变量中 运算的结果是新值(即临时增加的值)

对于后缀形式(x++):

    x 被评估以产生变量 变量的值被复制到一个临时位置 临时值递增以产生新值(不会覆盖临时值!) 新值存储在变量中 运算结果是临时值

注意事项:

首先,两种情况下事件的时间顺序完全相同。同样,绝对不是事件的时间顺序在前缀和后缀之间发生变化的情况。说评估发生在其他评估之前或其他评估之后是完全错误的。在这两种情况下,评估都以完全相同的顺序进行,您可以从步骤 1 到 4 看到相同。 唯一的区别在于最后一步 - 结果是临时值还是新的递增值。

您可以使用一个简单的 C# 控制台应用程序轻松演示这一点:

public class Application

    public static int currentValue = 0;

    public static void Main()
    
        Console.WriteLine("Test 1: ++x");
        (++currentValue).TestMethod();

        Console.WriteLine("\nTest 2: x++");
        (currentValue++).TestMethod();

        Console.WriteLine("\nTest 3: ++x");
        (++currentValue).TestMethod();

        Console.ReadKey();
    


public static class ExtensionMethods 

    public static void TestMethod(this int passedInValue) 
    
        Console.WriteLine("Current:0 Passed-in:1",
            Application.currentValue,
            passedInValue);
    

这是结果...

Test 1: ++x
Current:1 Passed-in:1

Test 2: x++
Current:2 Passed-in:1

Test 3: ++x
Current:3 Passed-in:3

在第一个测试中,您可以看到 currentValue 和传递给 TestMethod() 扩展的内容显示相同的值,正如预期的那样。

但是,在第二种情况下,人们会尝试告诉您currentValue 的增量发生在调用TestMethod() 之后,但正如您从结果中看到的那样,它发生了调用之前,如“Current:2”结果所示。

在这种情况下,首先将currentValue 的值存储在一个临时文件中。接下来,将该值的递增版本存储回currentValue,但不会触及仍存储原始值的临时值。最后将该临时文件传递给TestMethod()。如果增量发生在调用TestMethod() 之后,那么它会写出相同的非增量值两次,但它不会。

请务必注意,currentValue++++currentValue 操作返回的值基于任一操作退出时存储在变量中的临时值,而不是实际值。

按照上面的操作顺序回想一下,前两步将变量的当前值复制到临时变量中。那就是用来计算返回值的;在前缀版本的情况下,临时值增加,而在后缀版本的情况下,它是直接/不增加的值。变量本身在初始存储到临时后不会再次读取。

更简单地说,后缀版本返回从变量中读取的值(即临时值),而前缀版本返回写回变量的值(即临时值的递增值) .都不返回变量的值。

理解这一点很重要,因为变量本身可能是易失的,并且在另一个线程上发生了变化,这意味着这些操作的返回值可能与存储在变量中的当前值不同。

人们对优先级、关联性和副作用的执行顺序感到非常困惑是很常见的,我怀疑主要是因为它在 C 中非常令人困惑。C# 被精心设计为在所有方面都不那么令人困惑这些问候。对于这些问题的一些额外分析,包括我进一步证明前缀和后缀操作“及时移动东西”的想法是错误的,请参阅:

https://ericlippert.com/2009/08/10/precedence-vs-order-redux/

这导致了这个 SO 问题:

int[] arr=0; int value = arr[arr[0]++]; Value = 1?

您可能还对我之前关于该主题的文章感兴趣:

https://ericlippert.com/2008/05/23/precedence-vs-associativity-vs-order/

https://ericlippert.com/2007/08/14/c-and-the-pit-of-despair/

还有一个有趣的案例,其中 C 语言很难推理正确性:

https://docs.microsoft.com/archive/blogs/ericlippert/bad-recursion-revisited

此外,在考虑其他具有副作用的操作(例如链式简单赋值)时,我们也会遇到类似的微妙问题:

https://docs.microsoft.com/archive/blogs/ericlippert/chaining-simple-assignments-is-not-so-simple

这里有一篇有趣的文章,关于为什么增量运算符在 C# 中导致 values 而不是 variables

Why can't I do ++i++ in C-like languages?

【讨论】:

+1:对于真正好奇的人,您能否参考一下您所说的“C 中这些操作的设计缺陷”是什么意思? @Justin:我添加了一些链接。但基本上:在 C 中,您无法保证事情发生的顺序。当同一序列点中有两个突变时,符合标准的编译器可以做任何它喜欢的事情,并且永远不需要告诉您您正在做的事情是实现定义的行为。这会导致人们编写危险的不可移植代码,这些代码在某些编译器上运行,而在其他编译器上却完全不同。 我必须说,对于真正好奇的人来说,这是一个很好的知识,但对于普通的 C# 应用程序来说,其他答案中的措辞与实际发生的事情之间的差异远远低于抽象语言的水平,它真的没有区别。 C# 不是汇编程序,在代码中使用 i++++i 的 99.9% 中,后台发生的事情就是这样; 在后台。我编写 C# 是为了爬升到这个级别之上的抽象级别,所以如果这对你的 C# 代码真的很重要,那么你可能已经使用了错误的语言。 @Tomas:首先,我关心的是所有 C# 应用程序,而不仅仅是大量的普通应用程序。非平均应用程序的数量很大。其次,它没有任何区别除非有区别。我根据实际代码中的实际错误收到有关这些东西的问题,一年大概有六次。第三,我同意你的大点; ++ 的整个想法是一个非常低级的想法,在现代代码中看起来非常古怪。它确实存在,因为类 C 语言具有这样的运算符是惯用的。 +1 但我必须说实话......多年来我唯一使用后缀运算符的做法并不孤单(所以这不是i++;)在for (int i = 0; i < x; i++)... 我对此感到非常高兴! (而且我从不使用前缀运算符)。如果我必须写一些需要高级程序员 2 分钟才能破译的东西......嗯......最好再写一行代码或引入一个临时变量:-) 我认为你的“文章”(我赢了不要称之为“答案”)证明我的选择是正确的:-)【参考方案2】:

奇怪的是,其他两个答案似乎没有拼写出来,这绝对值得一说:


i++ 表示“告诉我i 的值,然后递增”

++i 表示'增加i,然后告诉我值'


它们是前置增量、后置增量运算符。 在这两种情况下,变量都是递增的,但如果你在完全相同的情况下取两个表达式的值,结果会有所不同。

【讨论】:

这似乎与Eric is saying的内容不一致 两种说法都不正确。考虑你的第一个陈述。 i++ 实际上的意思是“保存值,增加它,将它存储在 i 中,然后告诉我原来保存的值”。也就是说,告诉发生在之后递增,而不是之前,就像你所说的那样。考虑第二个陈述。 i++ 实际上的意思是“保存值,增加它,将它存储在 i 中,然后告诉我增加的值”。您所说的方式使您不清楚该值是i的值还是分配给i的值; 它们可能不同 当然,大部分时间描述操作语义的草率、不正确的方式与精确和正确的描述给出相同的结果。首先,我没有看到任何令人信服的 value 通过不正确的推理来获得正确的答案,其次,是的,我看到生产代码会出现这种错误。每年我可能会从真正的程序员那里收到六个问题,关于为什么某些特定的表达式充满了递增和递减以及数组取消引用,并不能像他们认为的那样工作。 @supercat:讨论是关于 C#,而不是 C。 @KierenJohnstone 下面的代码示例展示了在增加i 之前声称a 是“告诉i 的值”如何产生对a 影响的错误印象:@ 987654330@ 输出为 0,因为在 i 递增之前,a 不会告诉任何内容。递增i 会引发异常,a 保持不变。【参考方案3】:

如果你有:

int i = 10;
int x = ++i;

那么x 将是11

但如果你有:

int i = 10;
int x = i++;

那么x 将是10

请注意,正如 Eric 指出的那样,增量在两种情况下同时发生,但给出的结果不同(感谢 Eric!)。

一般来说,我喜欢使用++i,除非有充分的理由不这样做。比如写循环的时候,我喜欢用:

for (int i = 0; i < 10; ++i) 

或者,如果我只需要增加一个变量,我喜欢使用:

++x;

通常,一种方法或另一种方法没有太大意义,归结为编码风格,但如果您在其他分配中使用运算符(如在我的原始示例中),请务必注意潜在的副作用.

【讨论】:

您可以通过按顺序排列代码行来阐明您的示例 - 也就是说,说“var x = 10; var y = ++x;”和“var x = 10; var y = x++;”而是。 这个答案是非常错误的。 当增量发生时,剩余操作不会改变,所以说在一种情况下它在 剩余操作之前完成,而在另一种情况下它完成 after 剩余的操作具有很大的误导性。 两种情况下同时递增。不同的是作为结果给出什么值,而不是当增量完成时 我已将其编辑为使用 i 作为变量名,而不是 var,因为它是 C# 关键字。 所以没有分配变量,只声明“i++;”或“++i;”会给出完全相同的结果还是我弄错了? @Eric:你能解释一下为什么原来的措辞是“危险的”吗?它确实会导致正确的使用,即使它的细节是错误的。【参考方案4】:
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.

这能回答你的问题吗?

【讨论】:

可以想象后缀递增和前缀递减对变量没有任何影响:P【参考方案5】:

运算符的工作方式是同时递增,但如果它在变量之前,则表达式将使用递增/递减变量进行计算:

int x = 0;   //x is 0
int y = ++x; //x is 1 and y is 1

如果它在变量之后,当前语句将与原始变量一起执行,就好像它还没有被递增/递减一样:

int x = 0;   //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0

除非必要,否则我同意 dcp 使用预增量/减量 (++x)。真的,我唯一一次使用后增量/减量是在 while 循环或那种循环中。这些循环是相同的:

while (x < 5)  //evaluates conditional statement

    //some code
    ++x;       //increments x

while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented

    //some code

您也可以在索引数组等时执行此操作:

int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678;   //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);

等等等等……

【讨论】:

【参考方案6】:

仅作记录,在 C++ 中,如果您可以使用任何一个(即)您不关心操作的顺序(您只想递增或递减并稍后使用它),前缀运算符更有效,因为它不必创建对象的临时副本。不幸的是,大多数人使用 posfix (var++) 而不是 prefix (++var),只是因为这是我们最初学到的。 (我在一次采访中被问到这个问题)。不确定这在 C# 中是否正确,但我认为是这样。

【讨论】:

这就是为什么我在 c# 中使用 ++i 来单独使用时(即不在更大的表达式中)。每当我看到带有 i++ 的 c# 循环时,我都会流泪,但现在我知道在 c# 中这实际上是几乎相同的代码,我感觉好多了。就我个人而言,我仍然会使用 ++i,因为习惯很难改掉。

以上是关于i++ 和 ++i 有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章

C中的i++和++i有啥区别? [复制]

从任何角度来看 ++i 和 i+=1 有啥区别

df.iloc[[i]] 和 df.iloc[i] 有啥区别 [重复]

for循环中的i++和++i有啥区别? [复制]

+= 和 =+ 有啥区别?

makefile 中的 -I 和 -L 有啥区别?