Почему ++ считается l-значением, но я ++ - нет?
Почему ++ я - значение l? и я ++ not
Изначально было два вопроса, один из которых был удален, поскольку это был точный дубликат. Поэтому не голосуйте за ответы, ответившие на разницу между пре-и пост-приращением.
Ответы
Ответ 1
Ну, как еще один ответчик указал, что причиной того, что ++i
является lvalue, является передача его в ссылку.
int v = 0;
int const & rcv = ++v; // would work if ++v is an rvalue too
int & rv = ++v; // would not work if ++v is an rvalue
Причиной второго правила является разрешение инициализации ссылки с использованием литерала, когда ссылка является ссылкой на const:
void taking_refc(int const& v);
taking_refc(10); // valid, 10 is an rvalue though!
Почему мы вводим значение r вообще, о котором вы можете спросить. Ну, эти термины возникают при построении языковых правил для этих двух ситуаций:
- Мы хотим иметь значение локатора. Это будет представлять местоположение, которое содержит значение, которое можно прочитать.
- Мы хотим представить значение выражения.
Вышеуказанные две точки взяты из стандарта C99, который включает в себя эту замечательную сноску, весьма полезную:
[Первоначальное название "lvalue" из выражения присваивания E1 = E2, в котором левый операнд E1 равен должен быть (модифицируемым) значением l. Это, пожалуй, лучше рассматривать как представляющий объект "локатор" стоимость. То, что иногда называют 'Rvalue находится в этом Интернационале Стандарт описывается как "значение выражение. ]
Значение локатора называется lvalue, а значение, полученное при оценке этого местоположения, называется rvalue. Это правильно в соответствии с стандартом С++ (речь идет о преобразовании lvalue-to-rvalue):
4.1/2: значение, содержащееся в объекте обозначенный lvalue, является значением rvalue результат.
Заключение
Используя вышеприведенную семантику, теперь понятно, почему i++
не имеет значения lvalue, а rvalue. Поскольку возвращаемое выражение больше не находится в i
(оно увеличилось!), Это просто значение, которое может представлять интерес. Изменение этого значения, возвращаемое i++
, не будет иметь смысла, потому что у нас нет местоположения, из которого мы могли бы снова прочитать это значение. И поэтому стандарт говорит, что это значение rvalue, и поэтому он может привязываться только к ссылке-на-const.
Однако в constrast выражение, возвращаемое ++i
, - это местоположение (lvalue) i
. Провоцирование преобразования lvalue-to-rvalue, как в int a = ++i;
, будет считывать значение из него. В качестве альтернативы мы можем сделать для нее ориентир и зачитать значение позже: int &a = ++i;
.
Обратите внимание также на другие случаи, когда генерируются значения r. Например, все временные значения - это значения rvalues, результат двоичных/унарных + и минус и все выражения возвращаемого значения, которые не являются ссылками. Все эти выражения не находятся в именованном объекте, но содержат только значения. Конечно, эти значения могут быть скопированы объектами, которые не являются постоянными.
Следующая версия С++ будет содержать так называемый rvalue references
, который, даже если они указывают на nonconst, может связываться с rvalue. Обоснование заключается в том, чтобы иметь возможность "украсть" ресурсы из этих анонимных объектов и избегать копирования. Предполагая тип класса, который имеет перегруженный префикc++ (возвращающий Object&
) и postfix ++ (возвращающий Object
), следующее вызовет копию сначала, а во втором случае он украдет ресурсы из rvalue:
Object o1(++a); // lvalue => can't steal. It will deep copy.
Object o2(a++); // rvalue => steal resources (like just swapping pointers)
Ответ 2
Другие люди занимаются функциональной разницей между post и pre increment.
Что касается lvalue, i++
не может быть назначен, потому что он не относится к переменной. Он относится к вычисленному значению.
В терминах присвоения оба из них не имеют никакого смысла одним и тем же способом:
i++ = 5;
i + 0 = 5;
Поскольку pre-increment возвращает ссылку на добавленную переменную, а не временную копию, ++i
является lvalue.
Предпочтительным предварительным приращением по соображениям производительности становится особенно хорошая идея, когда вы увеличиваете что-то вроде объекта итератора (например, в STL), который может быть хорошим бит более тяжелым, чем int.
Ответ 3
Кажется, что многие люди объясняют, как ++i
является значением lvalue, но не why, как, например, почему комитет по стандартам С++ включил эту функцию, особенно в свете тот факт, что C не допускает ни lvalues. Из это обсуждение на comp.std.С++, похоже, что вы можете взять его адрес или назначить ссылку. Образец кода, взятый из сообщения Кристиана Бау:
int i;
extern void f (int* p);
extern void g (int& p);
f (&++i); /* Would be illegal C, but C programmers
havent missed this feature */
g (++i); /* C++ programmers would like this to be legal */
g (i++); /* Not legal C++, and it would be difficult to
give this meaningful semantics */
Кстати, если i
оказывается встроенным типом, то операторы присваивания, такие как ++i = 10
, вызывают поведение undefined, потому что i
изменяется дважды между последовательностью точек.
Ответ 4
Я получаю ошибку lvalue, когда пытаюсь скомпилировать
i++ = 2;
но не тогда, когда я меняю его на
++i = 2;
Это связано с тем, что префиксный оператор (++ i) меняет значение в i, а затем возвращает i, поэтому ему все равно можно назначить. Постфиксный оператор (i ++) меняет значение в i, но возвращает временную копию старого значения, которое не может быть изменено оператором присваивания.
Ответ на исходный вопрос:
Если вы говорите об использовании операторов инкремента в заявлении сами по себе, как в цикле for, это действительно не имеет значения. Preincrement, по-видимому, более эффективен, потому что postincrement должен увеличиваться и возвращать временное значение, но компилятор оптимизирует эту разницу.
for(int i=0; i<limit; i++)
...
совпадает с
for(int i=0; i<limit; ++i)
...
Все становится немного сложнее, когда вы используете возвращаемое значение операции как часть более крупного оператора.
Даже два простых утверждения
int i = 0;
int a = i++;
и
int i = 0;
int a = ++i;
разные. Какой оператор инкремента, который вы выбираете для использования в составе операторов нескольких операторов, зависит от того, что такое предполагаемое поведение. Короче говоря, нет, вы не можете просто выбрать один. Вы должны понимать оба.
Ответ 5
POD Pre increment:
Предварительное приращение должно действовать так, как если бы объект был увеличен до выражения и может использоваться в этом выражении, как если бы это произошло. Таким образом, компилятор стандартов С++ решил, что он также может использоваться как l-значение.
Приращение POD Post:
Пост-инкремент должен увеличивать объект POD и возвращать копию для использования в выражении (см. n2521, раздел 5.2.6). Поскольку копия на самом деле не является переменной, поэтому значение l не имеет никакого смысла.
Объекты:
Инкремент Pre и Post для объектов - это просто синтаксический сахар языка, который предоставляет средство вызова методов на объекте. Таким образом, технически объекты не ограничены стандартным поведением языка, а только ограничениями, налагаемыми вызовами метода.
Именно разработчик этих методов делает поведение этих объектов зеркальным отображением поведения объектов POD (это не требуется, но ожидается).
Объекты Pre-increment:
Требование (ожидаемое поведение) здесь заключается в том, что объекты увеличиваются (что зависит от объекта), и метод возвращает значение, которое может быть изменено и выглядит как исходный объект после того, как произошло приращение (как если бы приращение произошло до этого утверждение).
Чтобы сделать это, нужно только одно, и только требуется, чтобы метод возвращал ссылку на него. Ссылка является l-значением и, следовательно, будет вести себя так, как ожидалось.
Объекты Post-increment:
Требование (ожидаемое поведение) здесь заключается в том, что объект увеличивается (так же, как и предварительный приращение), и возвращаемое значение выглядит как старое значение и не изменяет (так что оно не ведет себя как l -значение).
Non-Mutable:
Для этого вы должны вернуть объект. Если объект используется в выражении, он будет скопирован во временную переменную. Временные переменные являются константами и, следовательно, будут не изменяемыми и будут вести себя так, как ожидалось.
Похоже на старое значение:
Это достигается просто путем создания копии оригинала (возможно, с использованием конструктора копирования), прежде чем вносить какие-либо изменения. Копия должна быть глубокой, иначе любые изменения в оригинале повлияют на копию, и, таким образом, состояние изменится в зависимости от выражения с использованием объекта.
Точно так же, как pre-increment:
Вероятно, лучше всего реализовать пост-прирост в терминах предварительного инкремента, чтобы вы получили одинаковое поведение.
class Node // Simple Example
{
/*
* Pre-Increment:
* To make the result non-mutable return an object
*/
Node operator++(int)
{
Node result(*this); // Make a copy
operator++(); // Define Post increment in terms of Pre-Increment
return result; // return the copy (which looks like the original)
}
/*
* Post-Increment:
* To make the result an l-value return a reference to this object
*/
Node& operator++()
{
/*
* Update the state appropriatetly */
return *this;
}
};
Ответ 6
Что касается LValue
-
В C
(и, например, Perl) ни ++i
, ни i++
не являются LValues.
-
В C++
, i++
нет и LValue, но ++i
.
++i
эквивалентно i += 1
, что эквивалентно i = i + 1
.
В результате мы все еще имеем дело с одним и тем же объектом i
.
Его можно рассматривать как:
int i = 0;
++i = 3;
// is understood as
i = i + 1; // i now equals 1
i = 3;
i++
, с другой стороны, можно рассматривать как:
Сначала мы используем значение i
, затем увеличиваем объект i
.
int i = 0;
i++ = 3;
// would be understood as
0 = 3 // Wrong!
i = i + 1;
(изменить: обновить после первой попытки).
Ответ 7
Основное отличие состоит в том, что я ++ возвращает значение pre-increment, тогда как ++ я возвращает значение после инкремента. Я обычно использую ++ i, если у меня нет очень веских оснований для использования я ++ - а именно, если мне действительно нужно значение pre-increment.
ИМХО - это хорошая практика использования формы "++ i". Хотя разница между пре-и пост-приращением не очень измерима, когда вы сравниваете целые числа или другие POD, дополнительная копия объекта, которую вы должны сделать и возвращаете при использовании "i ++", может представлять значительное влияние на производительность, если объект является либо довольно дорогим часто копировать или увеличивать.
Ответ 8
Кстати, избегайте использования нескольких операторов инкремента по одной и той же переменной в том же самом выражении. Вы попадаете в беспорядок "где находятся точки последовательности" и undefined порядок операций, по крайней мере, на C. Я думаю, что некоторые из них были очищены в Java nd С#.
Ответ 9
Возможно, это связано с тем, как выполняется пост-инкремент. Возможно, это что-то вроде этого:
- Создайте копию исходного значения в памяти
- Приращение исходной переменной
- Вернуть копию
Поскольку копия не является ни переменной, ни ссылкой на динамически выделенную память, она не может быть значением l.
Ответ 10
Пример:
var i = 1;
var j = i++;
// i = 2, j = 1
и
var i = 1;
var j = ++i;
// i = 2, j = 2
Ответ 11
С#:
public void test(int n)
{
Console.WriteLine(n++);
Console.WriteLine(++n);
}
/* Output:
n
n+2
*/