Russian Qt Forum

Программирование => С/C++ => Тема начата: xintrea от Ноябрь 19, 2017, 19:15



Название: Странные операторы в C++
Отправлено: xintrea от Ноябрь 19, 2017, 19:15
Я решил немного вернуться к плюсам, и наверстать те вещи, которые не осилил ранее.

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

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

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

Код:
int n3 = ++ ++n1;

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


Название: Re: Странные операторы в C++
Отправлено: gil9red от Ноябрь 19, 2017, 21:21
Код:
#include <iostream>
 
int main()
{
    int n1 = 1;
    int n2 = ++n1;
    int n3 = ++ ++ ++ ++n1;
    int n4 = n1++;
//  int n5 = n1++ ++;   // error
//  int n6 = n1 + ++n1; // undefined behavior
    std::cout << "n1 = " << n1 << '\n'
              << "n2 = " << n2 << '\n'
              << "n3 = " << n3 << '\n'
              << "n4 = " << n4 << '\n';
}

Output:

Цитировать
n1 = 7
n2 = 2
n3 = 6
n4 = 6
;D


Название: Re: Странные операторы в C++
Отправлено: kambala от Ноябрь 19, 2017, 22:24
Вопрос: что означает загадочный оператор "++", который не применяется ни к одной переменной? Догадаться сам, что это значит, я не могу.
++(++(n1))


Название: Re: Странные операторы в C++
Отправлено: __Heaven__ от Ноябрь 20, 2017, 07:30
++++++++++++n1


Название: Re: Странные операторы в C++
Отправлено: xintrea от Ноябрь 20, 2017, 17:47
++(++(n1))

Где-то еще используется такое синтаксическое правило? Или только в операторах префиксного инкремента/декремента?


Название: Re: Странные операторы в C++
Отправлено: __Heaven__ от Ноябрь 20, 2017, 19:57
xintrea, префикс возвращает обычно lvalue, а постфикс rvalue.


Название: Re: Странные операторы в C++
Отправлено: kambala от Ноябрь 22, 2017, 00:41
++(++(n1))

Где-то еще используется такое синтаксическое правило? Или только в операторах префиксного инкремента/декремента?

я показал, что это равносильно вызову функции: f(f(n1)). Вычисление изнутри наружу происходит для любых функций.


Название: Re: Странные операторы в C++
Отправлено: xintrea от Ноябрь 22, 2017, 14:04
Где-то еще используется такое синтаксическое правило? Или только в операторах префиксного инкремента/декремента?

я показал, что это равносильно вызову функции: f(f(n1)). Вычисление изнутри наружу происходит для любых функций.

То есть, это такой синтаксис, напоминающий функциональные языки типа Лиспа, где вызов функции идет без скобок, и пишут так:

Код:
func x y

Учитывая, что оператор - это, по сути, вызов функции одного или двух аргумеyтов (в данном случае - одного), то для унарного оператора ++ могут писать вот такие вызовы через пробел? Так что ли? Что-то это капец как сложно.


Название: Re: Странные операторы в C++
Отправлено: Авварон от Ноябрь 22, 2017, 17:24
http://ru.cppreference.com/w/cpp/language/operator_precedence
Цитировать
Постинкремент и постдекремент - Слева направо
Прединкремент и преддекремент  - Справа налево


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

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

Код
C++ (Qt)
int i=1;
int a = ++ ++i;
 
Код:
Result a: 3

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

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

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

Код
C++ (Qt)
int a = ++++i;

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

Код
C++ (Qt)
int a = ++(++i);

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


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

Код
C++ (Qt)
int a = ++i++;

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

Код
C++ (Qt)
int a = ++(i++);

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

Код:
error: lvalue required as increment operand

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


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


Название: Re: Странные операторы в C++
Отправлено: __Heaven__ от Ноябрь 23, 2017, 09:28
Та нету там никаких флагов :)
Грубо говоря мы имеем 2 сигнатуры.
Код
C++ (Qt)
int &operator(int &val); // prefix
int operator(int &val, int); // postfix

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

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


Название: Re: Странные операторы в C++
Отправлено: xintrea от Ноябрь 23, 2017, 12:36
Рисуется картина, что в результате префиксный оператор нам вернул ссылку на переменную, которую можно продолжать модифицировать, а постфиксный - значение, которое (могу ошибаться) адреса не имеет и хранится на регистре и по сути является константным, да и смысла нет редактировать результат, так как эта модификация не сохранится в памяти.

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

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

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

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

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

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


Название: Re: Странные операторы в C++
Отправлено: Igors от Ноябрь 23, 2017, 12:59
На гуру не претендую, но наверное не стоит так заморачиваться докапываясь "до самой сути" :) Можно смотреть на вещи проще: обе операции изменяют операнд одинаково, поэтому напр в for все равно "пре" или "пост". Но в одном случае возвращается новое значение, в другом старое - вот и все. Акцентировать внимание что префикс возвращает ссылку не стоит, для перекрытых операторов это может быть не так (см напр QAtomicInteger).

А конструкции типа таких
Код
C++ (Qt)
int a = ++(i++);
Однозначно должны считаться говнокодом независимо от того насколько они формально корректны - нет здесь никакой "глубины", а лишь желание "потренироваться" (для себя)


Название: Re: Странные операторы в C++
Отправлено: ViTech от Ноябрь 23, 2017, 13:30
Получается, что на самом деле никакого "изменения значения после использования" нет! Этот бред, записанный в половине учебников по C++, надо искоренять каленым железом.

Фраза может неудачная, двояко понять можно. Думается мне, для конструкции i++, имелось в виду: изменение значения i (инкремент) после использования i (получения текущего значения), а не: изменение значения полученного/вычисленного после использования(инкремента) i.

И да, в программном коде тоже лучше выражаться на нормальном, на гражданском языке :). Чтобы читатели не ломали голову над тем, что же хотел сказать автор таким кодом: b = ++++++a.