часто используемые редко используемые термины: lvalue - PullRequest
19 голосов
/ 24 февраля 2009

Что такое lvalue?

Ответы [ 8 ]

27 голосов
/ 24 февраля 2009

lvalue - это значение, которое может быть присвоено:

lvalue = rvalue;

Это сокращение от «левое значение» или «левое значение», и в основном это просто значение влево знака =, то есть значение, которое вы назначаете чему-либо.

В качестве примера того, что не lvalue (т.е. только rvalue):

printf("Hello, world!\n") = 100; // WTF?

Этот код не работает, потому что printf() (функция, которая возвращает int) не может быть lvalue, только rvalue.

12 голосов
/ 24 февраля 2009

Это традиционно левая часть оператора "=". Однако со временем значение «lvalue» / «rvalue» изменилось. В C ++ добавлен термин «немодифицируемое значение lvalue», которое представляет собой любое значение lvalue, которое не может быть присвоено: массивы и переменные, которые квалифицированы как «const», являются двумя примерами. В C вы не можете присвоить никакое значение (см. Ниже). Аналогично, в C ++ вы не можете присваивать значения, которые не принадлежат к определенному пользователем типу класса.

Можно сказать, что «lvalue» - это выражение, которое именует объект, который сохраняется со временем и занимает некоторое место в хранилище. Независимо от того, можете ли вы присвоить это выражение, не важно для этой классификации. Ссылка, в частности, также является lvalue, потому что она имеет имя, которое сохраняется с течением времени. Все последующие являются lvalues, потому что все они ссылаются на именованные объекты. Также обратите внимание, что const не влияет на lvalue-ness.

int a; lvalue: a;
       lvalue: ++a;
int a[2]; lvalue: a;
int &ra = a; lvalue: ra;
int *pa = &a; lvalue: *pa;

Термин «rvalue» используется для таких вещей, как литералы и значения перечислителей, а также для временных, которые не получают удовольствия от долгой жизни и сразу же уничтожаются в конце полного выражения. Для значений важен не аспект настойчивости, а аспект ценности. Функции в C ++ являются lvalues, потому что они являются постоянными и имеют адрес, даже если они не являются объектами. Я не учел их в приведенном выше обзоре lvalues, потому что легче понять lvalues, когда сначала только учитываем объекты. Все перечисленные ниже значения:

enum { FOO, BAR }; rvalue: BAR;
int a[2]; rvalue: (a+1);
rvalue: 42;
int a; rvalue: a++; // refering to a temporary
struct mystruct { }; mystruct f() { return mystruct(); } rvalue: f();

Между прочим, часто у вас есть lvalue, но оператору нужно rvalue. Например, двоичный встроенный оператор «+» добавляет два значения. Выражение lvalue first и for all указывает место, где значение должно быть сначала считано. Поэтому, когда вы добавляете две переменные, происходит преобразование «lvalue в rvalue». Стандарт говорит, что значение, содержащееся в выражении lvalue, является его результатом rvalue:

int a = 0, b = 1;
int c = a + b; // read values out of the lvalues of a and b. 

Другие операторы принимают не значения, а значения. Они не читают значение. Примером является адрес оператора &. Вы не можете взять адрес rvalue выражений. Некоторые значения даже не являются объектами: они не занимают места в хранилище. Примерами снова являются литералы (10, 3.3, ...) и значения перечислителя.

Чем полезны эти страшные вещи?

Ну, у этого есть несколько преимуществ, чтобы иметь различие между lvalue и rvalue

  • Разрешение компилятору исключать использование памяти для значений r и использование регистров / только для чтения для скалярных значений
  • Пометка выражений как неуловимых: rvalues ​​не проживет долго
    • Разрешает эффективную семантику копирования для компилятора внутри и в c ++ 1x, также предоставляемую программисту (см. Семантику перемещения и ссылки на rvalue): мы можем украсть ресурсы из значений r, которые в любом случае будут уничтожены.
  • Позволяет строить правила на этом свойстве
    • Не допускается создание значений r из еще неинициализированных объектов, на которые ссылается значение l. Но lvalues ​​может относиться к неинициализированным объектам просто отлично
    • значения не могут быть полиморфными. Их статический тип также должен быть их динамическим типом: упрощает правила для оператора typeid.

... Это нечто большее, я чувствую это ...

12 голосов
/ 24 февраля 2009

Что-то, что появляется в левой части назначения, то есть то, что может быть назначено.

Обратите внимание, что в C ++ вызов функции может быть lvalue, если:

int & func() {
   static int a = 0;
   return a;
}

, то:

func() = 42;     // is legal (and not uncommon)
8 голосов
/ 24 февраля 2009

Одно из лучших из известных мне объяснений можно найти в этой статье о ссылках RValue .

