有趣的面试练习结果:返回、后增和引用行为

60

这里是一段简单的控制台应用程序代码,它返回的结果我不完全理解。

请尝试思考它在控制台输出0、1还是2:

using System;

namespace ConsoleApplication
{
    class Program
    {
        static void Main()
        {
            int i = 0;
            i += Increment(ref i);

            Console.WriteLine(i);
            Console.ReadLine();
        }

        static private int Increment(ref int i)
        {
            return i++;
        }
    }
}

答案是0。
我不明白的是为什么后置递增运算符i++Increment方法中执行时(该方法在ref上执行而不是在传递变量的副本上执行),确实会增加变量,但稍后却被忽略了。
我的意思是在这个视频中: 有人能解释一下这个例子,以及为什么在调试期间我看到的值增加到1,但后来又回到了0?

是的,但是后自增运算符正在修改 i ,它是一个 ref。虽然我知道在“正常”情况下为什么会返回值 0,但我不知道为什么它会从 1 上升然后再下降到 0。 - Irwene
33
尽管这是一个解谜问题,但在我看来,这是一个相当愚蠢的面试问题。它应该被标记为“语言律师”,因为这种行为取决于一个非常小众的语言规则,你希望永远不会亲眼见到这种情况发生,因为(希望)在实际编程中你永远不会看到像这样的代码。 - Alexander
2
@Alexander同意。如果有人为我工作写了这样的代码,我会坐下来和他们“谈谈”。 - user2023861
1
有些相关的问题,也提供了一些关于幕后发生的事情的好信息:https://dev59.com/EVsX5IYBdhLWcg3wd_XS - Siyual
3
如果我在面试中看到这段代码,我的第一反应会是:“我能为这段代码编写测试(如果它们还不存在),然后重构它使其变得更加清晰易懂吗?”(请注意,此翻译尽可能保持原意和表达方式,同时增加了一些通俗易懂的措辞) - svick
显示剩余5条评论
3个回答

102

i += Increment(ref i); 的意思相当于

i = i + Increment(ref i);

赋值语句右侧的表达式从左到右进行求值,因此下一步是

i = 0 + Increment(ref i);

return i++ 返回当前 i 的值(即0),然后将 i 增加1。

i = 0 + 0;

在赋值之前,i 的值为1(在 Increment 方法中递增),但是赋值操作会使其重新变为0。


3
你比我快了一点点 ;) - DarkSquirrel42
谢谢Jakub提供的详细信息,现在明白了为什么它从1变成了0。 - Aremyst
8
免责声明:我有C++背景,对C#知之甚少。但是我对此感到困惑。显然,C#的定义强制编译器在像i+=f();这样的语句中开始制作一个本地匿名副本的i值,然后调用f,如果它有访问权限,就可以随意操纵变量i,因为知道该变量中留下的值将被遗忘,因为它将被先前制作的i的匿名副本和f返回的值的总和覆盖。非常好奇。似乎违反了使+=简单快速的想法。 - Marc van Leeuwen
4
我想,从我的记忆中看来,这应该是被很好地定义了。虽然C++标准基本上(而且有些懒散地)将 var+=expr 定义为等价于 var=var+expr,但它确实有两个例外:lvalue var 只被计算一次,并且“关于一个不确定顺序的函数调用,复合赋值的操作是单个评估”。虽然这并不完全清晰,但似乎禁止在函数调用之前获取 var 并在之后存储 var 来实现该操作。如果你感兴趣,可以参见https://dev59.com/VOo6XIcBkEYKwwoYJAzV。 - Marc van Leeuwen
4
如果评估顺序被严格定义为从左到右,那么这就有意义了。对于var=var+expr,并且采用从左到右的评估顺序,var将始终在评估expr之前被检查,因此exprvar所做的任何更改都不会反映在其中。我猜C#对此的排序比C++更严格或者其他什么原因。 - Justin Time - Reinstate Monica
显示剩余4条评论

18

我认为这里的"魔法"只是运算顺序。

i += Increment(ref i)

等同于

i = i + Increment(ref i)

加法运算从左到右执行。首先我们取 i,那时它的值为0。然后我们将Increment(ref i)方法的结果相加,它也是0。0+0=0,但是在得到这个结果之前,i被增加了。这个增量操作是在我们的加法操作的左操作数被求值之后发生的,所以它不会改变任何东西。0+0仍然是0。因此,在执行加法操作后,i被赋值为0。


3
与优先级无关,一切都与从左到右的求值有关。 - Pete Kirkham
请仔细阅读:操作符优先级!= 操作的优先级,这导致按从左到右的顺序执行。 - DarkSquirrel42
4
我认真阅读了你的内容。你的写作有些不规范。请使用正确的术语——"运算顺序",而非"优先级"。优先级决定了如何从模棱两可的输入构建AST(抽象语法树)。而运算顺序/计算顺序则决定了如何将AST评估为结果。使用"操作优先级"这个短语既不是优先级,也不是运算顺序。 - Pete Kirkham
嗯...感谢你的英语词汇课。 - DarkSquirrel42

0

正如你所提到的 - 后自增 "i++"。语句 - "return i++;" 将在返回原始值后将 'i' 的值设置在内存中。

尝试使用 "return ++i;",也许你会明白它。


1
我认为这不正确。语句return i++;会先将i加1,然后返回i之前的值,也就是0。我从Eric Lippert在这里的回答中得到了这个信息:https://dev59.com/tXA75IYBdhLWcg3wVniI#3346729 - Tanner Swett

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