Почему ++ я - значение l? и я ++ not
Изначально было два вопроса, один из которых был удален, поскольку это был точный дубликат. Поэтому не голосуйте за ответы, ответившие на разницу между пре-и пост-приращением.
Почему ++ я - значение l? и я ++ not
Изначально было два вопроса, один из которых был удален, поскольку это был точный дубликат. Поэтому не голосуйте за ответы, ответившие на разницу между пре-и пост-приращением.
Ну, как еще один ответчик указал, что причиной того, что ++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)
Другие люди занимаются функциональной разницей между post и pre increment.
Что касается lvalue, i++
не может быть назначен, потому что он не относится к переменной. Он относится к вычисленному значению.
В терминах присвоения оба из них не имеют никакого смысла одним и тем же способом:
i++ = 5;
i + 0 = 5;
Поскольку pre-increment возвращает ссылку на добавленную переменную, а не временную копию, ++i
является lvalue.
Предпочтительным предварительным приращением по соображениям производительности становится особенно хорошая идея, когда вы увеличиваете что-то вроде объекта итератора (например, в STL), который может быть хорошим бит более тяжелым, чем int.
Кажется, что многие люди объясняют, как ++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
изменяется дважды между последовательностью точек.
Я получаю ошибку 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;
разные. Какой оператор инкремента, который вы выбираете для использования в составе операторов нескольких операторов, зависит от того, что такое предполагаемое поведение. Короче говоря, нет, вы не можете просто выбрать один. Вы должны понимать оба.
Предварительное приращение должно действовать так, как если бы объект был увеличен до выражения и может использоваться в этом выражении, как если бы это произошло. Таким образом, компилятор стандартов С++ решил, что он также может использоваться как l-значение.
Пост-инкремент должен увеличивать объект POD и возвращать копию для использования в выражении (см. n2521, раздел 5.2.6). Поскольку копия на самом деле не является переменной, поэтому значение l не имеет никакого смысла.
Инкремент Pre и Post для объектов - это просто синтаксический сахар языка, который предоставляет средство вызова методов на объекте. Таким образом, технически объекты не ограничены стандартным поведением языка, а только ограничениями, налагаемыми вызовами метода.
Именно разработчик этих методов делает поведение этих объектов зеркальным отображением поведения объектов POD (это не требуется, но ожидается).
Требование (ожидаемое поведение) здесь заключается в том, что объекты увеличиваются (что зависит от объекта), и метод возвращает значение, которое может быть изменено и выглядит как исходный объект после того, как произошло приращение (как если бы приращение произошло до этого утверждение).
Чтобы сделать это, нужно только одно, и только требуется, чтобы метод возвращал ссылку на него. Ссылка является l-значением и, следовательно, будет вести себя так, как ожидалось.
Требование (ожидаемое поведение) здесь заключается в том, что объект увеличивается (так же, как и предварительный приращение), и возвращаемое значение выглядит как старое значение и не изменяет (так что оно не ведет себя как 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;
}
};
Что касается 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;
(изменить: обновить после первой попытки).
Основное отличие состоит в том, что я ++ возвращает значение pre-increment, тогда как ++ я возвращает значение после инкремента. Я обычно использую ++ i, если у меня нет очень веских оснований для использования я ++ - а именно, если мне действительно нужно значение pre-increment.
ИМХО - это хорошая практика использования формы "++ i". Хотя разница между пре-и пост-приращением не очень измерима, когда вы сравниваете целые числа или другие POD, дополнительная копия объекта, которую вы должны сделать и возвращаете при использовании "i ++", может представлять значительное влияние на производительность, если объект является либо довольно дорогим часто копировать или увеличивать.
Кстати, избегайте использования нескольких операторов инкремента по одной и той же переменной в том же самом выражении. Вы попадаете в беспорядок "где находятся точки последовательности" и undefined порядок операций, по крайней мере, на C. Я думаю, что некоторые из них были очищены в Java nd С#.
Возможно, это связано с тем, как выполняется пост-инкремент. Возможно, это что-то вроде этого:
Поскольку копия не является ни переменной, ни ссылкой на динамически выделенную память, она не может быть значением l.
Пример:
var i = 1;
var j = i++;
// i = 2, j = 1
и
var i = 1;
var j = ++i;
// i = 2, j = 2
С#:
public void test(int n)
{
Console.WriteLine(n++);
Console.WriteLine(++n);
}
/* Output:
n
n+2
*/