Пост-приращение в рамках самоопределения
Я понимаю различия между i++ and ++i
, но я не совсем уверен, почему я получаю результаты ниже:
static void Main(string[] args)
{
int c = 42;
c = c++;
Console.WriteLine(c); //Output: 42
}
В приведенном выше коде, поскольку это назначает переменную самому себе и затем увеличивает значение, я ожидаю, что результат будет 43
. Однако он возвращает 42
. Я получаю тот же результат при использовании c = c--;
.
Я понимаю, что могу просто использовать c++;
и делать с ним, но мне более любопытно, почему он ведет себя так, как есть. Может ли кто-нибудь объяснить, что здесь происходит?
Ответы
Ответ 1
Давайте рассмотрим код языка посредника для этого:
IL_0000: nop
IL_0001: ldc.i4.s 2A
IL_0003: stloc.0 // c
IL_0004: ldloc.0 // c
Это загружает константное целое число 42
в стек, а затем сохраняет его в переменной c
и сразу же загружает в стек.
IL_0005: stloc.1
IL_0006: ldloc.1
Это копирует значение в другой регистр и снова загружает его.
IL_0007: ldc.i4.1
IL_0008: add
Это добавляет константу 1 к загруженному значению
IL_0009: stloc.0 // c
... и сохраняет результат (43) в переменной c
.
IL_000A: ldloc.1
IL_000B: stloc.0 // c
Затем загружается значение из другого регистра (это все еще 42!) и сохраняется в переменной c
.
IL_000C: ldloc.0 // c
IL_000D: call System.Console.WriteLine
IL_0012: nop
IL_0013: ret
Затем значение (42) загружается из переменной и печатается.
Итак, что вы можете видеть из этого, так это то, что while c++
увеличивает значение переменной на единицу после того, как результат был возвращен, это увеличение происходит еще до того, как будет задано значение переменной. Таким образом, последовательность выглядит примерно так:
- Получить значение из
c
- Пост-приращение
c
- Назначить ранее прочитанное значение
c
И это должно объяснить, почему вы получаете этот результат:)
Чтобы добавить еще один пример, поскольку это было упомянуто в комментарии, который был удален:
c = c++ + c;
Это работает очень похоже: если сначала ввести начальное значение 2, сначала оценивается левая часть сложения. Таким образом, значение считывается из переменной (2), затем c
увеличивается (c
становится 3). Затем оценивается правая часть сложения. Значение c
читается (теперь 3). Тогда добавление имеет место (2 + 3), а результат (5) присваивается переменной.
Вывод из этого заключается в том, что вам следует избегать смешивания операций увеличения и уменьшения в нормальных выражениях. Хотя поведение очень четко определено и имеет абсолютный смысл (как показано выше), по-прежнему сложно обернуть вокруг себя голову. Особенно, когда вы назначаете что-то той же переменной, которую вы увеличиваете в выражении, это становится запутанным быстро. Так сделайте себе и другим пользу и избегайте операций приращения/уменьшения, когда они не полностью сами по себе:)
Ответ 2
Согласно странице MSDN на операторах С# оператор присваивания (=
) имеет более низкий приоритет, чем любой первичный оператор, например ++x
или x++
.
Это означает, что в строке
c = c++;
сначала оценивается правая часть. Выражение c++
увеличивает c
до 43, а затем возвращает исходное значение 42
в результате, которое используется для назначения.
Как документация, связанная с состояниями,
[Вторая форма - это операция приращения постфикса. Результатом операции является значение операнда до того, как он будет увеличен.
Другими словами, ваш код эквивалентен
// Evaluate the right hand side:
int incrementResult = c; // Store the original value, int incrementResult = 42
c = c + 1; // Increment c, i.e. c = 43
// Perform the assignment:
c = incrementResult; // Assign c to the "result of the operation", i.e. c = 42
Сравните это с формой префикса
c = ++c;
который будет оцениваться как
// Evaluate the right hand side:
c = c + 1; // Increment c, i.e. c = 43
int incrementResult = c; // Store the new value, i.e. int incrementResult = 43
// Perform the assignment:
c = incrementResult; // Assign c to the "result of the operation", i.e. c = 43
Ответ 3
Документы говорят о состоянии постфикса:
Результатом операции является значение операнда до того, как он был увеличен.
Это означает, что когда вы выполните:
c = c++;
На самом деле вы переназначаете 42
на c
, и почему вы видите консольную печать 42
. Но если вы это сделаете:
static void Main(string[] args)
{
int c = 42;
c++;
Console.WriteLine(c);
}
Вы увидите вывод 43
.
Если вы посмотрите, что генерирует компилятор (в режиме отладки), вы увидите:
private static void Main(string[] args)
{
int num = 42;
int num2 = num;
num = num2 + 1;
num = num2;
Console.WriteLine(num);
}
Что более ясно отображает перезапись. Если вы посмотрите в режиме Release, вы увидите, что компилятор оптимизирует весь вызов:
private static void Main(string[] args)
{
Console.WriteLine(42);
}
Ответ 4
... поскольку это назначает переменную самому себе, а затем увеличивает значение...
Нет, это не то, что он делает.
Оператор post -increment увеличивает эту переменную, а возвращает старое значение. Оператор pre -increment увеличивает эту переменную, а возвращает новое значение.
Итак, ваш c++
увеличивает c до 43, но возвращает 42, который затем снова присваивается c.
Ответ 5
Выражение в правой части присваивания оценивается полностью, затем выполняется присвоение.
c = c++;
То же, что и
// Right hand side is calculated first.
_tmp = c;
c = c + 1;
// Then the assignment is performed
c = _tmp;
Ответ 6
Думаю, я понимаю, о чем думал первоначальный вопросник. Они думали (я думаю), что postincrement означает приращение переменной после оценки всего выражения, например. что
x = a[i++] + a[j++]; // (0)
совпадает с
{ x = a[i] + a[j] ; i += 1 ; j += 1; } // (1)
(и, по общему признанию, они эквивалентны) и что
c = c++; // (2)
означает
{ c = c ; c +=1 ; } // (3)
и что
x = a[i++] + a[i++]; // (4)
означает
{ x = a[i] + a[i] ; i += 2 ; } // (5)
Но это не так. v++
означает приращение v
сразу, но используйте старое значение как значение выражения. Итак, в случае (4) фактически эквивалентная инструкция
{int t0 = a[i] ; i += 1 ; int t1 = a[i] ; i += 1 ; x = t0 + t1 ; } // (6)
Как отмечали другие, утверждения, подобные (2) и (4), хорошо определены в С# (и Java), но они не определены корректно в C и С++.
В выражениях C и С++, таких как (2) и (4), которые изменяют переменную, а также используют ее каким-либо другим способом, обычно undefined, что означает, что компилятор приветствуется (в отношении языковых законов) для перевода их вообще, например, для перевода денег с вашего банковского счета на автора компилятора.