13

今天我看到了Eric Lippert的一篇文章,他试图澄清运算符优先级和评估顺序之间的神话。最后有两个代码片段让我感到困惑,这是第一个片段:

      int[] arr = {0};
      int value = arr[arr[0]++];

现在当我考虑变量 value 的值时,我只是简单地将其计算为 1。这就是我认为它的工作方式。

  1. 首先将 arr 声明为一个 int 数组,其中包含一个项目;该项目的值为 0。
  2. 在这种情况下,第二次获取 arr[0] --0 的值。
  3. 第三次获取 arr[步骤 2 的值] 的值(仍为 0) -- 再次获取 arr[0] -- 仍为 0。
  4. 第四步将步骤 3 (0) 的值赋给变量值。--value = 0 现在
  5. 添加到第 2 步的值 1 -- 现在 arr[0] = 1。

显然这是错误的。我试图在 c# 规范中搜索一些关于何时实际发生增量的明确声明,但没有找到任何内容。
第二个片段来自 Eric关于该主题的博客文章的评论:

 int[] data = { 11, 22, 33 }; 
 int i = 1;
 data[i++] = data[i] + 5;

现在这就是我认为这个程序将如何执行——在声明数组并将 1 分配给 i 之后。[请忍受我]

  1. 获取数据[i] --1
  2. 将值 5 --6 添加到第 1 步的值
  3. 将第 2 步的值赋给 data[i](仍为 1) --data[i] = 6
  4. 递增 i -- i = 2

根据我的理解,这个数组现在应该包含值 { 11、27、33 }。但是,当我循环打印得到的数组值时:{11、38、33}。这意味着增量发生在取消引用数组之前!
怎么会?这个帖子增量不应该是帖子吗?即发生在其他一切之后。
我想念什么?

4

6 回答 6

20

后增量操作作为评估整个表达式的一部分发生。这是在评估值之后但在评估任何其他表达式之前发生的副作用。

换句话说,对于任何表达式 E,E++(如果合法)表示类似(伪代码):

T tmp = E;
E += 1;
return tmp;

在评估其他任何内容之前,这就是评估 E++的全部内容。

有关详细信息,请参阅 C# 3.0 规范的第 7.5.9 节。


此外,对于 LHS 被分类为变量的赋值操作(如本例所示),在评估RHS之前评估 LHS。

所以在你的例子中:

int[] data = { 11, 22, 33 }; 
int i = 1;
data[i++] = data[i] + 5;

相当于:

int[] data = { 11, 22, 33 }; 
int i = 1;
// Work out what the LHS is going to mean...
int index = i;
i++;
// We're going to assign to data[index], i.e. data[1]. Now i=2.

// Now evaluate the RHS
int rhs = data[i] + 5; // rhs = data[2] + 5 == 38

// Now assign:
data[index] = rhs;

规范的相关位是第 7.16.1 节(C# 3.0 规范)。

于 2009-08-11T13:16:45.657 回答
5

对于第一个片段,序列是:

  1. 按照您的描述声明 arr :
  2. 检索 arr[0] 的值,即 0
  3. 将 arr[0] 的值增加到 1。
  4. 检索 arr[(result of #2)] 的值,它是 arr[0],(每个 #3)是 1。
  5. 将该结果存储在value.
  6. 值 = 1

对于第二个片段,评估仍然是从左到右的。

  1. 我们将结果存储在哪里?在 data[i++] 中,即 data[1],但现在 i = 2
  2. 我们要添加什么?data[i] + 5,现在是 data[2] + 5,即 38。

缺少的部分是“发布”并不意味着“在一切之后”。它只是意味着“在我检索该变量的当前值之后立即”。发生在一行代码“中间”的后增量是完全正常的。

于 2009-08-11T13:27:57.983 回答
2
data[i++] // => data[1], then i is incremented to 2

data[1] = data[2] + 5 // => 33 + 5
于 2009-08-11T13:17:34.783 回答
0

我希望后增量运算符在使用变量值后递增变量。在这种情况下,变量在第二次引用变量之前递增。

如果不是这样,你可以写

data[i++] = data[i++] + data[i++] + data[i++] + 5

如果就像你说的那样,那么你可以删除增量运算符,因为它实际上并没有做任何事情,在我报告的指令中。

于 2009-08-11T13:27:11.917 回答
0

您必须分三个步骤考虑作业:

  1. 评估左侧(=获取应存储值的地址)
  2. 评估右手边
  3. 将步骤 2 中的值分配给步骤 1 中的内存位置。

如果你有类似的东西

A().B = C()

然后 A() 将首先运行,然后 C() 将运行,然后属性设置器 B 将运行。

本质上,您必须将您的陈述视为

StoreInArray(data, i++, data[i] + 5);
于 2009-08-11T13:31:28.960 回答
-4

原因可能是某些编译器将 i++ 优化为 ++i。大多数情况下,最终结果是相同的,但在我看来,这是编译器出错的罕见情况之一。

我现在无法访问 Visual Studio 来确认这一点,但请尝试禁用代码优化并查看结果是否保持不变。

于 2009-08-11T13:17:46.403 回答