【问题标题】:What Does i++ Really Mean?i++ 的真正含义是什么?
【发布时间】:2015-10-12 00:09:13
【问题描述】:

这个问题已经被问过很多次了,“i++ 和 ++i 有什么区别”。 What is the difference between i++ and ++i? 接受的答案是,我在许多其他地方也看到过这种语言,“i++ 的意思是‘告诉我 i 的值,然后递增’,而 ++i 的意思是‘递增 i,然后告诉我价值'。

让我感到困惑的是,我不知道我们正在讨论在 either 场景中取回 i 的值。我认为 i++ 在语法上等同于:

i = i + 1;

这是一个语句,而不是一个表达式,所以我根本不明白 i 在哪里被返回。

你能解释一下这句话的真正含义吗?

谢谢,

【问题讨论】:

  • 赋值可以是表达式也可以是语句;考虑var a = 1; var b = (a = 2);。 (更多来自 Eric Lippert 的信息可以在 here 找到)。
  • 根本的误解是认为i++++i句法上 完全等同于任何事物。如果您将它们视为糖而困扰您,则不必这样做。 它们本身就是表达式,并且具有非常仔细地定义的含义。忽略您链接到的问题中已接受的答案;请阅读我的答案,看看是否有帮助。
  • @DodgyCodeException: 自定义操作符就是一个很好的例子,不做语义分析就无法知道是否有自定义操作符,也就是说它是不是句法属性。
  • @DodgyCodeException:我们可能还会考虑像++q->i 这样的情况与(q+=1)->i 不同,但在这些情况下,将++q 视为一件事有点滥用,因为它实际上不是该程序片段中的子表达式。这是我们可以从语法上识别出来的东西。
  • @DodgyCodeException:我们也可以很容易地看到,等价并没有在另一个方向上运行。字符串 s 的 s += 1 完全合法,但 ++s 不合法。

标签: c#


【解决方案1】:

由于我不知道的原因,这个答案已被接受的老问题今天正在吸引新的答案,其中许多都包含重大错误或遗漏。让我尝试按问明确地回答这个问题。

这个问题已经被问过很多次了,“i++ 和 ++i 有什么区别”。 [...] 的公认答案是,我在许多其他地方也看到过这种语言,“i++ 的意思是'告诉我 i 的值,然后增加',而 ++i 的意思是'增加 i ,然后告诉我价值'。

正如我在对同一个问题的回答中指出的那样:这种表征很常见,并且是理解的合理第一步,但不幸的是,当您更仔细地查看语义时会产生误导。请不要被这种模糊且不完全准确的描述所误导。请阅读我对那个问题的回答。

让我感到困惑的是,我不知道我们正在讨论在任何一种情况下取​​回 i 的值。我认为 i++ 在语法上等同于 i = i + 1; 这是一个语句,而不是一个表达式,所以我根本不明白 i 在哪里返回。

您在这里有许多误解。与其全盘攻击,不如直说真相。

首先,++ii++ 在语法上并不完全等同于 任何事物。您不能必然采用包含++ii++ 的合法程序并将其仅在语法上转换为另一个合法程序。因此,请从您的脑海中消除这种想法。这些在道德上等价于递增和赋值,并且应该在语义上等价,但没有必要保留程序的句法去糖语义或合法性。

现在让我们说一些更真实的事情。但首先,有一些注意事项。出于讨论的目的,递增的表达式i 是一个int 类型的变量,它可以作为一个变量或一个没有副作用(包括异常)的值来计算。此外,我们假设递增操作不会产生异常。此外,我们假设一个执行线程。如果您想知道在评估变量可能引发或产生其他副作用、不是变量而是属性、操作是用户定义的或多个线程正在观察的情况下增量和赋值的语义或改变变量,详见规范

也就是说,这里有一些真实的事实:

  • ++ii++i = i + 1表达式
  • ++i;i++;i = i + 1;声明
  • ++i的语义如下:

    1. temp1 被赋予 i 的值
    2. temp2 的值是 temp1 + 1
    3. i 被赋予 temp2 的值
    4. 表达式的值为 temp2
  • i++的语义如下:

    1. temp1 被赋予 i 的值
    2. temp2 的值是 temp1 + 1
    3. i 被赋予 temp2 的值
    4. 表达式的值为 temp1
  • 请注意,这两种形式之间的区别是所产生的值。 在这两种情况下产生副作用所采取的步骤是相同的​​保证在单线程 C# 中,副作用在产生值之前 被观察到。

  • i = i + 1的语义如下:
    1. temp1 被赋予 i 的值
    2. temp2 的值是 temp1 + 1
    3. i 被赋予 temp2 的值
    4. 表达式的值为 temp2
  • 注意i = i + 1 的语义与++i 的语义相同。这不能保证您可以在任意程序中在语法上将i = i + 1 替换为++i,反之亦然。在某些程序中,这可能是可能的。
  • 注意i++ 的语义不允许“简单”的语义等价形式。 ((Func<int, int, int>)((int j, int k)=>j))(i, i=i+1) 具有相同的语义,但输入起来显然很疯狂。

  • 这三种语句形式的语义分别是:

    1. 正常评估表达式。
    2. 丢弃结果。

