Подтвердить что ты не робот

В чем разница между я ++ и ++ i?

Я видел, что они оба используются во многих фрагментах кода С#, и я хотел бы знать, когда использовать i++ или ++i (i как числовую переменную типа int, float, double и т.д.). Кто знает это?

4b9b3361

Ответ 1

Как ни странно, похоже, что два других ответа не говорят об этом, и это определенно стоит сказать:


i++ означает "скажите мне значение i, затем увеличивайте"

++i означает "приращение i, затем скажите мне значение"


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

Ответ 2

Типичный ответ на этот вопрос, к сожалению, уже опубликованный здесь, состоит в том, что один выполняет приращение "до" оставшихся операций, а другой - "после" оставшихся операций. Хотя эта идея интуитивно понятна, это утверждение совершенно неверно. Последовательность событий во времени чрезвычайно четко определена в С#, и совершенно не имеет значения, что префиксная (++var) и постфиксная (var++) версии ++ делают вещи в другом порядке по отношению к другим операции.

Неудивительно, что вы увидите много неправильных ответов на этот вопрос. Огромное количество книг "научи себя С#" также ошибаются. Кроме того, способ, которым С# делает это, отличается от того, как это делает C. Многие люди считают, что С# и C - это один и тот же язык; они не. На мой взгляд, конструкция операторов увеличения и уменьшения в С# позволяет избежать ошибок проектирования этих операторов в C.

Есть два вопроса, на которые нужно ответить, чтобы определить, что именно происходит с префиксом и постфиксом ++ в С#. Первый вопрос: каков результат? и второй вопрос: когда происходит побочный эффект приращения?

Не ясно, каков ответ на любой вопрос, но на самом деле все довольно просто, когда вы его видите. Позвольте мне объяснить вам, что именно x++ и ++x делают для переменной x.

Для префиксной формы (++x):

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

Для формы постфикса (x++):

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

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

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

Вы можете легко продемонстрировать это с помощью простого консольного приложения на С#:

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

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

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

Почему я не могу сделать ++ i ++ на C-подобных языках?

Ответ 3

Если у вас есть:

int i = 10;
int x = ++i;

то x будет 11.

Но если у вас есть:

int i = 10;
int x = i++;

то x будет 10.

Обратите внимание, как указывает Эрик, приращение происходит одновременно в обоих случаях, но какое значение дает в результате, которое отличается (спасибо Eric!).

Как правило, мне нравится использовать ++i, если нет веской причины. Например, при написании цикла я хотел бы использовать:

for (int i = 0; i < 10; ++i) {
}

Или, если мне просто нужно увеличивать переменную, мне нравится использовать:

++x;

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

Ответ 4

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.

Отвечает ли это на ваш вопрос?

Ответ 5

То, как работает оператор, заключается в том, что он получает приращение в одно и то же время, но если оно есть перед переменной, выражение будет оцениваться с помощью переменной/уменьшенной переменной:

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 в использовании pre-increment/decment (++ x), если это необходимо. Действительно, единственный раз, когда я использую post-increment/decment, находится в циклах или циклах такого типа. Эти петли одинаковы:

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

Etc и т.д.

Ответ 6

Только для записи на С++, если вы можете использовать либо (то есть), вам не нужно упорядочивать операции (вы просто хотите увеличить или уменьшить и использовать ее позже), префиксный оператор более эффективен, так как не нужно создавать временную копию объекта. К сожалению, большинство людей используют posfix (var ++) вместо префикса (++ var), просто потому, что это то, что мы узнали изначально. (Об этом меня спрашивали в интервью). Не уверен, что это правда в С#, но я предполагаю, что это будет.