Разница между прединкрементным и постинкрементным циклом?


303

Есть ли разница в ++iи i++в forцикле? Это просто синтаксис?



18
Я поражен тем, сколько ответов полностью упустили суть вопроса.
Грэм Перроу

3
Возможно, мы должны быть удивлены, что никто не редактировал вопрос, чтобы быть более ясным :)
Джон Б.

2
Этот вопрос может относиться к C, Java, C ++, PHP, C #, Javascript, JScript, Цель C: en.wikipedia.org/wiki/Category:C_programming_language_family
Крис С

1
Хороший ответ выложен здесь: stackoverflow.com/a/4706225/214296
Джим Фелл

Ответы:


233

А ++ известен как постфикс.

добавить 1 к a, возвращает старое значение.

++ a известен как префикс.

добавить 1 к a, возвращает новое значение.

C #:

string[] items = {"a","b","c","d"};
int i = 0;
foreach (string item in items)
{
    Console.WriteLine(++i);
}
Console.WriteLine("");

i = 0;
foreach (string item in items)
{
    Console.WriteLine(i++);
}

Вывод:

1
2
3
4

0
1
2
3

foreachи whileпетли зависят от типа приращения вы используете. С циклами for, как показано ниже, это не имеет значения, поскольку вы не используете возвращаемое значение i:

for (int i = 0; i < 5; i++) { Console.Write(i);}
Console.WriteLine("");
for (int i = 0; i < 5; ++i) { Console.Write(i); }

0 1 2 3 4
0 1 2 3 4

Если используется оцененное значение, тогда тип приращения становится значимым:

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

4
Это даже не то, о чем просил пользователь.
Дмитрий

224

Предварительное увеличение ++ i увеличивает значение i и возвращает новое увеличенное значение.

int i = 3;
int preIncrementResult = ++i;
Assert( preIncrementResult == 4 );
Assert( i == 4 );

Постинкремент i ++ увеличивает значение i и возвращает исходное неинкрементное значение.

int i = 3;
int postIncrementResult = i++;
Assert( postIncrementtResult == 3 );
Assert( i == 4 );

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

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

Так что, по крайней мере, в C ++ может быть разница в производительности, которая определяет ваш выбор.

Это в основном проблема только в том случае, если приращиваемая переменная является пользовательским типом с переопределенным оператором ++. Для примитивных типов (int и т. Д.) Нет разницы в производительности. Но в качестве ориентира стоит придерживаться оператора предварительного увеличения, если только оператор пост-увеличения не является тем, что требуется.

Здесь есть еще несколько дискуссий:
https://web.archive.org/web/20170405054235/http://en.allexperts.com/q/C-1040/Increment-operators.htm

В C ++, если вы используете STL, вы можете использовать циклы for с итераторами. В основном они имеют переопределенные операторы ++, поэтому придерживаться предварительного увеличения - хорошая идея. Тем не менее, компиляторы становятся все умнее, а более новые могут выполнять оптимизацию, что означает, что нет разницы в производительности, особенно если тип приращения определен встроенным в заголовочном файле (как это часто бывает с реализациями STL), так что компилятор может видеть, как метод реализован и может знать, какие оптимизации безопасно выполнить. Несмотря на это, вероятно, все еще стоит придерживаться предварительного увеличения, поскольку циклы выполняются много раз, и это означает, что небольшое ухудшение производительности может вскоре усилиться.


В других языках, таких как C #, где оператор ++ не может быть перегружен, разницы в производительности нет. Используемые в цикле для продвижения переменной цикла, операторы до и после приращения эквивалентны.

Исправление: перегрузка ++ в C # разрешена. Тем не менее, кажется, что по сравнению с C ++, в C # вы не можете перегружать предварительную и последующую версии независимо друг от друга. Итак, я бы предположил, что если результат вызова ++ в C # не будет назначен переменной или использован как часть сложного выражения, то компилятор сократит предварительную и последующую версии ++ до кода, который работает эквивалентно.


102
Не было бы замечательно, если бы C ++ был назван ++ C, что означает, что вы можете написать хорошо оптимизированный код, используя его ..
Naveen

9
Разве современные компиляторы не могут оптимизировать это, когда полученное значение, очевидно, все равно будет уничтожено?
Че

6
@che - они делают, когда это простой тип, однако классы, которые перегружают оператор ++ (например, итераторы), это отдельная история.
Ферруччо

7
@che: Это хороший вопрос. Причина, по которой компиляторы C ++ не заменяют «CustomType ++;» с "++ CustomType;" потому что нет гарантии, что обе пользовательские функции имеют одинаковый эффект. Они ДОЛЖНЫ ... но нет никакой гарантии.
Дрю Дорманн

2
@ michael.bartnett: Хорошая мысль, перегрузка ++ в C # действительно доступна. Тем не менее, кажется, что по сравнению с c ++, в c # вы не можете перегружать предварительную и последующую версии независимо друг от друга. Итак, я бы предположил, что если результат вызова ++ в C # не будет назначен переменной или использован как часть сложного выражения, то компилятор сократит предварительную и последующую версии ++ до кода, который работает эквивалентно.
Скотт Лэнгхэм

83

В C # нет разницы при использовании в цикле for .

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

выводит то же самое, что и

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

Как уже отмечали другие, при использовании в целом i ++ и ++ у меня есть небольшая, но существенная разница:

int i = 0;
Console.WriteLine(i++);   // Prints 0
int j = 0;
Console.WriteLine(++j);   // Prints 1

i ++ читает значение i, затем увеличивает его.

++ i увеличивает значение i, затем читает его.


Вывод: та же семантика пост / пред инкремента, что и в C ++.
xtofl

@xtofl - не уверен, в чем твоя точка зрения? Я просто случайно выбрал c # для моего примера.
Джон Б.

3
Я не думаю, что первый пункт актуален. В цикле for (c # или нет) часть приращения всегда выполняется после тела цикла. После выполнения переменная изменяется независимо от того, была ли использована запись или предварительное увеличение.
MatthieuP

9
@MatthieuP - я прочитал вопрос как "имеет ли значение, используете ли вы i ++ или ++ i в цикле for". Ответ "нет, это не так".
Джон Б

1
@JonB Порядок действий в ответе не совсем правильный. Обе ++iи i++выполняют одинаковые операции в одинаковом порядке: создайте временную копию i; увеличить временное значение, чтобы создать новое значение (не переопределять временное значение); сохранить новое значение в i; теперь, если это ++iрезультат, возвращается новое значение; если это i++результат, возвращается временная копия. Более подробный ответ здесь: stackoverflow.com/a/3346729/3330348
PiotrWolkowski

51

Вопрос в том:

Есть ли разница в ++ i и i ++ в цикле for?

Ответ: нет .

Почему каждый ответ должен входить в подробные объяснения до и после увеличения, когда это даже не спрашивается?

Это за цикл:

for (int i = 0; // Initialization
     i < 5;     // Condition
     i++)       // Increment
{
   Output(i);
}

Перевести на этот код без использования циклов:

int i = 0; // Initialization

loopStart:
if (i < 5) // Condition
{
   Output(i);

   i++ or ++i; // Increment

   goto loopStart;
}

Теперь имеет значение, если вы положили i++или ++iкак приращение здесь? Нет, это не так, поскольку возвращаемое значение операции приращения незначительно. iбудет увеличиваться ПОСЛЕ выполнения кода, который находится внутри тела цикла for.


2
Это буквально первый ответ, который идет прямо в точку. Спасибо.
Ясир

1
Это не лучший ответ, потому что, если цикл for увеличивает сложный объект (что-то отличное от int!), Реализация ++ x может быть быстрее, чем x ++ ... (см. Herbutter.com/2013/05/13/gotw -2-решение-временное-объекты )
JCx

30

Поскольку вы спрашиваете о разнице в цикле, я думаю, вы имеете в виду

for(int i=0; i<10; i++) 
    ...;

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

Причина, по которой это не имеет значения, заключается в том, что вы не используете значение i++. Другое дело, когда вы делаете

for(int i=0, a = 0; i<10; a = i++) 
    ...;

Теперь, есть есть разница, потому что , как другие указывают на то, i++означает приращение, но оценить к предыдущему значению , но ++iозначает приращение, но оценить,i (таким образом , он будет оценивать на новое значение). В приведенном выше случае aприсваивается предыдущее значение i, а i увеличивается.


3
В C ++ компилятору не всегда удается избежать создания временного кода, поэтому предпочтительна форма с предварительным приращением.
Дэвид Торнли

Когда я пишу, если у вас есть i определенного пользователем типа, они могут иметь различную семантику. но если вы используете i примитивного типа, то это не имеет значения для первого цикла. поскольку это вопрос, не зависящий от языка, я решил не слишком много писать о специфических для C ++ вещах.
Йоханнес Шауб -

15

Как показывает этот код (см. MSIL в комментариях), компилятор C # 3 не делает различий между i ++ и ++ i в цикле for. Если бы принимали значение i ++ или ++ i, разница была бы определена (это было скомпилировано в Visutal Studio 2008 / Release Build):

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace PreOrPostIncrement
{
    class Program
    {
        static int SomethingToIncrement;

        static void Main(string[] args)
        {
            PreIncrement(1000);
            PostIncrement(1000);
            Console.WriteLine("SomethingToIncrement={0}", SomethingToIncrement);
        }

        static void PreIncrement(int count)
        {
            /*
            .method private hidebysig static void  PreIncrement(int32 count) cil managed
            {
              // Code size       25 (0x19)
              .maxstack  2
              .locals init ([0] int32 i)
              IL_0000:  ldc.i4.0
              IL_0001:  stloc.0
              IL_0002:  br.s       IL_0014
              IL_0004:  ldsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
              IL_0009:  ldc.i4.1
              IL_000a:  add
              IL_000b:  stsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
              IL_0010:  ldloc.0
              IL_0011:  ldc.i4.1
              IL_0012:  add
              IL_0013:  stloc.0
              IL_0014:  ldloc.0
              IL_0015:  ldarg.0
              IL_0016:  blt.s      IL_0004
              IL_0018:  ret
            } // end of method Program::PreIncrement             
             */
            for (int i = 0; i < count; ++i)
            {
                ++SomethingToIncrement;
            }
        }

        static void PostIncrement(int count)
        {
            /*
                .method private hidebysig static void  PostIncrement(int32 count) cil managed
                {
                  // Code size       25 (0x19)
                  .maxstack  2
                  .locals init ([0] int32 i)
                  IL_0000:  ldc.i4.0
                  IL_0001:  stloc.0
                  IL_0002:  br.s       IL_0014
                  IL_0004:  ldsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
                  IL_0009:  ldc.i4.1
                  IL_000a:  add
                  IL_000b:  stsfld     int32 PreOrPostIncrement.Program::SomethingToIncrement
                  IL_0010:  ldloc.0
                  IL_0011:  ldc.i4.1
                  IL_0012:  add
                  IL_0013:  stloc.0
                  IL_0014:  ldloc.0
                  IL_0015:  ldarg.0
                  IL_0016:  blt.s      IL_0004
                  IL_0018:  ret
                } // end of method Program::PostIncrement
             */
            for (int i = 0; i < count; i++)
            {
                SomethingToIncrement++;
            }
        }
    }
}

14

Один (++ i) является преинкрементом, другой (i ++) является постинкрементом. Разница в том, какое значение сразу возвращается из выражения.

// Psuedocode
int i = 0;
print i++; // Prints 0
print i; // Prints 1
int j = 0;
print ++j; // Prints 1
print j; // Prints 1

Изменить: Woops, полностью игнорируется сторона цикла вещей. Фактически нет разницы в циклах for, когда это часть 'step' (for (...; ...;)), но она может вступить в действие в других случаях.


7

Нет разницы, если вы не используете значение после приращения в цикле.

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

Обе петли будут печатать 0123.

Но разница возникает, когда вы используете значение после увеличения / уменьшения в цикле, как показано ниже:

Цикл предварительного увеличения:

for (int i = 0,k=0; i < 4; k=++i){
cout<<i<<" ";       
cout<<k<<" "; 
}

Выход: 0 0 1 1 2 2 3 3

Цикл увеличения поста:

for (int i = 0, k=0; i < 4; k=i++){
cout<<i<<" ";       
cout<<k<<" "; 
}

Выход: 0 0 1 0 2 1 3 2

Я надеюсь, что разница ясна, сравнивая результаты. Обратите внимание: здесь увеличение / уменьшение всегда выполняется в конце цикла for и, следовательно, результаты могут быть объяснены.


7

Вот пример Java и Байт-код, post- и preIncrement не показывают различий в Байт-коде:

public class PreOrPostIncrement {

    static int somethingToIncrement = 0;

    public static void main(String[] args) {
        final int rounds = 1000;
        postIncrement(rounds);
        preIncrement(rounds);
    }

    private static void postIncrement(final int rounds) {
        for (int i = 0; i < rounds; i++) {
            somethingToIncrement++;
        }
    }

    private static void preIncrement(final int rounds) {
        for (int i = 0; i < rounds; ++i) {
            ++somethingToIncrement;
        }
    }
}

А теперь для байт-кода (javap -private -c PreOrPostIncrement):

public class PreOrPostIncrement extends java.lang.Object{
static int somethingToIncrement;

static {};
Code:
0:  iconst_0
1:  putstatic   #10; //Field somethingToIncrement:I
4:  return

public PreOrPostIncrement();
Code:
0:  aload_0
1:  invokespecial   #15; //Method java/lang/Object."<init>":()V
4:  return

public static void main(java.lang.String[]);
Code:
0:  sipush  1000
3:  istore_1
4:  sipush  1000
7:  invokestatic    #21; //Method postIncrement:(I)V
10: sipush  1000
13: invokestatic    #25; //Method preIncrement:(I)V
16: return

private static void postIncrement(int);
Code:
0:  iconst_0
1:  istore_1
2:  goto    16
5:  getstatic   #10; //Field somethingToIncrement:I
8:  iconst_1
9:  iadd
10: putstatic   #10; //Field somethingToIncrement:I
13: iinc    1, 1
16: iload_1
17: iload_0
18: if_icmplt   5
21: return

private static void preIncrement(int);
Code:
0:  iconst_0
1:  istore_1
2:  goto    16
5:  getstatic   #10; //Field somethingToIncrement:I
8:  iconst_1
9:  iadd
10: putstatic   #10; //Field somethingToIncrement:I
13: iinc    1, 1
16: iload_1
17: iload_0
18: if_icmplt   5
21: return

}

5

Да, есть. Разница в возвращаемом значении. Возвращаемое значение «++ i» будет значением после увеличения i. Возвращение «i ++» будет значением до приращения. Это означает, что код выглядит следующим образом:

int a = 0;
int b = ++a; // a is incremented and the result after incrementing is saved to b.
int c = a++; // a is incremented again and the result before incremening is saved to c.

Следовательно, a будет 2, а b и c каждый будет 1.

Я мог бы переписать код следующим образом:

int a = 0; 

// ++a;
a = a + 1; // incrementing first.
b = a; // setting second. 

// a++;
c = a; // setting first. 
a = a + 1; // incrementing second. 

4

В обоих случаях фактической разницы нет ' i' будет увеличено на 1.

Но есть разница, когда вы используете его в выражении, например:

int i = 1;
int a = ++i;
// i is incremented by one and then assigned to a.
// Both i and a are now 2.
int b = i++;
// i is assigned to b and then incremented by one.
// b is now 2, and i is now 3

3

В ++ i и i ++ есть больше, чем циклы и различия в производительности. ++ i возвращает l-значение, а i ++ возвращает r-значение. Исходя из этого, есть много вещей, которые вы можете сделать с (++ i), но не с (i ++).

1- It is illegal to take the address of post increment result. Compiler won't even allow you.
2- Only constant references to post increment can exist, i.e., of the form const T&.
3- You cannot apply another post increment or decrement to the result of i++, i.e., there is no such thing as I++++. This would be parsed as ( i ++ ) ++ which is illegal.
4- When overloading pre-/post-increment and decrement operators, programmers are encouraged to define post- increment/decrement operators like:

T& operator ++ ( )
{
   // logical increment
   return *this;
}

const T operator ++ ( int )
{
    T temp( *this );
    ++*this;
    return temp;
}

3

Мне непонятно, почему люди могут записывать выражение приращения в цикле for как i ++.

В цикле for, когда 3-й компонент является простым оператором приращения, как в

for (i=0; i<x; i++)  

или

for (i=0; i<x; ++i)   

нет никакой разницы в итоговых казнях.


Это ответ или вопрос?
Палек

2
Так как это не имеет значения, почему это ошеломило бы вас, если кто-то написал i ++? Есть ли какая-то причина, по которой кто-то предпочел бы написать ++ i?
Дронц

2

Как говорит @Jon B , в цикле for нет разницы.

Но в цикле whileили do...whileвы можете найти некоторые различия, если вы делаете сравнение с ++iилиi++

while(i++ < 10) { ... } //compare then increment

while(++i < 10) { ... } //increment then compare

два отрицательных голоса? Что не так с тем, что я написал? И это связано с вопросом (так же неопределенно, как это).
crashmstr

2

В javascript из-за следующего i ++ может быть лучше использовать:

var i=1;
alert(i++); // before, 1. current, 1. after, 2.
alert(i); // before, 2. current, 2. after, 2.
alert(++i); // before, 2. current, 3 after, 3.

В то время как массивы (я думаю все) и некоторые другие функции и вызовы используют 0 в качестве отправной точки, вы должны были бы установить для i значение -1, чтобы цикл работал с массивом при использовании ++ i .

При использовании i ++ следующее значение будет использовать увеличенное значение. Вы могли бы сказать, что i ++ - это способ подсчета людей, потому что вы можете начать с 0 .


2

Чтобы понять, что делает цикл FOR

введите описание изображения здесь

Изображение выше показывает, что FOR можно преобразовать в WHILE , поскольку в конечном итоге они имеют абсолютно одинаковый код сборки (по крайней мере, в gcc). Таким образом, мы можем разбить FOR на пару частей, чтобы осуществить то, что он делает.

for (i = 0; i < 5; ++i) {
  DoSomethingA();
  DoSomethingB();
}

равно WHILE версии

i = 0; //first argument (a statement) of for
while (i < 5 /*second argument (a condition) of for*/) {
  DoSomethingA();
  DoSomethingB();
  ++i; //third argument (another statement) of for
}

Это означает, что вы можете использовать FOR как простую версию WHILE :

  1. Первый аргумент FOR (int i) выполняется снаружи перед циклом.

  2. Третий аргумент FOR (i ++ или ++ i) выполняется внутри последней строки цикла.

TL: DR: независимо от того , i++или ++iмы знаем , что , когда они являются автономными, они не делают никакой разницы , но +1 на себя.

В школе они обычно преподают способ i ++, но есть также много людей, которые предпочитают способ ++ i по нескольким причинам .

ПРИМЕЧАНИЕ. В прошлом i ++ очень мало влиял на производительность, так как он не только добавляет один плюс, но также сохраняет исходное значение в регистре. Но сейчас это не имеет значения, так как компилятор делает плюс одну часть одинаковой.


1

Там может быть разница для петель. Это практическое применение post / pre-increment.

        int i = 0;
        while(i++ <= 10) {
            Console.Write(i);
        }
        Console.Write(System.Environment.NewLine);

        i = 0;
        while(++i <= 10) {
            Console.Write(i);
        }
        Console.ReadLine();

Первый считается до 11 и повторяется 11 раз, второй - нет.

Чаще всего это используется в простом while (x--> 0); - - Цикл для итерации, например, всех элементов массива (здесь исключаются конструкции foreach).


1

Они оба увеличивают число. ++iэквивалентно i = i + 1.

i++и ++iочень похожи, но не точно так же. Оба увеличивают число, но ++iувеличивают число до оценки текущего выражения, тогда как i++увеличивают число после вычисления выражения.

int i = 3;
int a = i++; // a = 3, i = 4
int b = ++a; // b = 4, a = 

Проверьте эту ссылку .


0

Да, есть разница между ++iи i++в forцикле, хотя в необычных случаях использования; когда переменная цикла с оператором увеличения / уменьшения используется в блоке for или в выражении теста цикла , или с одной из переменных цикла . Нет, это не просто синтаксис.

Как iв коде означает «оценить выражение», так iи оператор - не «оценка», а просто операция;

  • ++iозначает увеличение значения iна 1 и последующую оценку i,
  • i++означает оценку iи последующее увеличение значения iна 1.

Итак, то, что получается из каждых двух выражений, отличается, потому что то, что оценивается, отличается в каждом. Все же для --iиi--

Например;

let i = 0

i++ // evaluates to value of i, means evaluates to 0, later increments i by 1, i is now 1
0
i
1
++i // increments i by 1, i is now 2, later evaluates to value of i, means evaluates to 2
2
i
2

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

for(i=0, j=i; i<10; j=++i){
    console.log(j, i)
}

for(i=0, j=i; i<10; j=i++){
    console.log(j, i)
}

Что это добавляет к существующим ответам?
GManNickG

это отвечает более прямо на то, что спрашивают, чем ответы, которые я прочитал.
Сельчук

-2

Для iпользовательских типов эти операторы могут (но не должны ) иметь существенно различную семантику в контексте индекса цикла, и это может (но не должно) влиять на поведение описанного цикла.

Кроме того, c++обычно безопаснее использовать форму pre-increment ( ++i), поскольку ее легче оптимизировать. (Скотт Лэнгхэм избил меня до этого лакомого кусочка . Проклинаю тебя, Скотт)


Семантика постфикса должна быть больше префикса. -1
xtofl

-2

Я не знаю, для других языков, но в Java ++ i - приращение префикса, которое означает: увеличьте i на 1, а затем используйте новое значение i в выражении, в котором я находится, а i ++ - это постфиксный приращение, что означает следующее : использовать текущее значение i в выражении, а затем увеличить его на 1. Пример:

public static void main(String [] args){

    int a = 3;
    int b = 5;
    System.out.println(++a);
    System.out.println(b++);
    System.out.println(b);

} и вывод:

  • 4
  • 5
  • 6

-3

я ++; ++ я; оба похожи, поскольку они не используются в выражении.

class A {

     public static void main (String []args) {

     int j = 0 ;
     int k = 0 ;
     ++j;
     k++;
    System.out.println(k+" "+j);

}}

prints out :  1 1
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.