我看到它们都被用于许多C代码中,我想知道何时使用i++或++i(i是一个数字变量,如int、float、double等)。有人知道吗?
- 除了没有任何区别的地方,你不应该使用任何一个,因为它只会让人们问你现在问的同一个问题。"不要让我认为"适用于代码和设计"。
- 太多重复:stackoverflow.com/questions/467322/…、stackoverflow.com/questions/484462/…、stackoverflow.com/questions/1678519/difference-between-i-and‌&8203;-i等。
- 你提到的重复是关于C++的,我的是C语言的。
- @你读过我评论中的链接吗?第一个是关于C,第二个是语言不可知论者,有一个公认的答案,集中在C。
- GNovice,第一个询问性能差异,而我问的是实际的代码差异,第二个是关于环路中的差异,而我问的是一般的差异,第三个是关于C++。
- @丹尼尔:你应该用什么来代替呢?具体什么时候用?我扭曲的大脑更快地读取增量操作符,因为我信任的任务更少,我花了一秒钟确保它不会被错误地写入/执行增量之外的操作。这主要是指一个孤立的i = i + 1;。
- 我相信这不是循环中i++和++i之间的差异的复制?-正如dloar在他上面的评论中所说,另一个问题专门询问循环中的用法。
不幸的是,这个问题的典型答案已经在这里发布了,一个在剩余操作之前执行增量操作,另一个在剩余操作之后执行增量操作。尽管这一观点直观地得到了理解,但这种说法表面上是完全错误的。事件的时间顺序在C中定义得非常好,而且强调的是,与其他操作相比,前缀和后缀版本所做的事情顺序不同。好的。
毫无疑问,你会看到这个问题的很多错误答案。很多"自学C"的书也会弄错。而且,C做的方式与C做的方式不同。许多人认为C和C是同一种语言,但事实并非如此。在我看来,C中的递增和递减操作符的设计避免了C中这些操作符的设计缺陷。好的。
必须回答两个问题才能确定前缀和后缀+在C中的具体操作。第一个问题是结果如何?第二个问题是,增量的副作用何时发生?好的。
这两个问题的答案都不明显,但事实上,一旦你看到它,就相当简单了。让我为您详细说明X++和X++对变量X的作用。好的。
对于前缀形式:好的。
x被计算为产生变量
变量的值被复制到临时位置
临时值递增以生成新值(不覆盖临时值!)
新值存储在变量中
操作的结果是新值(即临时值的递增值)
对于后缀表单:好的。
x被计算为产生变量
变量的值被复制到临时位置
临时值递增以生成新值(不覆盖临时值!)
新值存储在变量中
操作的结果是临时
需要注意的一些事项:好的。
首先,两种情况下事件的时间顺序完全相同。同样,事件在时间上的顺序在前缀和后缀之间变化的情况绝对不是这样。说评估发生在其他评估之前或之后是完全错误的。在这两种情况下,评估的顺序完全相同,正如您在步骤1到4中看到的一样。唯一的区别是最后一步——结果是临时值还是新的递增值。好的。
您可以通过一个简单的C控制台应用程序轻松地演示这一点:好的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| public class Application
{
public static int currentValue = 0;
public static void Main()
{
Console.WriteLine("Test 1: ++x");
(++currentValue).TestMethod();
Console.WriteLine("
Test 2: x++");
(currentValue++).TestMethod();
Console.WriteLine("
Test 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);
}
} |
以下是结果…好的。
1 2 3 4 5 6 7 8
| 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"结果所示。好的。
在这种情况下,首先将EDOCX1的值(0)存储在临时文件中。接下来,该值的递增版本存储回currentValue,但不接触仍然存储原始值的临时值。最后,临时文件被转交给TestMethod()。如果增量发生在调用TestMethod()之后,那么它将写出相同的非增量值两次,但不会。好的。
It's important to note that the value returned from both the currentValue++ and ++currentValue operations are based on the temporary and not the actual value stored in the variable at the time either operation exits.
Ok.
Recall in the order of operations above, the first two steps copy the then-current value of the variable into the temporary. That is what's used to calculate the return value; in the case of the prefix version, it's that temporary value incremented while in the case of the suffix version, it's that value directly/non-incremented. The variable itself is not read again after the initial storage into the temporary.
Ok.
Put more simply, the postfix version returns the value that was read from the variable (i.e. the value of the temporary) while the prefix version returns the value that was written back to the variable (i.e. the incremented value of the temporary). Neither return the variable's value.
Ok.
This is important to understand because the variable itself could be volatile and have changed on another thread which means the return value of those operations could differ from the current value stored in the variable.
Ok.
令人惊讶的是,人们常常对优先级、关联性和副作用的执行顺序感到非常困惑,我怀疑主要是因为C.C.中的混乱被精心设计成在所有这些方面都不那么混乱。对于这些问题的一些额外分析,包括我进一步证明了前缀和后缀操作"及时移动内容"的观点是错误的,请参见:好的。
http://blogs.msdn.com/b/ericlippet/archive/2009/08/10/precedence-vs-order-redux.aspx好的。
这就引出了这样一个问题:好的。
int[]arr=0 int value=arr[arr[0]++];value=1?好的。
你也可能对我之前关于这个主题的文章感兴趣:好的。
http://blogs.msdn.com/b/ericlippet/archive/2008/05/23/precedence-vs-associative-vs-order.aspx好的。
和好的。
http://blogs.msdn.com/b/ericlippet/archive/2007/08/14/c-and-the-pit-of-deadise.aspx好的。
还有一个有趣的例子,C使得很难解释正确性:好的。
blogs.msdn.com http:/ / / / / B ericlippert archive/2005/04/28/bad-recursion-revisited.aspx>
我们也遇见类似问题,subtle when that have other操作considering such as chained单端效应,assignments:>
blogs.msdn.com http:/ / / / / / ericlippert B馆2010年chaining-simple-assignments-is-not-so-simple.aspx/02/11/>
后安布尔是由运营商的兴趣为什么the result中增量值比在C # Rather变量:>
为什么不给你的C + +类的语言吗?>
好吧。
- +1:真的很好奇,你能不能参考一下你所说的"C语言中这些操作的设计缺陷"?
- @贾斯汀:我添加了一些链接。但基本上:在C语言中,你无法保证事情在时间上发生的顺序。当在同一序列点上有两个突变时,一致性编译器可以做任何它喜欢做的事情,并且不需要告诉您您正在做的是实现定义的行为。这导致人们编写危险的不可移植代码,这些代码在某些编译器上工作,在其他编译器上做一些完全不同的事情。
- 那真的很有趣,谢谢。
- 很有帮助,也很有趣。我在"剩余作战行动之前/之后完成增量"营地。我现在看到了我的方法的错误。谢谢!!
- 我必须说,对于真正好奇的人来说,这是一个很好的知识,但是对于一般的C应用程序来说,其他答案中的措词和正在进行的实际操作之间的差异远低于语言的抽象级别,因此它实际上没有任何区别。C不是汇编程序,99.9%的i++或++i在代码中使用,后台发生的事情就是这样;在后台。我写C是为了爬到抽象级别,高于这个级别上正在发生的事情,所以如果这对您的C代码真的很重要,那么您可能已经使用了错误的语言。
- @托马斯:首先,我关心的是所有的C应用程序,而不仅仅是一般应用程序的质量。非平均应用程序的数量很大。第二,它没有什么区别,除非它有区别。我会根据真正的代码中的真正错误(可能一年有六次)来回答关于这个东西的问题。第三,我同意你更大的观点;++的整个思想是一个非常低级的思想,在现代代码中看起来非常古怪。它之所以存在,是因为类C语言具有这样的运算符是惯用的。
- "后缀版本的值不是x的新值,而是x中存储的值。"不应该读"前缀",而不是"后缀"?(postfix返回读取的值,而不是写入的值。)
- @布拉德利:可能是打字错误。
- 回答很好,埃里克,谢谢。我不同意这对于C编程来说可能太低了。如果您只将它作为一个独立的语句来增加变量的值,那么很好。但是,一旦您将这个语句放入一个内嵌的代码位(比如在方法调用中将它作为参数传入,就会被咬到,除非您意识到您真正在做什么。埃里克,再次感谢你!
- +但我必须说实话…多年来,我唯一使用的后缀操作符并不是排在同一排的(所以不是i++;,而是for (int i = 0; i < x; i++)……我对此非常高兴!(我从来没有用过前缀操作符)。如果我必须写一些需要高级程序员2分钟才能破译的东西…好...最好再写一行代码或引入一个临时变量:-)我认为你的"文章"(我不会称之为"答案")证明了我的选择:-)
- @布拉德利,你说得对。这是一个错字。然而,当我去纠正它时,我意识到这两种情况都是正确的,正如上面所解释的。查看我对Eric原始答案的编辑(我做了这些修改,以使其更清晰,至少对我和其他像我这样的人而言如此。)
- 当一个答案不是被接受的时候,被上投了很多次,我知道它属于两个人中的一个。
奇怪的是,其他两个答案似乎没有拼出来,绝对值得说:
i++的意思是"告诉我i的值,然后递增"
++i表示‘递增i,然后告诉我值’
它们是预增量、后增量运算符。在这两种情况下,变量都是递增的,但是如果您在完全相同的情况下获取这两个表达式的值,结果将不同。
- 这似乎与埃里克所说的不一致
- 两种说法都不正确。考虑你的第一句话。i++实际上意味着"保存值,增加值,存储在i中,然后告诉我原始保存值"。也就是说,告诉发生在递增之后,而不是像您所说的那样发生在之前。考虑第二个陈述。实际上,i++的意思是"保存值,增加值,存储在i中,并告诉我增加的值"。你说的方式让人不清楚这个值是i的值,还是分配给i的值;它们可能是不同的。
- @埃里克,在我看来,即使是你的回答,第二句话也是绝对正确的。尽管他排除了一些步骤。
- 埃里克,谢谢你的评论,但是我看不出在我所说的和物理底层过程之间的真正的C代码示例(即使在仔细检查下)有什么明显的区别?
- 当然,在大多数情况下,描述操作语义的草率、不正确的方法给出的结果与精确和正确的描述相同。首先,我认为通过不正确的推理获得正确答案没有任何令人信服的价值,其次,是的,我已经看到生产代码正是因为这种事情而出错。我每年可能会从真正的程序员那里得到六个问题,关于为什么某些特定的表达式充满了增量、减量和数组引用,并不像他们想象的那样工作。
- 我的意思是,我集中在你的观点上,"告诉发生在递增之后":这是一个单独的表达式正在被评估,我说它要么在递增之前评估值,要么在递增之后评估值。在对表达式进行评估之前(至少,这是我的想法,也可能是我感到困惑的原因),结果不会被使用,所以无论是在之前还是之后被"告知"都没有区别?
- 我很确定C标准明确地允许增量在前一个序列点和下一个序列点之间的任何时间发生。因此,在语句"*a=(*b)++"中,如果"a"和"b"恰好指向同一个变量,则赋值可能发生在递增之前(在这种情况下,变量将递增)或之后(在这种情况下,变量将递增,然后重新分配其旧值)。实际上,如果编译器生成的代码想要开始打印"99瓶啤酒",我认为这在标准下也是允许的。
- @超级卫星:讨论的是C,而不是C。
- @埃里克,如果可能的话会很感谢你的回复,所以我可以在必要的时候相应地更新我的回复。
- @Kieren,看看Eric的推荐帖子,它使用这个def引起了混乱。stackoverflow.com/questions/1260227/&hellip;
- 嗨,埃文,谢谢,我同意,我看不出这和我说的有什么不同。我并不是说增量发生在行中的其他事情之后;我只是说结果要么是增量之前的值,要么是增量之后的值。我支持我的陈述;"告诉我值然后加1"并不意味着"告诉我值,然后评估整个陈述,然后加1"。不过,我们还是欢迎一些具体的反馈。
- -1:这个答案仍然有误导性。
- @布莱恩-你看过我的评论吗?缺乏回应?你真的认为有些东西是误导性的——你想在不复制和粘贴Eric的情况下准确地分享它是什么吗?或者只是另一个不想自己去想的埃里克爱好者?那家伙甚至没有回复我。
- @基仁:误导性的部分是你说"i++意味着‘告诉我我的价值,然后递增’",当它真正意味着‘增加我,然后告诉我我的旧价值。有些情况下,这个命令很重要。
- @布赖恩——这不适用——我说的话不真实。正如我所说,我不会说"告诉我我的价值,然后评估其余的陈述,然后递增":我们说的是相同的事情,但我的回答只是更简洁而已!
- @布莱恩-我就是这么想的。:)
- @基仁:埃里克的例子(int[] arr = {0}; int value = arr[arr[0]++];是我能想到的最简单的例子,这个订单在哪里引起了问题。我不能提供更好的。
- @布莱恩-引用我自己的话,"i++"的意思是"告诉我我的值,然后递增"。在这个例子中,arr[0]++说告诉我arr[0]的值,然后递增。我不说它什么时候会发生,也不暗示它发生在任何事情被评估之前或之后。此外,埃里克的第一篇链接博客文章解释了评估和副作用发生的细节:但简单地说,我没有说什么不同的!
- @基仁:我认为你的解释与副作用的顺序相矛盾。显然,你没有。耸耸肩::
- 很高兴看到人们在给出清晰、简洁和正确的答案后仍然盲目地投票给我。不过,流行乐有点吃饱了。
- 我投了反对票,但我不是盲目的。你的回答不正确。除表达式计算结束时返回的内容外,I++和++以相同的顺序转换为相同的事件。你的回答似乎暗示了另一种情况。如果你还不清楚,请重新阅读埃里克的答案。
- Downvote。基仁,你说,"我不说什么时候会发生,也不暗示它发生在任何事情被评估之前或之后。"这是错误的。告诉我i的值,然后递增"清楚地表达了时间关系。请参阅"然后"的定义。你在第二种意义上使用这个词(如现在的链接所列);这个意义被定义为"在那之后;在那之后;在那之后。"你的陈述意思是"告诉我我的价值,在那之后,递增",而这个陈述是错误的。
- @马修-我清楚地说"告诉我EDOCX1的值(0),然后递增"-这是正确的。我对"我不说什么时候"的补充是关于用户可能希望在该语句/行中创建的任何其他内容。所以,再一次,"EDOCX1"(1)的意思是"告诉我i的值,然后递增"。另外一部分是说我不包括"(++i)[j++] = i++ + ++j"之类的内容。我是说i++和++i的区别。
- @基仁的说法不正确。增量出现在返回EDOCX1的值(0)之前。"递增,然后告诉我之前的EDOCX1[0]值是正确的。你不需要添加额外的细节来做出正确的陈述。
- @Kierenjohnstone我编辑了你的答案,让它和EricLippert(我想)说的一样。你能复习一下吗?
- @米兰加第,我收回了你的编辑。不管是对还是错,你的编辑改变了答案,这可能是运营商的决定。
- 不管别人怎么说,这是完全正确的。就生成的代码而言,甚至Eric都是错误的。他和其他人都假装基林说增量发生在表达式的其余部分之后。他什么也没说。
- @当95%的时间无关紧要的时候,人们很容易对事件的确切顺序喋喋不休。但5%的时间是错误的,这会让你的答案错了。我不能告诉你我看到了多少复杂的混乱,因为有人不理解抛出异常时事件的顺序。它触发的确切点、后续流和结果状态。例如(下一条注释),您的语句暗示catch块中的a == int.MaxValue。
- @Kierenjohnstone下面的代码示例显示,在i递增之前,声称a被"告知i"的值是如何对a产生错误的影响:int i = int.MaxValue; int a = 0; try { checked { a = i++; } } catch { Console.WriteLine(a); }输出是0,因为a在i递增之后才被告知。递增i引发异常,a保持不变。
如果你有:
1 2
| int i = 10;
int x = ++i; |
那么,x将是11。
但如果你有:
1 2
| int i = 10;
int x = i++; |
那么,x就是10。
请注意,正如Eric指出的,在这两种情况下,增量是同时发生的,但结果不同的是给出的值(谢谢Eric!).
一般来说,我喜欢使用++i,除非有充分的理由不这样做。例如,在编写循环时,我喜欢使用:
1 2
| for (int i = 0; i < 10; ++i) {
} |
或者,如果我只需要增加一个变量,我喜欢使用:
通常情况下,一种方法或另一种方法没有太大的意义,可以归结为编码样式,但是如果您在其他分配中使用运算符(如在我的原始示例中),则必须注意潜在的副作用。
- 您可以通过将代码行按顺序排列来澄清您的示例—也就是说,改为说"var x=10;var y=++x";"var x=10;var y=x++;"。
- @托马斯·莱肯-说得好,谢谢。我听从了你的建议。
- 这个答案是危险的错误。当增量发生时,相对于剩余的操作没有变化,因此在一种情况下,增量是在剩余的操作之前完成的,而在另一种情况下,增量是在剩余的操作之后完成的,这是一种严重的误导。在这两种情况下,增量是同时进行的。不同的是结果给出的值,而不是增量完成时给出的值。
- 我已经编辑了它,使用i作为变量名,而不是var,因为它是C关键字。
- @埃里克-谢谢你的纠正,埃里克,我调整了答案。杰夫,我想我们一定是同时编辑的,因为我看不到更改,但我会修改它,谢谢你指出。
- @DCP:已经完成。:)
- 我还认为,++x;和++i违反了我所见过的任何惯例,但我又没有见过这么多……
- 因此,如果不分配变量,只声明"i++;"或"++i;",会得到完全相同的结果,还是我弄错了?
- @dlaor-你是对的。它本身是相同的,在for循环中也是相同的。
- @Kobi-在我开始进行编程竞赛和学习其他人的代码之前,我并没有开始使用++I语法。我注意到许多非常好的编码员使用它,我养成了使用它的习惯,现在我一直使用它!
- @埃里克:你能解释一下为什么最初的措辞是"危险的"吗?它确实会导致正确的使用,即使细节是错误的。
- 感谢大家的快速回复,问题解决了:)
- @贾斯汀:埃里克对替代措辞为何危险的解释在对基仁答案的评论中提到。简而言之,最初的措辞是危险的,因为基于错误假设的编码意味着您的程序没有执行您认为它们正在执行的操作。这很容易导致错误。
运算符的工作方式是同时递增,但如果它在变量之前,则表达式将使用递增/递减的变量进行计算:
1 2
| int x = 0; //x is 0
int y = ++x; //x is 1 and y is 1 |
如果它在变量之后,则当前语句将使用原始变量执行,就好像它尚未递增/递减:
1 2
| 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循环或类似的循环中。这些循环是相同的:
1 2 3 4 5
| while (x < 5) //evaluates conditional statement
{
//some code
++x; //increments x
} |
或
1 2 3 4
| while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
//some code
} |
您还可以在索引数组等时执行此操作:
1 2 3 4 5
| 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); |
等等。
- 迈克,你说"在其他事情之前"——这不准确,它只是在计算增量表达式之前。
- @Kieren Johnstone:应该现在就修好。谢谢你的接球。
- 同时,纠正了一些错误的陈述
- +1用于显示两个变量的值。
1 2 3
| 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没有任何影响。
- +1;我真的很喜欢它;—)
- 这似乎是回答这个问题最不清楚的方法。
- +1.有创造力。-1.有创造力。
- 信息不多。
- 我的选票似乎也发生了类似的事情:他们起起落落……所以我坚持0:-)
- +因为答案完美地说明了两者之间的区别。
- 哦,太聪明了!
就记录而言,在C++中,如果你可以使用(即)你不关心操作的顺序(你只想增加或减量并在以后使用它),前缀操作符是更高效的,因为它不必创建对象的临时副本。不幸的是,大多数人使用posfix(var++)而不是前缀(++var),这是我们最初学到的。(我在一次采访中被问及此事)。不确定这在C中是否正确,但我想应该是。
- 这就是为什么我在C to中单独使用时(即不在更大的表达式中)使用++I的原因。每当我看到一个带有I++的C循环时,我都会有点哭泣,但现在我知道在C循环中,这实际上和我感觉更好的代码差不多。就我个人而言,我仍然会使用++I,因为习惯很难改变。