Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
 
Рейтинг: Рейтинг темы: голосов - 83, средняя оценка - 4.81
limelight
8 / 8 / 0
Регистрация: 17.04.2010
Сообщений: 112
#1

Разница между префиксной и постфиксной формой записи счетчика цикла - C++

26.04.2010, 17:56. Просмотров 12167. Ответов 57
Метки нет (Все метки)

Здравствуйте!

Когда оформлял циклы всегда использовал такую запись:

C++
1
for(int i=0; i<10; i++)
, которая означает что цикл будет выполнен не более 10 раз, счетчик будет увеличен только после выполнения тела цикла и успешного выполнения условия(i<10).

В последнее время стал встречать другую запись:
C++
1
for(int i=0; i<10; ++i)
В чем принципиальное различие этих форм записей для использования цикла?
Когда какой более предпочтителен?
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
26.04.2010, 17:56
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Разница между префиксной и постфиксной формой записи счетчика цикла (C++):

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

Перегрузка постфиксной и префиксной операции инкремента - C++
Здравствуйте! У меня возник вопрос: почему выводит разный результат, казалось бы, одинаковый код Вот здесь выводит как должно: ...

Подсчет выражения в постфиксной записи - C++
Как подсчитать значение выражения? #include&lt;iostream&gt; using namespace std; int prior(char x) { if ((x='*')||(x='/'))...

Странные значения счетчика цикла. ПОМОГИТЕ! - C++
#include &lt;iostream&gt; using namespace std; double unitInterval = 0.2 ; double OX = 0; double OY = 0; int main() {

программа по переводу из постфиксной формы записи в инфиксную с использованием скобок - C++
перевод буквенного выражения с проверкой правильности ввода

Как сделать чтобы в постфиксной записи кроме цифр выводились еще и обычные символы - C++
Есть код который делает конвертацию например 1 + 2 * 3 --&gt;123*+ но а + b* c --&gt;с -- то есть лиш последний введеный символ что здесь не...

57
_Ivana
3169 / 1786 / 153
Регистрация: 01.03.2013
Сообщений: 5,004
Записей в блоге: 2
19.08.2014, 21:08 #31
Вот ведь вопрос серьезный, как у остроконечников/тупоконечников Свифта. Если смотреть с точки зрения ассемблерного листинга, то вроде как одна бабка говорит, что префиксная эффективнее. А если забыть про эти копейки и смотреть с точки зрения стройности кода, то разработчики С (а не С++, кстати) вполне правильно сделали, придумав оба варианта, и в примерах кода применяют тот, который нагляднее отражает суть задачи - в последнем скрине пример заполнения массива по индексу, ну коряво бы выглядело начинать с -1 и делать предынкремент, гораздо стройнее смотрится начинать индекс с нуля и делать постинкремент.
ЗЫ а если счетчик еще и беззнаковый, то при предынкременте начинать надо вообще с 255 если он восьмибитный и с других чисел, если другой размер типа. И зачем с этим корячиться, если есть постинкремент?
0
taras atavin
3569 / 1752 / 91
Регистрация: 24.11.2009
Сообщений: 27,619
19.08.2014, 21:11 #32
А какое отношение цикл имеет к стартовому индексу? Этот блок в любом случае выполняется после тела.
0
_Ivana
3169 / 1786 / 153
Регистрация: 01.03.2013
Сообщений: 5,004
Записей в блоге: 2
19.08.2014, 21:15 #33
Если это так, тогда это я лохЪ, поскольку не применяю предынкремент по причине моего незнания и вытекающей из него неуверенности Тогда надо вышенаписанное мной считать бредом, а мне учить предынкремент ))))
0
John Prick
19.08.2014, 22:11
  #34

Не по теме:

Цитата Сообщение от _Ivana Посмотреть сообщение
вышенаписанное мной считать бредом, а мне учить предынкремент
Первое верное, второе - спорно, так как учить надо цикл for.

0
Jupiter
19.08.2014, 22:23
  #35

Не по теме:

Цитата Сообщение от _Ivana Посмотреть сообщение
ну коряво бы выглядело начинать с -1 и делать предынкремент, гораздо стройнее смотрится начинать индекс с нуля и делать постинкремент.
ЗЫ а если счетчик еще и беззнаковый, то при предынкременте начинать надо вообще с 255 если он восьмибитный и с других чисел, если другой размер типа. И зачем с этим корячиться, если есть постинкремент?
0xFF же, а то и ~0

