Часто используемые редко используемые термины: lvalue
Что такое lvalue?
8 ответов
Lvalue - это значение, которое может быть присвоено:
lvalue = rvalue;
Это сокращение от "левое значение" или "левое значение", и это в основном просто значение слева от =
знак, то есть значение, которое вы назначаете что-то.
В качестве примера того, что не является lvalue (то есть только rvalue):
printf("Hello, world!\n") = 100; // WTF?
Этот код не работает, потому что printf()
(функция, которая возвращает int
) не может быть lvalue, только rvalue.
Что-то, что появляется слева от назначения, то есть то, что может быть назначено.
Обратите внимание, что в C++ вызов функции может быть lvalue, если:
int & func() {
static int a = 0;
return a;
}
затем:
func() = 42; // is legal (and not uncommon)
Это традиционно левая часть оператора "=". Однако со временем значение "lvalue"/"rvalue" изменилось. В C++ добавлен термин "немодифицируемое значение lvalue", то есть любое значение lvalue, которое не может быть присвоено: массивы и переменные, для которых задано значение "const", являются двумя примерами. В C вы не можете присвоить никакое значение (см. Ниже). Аналогично, в C++ вы не можете назначать значения, которые не принадлежат к определенному пользователем типу класса.
Вы можете сказать, что "lvalue" - это выражение, которое называет объект, который сохраняется со временем и занимает некоторое место в хранилище. Независимо от того, можете ли вы присвоить это выражение, не важно для этой классификации. Ссылка, в частности, также является lvalue, потому что она имеет имя, которое сохраняется с течением времени. Все последующие являются lvalues, потому что все они ссылаются на именованные объекты. Также обратите внимание, что const
не влияет на lvalue-ность.
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.
... это еще не все, я чувствую это...
Одно из лучших объяснений, которые я знаю, можно найти в этой статье о ссылках RValue.
Другой способ определить, является ли выражение lvalue, - спросить "могу ли я взять его адрес?". Если вы можете, это lvalue. Если вы не можете, это ценность. Например, &obj, &*ptr, &ptr[index] и & ++ x допустимы (хотя некоторые из этих выражений глупы), а &1729, &(x + y), &std::string("meow") и & x ++ недопустимы. Почему это работает? Оператор address-of требует, чтобы его "операнд был lvalue" (C++03 5.3.1/2). Почему это требует этого? Взять адрес постоянного объекта - это хорошо, но взять временный адрес было бы чрезвычайно опасно, потому что временные испарения быстро испаряются.
Буква "L" в lvalue обычно обозначается как "местоположение". Lvalue указывает местоположение чего-либо; как указал другой ответчик, lvalues обычно может получить свой адрес. Вот почему числовые литералы и возвращаемые не ссылочные функции значения не являются lvalues.
L использовался для обозначения "left", пока const не был введен в C++. Постоянные значения не могут отображаться в левой части назначения.
Для языка программирования 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, которое фактически не обозначает объект, поведение не определено.
Выражение lvalue может быть более сложным, например:
((flag ? foo() : bar())->pointed_to_by_struct_member[42])[0]
если он компилируется, это выражение lvalue.
В основном все, что вы можете применить &
(адрес оператора) to в C является l-значением, кроме функций, поскольку функции не являются объектами в C.
Так что же тогда означает L?
64) Имя lvalue происходит от выражения присваивания
E1 = E2
, в котором левый операндE1
должен быть (изменяемым) lvalue. Возможно, это лучше рассматривать как представление значения локатора объекта. [...]
Заметить, что E1 = E2
не требуется компилировать для E1
быть lvalue в ISO C. 6.3.2.1p1 продолжает:
Модифицируемое lvalue - это lvalue, которое не имеет типа массива, не имеет неполного типа, не имеет
const
-квалифицированный тип, и если это структура или объединение, не имеет какого-либо члена (включая, рекурсивно, любой член или элемент всех содержащихся агрегатов или объединений) сconst
квалифицированный тип.
Раньше в языках до C (например, B) все значения локатора могли появляться в левой части присваивания, отсюда и название. Это больше не относится к Си, так как массивы никогда не могли быть назначены, а ISO C добавлен const
,
PS Та же самая сноска объясняет связанный термин rvalue следующим образом:
[...] То, что иногда называют rvalue, в этом международном стандарте описывается как значение выражения. [...]
Простой пример того, что определенно не является lvalue:
3 = 42;
Из этой статьи. Поскольку ОП немного ленив, задавая свой вопрос (хотя некоторые люди с этим не согласны, см. Комментарии), я тоже буду ленивым и просто вставлю сюда всю релевантную часть, возможно, нарушив некоторые законы об авторском праве.
Объект - это область хранения, которая может быть исследована и сохранена в. Lvalue - это выражение, которое относится к такому объекту. Значение l не обязательно допускает изменение объекта, который оно обозначает. Например, const-объект - это lvalue, которое нельзя изменить. Термин "модифицируемое значение lvalue" используется для того, чтобы подчеркнуть, что значение lvalue позволяет изменять обозначенный объект, а также проверять его. Следующие типы объектов являются lvalues, но не могут быть изменены lvalues:
- Тип массива
- Неполный тип
- Конст-квалифицированный тип
- Объект является структурой или типом объединения, и один из его членов имеет тип с константой
Поскольку эти l-значения не могут быть изменены, они не могут появляться с левой стороны оператора присваивания.
В C++ вызов функции, которая возвращает ссылку, является lvalue. В противном случае вызов функции является выражением rvalue. В C++ каждое выражение создает lvalue, rvalue или значение без значения.
Некоторые операторы требуют lvalues для некоторых из своих операндов. В таблице ниже перечислены эти операторы и дополнительные ограничения на их использование.
Требование оператора и (унарный) операнд должны быть lvalue. ++ - операнд должен быть lvalue. Это относится как к префиксным, так и к постфиксным формам. = += -= *= %= >= &= ^= |= Левый операнд должен быть l-значением.
Например, все операторы присваивания оценивают свой правый операнд и присваивают это значение своему левому операнду. Левый операнд должен быть модифицируемым lvalue или ссылкой на модифицируемый объект.
Оператор адреса (&) требует lvalue в качестве операнда, в то время как операторы увеличения (++) и декремента (-) требуют изменяемого lvalue в качестве операнда.