int[] arr={0}; int value = arr[arr[0]++]; Значение = 1?
Сегодня я натолкнулся на статью Эрика Липперта, в которой он пытался прояснить миф между приоритетом операторов и порядком оценки. В конце были два фрагмента кода, которые меня запутали, вот первый фрагмент:
int[] arr = {0};
int value = arr[arr[0]++];
Теперь, когда я думаю о значении значения переменной, я просто вычисляю его как единое целое. Вот как я думал, что это работает.
- Сначала объявите arr как массив int с одним элементом внутри него; значение этого элемента 0.
- Во-вторых, получите значение arr[0] - 0 в этом случае.
- В-третьих, получите значение arr [значение шага 2] (которое по-прежнему равно 0) - опять получает arr[0] - все еще 0.
- В-четвертых, присвойте значение шага 3 (0) значению переменной. --value = 0 сейчас
- Добавьте к значению шага 2 1 --Now arr[0] = 1.
Видимо, это неправильно. Я пытался найти в спецификациях C# какое-то явное утверждение о том, когда на самом деле происходит приращение, но ничего не нашел.
Второй фрагмент взят из комментария к сообщению Эрика в блоге:
int[] data = { 11, 22, 33 };
int i = 1;
data[i++] = data[i] + 5;
Теперь вот как я думаю, что эта программа будет выполняться - после объявления массива и присвоения 1 для i. [Пожалуйста, потерпите меня]
- Получить данные [я] - 1
- Добавьте к значению шага 1 значение 5 --6.
- Присвойте data[i] (который все еще равен 1) значение шага 2 --data[i] = 6
- Инкремент 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).
Для первого фрагмента последовательность выглядит следующим образом:
- Объявите обр, как вы описали:
- Получить значение arr[0], которое равно 0
- Увеличьте значение arr [0] до 1.
- Получите значение arr[(результат #2)], равное arr[0], которое (для #3) равно 1.
- Сохраните этот результат в
value
, - значение = 1
Для второго фрагмента оценка по-прежнему слева направо.
- Где мы храним результат? В данных [i++], которые являются данными [1], но теперь я = 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
Если бы это было так, как вы говорите, то вы могли бы удалить оператор приращения, потому что он фактически ничего не делает, в инструкции, которую я сообщил.
Вы должны думать о назначениях в три этапа:
- Оценить левую часть (= получить адрес, где должно храниться значение)
- Оценить правую сторону
- Назначьте значение из шага 2 в ячейку памяти, начиная с шага 1.
Если у вас есть что-то вроде
A().B = C()
Затем сначала будет выполняться A(), затем - C(), а затем будет запущен установщик свойств B.
По сути, вы должны думать о своем заявлении как
StoreInArray(data, i++, data[i] + 5);
Причиной может быть то, что некоторые компиляторы оптимизируют i ++, чтобы быть ++ i. В большинстве случаев конечный результат один и тот же, но мне кажется, что это один из тех редких случаев, когда компилятор ошибается.
У меня нет доступа к Visual Studio прямо сейчас, чтобы подтвердить это, но попробуйте отключить оптимизацию кода и посмотреть, останутся ли результаты такими же.