0
numizmat
0 / 0 / 0
Регистрация: 18.01.2015
Сообщений: 4
27.06.2015, 04:50 #36
C++
1
2
3
4
5
6
7
8
  int i1 = 0;
     int i2 = 0;
    
    i2 = ++i1;
           cout << "i1= " << i1 << "  i2= " << i2 << endl;   
    
    i2 = i1++;
        cout << "i1= " << i1 << "  i2= " << i2 << endl;
Получим.
i1= 1 i2= 1

i1= 2 i2= 1
0
daslex
1271 / 515 / 106
Регистрация: 02.08.2011
Сообщений: 2,706
27.06.2015, 07:38 #37
Вопрос внимательнее прочти и обрати внимание на дату...
А насчет
Цитата Сообщение от gru74ik Посмотреть сообщение
Так что "по стандарту" не получится Там даже внутри циклов оба варианта используются.
Скотт Мейерс (More Effective c++) писал
The prefix version is always preferred over the postfix in regards to objects, especially in regards to iterators.
Префиксная версия всегда предпочтительнее чем постфиксная в отношении объектов, особенно в отношении итераторов
По стандарту вообще мало что получится, если все уже не знать. Они разбросали важные кусочки по очень разным местам.
0
Oleg Frias
0 / 0 / 0
Регистрация: 21.02.2016
Сообщений: 2
24.02.2016, 23:15 #38
опоздал)
0
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3876 / 2134 / 548
Регистрация: 18.10.2014
Сообщений: 3,748
25.02.2016, 03:27 #39
Во-первых, у префиксного и постфиксного инкремента разная семантика результата. Первый возвращает новое значение (как lvalue), второй - старое значение (как rvalue). Поэтому в реальном коде следует выбирать именно тот вариант, который возвращает именно нужный вам результат. Это - главный критерий выбора между префиксным и постфиксным вариантами.

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

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

В-четвертых, никакого "как в стандарте" тут нет и быть не может. Стандарт такими вопросам не занимается.
0
hoggy
6652 / 2838 / 486
Регистрация: 15.11.2014
Сообщений: 6,273
Завершенные тесты: 1
25.02.2016, 03:54 #40
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Особо умный компилятор может уметь оптимизировать код постфиксного инкремента в ситуациях, когда результат оператора игнорируется, тем самым устраняя разницу в производительности. Но полагаться на это не стоит.
все топовые компиляторы уже давным давно
научились оптимизировать неиспользуемые значения.
0
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3876 / 2134 / 548
Регистрация: 18.10.2014
Сообщений: 3,748
25.02.2016, 04:32 #41
Цитата Сообщение от hoggy Посмотреть сообщение
все топовые компиляторы уже давным давно
научились оптимизировать неиспользуемые значения.
Речь в данном случае идет не о какой-то абстрактной "оптимизации неиспользуемых значений", а о вполне конкретной ситуации: устранении выполнения копирования старого значения объекта внутри нетривиальной реализации постфиксного оператора '++' в ситуации, когда это старое значение не используется снаружи, т.е. в вызове этого оператора.

Оптимизации такого рода весьма и весьма нетривиальны, в том смысле, что для не-inline вызова функций идеального способа выполнения такой оптимизации просто не существует: надо либо делать ветвление внутри реализации оператора, либо генерировать несколько версий оператора. Все варианты обладают своими достоинствами и недостатками.
0
hoggy
6652 / 2838 / 486
Регистрация: 15.11.2014
Сообщений: 6,273
Завершенные тесты: 1
25.02.2016, 05:03 #42
Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Речь в данном случае идет не о какой-то абстрактной "оптимизации неиспользуемых значений", а о вполне конкретной ситуации: устранении выполнения копирования старого значения объекта внутри нетривиальной реализации постфиксного оператора '++' в ситуации, когда это старое значение не используется снаружи, т.е. в вызове этого оператора.
почти верно. есть нюанс.
компиляторы уже давно научились стандартным RVO/NRVO оптимизациям.
и в ситуации, когда они фиксят, что снаружи значение никому не нужно,
они запросто могут выпилить конструктор

