C ++

Таксономия категорий выражений в C ++

Таксономия категорий выражений в C ++

Вычисление - это любой тип вычислений, который следует четко определенному алгоритму. Выражение - это последовательность операторов и операндов, определяющая вычисление. Другими словами, выражение - это идентификатор, литерал или их последовательность, соединенные операторами.В программировании выражение может приводить к значению и / или вызывать некоторые события. Когда оно приводит к значению, выражение представляет собой glvalue, rvalue, lvalue, xvalue или prvalue. Каждая из этих категорий представляет собой набор выражений. Каждый набор имеет определение и конкретные ситуации, в которых его значение преобладает, что отличает его от другого набора. Каждый набор называется ценностной категорией.

Примечание: Значение или литерал по-прежнему является выражением, поэтому эти термины классифицируют выражения, а не настоящие значения.

glvalue и rvalue - это два подмножества из выражения большого набора. glvalue существует еще в двух подмножествах: lvalue и xvalue. rvalue, другое подмножество выражения, также существует в двух дополнительных подмножествах: xvalue и prvalue. Итак, xvalue - это подмножество как glvalue, так и rvalue: то есть xvalue является пересечением как glvalue, так и rvalue. Следующая диаграмма таксономии, взятая из спецификации C ++, иллюстрирует взаимосвязь всех наборов:

prvalue, xvalue и lvalue - значения основных категорий. glvalue - это объединение lvalues ​​и xvalues, а rvalue - это объединение xvalues ​​и prvalue.

Для понимания этой статьи вам потребуются базовые знания C ++; вам также необходимо знание Scope в C++.

Содержание статьи

Основы

Чтобы действительно понять таксономию категорий выражений, вам необходимо сначала вспомнить или знать следующие основные функции: местоположение и объект, хранилище и ресурс, инициализация, идентификатор и ссылка, ссылки lvalue и rvalue, указатель, бесплатное хранилище и повторное использование ресурс.

Расположение и объект

Рассмотрим следующее объявление:

int идент;

Это объявление, которое определяет место в памяти. Расположение - это определенный набор последовательных байтов в памяти. Местоположение может состоять из одного байта, двух байтов, четырех байтов, шестидесяти четырех байтов и т. Д. Расположение целого числа для 32-битной машины - четыре байта. Также местоположение можно определить по идентификатору.

В приведенном выше объявлении местоположение не имеет содержания. Это означает, что он не имеет никакого значения, поскольку содержание - это значение. Итак, идентификатор определяет местоположение (небольшое непрерывное пространство). Когда местоположению присваивается конкретный контент, идентификатор затем идентифицирует как местоположение, так и контент; то есть идентификатор затем идентифицирует как местоположение, так и значение.

Рассмотрим следующие утверждения:

int идент1 = 5;
int идентификатор2 = 100;

Каждое из этих утверждений является декларацией и определением. Первый идентификатор имеет значение (содержание) 5, а второй идентификатор имеет значение 100. В 32-битной машине длина каждого из этих ячеек составляет четыре байта. Первый идентификатор идентифицирует как местоположение, так и значение. Второй идентификатор также идентифицирует как.

Объект - это именованная область хранения в памяти. Итак, объект - это либо местоположение без значения, либо местоположение со значением.

Хранилище объектов и ресурс

Местоположение объекта также называется хранилищем или ресурсом объекта.

Инициализация

Рассмотрим следующий фрагмент кода:

int идент;
identity = 8;

В первой строке объявляется идентификатор. Это объявление обеспечивает местоположение (хранилище или ресурс) для целочисленного объекта, идентифицируя его с помощью имени, идентификатора. Следующая строка помещает значение 8 (в битах) в место, указанное идентификатором. Установка этого значения и есть инициализация.

Следующий оператор определяет вектор с содержимым 1, 2, 3, 4, 5, идентифицированным vtr:

std :: vector vtr 1, 2, 3, 4, 5;

