Поэтому я читал lvalue и rvalues, и я немного смущен различием между ++x
и x++
когда дело доходит до этой категоризации.
Почему ++x
lvalue и x++
значение r?
Поэтому я читал lvalue и rvalues, и я немного смущен различием между ++x
и x++
когда дело доходит до этой категоризации.
Почему ++x
lvalue и x++
значение r?
++x
возвращает ссылку на объект, который вы увеличиваете, где x++
возвращает временную копию старого значения x
.
По крайней мере, это было бы "нормальным" путем внедрения этих операторов по соглашению. И все встроенные типы работают таким образом. И если вы прочитали о lvalues /rvalues, вы увидите, что, поскольку префиксный оператор возвращает сам именованный объект, это будет lvalue, где, поскольку оператор postfix возвращает копию локального временного кода, который затем будет квалифицироваться как rvalue,
Примечание. Кроме того, мы имеем prvalues, xvalues и так далее, поэтому это технически немного сложнее в эти дни. Посмотрите здесь, чтобы узнать больше.
C++ (в отличие от C) - это посвященный язык, сохраняющий значение: он стремится к кропотливому сохранению "lvalueness" выражения всякий раз, когда это возможно.
Очень легко сохранить "lvalueness" pre-increment: просто увеличьте операнд и верните его как lvalue. Готово. Возвращенное значение lvalue будет содержать точно результат, который предполагается содержать: новое (увеличиваемое) значение операнда.
И в то же время практически невозможно сохранить "lvalueness" пост-приращения: по определению результатом пошагового увеличения является старое (исходное) значение операнда. Если вы попытаетесь вернуть значение lvalue из пост-инкремента, вам придется как-то одновременно обеспечить две вещи: 1) значение lvalue увеличивается, 2) код вызова видит старое значение, когда он смотрит на ту же самую lvalue (!). Эта комбинация требований настолько противоречива, что в принципе невозможно реализовать в объектной модели C++.
Чтобы реализовать правильное поведение после инкремента, нужно убедиться, что вызывающий код не смотрит прямо в операнд, а смотрит на какой-то концептуальный или физический "прокси", который заставляет код вызова "видеть" старое значение из операнда. Этот прокси-сервер может быть временным объектом, который содержит старое значение. Или этот прокси может быть чем-то, что генерирует старое значение "на лету", вычитая 1
из нового значения. В любом случае, этот прокси-сервер запрещает коду вызова получать доступ к исходному значению lvalue.
Вот почему C++ использует легко достижимую возможность сохранить "lvalueness" pre-increment, но уступает невозможности достичь того же с последующим приращением. В случае пост-приращения просто не стоит стараться отклоняться от классического стандартного поведения С, которое, как правило, отбрасывает "lvalueness" быстро и счастливо.
Возможно, запись работы в псевдокоде операторов для int
делает его более понятным:
префикс:
int& Prefix(int& val)
{
int& value = val;
value += 1;
return value;
}
постфикс:
int Postfix(int& val)
{
int oldValue = val;
val += 1;
return oldValue;
}
Разница заключается в том, что возвращается двумя операторами: по значению и по ссылке.
Почему ++x lvalue
Я предполагаю, что это lvalue, потому что это может быть.
++x
это можно представить как
((x = x + 1), x) // Increment x. Then evaluate to the value of x
или же
((x += 1), x) // Increment x. Then evaluate to the value of x
Имеет смысл оценить ++x
значение lvalue.
x++ значение r?
Потому что это не может быть lvalue.
x++
это можно представить себе как
((unnamed_variable = x), (x = x + 1), unnamed_variable)
или же
((unnamed_variable = x), (x += 1), unnamed_variable)
x++
оценивает значение до того, как x
увеличивается. Поскольку нет переменной, которая может сохранить это значение, она не может быть lvalue.
Для встроенных типов, таких как int
, x++
дает старое значение x
. С этим не связано хранилище, поэтому было бы невозможно, чтобы выражение было lvalue.
В C ++x
новое значение x
(и не было lvalue). Так как объект x
фактически содержит то же значение, можно ++x
объект x
(т.е. быть lvalue). Это добавленная функциональность по сравнению с получением rvalue, и разработчик C++ решил, что это будет улучшение языка.
Для типов классов можно перегрузить любой оператор таким образом, чтобы использование оператора могло давать lvalue, xvalue или prvalue. Однако считается, что хороший стиль делает перегруженные операторы похожими семантикой на встроенные операторы, поэтому для людей нормально перегружать ++x
чтобы получить значение lvalue и перегрузить x++
чтобы получить значение rvalue.
Прежде всего, "lvalue" - это выражение, легальное с левой стороны ("l" в "lvalue") задания. Это означает, что он представляет адрес, содержимое которого может быть изменено назначением. (Стандарт C вызывает такую вещь как "объект".) Вот почему, например, выражения операторного выражения и вызовы функций не являются значениями lvalues.
"rvalue" - глупый термин; любое правильно сформированное выражение является правовым с правой стороны задания (исключая проблемы с преобразованием типа).
В стандарте говорится, что, например:
i = ++i + 1;
a[i++] = i;
.. являются "неопределенными выражениями выражения", то есть их реализация (и, следовательно, поведение) не стандартизирована.
Значение здравого смысла "++i" означает "увеличивать значение объекта я и оценивать результат". Аналогично, "i++" означает "оценивать текущее значение объекта я и увеличивать значение объекта после". Ни то, что происходит, когда "++i" или "i++" используется как lvalue.
В стандарте говорится о выражениях: "Вычисления значений операндов оператора секвенированы перед вычислением значения результата оператора". Это действительно двусмысленно для "++i", так как вычисление значения операнда зависит от вычисления значения оператора.
Что для меня наиболее важно, так это:
i = 5;
++i = i + 1; /*i is set to 6*/
i++ = 1; /*i is set to 1*/
++i = ++i + 1; /*i is set to 8, but problematic*/
i++ = i++ + 1; /*i is set to 8, but problematic*/
++i = i++ + 1; /*i is set to 8, but problematic*/
Мой совет - не используйте выражения, которые так трудно понять!