- c - 在位数组中找到第一个零
- linux - Unix 显示有关匹配两种模式之一的文件的信息
- 正则表达式替换多个文件
- linux - 隐藏来自 xtrace 的命令
调用 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/
我对这个错误很困惑: Cannot implicitly convert type 'System.Func [c:\Program Files (x86)\Reference Assemblies\
考虑这段代码: pub trait Hello { fn hello(&self); } impl Hello for Any { fn hello(&self) {
问题很简单。是否可以构造这样一个类型 T,对于它下面的两个变量声明会产生不同的结果? T t1 = {}; T t2{}; 我已经研究 cppreference 和标准一个多小时了,我了解以下内容:
Intellij idea 给我这个错误:“Compare (T, T) in Comparator cannot be applied to (T, T)” 对于以下代码: public class
任何人都可以告诉我 : n\t\t\t\t\n\t\t\t 在以下来自和 dwr 服务的响应中的含义和用途是什么. \r\n\t\t\t \r\n\t\t\t
让 T 成为一个 C++ 类。 下面三个指令在行为上有什么区别吗? T a; T a(); T a = T(); T 为不带参数的构造函数提供了显式定义这一事实是否对问题有任何改变? 后续问题:如果
Rust中的智能指针是什么 智能指针(smart pointers)是一类数据结构,是拥有数据所有权和额外功能的指针。是指针的进一步发展 指针(pointer)是一个包含内存地
比如我有一个 vector vector > v={{true,1},{true,2},{false,3},{false,4},{false,5},{true,6},{false,7},{true,8
我有一个来自 .xls 电子表格的数据框,我打印了 print(df.columns.values) 列,输出包含一个名为:Poll Responses\n\t\t\t\t\t。 我查看了 Excel
This question already has answers here: What are good reasons for choosing invariance in an API like
指针类型作为类型前缀与在类型前加斜杠作为后缀有什么区别。斜线到底是什么意思? 最佳答案 语法 T/~ 和 T/& 基本上已被弃用(我什至不确定编译器是否仍然接受它)。在向新向量方案过渡的初始阶段,[T
我正在尝试找到一种方法来获取模板参数的基类。 考虑以下类: template class Foo { public: Foo(){}; ~Foo(){};
这是一个让我感到困惑的小问题。我不知道如何描述它,所以只看下面的代码: struct B { B() {} B(B&) { std::cout ::value #include
为什么有 T::T(T&) 而 T::T(const T&) 更适合 copy ? (大概是用来实现move语义的???) 原始描述(被melpomene证明是错误的): 在C++11中,支持了一种新
在 Java 7 中使用 eclipse 4.2 并尝试实现 List 接口(interface)的以下方法时,我收到了警告。 public T[] toArray(T[] a) { ret
假设有三个函数: def foo[T](a:T, b:T): T = a def test1 = foo(1, "2") def test2 = foo(List(), ListBuffer()) 虽
我对柯里化(Currying)和非柯里化(Currying)泛型函数之间类型检查的差异有点困惑: scala> def x[T](a: T, b: T) = (a == b) x: [T](a: T,
考虑一个类A,我如何编写一个具有与相同行为的模板 A& pretty(A& x) { /* make x pretty */ return x; } A pretty(A&& x) {
Eclipse 表示由于泛型类型橡皮擦,类型参数不允许使用 instanceof 操作。 我同意在运行时不会保留任何类型信息。但是请考虑以下类的通用声明: class SomeClass{ T
在 C++14 中: 对于任何整数或枚举类型 T 以及对于任何表达式 expr: 有没有区别: struct S { T t { expr }; }; 和 struct S { T t = { exp
我是一名优秀的程序员,十分优秀!