В чем разница между 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. х оценивается для получения переменной
  2. Значение переменной копируется во временную папку
  3. Временное значение увеличивается для создания нового значения (не перезаписывая временное!)
  4. Новое значение сохраняется в переменной
  5. Результатом операции является новое значение (то есть увеличенное значение временного)

Для формы постфикса:

  1. х оценивается для получения переменной
  2. Значение переменной копируется во временную папку
  3. Временное значение увеличивается для создания нового значения (не перезаписывая временное!)
  4. Новое значение сохраняется в переменной
  5. Результатом операции является значение временного

Некоторые вещи, на которые стоит обратить внимание:

Во-первых, порядок событий во времени одинаков в обоих случаях. Опять же, это абсолютно не тот случай, когда порядок событий во времени изменяется между префиксом и постфиксом. Совершенно неверно утверждать, что оценка происходит до других оценок или после других оценок. Оценки выполняются в одном и том же порядке в обоих случаях, как можно видеть на шагах с 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

Кроме того, мы сталкиваемся с подобными тонкими проблемами при рассмотрении других операций, имеющих побочные эффекты, таких как цепочка простых назначений:

http://blogs.msdn.com/b/ericlippert/archive/2010/02/11/chaining-simple-assignments-is-not-so-simple.aspx

И вот интересный пост о том, почему операторы приращения приводят к значениям в C#, а не в переменных:

Почему я не могу сделать ++i++ на 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работает, это должно прояснить это очень хорошо. Объяснять это на английском, на мой взгляд, гораздо более неуклюже.

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