Здесь инициализация с помощью 1, 2, 3, 4, 5 выполняется в том же операторе определения (объявления). Оператор присваивания не используется. Следующий оператор определяет массив с содержимым 1, 2, 3, 4, 5:

int arr [] = 1, 2, 3, 4, 5;

На этот раз для инициализации был использован оператор присваивания.

Идентификатор и ссылка

Рассмотрим следующий фрагмент кода:

int идент = 4;
int & ref1 = идент;
int & ref2 = идент;
cout<< ident <<"<< ref1 <<"<< ref2 << '\n';

Результат:

4 4 4

Идентификатор - это идентификатор, а ref1 и ref2 - ссылки; они ссылаются на одно и то же место. Ссылка - это синоним идентификатора. Условно ref1 и ref2 - это разные имена одного объекта, а идентификатор - идентификатор одного и того же объекта. Тем не менее, идентификатор по-прежнему может называться именем объекта, что означает, что идентификатор, ссылка1 и ссылка2 называют одно и то же местоположение.

Основное различие между идентификатором и ссылкой заключается в том, что при передаче в качестве аргумента функции, если передается по идентификатору, создается копия идентификатора в функции, а при передаче по ссылке то же расположение используется внутри функция. Таким образом, передача по идентификатору заканчивается двумя местоположениями, а передача по ссылке заканчивается тем же самым местоположением.

Ссылка lvalue и ссылка rvalue

Обычный способ создания ссылки следующий:

int идент;
идент = 4;
int & ref = идент;

Хранилище (ресурс) сначала находится и идентифицируется (с именем, например, идентификатором), а затем делается ссылка (с именем, например, ref). При передаче в качестве аргумента функции копия идентификатора будет сделана в функции, в то время как в случае ссылки исходное местоположение будет использоваться (упомянуто) в функции.

Сегодня можно просто иметь ссылку, не идентифицируя ее. Это означает, что можно сначала создать ссылку, не имея идентификатора местоположения. Здесь используется &&, как показано в следующем заявлении:

int && ref = 4;

Здесь нет предшествующей идентификации. Чтобы получить доступ к значению объекта, просто используйте ref, как вы использовали бы идентификатор выше.

С объявлением && нет возможности передать аргумент функции по идентификатору. Единственный выбор - пройти по ссылке. В этом случае в функции используется только одно местоположение, а не второе скопированное местоположение, как с идентификатором.

Объявление ссылки с & называется ссылкой lvalue. Объявление ссылки с && называется ссылкой rvalue, которая также является ссылкой на prvalue (см. Ниже).

Указатель

Рассмотрим следующий код:

int ptdInt = 5;
int * ptrInt;
ptrInt = &ptdInt;
cout<< *ptrInt <<'\n';

На выходе 5.

Здесь ptdInt - это идентификатор, подобный идентификатору выше. Здесь два объекта (местоположения) вместо одного: заостренный объект, ptdInt, идентифицированный ptdInt, и объект-указатель, ptrInt, идентифицированный ptrInt. & ptdInt возвращает адрес указанного объекта и помещает его как значение в указатель объекта ptrInt. Чтобы вернуть (получить) значение указанного объекта, используйте идентификатор объекта-указателя, как в «* ptrInt».

Примечание: ptdInt - это идентификатор, а не ссылка, в то время как имя ref, упомянутое ранее, является ссылкой.

Вторая и третья строки в приведенном выше коде могут быть сокращены до одной строки, что приведет к следующему коду:

int ptdInt = 5;
int * ptrInt = &ptdInt;
cout<< *ptrInt <<'\n';

Примечание: Когда указатель увеличивается, он указывает на следующую позицию, которая не является добавлением значения 1. Когда указатель уменьшается, он указывает на предыдущее местоположение, которое не является вычитанием значения 1.

Бесплатный магазин

Операционная система выделяет память для каждой запущенной программы. Память, которая не выделена ни одной программе, называется свободным хранилищем. Выражение, которое возвращает местоположение для целого числа из бесплатного магазина:

новый int

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

int * ptrInt = новый int;
* ptrInt = 12;
cout<< *ptrInt  <<'\n';

На выходе 12.

Чтобы уничтожить объект, используйте выражение удаления следующим образом:

удалить ptrInt;

Аргументом выражения удаления является указатель. Следующий код иллюстрирует его использование:

int * ptrInt = новый int;
* ptrInt = 12;
удалить ptrInt;
cout<< *ptrInt <<'\n';

На выходе 0, и ничего вроде null или undefined. delete заменяет значение местоположения значением по умолчанию для конкретного типа местоположения, а затем разрешает повторное использование местоположения. Значение по умолчанию для местоположения int - 0.

Повторное использование ресурса

В таксономии категорий выражений повторное использование ресурса аналогично повторному использованию местоположения или хранилища для объекта. Следующий код показывает, как можно повторно использовать местоположение из бесплатного магазина:

int * ptrInt = новый int;
* ptrInt = 12;
cout<< *ptrInt <<'\n';
удалить ptrInt;
cout<< *ptrInt <<'\n';
* ptrInt = 24;
cout<< *ptrInt <<'\n';

Результат:

12
0
24

Неопознанному местоположению сначала присваивается значение 12. Затем содержимое локации удаляется (теоретически удаляется объект). Значение 24 переназначено тому же месту.

Следующая программа показывает, как повторно используется целочисленная ссылка, возвращаемая функцией:

#включать
используя пространство имен std;
int & fn ()

int я = 5;
int & j = я;
return j;

int main ()

int & myInt = fn ();
cout<< myInt <<'\n';
myInt = 17;
cout<< myInt <<'\n';
возврат 0;

Результат:

5
17

Такой объект, как i, объявленный в локальной области (области действия), перестает существовать в конце локальной области видимости. Однако приведенная выше функция fn () возвращает ссылку на i. Посредством этой возвращенной ссылки имя myInt в функции main () повторно использует местоположение, указанное i, для значения 17.

lvalue

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

int myInt = 512;
int & myRef = myInt;
int * ptr = &myInt;
int fn ()

++ptr; --ptr;
return myInt;

Здесь myInt - это lvalue; myRef - это ссылочное выражение lvalue; * ptr является выражением lvalue, потому что его результат идентифицируется с помощью ptr; ++ ptr или -ptr - это выражение lvalue, потому что его результат идентифицируется с новым состоянием (адресом) ptr, а fn - это lvalue (выражение).

Рассмотрим следующий фрагмент кода:

int a = 2, b = 8;
int c = a + 16 + b + 64;

Во втором утверждении местоположение для 'a' имеет 2 и идентифицируется 'a', как и lvalue. Местоположение для b имеет 8 и идентифицируется по b, как и lvalue. Местоположение для c будет иметь сумму, и его можно будет идентифицировать по c, как и lvalue. Во втором операторе выражения или значения 16 и 64 являются значениями r (см. Ниже).

Рассмотрим следующий фрагмент кода:

char seq [5];
seq [0] = 'l', seq [1] = 'o', seq [2] = 'v', seq [3] = 'e', ​​seq [4] = '\ 0';
cout<< seq[2] <<'\n';

На выходе получается 'v';

seq - это массив. Местоположение для 'v' или любого подобного значения в массиве идентифицируется seq [i], где i - индекс. Итак, выражение seq [i] является выражением lvalue. seq, который является идентификатором всего массива, также является lvalue.

prvalue

Prvalue - это выражение, оценка которого инициализирует объект или битовое поле или вычисляет значение операнда оператора, как указано в контексте, в котором оно появляется.

В заявлении,

int myInt = 256;

256 - это prvalue (выражение prvalue), которое инициализирует объект, идентифицированный myInt. На этот объект нет ссылок.

В заявлении,

int && ref = 4;

4 - это prvalue (выражение prvalue), которое инициализирует объект, на который ссылается ref. Этот объект официально не идентифицирован. ref - это пример ссылочного выражения rvalue или ссылочного выражения prvalue; это имя, но не официальный идентификатор.