Цитата Сообщение от TheCalligrapher Посмотреть сообщение
Оптимизации такого рода весьма и весьма нетривиальны, в том смысле, что для не-inline вызова функций идеального способа выполнения такой оптимизации просто не существует:
RVO/NRVO вообще то стандартны.
их обязаны уметь все компиляторы.
и они их умеют уже с незапамятных времен.

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

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

я хочу сказать, что совершенно не важно насколько нетривиальна функция.

компилятор оптимизирует конструкцию вида:
C++
1
obj++;
для любых типов так же просто,
как и для обычного инта.
0
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3876 / 2134 / 548
Регистрация: 18.10.2014
Сообщений: 3,748
25.02.2016, 05:40 #43
Цитата Сообщение от hoggy Посмотреть сообщение
почти верно. есть нюанс.
компиляторы уже давно научились стандартным RVO/NRVO оптимизациям.
и в ситуации, когда они фиксят, что снаружи значение никому не нужно,
они запросто могут выпилить конструктор
Почти верно, но есть нюанс.

Термины RVO и NRVO в своем каноническом значении относятся именно и только к ситуациям, когда возвращаемое значение именно нужно вызывающему коду. RVO и NRVO описывают стуации, когда внутреннему коду функции разрешается исключать формирование промежуточных локальные объектов (именованных или не именованных), и взамен формировать результат напрямую в объекте-приемнике, предоставленном вызывающим кодом.

Ситуации, когда результат вызова функции вообще никому не нужен, непосредственно к RVO и NRVO не относятся. Но если вам нравится включать в RVO и NRVO и такие ситуации - пожалуйста, включайте, никто вам не запретит. Термины RVO и NRVO, как никак, [полу-]неформальны.

Тем не менее это ничего не меняет. Реализация оптимизаций для случая игнорируемого значения все равно требует, как я сказал выше, либо run-time branching, либо генерации нескольких версий оператора, либо еще чего-то в этом роде. Ни один из этих вариантов не является "бесплатным".

Цитата Сообщение от hoggy Посмотреть сообщение
RVO/NRVO вообще то стандартны.
Замечательно Правильнее будет сказать: разрешены стандартом. Но это никоим образом ничего не меняет. К чему это здесь?

Цитата Сообщение от hoggy Посмотреть сообщение
их обязаны уметь все компиляторы.
Нет, конечно. Оптимизация - то всегда вопрос качества реализации (quality-of-implementation). Компилятор вообще имеет право ничего не уметь оптимизировать, а просто тупо транслировать код в строгом соответствии с правилами абстрактной C++-машины.

Цитата Сообщение от hoggy Посмотреть сообщение
и они их умеют уже с незапамятных времен.
RVO существует "с незапамятных времен". NRVO же существенно более позднее изобретение - его формально не существовало в C++98.

Цитата Сообщение от hoggy Посмотреть сообщение
все очень просто: стандарт разрешает компиляторам покласть болт
на любые возможные эффекты в конструкторах копий.
Это не верно. Стандарт разрешает компиляторам покласть болт на любые возможные эффекты в конструкторах временных (безымынных) копий. Вы пропустили очень важное слово "временных". На этом принципе было основано copy elision и RVO в С++98. Позже стандарт разрешил "покласть болт" и в ряде частных, четко очерченных случаев для именованных копий, что открыло возможности для NRVO.

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

Цитата Сообщение от hoggy Посмотреть сообщение
например, когда значение снаружи все равно никому не нужно.
Опять мимо кассы. Речи идет не о том, что компилятор "может" или "не может", а о том, что его "может" не является бесплатным в сгенерированом коде и выливается либо в run-time branching, либо в code bloat.

Цитата Сообщение от hoggy Посмотреть сообщение
компилятор оптимизирует конструкцию вида:
C++Выделить код
1
obj++;
для любых типов так же просто,
как и для обычного инта.
Это совершено не верно. Оптимизация для типов, чья семантика прекрасно известна на уровне core language, и оптимизация для типов, чья семантика определена на уровне library/user code, отличается принципиально. Т.е. тут вообще даже смешно сравнивать - ничего общего эти оптимизации не имеют и ни о каком "так же просто" речи быть не может.

Собственно по этой причине мы и наблюдаем развития языка в сторону NRVO, move semantics и т.п. Эти свойства языка появились именно из-за мешающих оптимизациям принципиальных различий в семантике встроенных и пользовательских типов.
2
AlexVRud
444 / 155 / 40
Регистрация: 04.07.2014
Сообщений: 444
25.02.2016, 09:08 #44
Рассматривать стоит, например, такой код:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <vector>
 
