C++

Taksonomia kategorii wyrażeń w C++

Taksonomia kategorii wyrażeń w C++

Obliczenie to dowolny rodzaj obliczenia zgodny z dobrze zdefiniowanym algorytmem. Wyrażenie to sekwencja operatorów i operandów, która określa obliczenia. Innymi słowy, wyrażenie jest identyfikatorem lub literałem lub sekwencją obu połączonych operatorami.W programowaniu wyrażenie może skutkować wartością i/lub spowodować pewne zdarzenie. Gdy daje w wyniku wartość, wyrażenie to glwartość, rwartość, lwartość, xwartość lub prwartość. Każda z tych kategorii to zbiór wyrażeń. Każdy zestaw ma definicję i szczególne sytuacje, w których dominuje jego znaczenie, odróżniając go od innego zestawu. Każdy zestaw nazywa się kategorią wartości.

Uwaga: wartość lub literał nadal jest wyrażeniem, więc te terminy klasyfikują wyrażenia, a nie wartości.

glvalue i rvalue to dwa podzbiory z dużego wyrażenia zbiorowego. glvalue istnieje w dwóch kolejnych podzbiorach: lwartość i xwartość. rwartość, drugi podzbiór wyrażenia, istnieje również w dwóch dalszych podzbiorach: xwartość i prwartość. Tak więc wartość x jest podzbiorem zarówno wartości gl, jak i wartości r: to znaczy, wartość x jest punktem przecięcia zarówno wartości gl, jak i r-wartości. Poniższy diagram taksonomii, zaczerpnięty ze specyfikacji C++, ilustruje relację wszystkich zbiorów:

prvalue, xvalue i lvalue to wartości kategorii podstawowej primary. glvalue to suma l-wartości i xvalues, podczas gdy r-values ​​to suma xvalues ​​i pr-values.

Aby zrozumieć ten artykuł, potrzebujesz podstawowej wiedzy w C++; potrzebujesz również wiedzy o zakresie w C++.

Treść artykułu

Podstawy

Aby naprawdę zrozumieć taksonomię kategorii wyrażeń, musisz najpierw przypomnieć lub znać następujące podstawowe cechy: lokalizacja i obiekt, pamięć i zasób, inicjalizacja, identyfikator i odniesienie, odwołania do l-wartości i r-wartości, wskaźnik, wolne przechowywanie i ponowne użycie ratunek.

Lokalizacja i obiekt

Rozważ następującą deklarację:

int ident;

To jest deklaracja, która identyfikuje lokalizację w pamięci. Lokalizacja to określony zestaw kolejnych bajtów w pamięci. Lokalizacja może składać się z jednego bajtu, dwóch bajtów, czterech bajtów, sześćdziesięciu czterech bajtów itd. Lokalizacja liczby całkowitej dla maszyny 32-bitowej to cztery bajty. Ponadto lokalizację można zidentyfikować za pomocą identyfikatora.

W powyższej deklaracji lokalizacja nie zawiera żadnej treści. Oznacza to, że nie ma żadnej wartości, ponieważ zawartość jest wartością. Tak więc identyfikator identyfikuje lokalizację (mała ciągła przestrzeń). Gdy lokalizacja otrzymuje konkretną treść, identyfikator następnie identyfikuje zarówno lokalizację, jak i treść; oznacza to, że identyfikator następnie identyfikuje zarówno lokalizację, jak i wartość.

Rozważ następujące stwierdzenia:

int ident1 = 5;
int ident2 = 100;

Każde z tych stwierdzeń jest deklaracją i definicją. Pierwszy identyfikator ma wartość (treść) 5, a drugi identyfikator ma wartość 100. W maszynie 32-bitowej każda z tych lokalizacji ma długość czterech bajtów. Pierwszy identyfikator identyfikuje zarówno lokalizację, jak i wartość. Drugi identyfikator identyfikuje również oba.

Obiekt jest nazwanym regionem przechowywania w pamięci. Tak więc obiekt jest albo lokalizacją bez wartości, albo lokalizacją z wartością.

