Почему ++ считается 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
*/