← Назад к вопросам

В чем разница между преинкрементом и постинкрементом?

1.0 Junior🔥 151 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

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

Быстрый ответ

Преинкремент (++i) — инкрементирует значение и возвращает новое значение. Постинкремент (i++) — инкрементирует значение, но возвращает старое значение.

int i = 5;
int a = ++i;  // i = 6, a = 6 (новое значение)

int j = 5;
int b = j++;  // j = 6, b = 5 (старое значение)

Это простое объяснение, но есть глубокие последствия для производительности.

Реализация: почему они разные?

Преинкремент (++i):

// Версия для встроенных типов (примерно так компилятор генерирует)
class Integer {
public:
    Integer& operator++() {  // возвращает ссылку
        ++value;
        return *this;  // возвращаем самого себя
    }
    
private:
    int value;
};

// Использование
Integer i(5);
Integer& ref = ++i;  // Просто инкрементируем и возвращаем ссылку
// ref и i указывают на тот же объект

Постинкремент (i++):

class Integer {
public:
    Integer operator++(int) {  // int — фиктивный параметр для различия
        Integer temp(*this);   // Создаём КОПИЮ старого значения
        ++value;               // Инкрементируем
        return temp;           // Возвращаем копию
    }
    
private:
    int value;
};

// Использование
Integer i(5);
Integer result = i++;  // Создаётся временный объект (копия)
// Это дороже!

Производительность: критическое отличие

Преинкремент эффективнее, потому что:

  • Не создаёт временный объект
  • Возвращает ссылку, а не копию
  • Компилятор не может оптимизировать постинкремент
class Counter {
public:
    Counter& operator++() {  // Преинкремент
        value++;
        return *this;
    }
    
    Counter operator++(int) {  // Постинкремент
        Counter temp = *this;  // КОПИЯ — дорого!
        value++;
        return temp;           // возвращаем копию
    }
    
private:
    int value;
};

// При использовании в цикле
for (int i = 0; i < 1000000; ++i) {}   // Быстро (преинкремент)
for (int i = 0; i < 1000000; i++) {}   // Медленнее (постинкремент создаёт копии)

Для встроенных типов (int, double) компилятор часто оптимизирует разницу, но для объектов это критично.

Практические примеры

1. Встроенные типы (разницы почти нет после оптимизации):

int i = 0;
++i;  // эффективнее
i++;  // немного медленнее (теория)

// Но хороший компилятор -O2 оптимизирует оба варианта одинаково

2. Объекты (огромная разница):

std::vector<int> vec = {1, 2, 3, 4, 5};

// ПЛОХО: создаёт копию итератора каждую итерацию
for (auto it = vec.begin(); it != vec.end(); it++) {
    std::cout << *it << " ";
}

// ХОРОШО: не создаёт копий
for (auto it = vec.begin(); it != vec.end(); ++it) {
    std::cout << *it << " ";
}

// Разница может быть 2-3x для больших объектов!

3. С пользовательскими классами:

class BigObject {
public:
    BigObject& operator++() {
        // простая операция
        counter++;
        return *this;
    }
    
    BigObject operator++(int) {
        // создаём ПОЛНУЮ КОПИЮ объекта (может быть большой!)
        BigObject temp = *this;  // Копирование конструктор
        counter++;
        return temp;
    }
    
private:
    int counter;
    char buffer[1024];  // Большие данные
};

// Тест
void benchmark() {
    BigObject obj;
    
    // Быстро: 1000 простых инкрементов
    for (int i = 0; i < 1000; ++i) ++obj;
    
    // Медленно: 1000 копирований объекта!
    for (int i = 0; i < 1000; i++) obj++;
}

Почему "int" в постинкременте?

В C++ этот параметр используется просто для различия сигнатур:

class MyClass {
public:
    MyClass& operator++() { }      // преинкремент: ++obj
    MyClass operator++(int) { }    // постинкремент: obj++
    
    // int не используется в реализации, это просто маркер
};

Перед C++98 существовал только преинкремент. После добавили постинкремент с int параметром для перегрузки.

Best Practices

1. Правило: всегда используй преинкремент

// ✅ Правильно
for (auto it = vec.begin(); it != vec.end(); ++it) {}
for (int i = 0; i < 10; ++i) {}

Counter c;
++c;  // лучше

// ❌ Старый стиль (можно, но медленнее)
for (int i = 0; i < 10; i++) {}  // создаёт временные копии

2. В цикле всегда ++i, а не i++

// BAD
for (int i = 0; i < 1000000; i++) {
    complexOperation();
}

// GOOD
for (int i = 0; i < 1000000; ++i) {
    complexOperation();
}

// Разница может быть процентов 5-10 для объектов

3. Для итераторов особенно важно

std::list<int> lst = {1, 2, 3};

// Очень медленно для list (двусвязный список)
for (auto it = lst.begin(); it != lst.end(); it++) {}

// Нормально
for (auto it = lst.begin(); it != lst.end(); ++it) {}

// Даже лучше (range-based for)
for (int val : lst) {}

Под капотом: компиляция

int i = 5;
int a = ++i;  // Компилятор генерирует: i = i + 1; a = i; (одна операция)

int j = 5;
int b = j++;  // Компилятор генерирует: temp = j; j = j + 1; b = temp; (две операции)

Для простых int компилятор -O2 оптимизирует оба в одно. Но для объектов нет:

Integer i(5);
Integer a = ++i;  // Ровно столько же кода

Integer j(5);
Integer b = j++;  // Создаётся Integer(j), копируется в b, потом удаляется

Исключение: когда нужен постинкремент?

Когда тебе нужно старое значение:

// Нужно обработать старое значение перед инкрементом
int lastValue = i++;
processOldValue(lastValue);

// Но обычно лучше:
processOldValue(i);
++i;

Заключение

  • ++i (преинкремент) — возвращает новое значение, эффективнее
  • i++ (постинкремент) — возвращает старое значение, создаёт временный объект
  • Для встроенных типов разница минимальна (хороший компилятор оптимизирует)
  • Для объектов разница может быть 2-5x
  • Правило: всегда используй ++i в циклах по привычке
  • Исключение: когда нужно именно старое значение
В чем разница между преинкрементом и постинкрементом? | PrepBro