Przechowywanie obiektów i zasoby

Lokalizacja obiektu jest również nazywana magazynem lub zasobem obiektu.

Inicjalizacja

Rozważ następujący segment kodu:

int ident;
identyfikator = 8;

Pierwsza linia deklaruje identyfikator. Ta deklaracja podaje lokalizację (magazyn lub zasób) dla obiektu typu integer, identyfikując go nazwą, ident. Kolejna linia umieszcza wartość 8 (w bitach) w lokalizacji zidentyfikowanej przez ident. Wprowadzenie tej wartości to inicjalizacja.

Poniższa instrukcja definiuje wektor o zawartości 1, 2, 3, 4, 5, identyfikowany przez vtr:

std::wektor vtr1, 2, 3, 4, 5;

Tutaj inicjalizacja za pomocą 1, 2, 3, 4, 5 odbywa się w tym samym oświadczeniu definicji (deklaracji). Operator przypisania nie jest używany. Poniższa instrukcja definiuje tablicę o zawartości 1, 2, 3, 4, 5:

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

Tym razem do inicjalizacji użyto operatora przypisania.

Identyfikator i odniesienie

Rozważ następujący segment kodu:

int ident = 4;
int& ref1 = ident;
int& ref2 = ident;
Cout<< ident <<"<< ref1 <<"<< ref2 << '\n';

Dane wyjściowe to:

4 4 4

ident jest identyfikatorem, a ref1 i ref2 są referencjami; odnoszą się do tej samej lokalizacji. Odwołanie jest synonimem identyfikatora. Konwencjonalnie ref1 i ref2 to różne nazwy jednego obiektu, podczas gdy ident jest identyfikatorem tego samego obiektu. Jednak ident nadal można nazwać nazwą obiektu, co oznacza, że ​​ident, ref1 i ref2 nazywają tę samą lokalizację.

Główna różnica między identyfikatorem a referencją polega na tym, że po przekazaniu jako argument do funkcji, jeśli jest ona przekazywana przez identyfikator, tworzona jest kopia identyfikatora w funkcji, natomiast jeśli jest przekazywana przez odwołanie, ta sama lokalizacja jest używana w ramach funkcjonować. Tak więc przekazywanie przez identyfikator kończy się na dwóch lokalizacjach, podczas gdy przekazywanie przez odniesienie kończy się na tej samej jednej lokalizacji.

lwartość Odniesienie i rwartość Odniesienie

Normalny sposób tworzenia referencji jest następujący:

int ident;
identyfikator = 4;
int& ref = ident;

Magazyn (zasób) jest najpierw lokalizowany i identyfikowany (z nazwą np. ident), a następnie tworzone jest odwołanie (z nazwą np. ref). Przy przekazywaniu jako argument do funkcji, w funkcji zostanie wykonana kopia identyfikatora, natomiast w przypadku referencji zostanie użyta oryginalna lokalizacja (do której się odwołujemy) w funkcji.

Dziś można po prostu mieć odniesienie bez identyfikowania go. Oznacza to, że można najpierw utworzyć odniesienie bez posiadania identyfikatora lokalizacji. Używa &&, jak pokazano w poniższym oświadczeniu:

int&& ref = 4;

Tutaj nie ma wcześniejszej identyfikacji. Aby uzyskać dostęp do wartości obiektu, po prostu użyj ref, tak jakbyś używał ident powyżej.

W przypadku deklaracji && nie ma możliwości przekazania argumentu do funkcji przez identyfikator. Jedynym wyborem jest przekazanie przez odniesienie. W tym przypadku w funkcji używana jest tylko jedna lokalizacja, a nie druga skopiowana lokalizacja, jak w przypadku identyfikatora.

Deklaracja referencji z & nazywa się referencją do lwartościvalue. Deklaracja referencji z && nazywa się referencją do rvalue, która jest również referencją do prvalue (patrz poniżej).

Wskaźnik

Rozważ następujący kod:

