Инкремент в C++ - Когда использовать x++ или ++x?
В настоящее время я изучаю C++, и я узнал об увеличении некоторое время назад. Я знаю, что вы можете использовать "++ x", чтобы сделать приращение до, и "x ++", чтобы сделать это после.
Тем не менее, я действительно не знаю, когда использовать любой из двух... Я никогда не использовал "++ x", и до сих пор все работало нормально - так, когда я должен его использовать?
Пример: когда в цикле предпочтительно использовать "++ x"?
Кроме того, кто-то может объяснить, как именно работают различные приращения (или вычитания)? Я был бы очень признателен.
12 ответов
Это не вопрос предпочтения, а логика.
x++
увеличивает значение переменной x после обработки текущего оператора.
++x
увеличивает значение переменной x перед обработкой текущего оператора.
Так что просто определитесь с логикой, которую вы пишете.
x += ++i
увеличит i и добавит i+1 к x.x += i++
добавим i к x, затем увеличим i.
Скотт Мейерс говорит, что вы предпочитаете префикс, за исключением тех случаев, когда логика диктует, что постфикс подходит.
Пункт 6 "Более эффективный C++" - это достаточный авторитет для меня.
Для тех, кто не владеет книгой, вот соответствующие цитаты. Со страницы 32:
Из ваших дней программиста на C вы можете вспомнить, что префиксную форму оператора приращения иногда называют "приращением и извлечением", в то время как постфиксную форму часто называют "извлечением и приращением". Эти две фразы важно помнить, потому что они действуют как формальные спецификации...
И на странице 34:
Если вы из тех, кто беспокоится об эффективности, вы, вероятно, вспылили, когда впервые увидели функцию увеличения постфикса. Эта функция должна создать временный объект для своего возвращаемого значения, и приведенная выше реализация также создает явный временный объект, который должен быть создан и уничтожен. Функция приращения префикса не имеет таких временных...
Из cppreference при увеличении итераторов:
Вы должны предпочесть оператор предварительного увеличения (++iter) оператору постинкремента (iter++), если вы не собираетесь использовать старое значение. Постинкремент обычно реализуется следующим образом:
Iter operator++(int) {
Iter tmp(*this); // store the old value in a temporary object
++*this; // call pre-increment
return tmp; // return the old value }
Очевидно, это менее эффективно, чем предварительное увеличение.
Предварительное увеличение не создает временный объект. Это может существенно повлиять на стоимость создания вашего объекта.
Самая важная вещь, которую нужно иметь в виду, imo, - это то, что x++ должен вернуть значение до того, как фактически произойдет приращение, поэтому он должен сделать временную копию объекта (предварительное приращение). Это менее эффективно, чем ++x, который увеличивается на месте и возвращается.
Однако стоит упомянуть еще и то, что большинство компиляторов смогут по возможности оптимизировать такие ненужные вещи, например, оба варианта приведут к одному и тому же коду:
for (int i(0);i<10;++i)
for (int i(0);i<10;i++)
Я просто хочу заметить, что сгенерированный код отключается, если вы используете приращение до / после, когда семантика (до / после) не имеет значения.
пример:
pre.cpp:
#include <iostream>
int main()
{
int i = 13;
i++;
for (; i < 42; i++)
{
std::cout << i << std::endl;
}
}
post.cpp:
#include <iostream>
int main()
{
int i = 13;
++i;
for (; i < 42; ++i)
{
std::cout << i << std::endl;
}
}
_
$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s
1c1
< .file "pre.cpp"
---
> .file "post.cpp"
Я согласен с @BeowulfOF, хотя для ясности я бы всегда выступал за разделение утверждений так, чтобы логика была абсолютно ясной, то есть:
i++;
x += i;
или же
x += i;
i++;
Поэтому мой ответ: если вы пишете чистый код, то это должно иметь значение редко (а если это имеет значение, то ваш код, вероятно, недостаточно ясен).
Если
count{5};
Если вы используете ++count, он будет обработан перед оператором
total = --count +6;
Итого будет равно 10
Если вы используете count++, он будет обработан после оператора
total = count-- +6;
Итого будет равно 11
Постфиксная форма ++,- оператор следует правилу use-then-change,
Форма префикса (++x,- x) соответствует правилу " изменить-потом-использовать".
Пример 1:
Когда несколько значений каскадируются с помощью << cout, тогда вычисления (если таковые имеются) производятся справа налево, а печать - слева направо, например, (если val изначально 10)
cout<< ++val<<" "<< val++<<" "<< val;
приведет к
12 10 10
Пример 2:
В Turbo C++, если в выражении найдено несколько вхождений ++ или (в любой форме), сначала вычисляются все префиксные формы, затем вычисляется выражение и, наконец, вычисляются постфиксные формы, например,
int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;
Это вывод в Turbo C++ будет
48 13
Принимая во внимание, что это будет выходной в современных днях компилятор будет (потому что они строго следуют правилам)
45 13
- Примечание. Многократное использование операторов увеличения / уменьшения для одной и той же переменной в одном выражении не рекомендуется. Обработка / результаты такого
выражения варьируются от компилятора к компилятору.
Просто хотел еще раз подчеркнуть, что ++x, как ожидается, будет быстрее, чем x++ (особенно, если x является объектом произвольного типа), поэтому, если это не требуется по логическим причинам, следует использовать ++x.
Вы просили привести пример:
Этот (
order
это std ::vector) произойдет сбой для
i == order.size()-1
по доступу:
while(i++ < order.size() && order[i].size() > currLvl);
Это не приведет к сбою в
order[i].size()
, в качестве
i
будет увеличиваться, проверяться и цикл будет завершен:
while(++i < order.size() && order[i].size() > currLvl);
Вы правильно объяснили разницу. Это зависит только от того, хотите ли вы увеличивать x перед каждым циклом или после него. Это зависит от вашей логики программы, что подходит.
Важное отличие при работе с STL-итераторами (которые также реализуют эти операторы) состоит в том, что он ++ создает копию объекта, на который указывает итератор, затем увеличивает его, а затем возвращает копию. ++ он, с другой стороны, сначала делает приращение, а затем возвращает ссылку на объект, на который теперь указывает итератор. Это в основном актуально, когда важен каждый бит производительности или когда вы реализуете свой собственный STL-итератор.
Редактировать: исправлено смешение префикса и суффикса
Понимание синтаксиса языка важно при рассмотрении ясности кода. Попробуйте скопировать строку символов, например, с пост-приращением:
char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
b[i] = a[i];
} while (a[i++]);
Мы хотим, чтобы цикл выполнялся, встречая нулевой символ (который проверяет ложь) в конце строки. Это требует тестирования предварительного увеличения значения, а также увеличения индекса. Но не обязательно в таком порядке - способ кодирования с предварительным приращением будет:
int i = -1;
do {
++i;
b[i] = a[i];
} while (a[i]);
Это вопрос вкуса, который яснее, и если у машины имеется несколько регистров, оба должны иметь одинаковое время выполнения, даже если [i] является дорогой функцией или имеет побочные эффекты. Существенной разницей может быть выходное значение индекса.