Ответ
Разница заключается в значении, которое возвращает оператор, и в потенциальной эффективности.
Семантика
- Преинкремент (
++i): "Увеличить, затем использовать".- Значение
iувеличивается на 1. - Возвращается ссылка на уже изменённый объект
i.
- Значение
- Постинкремент (
i++): "Использовать, затем увеличить".- Сохраняется копия исходного значения
i. - Значение
iувеличивается на 1. - Возвращается сохранённая копия (старое значение).
- Сохраняется копия исходного значения
Наглядный пример с встроенными типами:
int i = 5;
int a = ++i; // Шаг 1: i = 6. Шаг 2: a = i (т.е. 6).
// Результат: i == 6, a == 6
int j = 5;
int b = j++; // Шаг 1: сохранить старое j (5). Шаг 2: j = 6. Шаг 3: b = старое значение (5).
// Результат: j == 6, b == 5
Вопрос эффективности (для пользовательских типов)
Для встроенных типов (int, char) современные компиляторы генерируют идентичный оптимизированный код, и разницы в скорости нет.
Однако для типов с перегруженными операторами (например, итераторы) ++i обычно предпочтительнее:
class MyIterator {
// Преинкремент: работает с текущим объектом, возвращает ссылку.
MyIterator& operator++() {
// ... логика инкремента ...
return *this;
}
// Постинкремент: должен создать копию ДО изменения, вернуть её.
MyIterator operator++(int) { // int — фиктивный параметр для отличия сигнатур
MyIterator temp = *this; // ВОТ ОНА, ДОПОЛНИТЕЛЬНАЯ КОПИЯ!
++(*this); // Используем уже реализованный преинкремент
return temp; // Возвращаем старую копию
}
};
Создание временного объекта в operator++(int) может быть накладным для сложных типов.
Рекомендация (best practice):
- В циклах
forвсегда используйте++i. Это привычка, которая гарантирует максимальную эффективность для любых типов.for (auto it = vec.begin(); it != vec.end(); ++it) { // Хорошо // for (...; ...; it++) { // Менее оптимально для не-POD типов - Используйте
i++только тогда, когда вам действительно нужно старое значение в выражении.auto old_value = counter++; // Логично: сохраняем старое значение, счётчик растёт. process(ptr++); // Обработать текущий элемент, затем сдвинуть указатель.
Ответ 18+ 🔞
А, ну это классика, блядь! Сейчас объясню, в чём тут собака зарыта, а то народ путается, как последний распиздяй.
Смотри, суть в том, что именно возвращает эта штука и насколько она тебе мозги выносит по производительности.
Что они творят, эти уроды?
- Преинкремент (
++i): Логика проще пареной репы. "Сперва накинуть, потом отдать".- Берёшь
iи нахуяриваешь ему единичку сверху. - Возвращаешь ссылку на этого уже обдолбанного
i. Всё честно.
- Берёшь
- Постинкремент (
i++): А вот это уже хитрая жопа. "Сперва отдать, потом накинуть".- Ты делаешь копию того, чем
iбыл до того, как всё пошло по пизде. - Потом уже самому
iприбавляешь единицу. - А на выход подсовываешь ту самую сохранённую копию, старую, не тронутую. Подстава, да?
- Ты делаешь копию того, чем
Смотри на пальцах с обычными числами:
int i = 5;
int a = ++i; // Шаг 1: i стало 6. Шаг 2: a получает i (то есть 6).
// Итог: i == 6, a == 6. Всё прозрачно, как слёзы ребёнка.
int j = 5;
int b = j++; // Шаг 1: запомнили старый j (5). Шаг 2: j стало 6. Шаг 3: b получает то самое старое значение (5).
// Итог: j == 6, b == 5. Вот тебе и "использовать, потом увеличить". Сам от себя охуел?
А где тут собака порылась? (Про эффективность)
С обычными типами вроде int — да похуй, честно. Компилятор сейчас такой умный, что разницы нихуя не будет, оптимизирует на раз.
Но вот когда дело доходит до твоих собственных классов, или там итераторов — тут начинается ёперный театр. ++i почти всегда лучше, и вот почему:
class MyIterator {
// Преинкремент: работает с собой, возвращает себя же.
MyIterator& operator++() {
// ... делаем свои грязные делишки ...
return *this; // И всё, доволен как слон.
}
// Постинкремент: должен создать копию ДО того, как начнёт ебашить.
MyIterator operator++(int) { // Этот int — просто финт ушами, чтобы компилятор их различил
MyIterator temp = *this; // ВОТ ОНА, БЛЯДЬ, ДОПОЛНИТЕЛЬНАЯ КОПИЯ! Лишняя работа!
++(*this); // Используем уже готовый преинкремент, чтобы не повторяться
return temp; // А возвращаем-то старьё!
}
};
Создание этого временного объекта temp в operator++(int) — это лишняя нагрузка, особенно если объект тяжёлый. Зачем плодить сущности, а?
Так как же жить, спросишь ты?
- В циклах
for— всегда пиши++i. Выработай привычку, и будет тебе счастье. Это гарантирует, что даже с итераторами от какого-нибудь ёбанько-контейнера не будет лишних телодвижений.for (auto it = vec.begin(); it != vec.end(); ++it) { // Отлично! Красава! // for (...; ...; it++) { // Так-то тоже работает, но для сложных штук может быть медленнее. Не надо так. - А
i++оставь для тех редких случаев, когда тебе реально нужно старое значение прямо в выражении. Тогда да, без него никуда.auto old_value = counter++; // Логично: сохранили, что было, а счётчик уже ушёл вперёд. process(ptr++); // Обработали текущий элемент, а указатель сами того не заметив, уже сдвинули. Удобно!
Вот и вся магия. Главное — понимать, что возвращаешь, и не создавать лишних копий на ровном месте. А то доверия к такому коду — ноль ебать.