wewn ptdInt = 5;
int *ptInt;
ptrInt = &ptdInt;
Cout<< *ptrInt <<'\n';

Wyjście to 5.

Tutaj ptdInt jest identyfikatorem takim jak ident powyżej. Są tu dwa obiekty (lokalizacje) zamiast jednego: wskazany obiekt, ptdInt identyfikowany przez ptdInt i obiekt wskaźnikowy, ptrInt identyfikowany przez ptrInt. &ptdInt zwraca adres wskazanego obiektu i umieszcza go jako wartość w obiekcie pointer ptrInt. Aby zwrócić (uzyskać) wartość wskazanego obiektu, użyj identyfikatora dla obiektu wskaźnika, jak w „*ptrInt”.

Uwaga: ptdInt jest identyfikatorem, a nie referencją, podczas gdy wspomniana wcześniej nazwa ref jest referencją.

Drugi i trzeci wiersz w powyższym kodzie można zredukować do jednego wiersza, co prowadzi do następującego kodu:

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

Uwaga: Kiedy wskaźnik jest zwiększany, wskazuje na następną lokalizację, która nie jest dodatkiem do wartości 1. Kiedy wskaźnik jest zmniejszany, wskazuje poprzednią lokalizację, która nie jest odejmowaną wartością 1.

Bezpłatny sklep

System operacyjny przydziela pamięć dla każdego uruchomionego programu. Pamięć, która nie jest przypisana do żadnego programu, nazywana jest wolnym magazynem. Wyrażenie, które zwraca lokalizację dla liczby całkowitej ze sklepu darmowego, to:

nowa int

Zwraca lokalizację dla liczby całkowitej, która nie została zidentyfikowana. Poniższy kod ilustruje, jak używać wskaźnika z bezpłatnym sklepem:

int *ptrInt = nowy int;
*ptInt = 12;
Cout<< *ptrInt  <<'\n';

Wyjście to 12.

Aby zniszczyć obiekt, użyj wyrażenia delete w następujący sposób:

usuń ptrInt;

Argumentem wyrażenia delete jest wskaźnik. Poniższy kod ilustruje jego użycie:

int *ptrInt = nowy int;
*ptInt = 12;
usuń ptrInt;
Cout<< *ptrInt <<'\n';

Wyjście to 0, a nie coś takiego jak null lub undefined. delete zastępuje wartość lokalizacji wartością domyślną określonego typu lokalizacji, a następnie zezwala na ponowne wykorzystanie lokalizacji. Domyślna wartość lokalizacji int to 0.

Ponowne wykorzystanie zasobu

W taksonomii kategorii wyrażeń ponowne użycie zasobu jest tym samym, co ponowne użycie lokalizacji lub magazynu dla obiektu. Poniższy kod ilustruje, jak można ponownie wykorzystać lokalizację z darmowego sklepu:

int *ptrInt = nowy int;
*ptInt = 12;
Cout<< *ptrInt <<'\n';
usuń ptrInt;
Cout<< *ptrInt <<'\n';
*ptInt = 24;
Cout<< *ptrInt <<'\n';

Dane wyjściowe to:

12
0
24

Wartość 12 jest najpierw przypisywana do niezidentyfikowanej lokalizacji. Następnie zawartość lokalizacji jest usuwana (teoretycznie obiekt jest usuwany). Wartość 24 zostaje ponownie przypisana do tej samej lokalizacji.

Poniższy program pokazuje, w jaki sposób odwołanie do liczby całkowitej zwrócone przez funkcję jest ponownie wykorzystywane:

#zawierać
przy użyciu standardowej przestrzeni nazw;
int& fn()

int i = 5;
int& j = i;
powrót j;

int main()

int& mojeInt = fn();
Cout<< myInt <<'\n';
mojeInt = 17;
Cout<< myInt <<'\n';
zwróć 0;

Dane wyjściowe to:

5
17

