MyTetra Share
Делитесь знаниями!
Понимание преинкремента и постинкремента в языке C++
Время создания: 23.11.2017 12:46
Автор: Xintrea
Текстовые метки: язык, Си++, c++, преинкремент, постинкремент, инкремент, декремент, объяснение, понимание, префиксная, постфиксная, форма, increment, decrement
Раздел: Компьютер - Программирование - Язык C++ (Си++)
Запись: xintrea/mytetra_syncro/master/base/15114303638wejy7emxh/text.html на raw.github.com

Здесь приводится переписка на форуме Prog.Org.Ru в теме "Странные операторы в C++". Обсуждение начинается с разбора оператора инкремента "++", записанного, казалось бы, без операнда. А заканчивается очень хорошим пояснением, как вообще работает пост-инкремент/пост-декремент.


----- 8< -----


Я решил немного вернуться к плюсам, и наверстать те вещи, которые не осилил ранее.


Вот, например, есть такой текст на буржуйском:


http://en.cppreference.com/w/cpp/language/operator_incdec#Built-in_prefix_operators


В нем неожиданно появляется загадочная конструкция:


int n3 = ++ ++n1;


Вопрос: что означает загадочный оператор "++", который не применяется ни к одной переменной? Догадаться сам, что это значит, я не могу.


----- 8< -----


... ботва пропущена ...


----- 8< -----


В общем, мня сбил с толку этот унарный оператор "++" без самого операнда. Просто никто не сказал, что на самом деле операнд у него есть. Стали говорить про равносильные вызовы функций, про приоритет операций, про префикс lvalue и постфикс rvalue. Но вопрос-то был не про это.


Значит, есть конструкция:


int i=1;

int a = ++ ++i;

Result a: 3


Здесь мы видим два инкремента, назовем их слева-направо первый и второй.


Первый, как будто, не имеет операнда и даже непонятно, каким инкрементом он является - префиксным или постфиксным. Второй инкремент имеет операнд i и является префиксным.


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


int a = ++++i;


И такая запись даже работает в GCC (как в других компилерах - не знаю). Теперь становится очевидным, что первый оператор инкремента - он префиксный, а конструкция "++i" - это операнд для первого инкремента. И тогда, учитывая что сказал Авварон про приоритет операций (http://ru.cppreference.com/w/cpp/language/operator_precedence), и учитывая что сказал Kambala про равносильный вызов функций, мы из этой мозаики понимаем, что по-сути запись должна быть такая:


int a = ++(++i);


И "разворачивается" эта конструкция изнутри: сначала инкремент в скобках, потом инкремент результата, полученного в скобках.


Казалось бы, разобрались. Но нет. Возьмем теперь другой пример:


int a = ++i++;


Вроде все просто: у нас есть таблица приоритета операций, и в ней постинкремент имеет больший приоритет чем преинкремент. И поэтому компилер мог бы эту конструкцию прожевать как:


int a = ++(i++);


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


error: lvalue required as increment operand


Вот теперь с преинкрементами/постинкрементами стало чуть более понятно.


Ремарка: фраза "изменение значения после использования", которую я вижу везде, не отвечает на главный вопрос: в какой же момент происходит изменение значения? После использования значения, хранимого в операнде, или после вычисления всего выражения? Внятного ответа я не нашел.


----- 8< -----


Та нету там никаких флагов :) Грубо говоря мы имеем 2 сигнатуры:


int &operator(int &val); // prefix

int operator(int &val, int); // postfix


Префиксный сначала увеличивает значение, а потом возвращает ссылку на значение.


Постфиксный же увеличивает значение, а возвращает прежнее состояние переменной. Если бы он возвращал ссылку, то по ней было бы доступно увеличенное значение.


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


----- 8< -----


Вот, это очень хорошее объяснение. Из него становится понятно, что на самом деле происходит, когда говорят "изменение значения после использования".


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


Происходит следующее:


  • Запоминается значение переменной в регистр или в стек (все зависит от реализации). То есть, по сути, делается копия значения.
  • Значение переменной изменяется. То есть, если не смотреть на предыдущий шаг с запоминанием, можно сказать, что значение переменной изменяется сразу, без всяких там "изменений значения после использования".
  • В качестве результата операции постинкремента возвращается не ссылка на значение переменной (что имело бы тип lvalue, и значение было бы уже увеличенное на единицу), а запомненное на первом шаге число (оно возвращается в виде сущности "некое значение" а не "значение переменной", и поэтому имеет тип rvalue).


И из-за того, что результат постинкремента представляет собой "просто число", которое возвращается в результате этой операции, то для такого числа невозможно изменение, потому что оно расценивается, по сути, как константа. Причем слово "невозможно" тут следует понимать так: конечно, хранимое в регистре или на стеке число возможно изменить. Просто компилятор будет генерить такой код, что результат операции постинкремента будет представлять из себя rvalue, вследствие чего этот результат будет "неизменяемым". И поэтому конструкция a=++(i++) работать не будет.


Объяснение это тоже корявое, но вроде правильное, если не будет возражений у наших гуру.


Чтобы увидеть, как пишется реализация преинкремента и постинкремента, рекомендуется прочитать следующую статью: Перегрузка префиксного и постфиксного оператора в C++.



Так же в этом разделе:
 
MyTetra Share v.0.59
Яндекс индекс цитирования