int[] arr={0}; int value = arr[arr[0]++]; Значение = 1?

Сегодня я натолкнулся на статью Эрика Липперта, в которой он пытался прояснить миф между приоритетом операторов и порядком оценки. В конце были два фрагмента кода, которые меня запутали, вот первый фрагмент:

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

Теперь, когда я думаю о значении значения переменной, я просто вычисляю его как единое целое. Вот как я думал, что это работает.

  1. Сначала объявите arr как массив int с одним элементом внутри него; значение этого элемента 0.
  2. Во-вторых, получите значение arr[0] - 0 в этом случае.
  3. В-третьих, получите значение arr [значение шага 2] (которое по-прежнему равно 0) - опять получает arr[0] - все еще 0.
  4. В-четвертых, присвойте значение шага 3 (0) значению переменной. --value = 0 сейчас
  5. Добавьте к значению шага 2 1 --Now arr[0] = 1.

Видимо, это неправильно. Я пытался найти в спецификациях C# какое-то явное утверждение о том, когда на самом деле происходит приращение, но ничего не нашел.
Второй фрагмент взят из комментария к сообщению Эрика в блоге:

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

Теперь вот как я думаю, что эта программа будет выполняться - после объявления массива и присвоения 1 для i. [Пожалуйста, потерпите меня]

  1. Получить данные [я] - 1
  2. Добавьте к значению шага 1 значение 5 --6.
  3. Присвойте data[i] (который все еще равен 1) значение шага 2 --data[i] = 6
  4. Инкремент i - i = 2

Насколько я понимаю, этот массив теперь должен содержать значения {11, 27, 33}. Однако, когда я зациклился, чтобы напечатать значения массива, я получил: {11, 38, 33}. Это означает, что инкремент записи произошел до разыменования массива!
Как так? Разве этот пост не должен быть постом? т.е. случиться после всего остального.
Что я скучаю по парням?

6 ответов

Решение

Операция postincrement происходит как часть оценки общего выражения. Это побочный эффект, который возникает после оценки значения, но до оценки любых других выражений.

Другими словами, для любого выражения E E++ (если допустимо) представляет что-то вроде (псевдокод):

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

Это все часть оценки E++, прежде чем что-либо еще будет оценено.

См. Раздел 7.5.9 спецификации C# 3.0 для получения более подробной информации.


Кроме того, для операций присвоения, где LHS классифицируется как переменная (как в этом случае), LHS оценивается до оценки RHS.

Итак, в вашем примере:

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).

Для первого фрагмента последовательность выглядит следующим образом:

  1. Объявите обр, как вы описали:
  2. Получить значение arr[0], которое равно 0
  3. Увеличьте значение arr [0] до 1.
  4. Получите значение arr[(результат #2)], равное arr[0], которое (для #3) равно 1.
  5. Сохраните этот результат в value,
  6. значение = 1

Для второго фрагмента оценка по-прежнему слева направо.

  1. Где мы храним результат? В данных [i++], которые являются данными [1], но теперь я = 2
  2. Что мы добавляем? данные [i] + 5, которые теперь являются данными [2] + 5, что составляет 38.

Недостающим является то, что "пост" не означает "после ВСЕГО". Это просто означает "сразу после того, как я получу текущее значение этой переменной". Пост-инкремент, происходящий "в середине" строки кода, совершенно нормален.

data[i++] // => data[1], then i is incremented to 2

data[1] = data[2] + 5 // => 33 + 5

Я ожидал бы, что оператор постинкремента будет увеличивать переменную после использования ее значения. В этом случае переменная увеличивается до второй ссылки на переменную.

Если бы это было не так, вы могли бы написать

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

Если бы это было так, как вы говорите, то вы могли бы удалить оператор приращения, потому что он фактически ничего не делает, в инструкции, которую я сообщил.

Вы должны думать о назначениях в три этапа:

  1. Оценить левую часть (= получить адрес, где должно храниться значение)
  2. Оценить правую сторону
  3. Назначьте значение из шага 2 в ячейку памяти, начиная с шага 1.

Если у вас есть что-то вроде

A().B = C()

Затем сначала будет выполняться A(), затем - C(), а затем будет запущен установщик свойств B.

По сути, вы должны думать о своем заявлении как

StoreInArray(data, i++, data[i] + 5);

Причиной может быть то, что некоторые компиляторы оптимизируют i ++, чтобы быть ++ i. В большинстве случаев конечный результат один и тот же, но мне кажется, что это один из тех редких случаев, когда компилятор ошибается.

У меня нет доступа к Visual Studio прямо сейчас, чтобы подтвердить это, но попробуйте отключить оптимизацию кода и посмотреть, останутся ли результаты такими же.

Другие вопросы по тегам