Obiekt taki jak i, zadeklarowany w zakresie lokalnym (zakres funkcji), przestaje istnieć na końcu zakresu lokalnego. Jednak powyższa funkcja fn() zwraca odwołanie do i. Poprzez to zwrócone odwołanie nazwa myInt w funkcji main() ponownie wykorzystuje lokalizację zidentyfikowaną przez i dla wartości 17.

lwartość

lwartość to wyrażenie, którego ocena określa tożsamość obiektu, pola bitowego lub funkcji. Tożsamość jest oficjalną tożsamością, taką jak ident powyżej lub nazwa odniesienia do lwartości, wskaźnik lub nazwa funkcji. Rozważ następujący kod, który działa:

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

++ptr; --ptr;
zwróć myInt;

Tutaj myInt jest lwartością; myRef jest wyrażeniem referencyjnym l-wartości; *ptr jest wyrażeniem l-wartościowym, ponieważ jego wynik jest identyfikowany z ptr; ++ptr lub -ptr jest wyrażeniem lwartości, ponieważ jego wynik jest identyfikowalny z nowym stanem (adresem) ptr, a fn jest lwartością (wyrażeniem).

Rozważ następujący segment kodu:

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

W drugim zdaniu lokalizacja dla 'a' ma 2 i jest identyfikowana przez 'a', podobnie jak l-wartość. Lokalizacja dla b ma 8 i jest identyfikowana przez b, podobnie jak l-wartość. Lokalizacja c będzie miała sumę i jest identyfikowana przez c, podobnie jak l-wartość. W drugim stwierdzeniu wyrażenia lub wartości 16 i 64 są r-wartościami (patrz poniżej).

Rozważ następujący segment kodu:

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

Dane wyjściowe to 'v';

seq to tablica. Lokalizacja „v” lub dowolnej podobnej wartości w tablicy jest identyfikowana przez seq[i], gdzie i jest indeksem. Zatem wyrażenie seq[i] jest wyrażeniem o lwartości. seq, który jest identyfikatorem całej tablicy, jest również lwartością.

prvalue

Prvalue to wyrażenie, którego ocena inicjuje obiekt lub pole bitowe lub oblicza wartość operandu operatora, zgodnie z kontekstem, w którym się pojawia.

W oświadczeniu,

int mojeInt = 256;

256 to prvalue (wyrażenie prvalue), które inicjuje obiekt zidentyfikowany przez myInt. Ten obiekt nie jest przywoływany.

W oświadczeniu,

int&& ref = 4;

4 to prvalue (wyrażenie prvalue), które inicjuje obiekt, do którego odwołuje się ref. Ten obiekt nie jest oficjalnie zidentyfikowany. ref jest przykładem wyrażenia odwołania do rwartości lub wyrażenia odwołania do prwartości; to jest nazwa, ale nie oficjalny identyfikator.

Rozważ następujący segment kodu:

int ident;
identyfikator = 6;
int& ref = ident;

6 jest wartością pr, która inicjuje obiekt identyfikowany przez ident; do obiektu odwołuje się również ref. W tym przypadku ref jest odwołaniem do l-wartości, a nie do pr-wartości pr.

Rozważ następujący segment kodu:

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

15 i 63 to każda stała, która sama się oblicza, tworząc operand (w bitach) dla operatora dodawania. Tak więc 15 lub 63 to wyrażenie prvalue.

Dowolny literał, z wyjątkiem literału łańcuchowego, jest wartością pr (i.mi., wyrażenie prvalue). Tak więc dosłowny taki jak 58 lub 58.53, lub prawda lub fałsz, jest wartością pr. Literał może być użyty do inicjalizacji obiektu lub może obliczyć się do siebie (do innej postaci w bitach) jako wartość operandu dla operatora. W powyższym kodzie literał 2 inicjuje obiekt a. Oblicza się również jako operand dla operatora przypisania.

Dlaczego literał ciągu nie jest wartością pr?? Rozważ następujący kod:

char str[] = "miłość nie nienawiść";
Cout << str <<'\n';
Cout << str[5] <<'\n';

Dane wyjściowe to:

miłość nie nienawiść
nie