Другой способ определить, является ли выражение lvalue, - это спросить «могу ли я взять его адрес?». Если вы можете, это lvalue. Если вы не можете, это ценность. Например, & obj, & * ptr, & ptr [index] и & ++ x все допустимы (хотя некоторые из этих выражений глупы), а & 1729, & (x + y), & std :: string ("meow ") и & x ++ недопустимы. Почему это работает? Оператор address-of требует, чтобы его «операнд был lvalue» (C ++ 03 5.3.1 / 2). Почему это требует этого? Взять адрес постоянного объекта - это хорошо, но взять временный адрес было бы крайне опасно, потому что временные испарения быстро испаряются.

2 голосов
/ 24 февраля 2009

«L» в lvalue обычно обозначается как «местоположение». Lvalue указывает местоположение чего-либо; как указал другой ответчик, lvalues ​​обычно может получить свой адрес. Вот почему числовые литералы и возвращаемые не ссылочные функции не являются lvalues.

L использовался для обозначения «left», пока const не был введен в C ++. Постоянные значения не могут отображаться в левой части назначения.

1 голос
/ 16 октября 2018

Для языка программирования C C11 draft n1570 6.3.2.1p1 выражается кратко:

lvalue - это выражение (с типом объекта, отличным от void), которое потенциально обозначает объект [...]

Это так просто.

Википедия определение в день написания этого ответа было почти так же хорошо

[...] значение, указывающее на местоположение хранилища, потенциально позволяющее назначать новые значения, или значение, обеспечивающее доступный адрес памяти переменной, в которую можно записывать данные, оно называется значением местоположения.


Примеры выражений lvalue? Учитывая

int a, b[1], *c;

мы можем иметь следующие выражения lvalue: a, b и c, каждое из которых определенно обозначает объект. b[0] также обозначает объект, следовательно, это выражение lvalue. А как насчет потенции тогда? *c всегда является выражением lvalue, но оно не обозначает объект, если c не содержит указатель на действительный объект. b[1] может семантически определять объект, но это не так, потому что он обращается к массиву за пределами. Оба они являются lvalue выражениями.

Когда вычисляется выражение lvalue, которое фактически не обозначает объект, поведение будет undefined .

Выражение lvalue может быть более сложным, например:

((flag ? foo() : bar())->pointed_to_by_struct_member[42])[0] 

если он компилируется, это выражение lvalue.

В основном все, к чему вы можете применить & (оператор адреса) в C, это lvalue , за исключением функций , поскольку функции являются не объекты в кл.

Так что же тогда означает L ? 1058 *

C11 n1570 Сноска 64 :

64) Имя lvalue происходит от выражения присваивания E1 = E2, в котором левый операнд E1 должен быть (модифицируемым) lvalue. Возможно, это лучше рассматривать как представление объекта значение локатора . [...]


Обратите внимание, что E1 = E2 - это , а не , необходимое для компиляции, чтобы E1 было lvalue в ISO C. 6.3.2.1p1 продолжается:

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

Раньше в языках до C (например, B) все значения локатора могли появляться в левой части присваивания, отсюда и название. Это больше не относится к Си, так как массивы никогда не могли быть назначены, и ISO C добавил const.


P.S. В той же сноске объясняется связанный термин rvalue , например:

[...] То, что иногда называют rvalue , в этом международном стандарте описывается как значение выражения . [...]

0 голосов
/ 24 февраля 2009

Простой пример того, что определенно не является lvalue:

3 = 42;
0 голосов
/ 24 февраля 2009

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

Объект - это область хранения, которая может быть исследовано и сохранено в. lvalue это выражение, которое относится к такой объект. Lvalue не обязательно разрешить модификацию объект это обозначает. Например, константный объект является lvalue, который не может быть изменены. Термин модифицируемый lvalue используется, чтобы подчеркнуть, что lvalue позволяет назначенному объекту быть изменены, а также проверены. следующие типы объектов являются lvalues, но не изменяемые lvalues:

  • Тип массива
  • Неполный тип
  • Константный тип
  • Объект является структурой или типом объединения, и один из его членов имеет константный тип

Потому что эти значения не изменяемые, они не могут появляться на левая сторона оператора присваивания.

В C ++ вызов функции, который возвращает ссылка lvalue. В противном случае, Вызов функции является выражением rvalue. В C ++ каждое выражение производит lvalue, rvalue или нет значения.

Некоторые операторы требуют lvalues ​​для некоторые из их операндов. Стол ниже перечислены эти операторы и дополнительные ограничения на их использование.

     Operator                          Requirement
     & (unary)                         Operand must be an lvalue.
     ++ --                             Operand must be an lvalue.
                                          This applies to both prefix
                                            and postfix forms.
     = += -= *= %= >= &= ^= |=         Left operand must be an lvalue.

Например, все операторы присваивания оценить их правильный операнд и присвоить это значение слева операнд. Левый операнд должен быть изменяемое значение или ссылка на модифицируемый объект.

Оператору адреса (&) требуется lvalue как операнд, в то время как увеличение (++) и уменьшение (-) операторы требуют изменяемого lvalue как операнд.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...