Рассмотрим следующий фрагмент кода:

int идент;
identity = 6;
int & ref = идент;

6 - значение prvalue, которое инициализирует объект, идентифицированный идентификатором; на объект также ссылается ref. Здесь ссылка - это ссылка на lvalue, а не на prvalue.

Рассмотрим следующий фрагмент кода:

int a = 2, b = 8;
int c = a + 15 + b + 63;

15 и 63 - каждая константа, которая вычисляется сама с собой, создавая операнд (в битах) для оператора сложения. Итак, 15 или 63 - это выражение prvalue.

Любой литерал, кроме строкового, является prvalue (i.е., выражение prvalue). Итак, литерал, например 58 или 58.53, или правда или ложь, является prvalue. Литерал может использоваться для инициализации объекта или вычислять для себя (в некоторой другой форме в битах) как значение операнда для оператора. В приведенном выше коде литерал 2 инициализирует объект,. Он также вычисляет себя как операнд для оператора присваивания.

Почему строковый литерал не является значением prvalue? Рассмотрим следующий код:

char str [] = "любить, а не ненавидеть";
cout << str <<'\n';
cout << str[5] <<'\n';

Результат:

любить, а не ненавидеть
п

str идентифицирует всю строку. Итак, выражение str, а не то, что оно идентифицирует, является lvalue. Каждый символ в строке можно идентифицировать по str [i], где i - индекс. Выражение str [5], а не символ, который оно идентифицирует, является lvalue. Строковый литерал - это lvalue, а не prvalue.

В следующем заявлении литерал массива инициализирует объект arr:

ptrInt ++ или ptrInt-- 

Здесь ptrInt - указатель на целочисленное местоположение. Все выражение, а не окончательное значение местоположения, на которое оно указывает, является prvalue (выражение). Это связано с тем, что выражение ptrInt ++ или ptrInt- определяет исходное первое значение своего местоположения, а не второе конечное значение того же местоположения. С другой стороны, -ptrInt или -ptrInt - это lvalue, потому что они идентифицируют единственное значение интереса в местоположении. С другой стороны, исходное значение вычисляет второе конечное значение.

Во втором операторе следующего кода a или b все еще можно рассматривать как prvalue:

int a = 2, b = 8;
int c = a + 15 + b + 63;

Итак, a или b во втором выражении - это lvalue, потому что они идентифицируют объект. Это также prvalue, поскольку оно вычисляет целое число операнда для оператора сложения.

(новый int), а не место, которое он устанавливает, является prvalue. В следующем заявлении адрес возврата местоположения назначается объекту-указателю:

int * ptrInt = новый int

Здесь * ptrInt - это lvalue, а (new int) - это prvalue. Помните, lvalue или prvalue - это выражение. (новый int) не идентифицирует какой-либо объект. Возврат адреса не означает отождествление объекта с именем (например, идентификатором, приведенным выше). В * ptrInt имя ptrInt - это то, что действительно идентифицирует объект, поэтому * ptrInt - это lvalue. С другой стороны, (new int) является prvalue, поскольку он вычисляет новое местоположение по адресу значения операнда для оператора присваивания =.

xvalue

Сегодня lvalue означает значение местоположения; prvalue означает «чистое» rvalue (см. что обозначает rvalue ниже). Сегодня xvalue означает «eXpiring» lvalue.

Определение xvalue, цитируемое из спецификации C ++, выглядит следующим образом:

«Xvalue - это glvalue, который обозначает объект или битовое поле, ресурсы которого могут быть повторно использованы (обычно потому, что срок его существования приближается к концу). [Пример: некоторые виды выражений, включающие ссылки на rvalue, дают значения x, например, вызов функции, тип возвращаемого значения которой является ссылкой rvalue, или приведение к типу ссылки rvalue - конец примера] »