str identyfikuje cały ciąg. Zatem wyrażenie str, a nie to, co identyfikuje, jest lwartością. Każdy znak w ciągu można zidentyfikować za pomocą str[i], gdzie i jest indeksem. Wyrażenie str[5], a nie znak, który identyfikuje, jest lwartością. Literał ciągu jest lwartością, a nie prwartością.

W poniższej instrukcji literał tablicowy inicjuje obiekt arr:

ptrInt++ lub ptrInt-- 

Tutaj ptrInt jest wskaźnikiem do lokalizacji całkowitej. Całe wyrażenie, a nie ostateczna wartość lokalizacji, na którą wskazuje, jest wartością pr (wyrażenie). Dzieje się tak, ponieważ wyrażenie ptrInt++ lub ptrInt- identyfikuje pierwotną pierwszą wartość jego lokalizacji, a nie drugą końcową wartość tej samej lokalizacji. Z drugiej strony -ptrInt lub –ptrInt jest lwartością, ponieważ identyfikuje jedyną wartość zainteresowania lokalizacją. Innym sposobem patrzenia na to jest to, że pierwotna wartość oblicza drugą wartość końcową.

W drugiej instrukcji poniższego kodu a lub b nadal można uznać za prwartość:

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

Tak więc a lub b w drugim zdaniu jest lwartością, ponieważ identyfikuje obiekt. Jest to również wartość wstępna, ponieważ oblicza się na liczbę całkowitą operandu dla operatora dodawania.

(nowy int), a nie lokalizacja, którą ustala, jest prvalue. W poniższej instrukcji adres zwrotny lokalizacji jest przypisany do obiektu wskaźnika:

int *ptrInt = nowy int

Tutaj *ptrInt jest lwartością, podczas gdy (new int) jest prwartością. Pamiętaj, że l-wartość lub pr-wartość to wyrażenie. (nowy int) nie identyfikuje żadnego obiektu. Zwrot adresu nie oznacza identyfikacji obiektu nazwą (np. ident, powyżej). W *ptrInt nazwa, ptrInt, jest tym, co naprawdę identyfikuje obiekt, więc *ptrInt jest lwartością. Z drugiej strony (new int) jest wartością pr, ponieważ oblicza nową lokalizację do adresu wartości operandu dla operatora przypisania =.

xwartość

Dziś lvalue oznacza wartość lokalizacji; prvalue oznacza „czystą” rvalue (zobacz poniżej, co oznacza rvalue). Dzisiaj xvalue oznacza „eXpiring” lvalue.

Definicja xvalue, cytowana ze specyfikacji C++, jest następująca:

„Wartość x to wartość gl, która oznacza obiekt lub pole bitowe, którego zasoby można ponownie wykorzystać (zwykle dlatego, że zbliża się koniec swojego życia). [Przykład: niektóre rodzaje wyrażeń zawierających odwołania do rvalue dają wartości x, takie jak wywołanie funkcji, której typem zwracanym jest odwołanie do rvalue lub rzutowanie na typ odwołania rvalue — przykład zakończenia]”

Oznacza to, że zarówno lvalue, jak i prvalue mogą wygasnąć. Poniższy kod (skopiowany z góry) pokazuje, w jaki sposób magazyn (zasób) lwartości, *ptrInt jest ponownie używany po jego usunięciu.

int *ptrInt = nowy int;
*ptInt = 12;
Cout<< *ptrInt <<'\n';
usuń ptrInt;
Cout<< *ptrInt <<'\n';
*ptInt = 24;
Cout<< *ptrInt <<'\n';

Dane wyjściowe to:

12
0
24

Poniższy program (skopiowany powyżej) pokazuje, w jaki sposób przechowywanie odwołania do liczby całkowitej, która jest odwołaniem do l-wartości zwracanej przez funkcję, jest ponownie wykorzystywane w funkcji main():

#zawierać
przy użyciu standardowej przestrzeni nazw;
int& fn()

int i = 5;
int& j = i;
powrót j;

int main()

int& mojeInt = fn();
Cout<< myInt <<'\n';
mojeInt = 17;
Cout<< myInt <<'\n';
zwróć 0;

Dane wyjściowe to:

5
17

Kiedy obiekt taki jak i w funkcji fn() wychodzi poza zakres, w naturalny sposób zostaje zniszczony. W tym przypadku pamięć i nadal została ponownie wykorzystana w funkcji main().

Powyższe dwie próbki kodu ilustrują ponowne wykorzystanie przechowywania lwartości. Możliwe jest ponowne wykorzystanie wartości prvalue (rvalues) do przechowywania (patrz dalej).

Poniższy cytat dotyczący xvalue pochodzi ze specyfikacji C++:

„Ogólnie rzecz biorąc, efektem tej reguły jest to, że nazwane referencje rwartości są traktowane jako lwartości, a nienazwane referencje rwartości do obiektów są traktowane jako xwartości. odwołania do r-wartości do funkcji są traktowane jako l-wartości niezależnie od tego, czy są nazwane, czy nie." (Zobaczymy później).

Zatem xvalue to l-wartość lub pr-wartość, której zasoby (magazyny) można ponownie wykorzystać. xvalues ​​to zbiór przecięcia l-wartości i pr-wartości.

Xvalue to coś więcej niż to, co zostało omówione w tym artykule. Jednak wartość x zasługuje na cały artykuł sam w sobie, więc dodatkowe specyfikacje dla wartości x nie są omawiane w tym artykule.

Zestaw taksonomii kategorii wyrażenia

Kolejny cytat ze specyfikacji C++:

Uwaga: Historycznie l-wartości i r-wartości były nazywane tak, ponieważ mogły pojawić się po lewej i prawej stronie przypisania (chociaż nie jest to już ogólnie prawdą); glwartości są „uogólnionymi” lwartościami, prwartości są „czystymi” rwartościami, a xwartości są „eXpiring” lwartościami. Pomimo swoich nazw terminy te klasyfikują wyrażenia, a nie wartości. - uwaga końcowa”

Tak więc glvalues ​​jest sumą lwartości i xvalues, a rvalues ​​to suma xvalues ​​i prvalues. xvalues ​​to zbiór przecięcia l-wartości i pr-wartości.

W chwili obecnej taksonomia kategorii wyrażeń jest lepiej zilustrowana za pomocą diagramu Venna w następujący sposób:

Wniosek

lwartość to wyrażenie, którego ocena określa tożsamość obiektu, pola bitowego lub funkcji.

Prvalue to wyrażenie, którego ocena inicjuje obiekt lub pole bitowe lub oblicza wartość operandu operatora, zgodnie z kontekstem, w którym się pojawia.

Wartość x to l-wartość lub pr-wartość, z dodatkową właściwością, że jej zasoby (magazyny) mogą być ponownie wykorzystane.

Specyfikacja C++ ilustruje taksonomię kategorii wyrażeń za pomocą diagramu drzewa, wskazującego na pewną hierarchię w taksonomii. W tej chwili nie ma hierarchii w taksonomii, więc niektórzy autorzy używają diagramu Venna, ponieważ lepiej ilustruje taksonomię niż diagram drzewa.

Gry Najlepsze gry w laboratorium aplikacji Oculus
Najlepsze gry w laboratorium aplikacji Oculus
Jeśli jesteś posiadaczem gogli Oculus, musisz wiedzieć o sideloadingu. Sideloading to proces instalowania w zestawie nagłownym treści innych niż sklep...
Gry Top 10 Games to Play on Ubuntu
Top 10 Games to Play on Ubuntu
Windows platform has been one of the dominating platforms for gaming because of the huge percentage of games that are developing today to natively sup...
Gry 5 najlepszych gier zręcznościowych dla systemu Linux
5 najlepszych gier zręcznościowych dla systemu Linux
W dzisiejszych czasach komputery to poważne maszyny używane do gier. Jeśli nie możesz uzyskać nowego wysokiego wyniku, będziesz wiedział, o co mi chod...