В чем разница между i++ и ++i?
Я видел, как они оба используются в многочисленных частях кода C#, и я хотел бы знать, когда использовать i++
или же ++i
(i
будучи числовой переменной, как int
, float
, double
, так далее). Кто-нибудь, кто знает это?
8 ответов
Как ни странно, остальные два ответа ничего не объясняют, и определенно стоит сказать:
i++
означает "скажи мне ценность i
, а затем увеличить
++i
означает "приращение" i
тогда скажи мне ценность
Это операторы перед инкрементом, постинкрементные. В обоих случаях переменная увеличивается, но если вы берете значение обоих выражений в одинаковых случаях, результат будет отличаться.
Типичный ответ на этот вопрос, к сожалению, уже опубликованный здесь, заключается в том, что один выполняет приращение "до" оставшихся операций, а другой - "после" оставшихся операций. Хотя это интуитивно передает идею, это утверждение на первый взгляд совершенно неверно. Последовательность событий во времени чрезвычайно четко определена в C#, и это совершенно не тот случай, когда префиксные и постфиксные версии ++ делают вещи в другом порядке по сравнению с другими операциями.
Неудивительно, что вы увидите много неправильных ответов на этот вопрос. Огромное количество книг "научи себя C#" также ошибаются. Кроме того, способ, которым C# делает это, отличается от того, как это делает C. Многие люди считают, что C# и C- это один и тот же язык; они не. На мой взгляд, конструкция операторов увеличения и уменьшения в C# позволяет избежать ошибок проектирования этих операторов в C.
Есть два вопроса, на которые нужно ответить, чтобы определить, что именно выполняет префикс и постфикс ++ в C#. Первый вопрос: каков результат? и второй вопрос: когда происходит побочный эффект приращения?
Не ясно, каков ответ на любой вопрос, но на самом деле это довольно просто, когда вы видите это. Позвольте мне объяснить вам, что именно x ++ и ++x делают для переменной x.
Для префикса формы:
- х оценивается для получения переменной
- Значение переменной копируется во временную папку
- Временное значение увеличивается для создания нового значения (не перезаписывая временное!)
- Новое значение сохраняется в переменной
- Результатом операции является новое значение (то есть увеличенное значение временного)
Для формы постфикса:
- х оценивается для получения переменной
- Значение переменной копируется во временную папку
- Временное значение увеличивается для создания нового значения (не перезаписывая временное!)
- Новое значение сохраняется в переменной
- Результатом операции является значение временного
Некоторые вещи, на которые стоит обратить внимание:
Во-первых, порядок событий во времени одинаков в обоих случаях. Опять же, это абсолютно не тот случай, когда порядок событий во времени изменяется между префиксом и постфиксом. Совершенно неверно утверждать, что оценка происходит до других оценок или после других оценок. Оценки выполняются в одном и том же порядке в обоих случаях, как можно видеть на шагах с 1 по 4, которые идентичны. Единственное отличие заключается в последнем шаге - является ли результат значением временного или нового, увеличенного значения.
Вы можете легко продемонстрировать это с помощью простого консольного приложения C#:
public class Application
{
public static int currentValue = 0;
public static void Main()
{
Console.WriteLine("Test 1: ++x");
(++currentValue).TestMethod();
Console.WriteLine("\nTest 2: x++");
(currentValue++).TestMethod();
Console.WriteLine("\nTest 3: ++x");
(++currentValue).TestMethod();
Console.ReadKey();
}
}
public static class ExtensionMethods
{
public static void TestMethod(this int passedInValue)
{
Console.WriteLine("Current:{0} Passed-in:{1}",
Application.currentValue,
passedInValue);
}
}
Вот результаты...
Test 1: ++x
Current:1 Passed-in:1
Test 2: x++
Current:2 Passed-in:1
Test 3: ++x
Current:3 Passed-in:3
В первом тесте вы можете увидеть, что оба currentValue
и что было передано в TestMethod()
Расширение покажет то же значение, что и ожидалось.
Однако во втором случае люди попытаются сказать вам, что приращение currentValue
происходит после звонка TestMethod()
, но, как вы можете видеть из результатов, это происходит до вызова, как указано в результате "Текущий:2".
В этом случае сначала значение currentValue
хранится во временном. Затем увеличенная версия этого значения сохраняется в currentValue
но не касаясь временного, который все еще хранит исходное значение. Наконец, этот временный TestMethod()
, Если приращение произошло после вызова TestMethod()
тогда он выписал бы одно и то же не приращенное значение дважды, но это не так.
Важно отметить, что значение, возвращаемое как
currentValue++
а также++currentValue
Операции основаны на временном, а не фактическом значении, сохраненном в переменной в момент выхода из любой операции.Напомним, что в порядке операций, указанных выше, первые два шага копируют текущее значение переменной во временное. Это то, что используется для расчета возвращаемого значения; в случае префиксной версии это временное значение увеличивается, в то время как в случае суффиксной версии это значение напрямую / не увеличивается. Сама переменная не читается снова после первоначального хранения во временную.
Проще говоря, постфиксная версия возвращает значение, которое было прочитано из переменной (т. Е. Значение временного), в то время как префиксная версия возвращает значение, которое было записано обратно в переменную (т. Е. Увеличенное значение временного). Ни один из них не возвращает значение переменной.
Это важно понимать, потому что сама переменная может быть изменчивой и изменяться в другом потоке, что означает, что возвращаемое значение этих операций может отличаться от текущего значения, хранящегося в переменной.
Люди на удивление часто запутываются в отношении приоритета, ассоциативности и порядка выполнения побочных эффектов, я подозреваю, в основном потому, что это так запутанно в C. C# был тщательно спроектирован, чтобы быть менее запутанным во всех этих отношениях. Для некоторого дополнительного анализа этих проблем, включая мою дальнейшую демонстрацию ложности идеи о том, что префиксные и постфиксные операции "перемещают вещи во времени", смотрите:
http://blogs.msdn.com/b/ericlippert/archive/2009/08/10/precedence-vs-order-redux.aspx
что привело к такому вопросу:
int [] arr = {0}; int value = arr [arr [0] ++]; Значение = 1?
Вас также могут заинтересовать мои предыдущие статьи на эту тему:
http://blogs.msdn.com/b/ericlippert/archive/2008/05/23/precedence-vs-associativity-vs-order.aspx
а также
http://blogs.msdn.com/b/ericlippert/archive/2007/08/14/c-and-the-pit-of-despair.aspx
и интересный случай, когда C затрудняет рассуждения о правильности:
http://blogs.msdn.com/b/ericlippert/archive/2005/04/28/bad-recursion-revisited.aspx
Кроме того, мы сталкиваемся с подобными тонкими проблемами при рассмотрении других операций, имеющих побочные эффекты, таких как цепочка простых назначений:
И вот интересный пост о том, почему операторы приращения приводят к значениям в C#, а не в переменных:
Если у вас есть:
int i = 10;
int x = ++i;
затем x
будет 11
,
Но если у вас есть:
int i = 10;
int x = i++;
затем x
будет 10
,
Обратите внимание, как указывает Эрик, приращение происходит одновременно в обоих случаях, но это то, какое значение дается в качестве результата, который отличается (спасибо, Эрик!).
Вообще, я люблю использовать ++i
если нет веской причины не делать этого. Например, при написании цикла я люблю использовать:
for (int i = 0; i < 10; ++i) {
}
Или, если мне просто нужно увеличить переменную, я хотел бы использовать:
++x;
Обычно тот или иной путь не имеет большого значения и сводится к стилю кодирования, но если вы используете операторы внутри других назначений (как в моих исходных примерах), важно знать о потенциальных побочных эффектах.
int i = 0;
Console.WriteLine(i++); // Prints 0. Then value of "i" becomes 1.
Console.WriteLine(--i); // Value of "i" becomes 0. Then prints 0.
Отвечает ли это на ваш вопрос?
Оператор работает так, что он увеличивается в одно и то же время, но если он находится перед переменной, выражение будет вычисляться с помощью увеличенной / уменьшенной переменной:
int x = 0; //x is 0
int y = ++x; //x is 1 and y is 1
Если это после переменной, текущий оператор будет выполнен с исходной переменной, как если бы он еще не был увеличен / уменьшен:
int x = 0; //x is 0
int y = x++; //'y = x' is evaluated with x=0, but x is still incremented. So, x is 1, but y is 0
Я согласен с dcp в использовании предварительного увеличения / уменьшения (++x) без необходимости. Действительно единственный раз, когда я использую постинкремент / декремент, это циклы while или петли такого рода. Эти петли одинаковы:
while (x < 5) //evaluates conditional statement
{
//some code
++x; //increments x
}
или же
while (x++ < 5) //evaluates conditional statement with x value before increment, and x is incremented
{
//some code
}
Вы также можете сделать это при индексации массивов и тому подобного:
int i = 0;
int[] MyArray = new int[2];
MyArray[i++] = 1234; //sets array at index 0 to '1234' and i is incremented
MyArray[i] = 5678; //sets array at index 1 to '5678'
int temp = MyArray[--i]; //temp is 1234 (becasue of pre-decrement);
И т. Д., И т. Д.
Просто для записи, в C++, если вы можете использовать любой (то есть), вы не заботитесь о порядке операций (вы просто хотите увеличить или уменьшить и использовать его позже), оператор префикса более эффективен, так как он не необходимо создать временную копию объекта. К сожалению, большинство людей используют posfix (var ++) вместо префикса (++ var), просто потому, что это то, что мы узнали изначально. (Меня об этом спросили в интервью). Не уверен, что это правда в C#, но я предполагаю, что это будет.
Простое объяснение только кода
int i = 0;
if (i++ > 0)
{
//will not execute, but "i" is icremented
}
Я думаю, что попробую ответить на вопрос, используя код. Представьте себе следующие методы, скажем,int
:
// The following are equivalent:
// ++i;
// PlusPlusInt(ref i);
//
// The argument "value" is passed as a reference,
// meaning we're not incrementing a copy.
static int PlusPlusInt(ref int value)
{
// Increment the value.
value = value + 1;
// Return the incremented value.
return value;
}
// The following are equivalent:
// i++;
// IntPlusPlus(ref i);
//
// The argument "value" is passed as a reference,
// meaning we're not incrementing a copy.
static int IntPlusPlus(ref int value)
{
// Keep the original value around before incrementing it.
int temp = value;
// Increment the value.
value = value + 1;
// Return what the value WAS.
return temp;
}
Предполагая, что вы знаете, какref
работает, это должно прояснить это очень хорошо. Объяснять это на английском, на мой взгляд, гораздо более неуклюже.