int main() {
 
  std::vector<int> v {0,1,2,3,4,5,6};
  int s=0;
  for(auto i=v.cbegin(); i<v.cend(); i++) { // ++i
    s+=*i;
  }
  std::cout << s << std::endl;
 
}
При выключенной оптимизации он даст немного разный код. При оптимизации компилятор может знать, что ++i для данного итератора делает тоже но быстрее и заменит на него (а если ещё это inline, то оптимизирует ещё лучше). Но если он ничего не знает об итераторе, то выставит то, что уже есть. А теперь посмотрим на стандартный пример реализации инкремента:

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Number 
{
    public:
        Number& operator++ ()     // prefix ++
        {
           // Do work on this.   (increment your object here)
           return *this;
        }
 
        // You want to make the ++ operator work like the standard operators
        // The simple way to do this is to implement postfix in terms of prefix.
        //
        Number  operator++ (int)  // postfix ++
        {
           Number result(*this);   // make a copy for result
           ++(*this);              // Now use the prefix version to do the work
           return result;          // return the copy (the old) value.
        }
};
Вспомогательный объект создан внутри Number++!!! И если его реализация скрыта (т.е. в другом файле), то компилятор не сможет оптимизировать код. Кроме этого никто не обязан давать одинаковый результат для {++i;} и {i++;}.
0
TheCalligrapher
С чаем беда...
Эксперт CЭксперт С++
3876 / 2134 / 548
Регистрация: 18.10.2014
Сообщений: 3,748
25.02.2016, 09:50 #45
Цитата Сообщение от AlexVRud Посмотреть сообщение
Вспомогательный объект создан внутри Number++!!! И если его реализация скрыта (т.е. в другом файле), то компилятор не сможет оптимизировать код. Кроме этого никто не обязан давать одинаковый результат для {++i;} и {i++;}.
Компилятор так не оптимизирует. Компилятор не имеет права заменить 'i++' на '++i' для невстроенного типа именно потому, что, как вы правильно заметили, компилятор не знает, делают ли эти функции "одно и то же".

Возможности для оптимизации тут кроются в другом. Например, при компиляции вашего постфиксного оператора '++' компилятор может заранее подумать о том, что в некоторых контекстах этот оператор может вызываться с игнорированием результата (вариант A), а в некоторых - с неигнорированием результата (вариант B). По этой причине компилятор может использовать ваше определение постфиксного оператора '++' для того, чтобы втихаря сгенерировать две версии скомпилированного кода: для вариантов A и B отдельно. Внутренние имена для этих двух реализаций будут разные, сгенерированные в соответствии с неким соглашением. Для варианта B оператор будет странслирован честно, как вы написали - с формированием возвращаемого значения. А вот для варианта A будет странслирована "урезанная" версия без возвращаемого значения, которая элементарно соптимизируется до

C++
1
2
3
4
        void  operator++ (int)  // postfix ++
        {
           ++(*this);              // Now use the prefix version to do the work
        }
И именно эту версию умный компилятор будет потом вызывать в цикле 'for (...; ...; i++)'.

Вот об этой оптимизации и идет речь. Так, например, работает GCC. Понятно, что за такую оптимизацию приходится платить увеличением размера кода, ибо потенциально каждая функция с не-'void' типом возвращаемого значения может генерироваться в двух вариантах.
0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
25.02.2016, 09:50
Привет! Вот еще темы с ответами:

++i и i++ разница при выполнении цикла - C++
Доброго времени суток! Действительно ли в цикле вида: for(int i = 0; i &lt; n; ++i) {} ++i Будет быстрее чем i++?

Разница между != и <= - C++
Здравствуйте! Писал программу с использованием QuickSort и заинтересовала такая вещь: void quickSort(int arr, int left, int right) { ...

Разница между С и С++ - C++
Если не брать во внимание объекты и классы, то разница состоит только лишь в cin, cout и директивах?

Разница между 0 и 00 ? - C++
Как заставить программу различать 0 и 00 (ноль и два ноля, как в рулетке)? Вариант с заменой 00 на какое-то число не подходит, так как...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
45
Yandex
Объявления
25.02.2016, 09:50
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru