Різниця між попереднім збільшенням і післякріпленням у циклі?


303

Чи є різниця в ++iі i++в forциклі? Це просто річ синтаксису?



18
Я вражений тим, скільки відповідей повністю пропустили суть питання.
Graeme Perrow

3
Можливо, ми повинні бути вражені, що ніхто не редагував питання, щоб бути більш зрозумілим :)
Джон Б,

2
Це питання може стосуватися C, Java, C ++, PHP, C #, Javascript, JScript, Об'єктив C: en.wikipedia.org/wiki/Category:C_programming_language_family
Chris S

1
Хороша відповідь розміщена тут: stackoverflow.com/a/4706225/214296
Джим Fell

Відповіді:


233

a ++ відомий як постфікс.

додати 1 до a, повертає старе значення.

++ a відомий як префікс.

додати 1 до а, повертає нове значення.

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петлі залежать від того, який тип приросту ви використовуєте. Для циклів, як показано нижче, це не має значення, оскільки ви не використовуєте значення повернення 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
Це навіть не те, що просив користувач.
Димитрій

223

Попереднє збільшення ++ 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, ви можете використовувати для циклів з ітераторами. В основному вони мають перевершені оператори ++, тому дотримуватися попереднього збільшення - хороша ідея. Хоча компілятори стають розумнішими весь час, і новіші, можливо, зможуть проводити оптимізацію, що означає відсутність різниці в продуктивності - особливо якщо тип, який збільшується, визначений вбудованим файлом заголовка (як часто це реалізація 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 потім зчитує його.


Висновок: та сама семантика після / попереднього збільшення, що і в C ++.
xtofl

@xtofl - не впевнений, який твій погляд? Мені просто трапилось обрати c # для свого прикладу.
Джон Б

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

9
@MatthieuP - я читав питання як "чи не має значення ви використовуєте i ++ або ++ i в циклі for?" Відповідь «ні, ні».
Джон Б

1
@JonB Порядок операцій у відповіді не зовсім правильний. І те, ++iі i++інше виконують ті самі операції в одному порядку: створити темп-копію i; збільшити значення temp для отримання нового значення (не перевизначати темп); зберігати нове значення в 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буде збільшуватися ПІСЛЯ виконання коду, який знаходиться всередині корпусу циклу.


2
Це буквально перша відповідь, яка йде прямо в суть. Дякую.
Ясір

1
Це не найкращий відповідь , тому що , якщо цикл буде приріст складного об'єкта (щось інше , ніж в міжнародному!) Здійснення ++ х може бути швидше , ніж х ++ ... (див herbsutter.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 примітивного типу, то це не має значення для першого циклу. так як це мовне агностичне питання, я зрозумів, що не писати занадто багато про специфічні для C ++ речі.
Йоханнес Шауб - ліб

15

Як показує цей код (див. Розібраний MSIL у коментарях), компілятор C # 3 не робить різниці між i ++ та ++ i у циклі for. Якщо значення 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, повністю ігноруючи петлеву сторону речей. Немає фактичної різниці для циклів, коли це "крокова" частина (для (...; ...;)), але вона може вступати в гру в інших випадках.


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-зразок та байт-код, пост- та попереднє збільшення не бачать різниці в байт-коді:

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 ++, ніж циклі та відмінності в продуктивності. ++ я повертає значення 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-loop як 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 ++ наступне значення використовуватиме збільшене значення. Можна сказати, що я ++ - це спосіб підрахунку людей, тому що ви можете почати з 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

Для петель може бути різниця. Це практичне застосування після / попереднього збільшення.

        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 разів, другий - ні.

Переважно це скоріше використовується у простий час (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циклі, хоча у незвичних випадках використання; коли змінна циклу з оператором збільшення / зменшення використовується в блоці для блоку або в виразі тестового циклу , або з однією зі змінних циклу . Ні, це не просто річ синтаксису.

Як і 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++як правило, найбезпечніше використовувати форму попереднього збільшення ( ++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

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

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.