gpt4 book ai didi

c# - Stack 相关一行中的意外操作顺序

转载 作者:太空狗 更新时间:2023-10-29 22:15:13 26 4
gpt4 key购买 nike

调用 Push()Pop() Stack<T> 的实例在一行中,我得到的行为与在两行中执行恕我直言的相同代码不同。

以下代码片段重现了该行为:

static void Main(string[] args)
{
Stack<Element> stack = new Stack<Element>();
Element e1 = new Element { Value = "one" };
Element e2 = new Element { Value = "two" };
stack.Push(e1);
stack.Push(e2);

Expected(stack); // element on satck has value "two"
//Unexpected(stack); // element on stack has value "one"

Console.WriteLine(stack.Peek().Value);
Console.ReadLine();
}

public static void Unexpected(Stack<Element> stack)
{
stack.Peek().Value = stack.Pop().Value;
}

public static void Expected(Stack<Element> stack)
{
Element e = stack.Pop();
stack.Peek().Value = e.Value;
}

Element 类非常基础:

public class Element
{
public string Value
{
get;
set;
}
}

使用此代码我得到以下结果(.NET 3.5,Win 7,已完全修补):

  • 调用 Expected() (版本与两行)留下一个元素堆叠Value设置为 "two" .
  • 调用Unexpected()时(版本用一行)我得到一个元素Value 的堆栈调成 "one" .

我能想到的差异的唯一原因是运算符优先级。由于赋值运算符 ( = ) 具有 lowest precedence我看不出为什么这两种方法的行为会有所不同。

我还查看了生成的 IL:

.method public hidebysig static void Unexpected(class [System]System.Collections.Generic.Stack`1<class OperationOrder.Element> stack) cil managed
{
.maxstack 8
L_0000: ldarg.0
L_0001: callvirt instance !0 [System]System.Collections.Generic.Stack`1<class OperationOrder.Element>::Peek()
L_0006: ldarg.0
L_0007: callvirt instance !0 [System]System.Collections.Generic.Stack`1<class OperationOrder.Element>::Pop()
L_000c: callvirt instance string OperationOrder.Element::get_Value()
L_0011: callvirt instance void OperationOrder.Element::set_Value(string)
L_0016: ret
}

我不是 IL 高手,但对我来说,这段代码看起来仍然不错,并且应该在堆栈中保留一个元素并将其值设置为“二”。

任何人都可以向我解释为什么使用 Unexpected() 方法的原因吗?做的事情不同于 Expected()

非常感谢!

卢卡斯

最佳答案

在 C# 中,操作数是从左到右求值的。总是总是总是从左到右。因此 = 运算符的操作数是从左到右求值的。在您的“预期”示例中, Pop() 表达式发生在 Peek() 表达式语句之前运行的语句中。在您的“意外”示例中, Peek() 表达式位于 Pop() 表达式的左侧,因此首先对其进行评估。

SLaks answer 指出,调用的接收者总是在调用的参数之前被评估。这是正确的——那是因为调用的接收者总是在参数的左边!但是 SLaks 声称这与它是属性 setter 这一事实有关是不正确的。如果 Value 是一个字段,您将获得完全相同的行为;包含字段访问权限的表达式位于所赋值的左侧,因此首先计算。

您提到了“优先级”,这表明您可能赞同一个完全虚构且完全不真实的概念,即优先级与执行顺序有关。 它没有。摆脱对这个神话的信仰。子表达式的执行顺序是从左到右。运算符的操作按优先顺序进行。

例如,考虑 F() + G() * H()。 * 的优先级高于 +。在您的神话世界中,优先级较高的操作首先完成,因此计算 G(),然后计算 H(),然后相乘,然后 F(),然后加法。

这是完全错误的。跟我说:优先级与执行顺序无关。子表达式是从左到右计算的,所以我们首先计算 F(),然后是 G(),然后是 H()。然后我们计算 G() 和 H() 的结果的乘积。然后我们计算乘积与 F() 的结果之和。也就是说,这个表达式等同于:

temp1 = F();
temp2 = G();
temp3 = H();
temp4 = temp2 * temp3;
result = temp1 + temp4;

= 运算符和其他运算符一样;一个低优先级的运算符,但是一个运算符。它的操作数是从左到右求值的,因为它是一个低优先级运算符,所以运算符的效果——赋值——比所有其他运算符的效果晚完成。运算符的作用和它的操作数的计算是完全不同的事情。前者是按优先顺序完成的。后者按从左到右的顺序完成。

清楚了吗?

更新:混淆优先级、关联性和执行顺序非常普遍;许多在编程语言设计方面具有长期经验的书籍作者都犯了错误。 C# 有非常严格的规则来定义每一个;如果您对这一切如何运作的更多细节感兴趣,您可能会对我写的关于该主题的这些文章感兴趣:

http://blogs.msdn.com/ericlippert/archive/tags/precedence/default.aspx

关于c# - Stack<T> 相关一行中的意外操作顺序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/2273597/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com