Russian Qt Forum
Ноябрь 17, 2019, 11:45 *
Добро пожаловать, Гость. Пожалуйста, войдите или зарегистрируйтесь.
Вам не пришло письмо с кодом активации?

Войти
 
  Начало   Форум  WIKI (Вики)FAQ Помощь Поиск Войти Регистрация  

Страниц: [1]   Вниз
  Печать  
Автор Тема: Странные операторы в C++  (Прочитано 2978 раз)
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 740



Просмотр профиля WWW
« : Ноябрь 19, 2017, 19:15 »

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

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

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

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

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

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

Собираю информацию по крупицам
http://webhamster.ru
gil9red
Administrator
Джедай : наставник для всех
*****
Offline Offline

Сообщений: 1823



Просмотр профиля WWW
« Ответ #1 : Ноябрь 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
Смеющийся
Записан

kambala
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4094



Просмотр профиля WWW
« Ответ #2 : Ноябрь 19, 2017, 22:24 »

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

Изучением C++ вымощена дорога в Qt.

UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created any 8-bit character content since 1996 in anything other than UTF-8, then I hate you. © Matt Gallagher
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2070



Просмотр профиля
« Ответ #3 : Ноябрь 20, 2017, 07:30 »

++++++++++++n1
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 740



Просмотр профиля WWW
« Ответ #4 : Ноябрь 20, 2017, 17:47 »

++(++(n1))

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

Собираю информацию по крупицам
http://webhamster.ru
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2070



Просмотр профиля
« Ответ #5 : Ноябрь 20, 2017, 19:57 »

xintrea, префикс возвращает обычно lvalue, а постфикс rvalue.
Записан
kambala
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 4094



Просмотр профиля WWW
« Ответ #6 : Ноябрь 22, 2017, 00:41 »

++(++(n1))

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

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

Изучением C++ вымощена дорога в Qt.

UTF-8 has been around since 1993 and Unicode 2.0 since 1996; if you have created any 8-bit character content since 1996 in anything other than UTF-8, then I hate you. © Matt Gallagher
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 740



Просмотр профиля WWW
« Ответ #7 : Ноябрь 22, 2017, 14:04 »

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

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

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

Код:
func x y

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

Собираю информацию по крупицам
http://webhamster.ru
Авварон
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2887


Просмотр профиля
« Ответ #8 : Ноябрь 22, 2017, 17:24 »

http://ru.cppreference.com/w/cpp/language/operator_precedence
Цитировать
Постинкремент и постдекремент - Слева направо
Прединкремент и преддекремент  - Справа налево
Записан
xintrea
Супер активный житель
*****
Offline Offline

Сообщений: 740



Просмотр профиля WWW
« Ответ #9 : Ноябрь 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

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


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

Собираю информацию по крупицам
http://webhamster.ru
__Heaven__
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 2070



Просмотр профиля
« Ответ #10 : Ноябрь 23, 2017, 09:28 »

Та нету там никаких флагов Улыбающийся
Грубо говоря мы имеем 2 сигнатуры.
Код
C++ (Qt)
int &operator(int &val); // prefix
int operator(int &val, int); // postfix

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

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

Сообщений: 740



Просмотр профиля WWW
« Ответ #11 : Ноябрь 23, 2017, 12:36 »

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

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

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

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

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

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

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

Собираю информацию по крупицам
http://webhamster.ru
Igors
Джедай : наставник для всех
*******
Offline Offline

Сообщений: 10600


Просмотр профиля
« Ответ #12 : Ноябрь 23, 2017, 12:59 »

На гуру не претендую, но наверное не стоит так заморачиваться докапываясь "до самой сути" Улыбающийся Можно смотреть на вещи проще: обе операции изменяют операнд одинаково, поэтому напр в for все равно "пре" или "пост". Но в одном случае возвращается новое значение, в другом старое - вот и все. Акцентировать внимание что префикс возвращает ссылку не стоит, для перекрытых операторов это может быть не так (см напр QAtomicInteger).

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

Сообщений: 726



Просмотр профиля
« Ответ #13 : Ноябрь 23, 2017, 13:30 »

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

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

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

Пока сам не сделаешь...
Страниц: [1]   Вверх
  Печать  
 
Перейти в:  


Страница сгенерирована за 0.173 секунд. Запросов: 22.