希望这能彻底消除关于什么是表达式、什么是语句、表达式产生什么副作用和值以及它们发生的顺序的任何误解。同样,请注意,此解释仅针对涉及整数变量的简单情况,而不会对单个线程产生副作用。有关这些操作如何在其他类型上工作、与异常交互或在多线程程序中如何工作的详细信息,请参阅 C# 规范或提出更具体的问题。

最后:我个人仍然觉得这一切令人困惑,而且我已经使用 C 后代语言编程 30 年,并且我在 C# 中实现这些语义。如果我发现它们令人困惑,并且如果我在这些运算符上看到的每个问题的几乎每个答案都包含重大错误或遗漏,那么我们可以有把握地得出结论,这些运算符令人困惑。因此,我几乎从不在生产代码中使用++--。我认为有一个对它的价值和副作用都有用的表达式是不好的风格。

尝试找到一种方法来构建您的程序,以便任何语句都有一个副作用,并且副作用表达式仅限于表达式语句。避免使用++,尤其是避免i++++i 具有不同语义的任何情况,因为这会导致程序更难理解,因此更难正确维护。

【讨论】:

  • 首先,我认为这是一个高质量的答案,并且已经投了赞成票。但是关于第一行;我认为 SO 的一大优点是它是一种不断发展的知识资源。我认为让人们回来检查旧问题以确保他们得到充分回答是很好的(这个答案就是一个很好的例子),我不想让人们觉得他们不应该尝试这样做(当我们真的可以使用更多它时),即使是错误的(上下投票,与 cmets 一起,将解决好与坏)。
  • i += 1 怎么样?是否等同于i = i + 1
  • @LucaCremonesi:如果i 是一个整数变量,则没有例外,依此类推,如上所述,那么是的。如果您想了解规则,例如,如果 i 很短或有副作用或类似情况怎么办,请查阅规范。
  • @LucaCremonesi:小测验:假设imndoublei += m + n 是否保证与i = i + m + n 相同?
  • i += m + n 是否等同于i = i + (m + n)i 仅评估一次)而i = i + m + n 等同于i = (i + m) + n?如果是这样,由于双精度浮点运算,这两个赋值通常可能不同。
【解决方案2】:

编辑:Eric's answer,我的没啥。


你是对的。 ++等价于i = i + 1,但要记住的是i = i + 1不仅是语句,还可以用作表达式:

Console.WriteLine((i = i + 1) * 42); // will inc i, and then print i*42
Console.WriteLine(++i * 42); // exactly same

Console.WriteLine(i++ * 42); // will inc i, and print old_i*42
Console.WriteLine((i++) * 42); // exactly same

简单总结一下:

// this
int j = ++i * 42;
// behaves like
int j = (i = i + 1) * 42;

// but this
int j = i++ * 42;
// actually behaves like
int prev = i;
i = i + 1;
int j = prev * 42;

【讨论】:

  • 当i以2开头时,结果:126、168、168、210
  • 并不是要逐行执行,只是展示不同的方式和它们的逻辑来展示它们与i = i + 1的关系
  • 一定有人来自hitchhikersguidetothegalaxy.stackexchange.com
  • @BradChristie 或者我只是抽到零。
  • 虽然这个答案可以理解i++ 的简单用法,但对于更复杂的用法是错误的。考虑例如class C { int i; int M() { return i; } int N() { int j = i++ * 42 + M(); return j); }} 这不等于int j = i * 42 + M(); i = i + 1; return j; 它等于int temp = i; i = i + 1; int j = temp * 42 + M(); return j; 产生不同的结果。 在 C# 中,副作用发生在值生成之前,而不是之后,因此您的脱糖应该反映这一点。
【解决方案3】:

我认为您可以考虑以下两个函数来类比 i++++i

Fiddle

public static int IPlusPlus(ref int i) {
    // simply increment i before returning it
    i = i + 1;
    return i;
}

public static int PlusPlusI(ref int i) {
    // increment i only after you already returned it
    try {
        return i;
    }
    finally {
        i = i + 1;
    }
}

就像函数调用一样,i++++i右侧表达式,这意味着 var a = i++;var a = ++i; 是有效语句。但i++ = 5;++i = 5; 无效,因为这会将它们用作左侧表达式。

【讨论】:

  • 虽然这是一种非常合理的方式来传达这个想法,但我注意到有几个小问题。首先,我们真的不需要try-finally的所有机制; int temp = i; i = i + 1; return temp; 更容易理解。其次,如果函数是 ref-returning,则 C# 7 现在允许您将函数调用放在赋值的左侧。
  • @EricLippert 首先。我也考虑过使用 temp-variant,但认为 try-finally 会更清楚。第二。谢谢,我不知道(LHS 分配给)引用返回函数,这听起来很酷。
  • C# 中的规则是,要在赋值的左侧,表达式必须被分类为变量(或带有 setter 的属性,或带有 setter 的索引器,等等on.) 通常一个函数返回一个值,而不是一个变量,但是返回 ref 的函数调用被归类为变量。
猜你喜欢
  • 2017-05-06
  • 2012-03-30
  • 2011-10-10
  • 2012-08-03
  • 2012-01-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-02
相关资源
最近更新 更多