Это означает, что оба lvalue и prvalue могут истечь. Следующий код (скопированный сверху) показывает, как хранилище (ресурс) lvalue, * ptrInt повторно используется после его удаления.

int * ptrInt = новый int;
* ptrInt = 12;
cout<< *ptrInt <<'\n';
удалить ptrInt;
cout<< *ptrInt <<'\n';
* ptrInt = 24;
cout<< *ptrInt <<'\n';

Результат:

12
0
24

Следующая программа (скопированная сверху) показывает, как хранилище целочисленной ссылки, которая является ссылкой lvalue, возвращаемой функцией, повторно используется в функции main ():

#включать
используя пространство имен std;
int & fn ()

int я = 5;
int & j = я;
return j;

int main ()

int & myInt = fn ();
cout<< myInt <<'\n';
myInt = 17;
cout<< myInt <<'\n';
возврат 0;

Результат:

5
17

Когда объект, такой как i в функции fn (), выходит за пределы области видимости, он, естественно, уничтожается. В этом случае хранилище i все еще было повторно использовано в функции main ().

Два приведенных выше примера кода иллюстрируют повторное использование хранилища lvalue. Возможно повторное использование хранилища prvalues ​​(rvalues) (см. Ниже).

Следующая цитата относительно xvalue взята из спецификации C ++:

«В общем, эффект этого правила заключается в том, что именованные ссылки rvalue обрабатываются как lvalue, а безымянные ссылки rvalue на объекты обрабатываются как xvalue. Ссылки rvalue на функции обрабатываются как lvalue независимо от того, названы они или нет." (увидим позже).

Итак, xvalue - это lvalue или prvalue, ресурсы (хранилище) которых можно использовать повторно. xvalues ​​- это набор пересечений lvalues ​​и prvalues.

Для xvalue есть нечто большее, чем то, что было рассмотрено в этой статье. Однако xvalue заслуживает отдельной статьи, поэтому дополнительные спецификации для xvalue в этой статье не рассматриваются.

Набор таксономии категорий выражений

Еще одна цитата из спецификации C ++:

«Примечание: Исторически lvalues ​​и rvalues ​​назывались так, потому что они могли появляться слева и справа от присваивания (хотя это больше не так); glvalues ​​- это «обобщенные» lvalue, prvalues ​​- «чистые» rvalue, а xvalues ​​- «eXpiring» lvalue. Несмотря на названия, эти термины классифицируют выражения, а не значения. - конец примечания »

Итак, glvalues ​​- это объединенный набор lvalue, а xvalues ​​и rvalues ​​- это объединенный набор xvalues ​​и prvalue. xvalues ​​- это набор пересечений lvalues ​​и prvalues.

На данный момент таксономия категорий выражений лучше проиллюстрирована следующей диаграммой Венна:

Заключение

Lvalue - это выражение, оценка которого определяет идентичность объекта, битового поля или функции.

Prvalue - это выражение, оценка которого инициализирует объект или битовое поле или вычисляет значение операнда оператора, как указано в контексте, в котором оно появляется.

Xvalue - это lvalue или prvalue с дополнительным свойством, что его ресурсы (хранилище) могут быть повторно использованы.

Спецификация C ++ иллюстрирует таксономию категорий выражений с помощью древовидной диаграммы, что указывает на наличие некоторой иерархии в таксономии. На данный момент в таксономии нет иерархии, поэтому некоторые авторы используют диаграмму Венна, поскольку она иллюстрирует таксономию лучше, чем древовидная диаграмма.

Как разработать игру в Linux
Десять лет назад не многие пользователи Linux могли бы предсказать, что их любимая операционная система однажды станет популярной игровой платформой д...
Порты коммерческих игровых движков с открытым исходным кодом
Бесплатные игры с открытым исходным кодом и кроссплатформенные версии игрового движка можно использовать для игры как в старые, так и в некоторые из с...
Лучшие игры с командной строкой для Linux
Командная строка - не только ваш главный союзник при использовании Linux - она ​​также может быть источником развлечений, потому что вы можете использ...