Taksonomia kategorii wyrażeń w C ++

Taksonomia kategorii wyrażeń w C ++
Obliczanie to każdy rodzaj obliczeń, który następuje po dobrze zdefiniowanym algorytmie. Wyrażenie jest sekwencją operatorów i operandów, które określają obliczenia. Innymi słowy, wyrażenie jest identyfikatorem, literacją lub sekwencją obu, połączonych przez operatorów.W programowaniu wyrażenie może powodować wartość i/lub powodować niektóre wydarzenia. Kiedy powoduje wartość, wyrażeniem jest glalue, rValue, lvalue, xvalue lub pralera. Każda z tych kategorii jest zestawem wyrażeń. Każdy zestaw ma definicję i szczególne sytuacje, w których panuje jego znaczenie, odróżniając go od innego zestawu. Każdy zestaw nazywa się kategorią wartości.

Notatka: Wartość lub literatura jest nadal wyrażeniem, więc te terminy klasyfikują wyrażenia, a nie tak naprawdę wartości.

GLVALUE i RVALUE to dwa podzbiory z wielkiego wyrażenia. GLAVELUE istnieje w dwóch kolejnych podzbiorach: LVALUE i XVIUE. RVALUE, drugi podzbiór wyrażenia, istnieje również w dwóch kolejnych podzbiorach: xvalue i pralue. Tak więc XValue jest podzbiorem zarówno glachy, jak i rValue: to znaczy xvalue jest przecięciem zarówno glachy, jak i rValue. Poniższy schemat taksonomii, pobrany ze specyfikacji C ++, ilustruje związek wszystkich zestawów:

PrValue, XValue i LVALUE są podstawowymi wartościami kategorii. GLAVELUE to związek LVALUES i XVIUES, podczas gdy RVAUES są związkiem XVIUES i PRALUES.

Potrzebujesz podstawowej wiedzy w C ++, aby zrozumieć ten artykuł; Potrzebujesz także znajomości zakresu w C++.

Treść artykułu

  • Podstawy
  • lvalue
  • pralue
  • XValue
  • Zestaw taksonomii kategorii wyrażeń
  • Wniosek

Podstawy

Aby naprawdę zrozumieć taksonomię kategorii wyrażenia, najpierw musisz przypomnieć lub znać następujące podstawowe funkcje: lokalizacja i obiekt, pamięć i zasoby, inicjalizacja, identyfikator i referencje, LVALUE i RVALUE REFERENCE, Wskaźnik, bezpłatny sklep i ponowne wykorzystanie A ratunek.

Lokalizacja i obiekt

Rozważ następującą deklarację:

Int Identyfikator;

Jest to deklaracja, która identyfikuje lokalizację w pamięci. Lokalizacja to szczególny 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 itp. Lokalizacja liczby całkowitej dla maszyny 32 -bitowej to cztery bajty. Lokalizacja może być również zidentyfikowana przez identyfikator.

W powyższej deklaracji lokalizacja nie ma żadnej treści. Oznacza to, że nie ma żadnej wartości, ponieważ treść jest wartością. Tak więc identyfikator identyfikuje lokalizację (mała przestrzeń ciągła). Gdy lokalizacja otrzymuje określoną treść, identyfikator identyfikuje zarówno lokalizację, jak i treść; to znaczy identyfikator 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. Na maszynie 32 -bitowej każda z tych lokalizacji ma cztery bajty długości. Pierwszy identyfikator identyfikuje zarówno lokalizację, jak i wartość. Drugi identyfikator identyfikuje również oba.

Obiekt jest nazwanym regionem pamięci w pamięci. Tak więc obiekt jest lokalizacją bez wartości lub lokalizacji o wartości.

Przechowywanie obiektów i zasób

Lokalizacja obiektu jest również nazywana pamięcią lub zasobem obiektu.

Inicjalizacja

Rozważ następujący segment kodu:

Int Identyfikator;
identyfikator = 8;

Pierwsza linia deklaruje identyfikator. Niniejsza deklaracja zapewnia lokalizację (pamięć lub zasób) dla obiektu liczb całkowitych, identyfikując go z nazwą, identyfikator. Następny wiersz umieszcza wartość 8 (w bitach) w lokalizacji zidentyfikowanej według identyfikatora. Ustawienie tej wartości to inicjalizacja.

Poniższe oświadczenie definiuje wektor z treścią, 1, 2, 3, 4, 5, zidentyfikowane przez VTR:

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

Tutaj inicjalizacja z 1, 2, 3, 4, 5 odbywa się w tym samym stwierdzeniu definicji (deklaracja). Operator przypisania nie jest używany. Poniższe stwierdzenie definiuje tablicę z treścią 1, 2, 3, 4, 5:

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

Tym razem do inicjalizacji zastosowano operator przypisania.

Identyfikator i odniesienie

Rozważ następujący segment kodu:

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

Wyjście to:

4 4 4

Identyfikator jest identyfikatorem, podczas gdy Ref1 i Ref2 są referencjami; Odwołują się do tej samej lokalizacji. Odniesienie jest synonimem identyfikatora. Konwencjonalnie ref1 i ref2 są różnymi nazwami jednego obiektu, podczas gdy identyfikator jest identyfikatorem tego samego obiektu. Jednak identyfikator można nadal nazwać nazwą obiektu, co oznacza, tożsamość, ref1 i ref2 nazwa tej samej lokalizacji.

Główną różnicą między identyfikatorem a odniesieniem jest to, że po przekazaniu jako argument funkcji, jeśli jest przekazywany przez identyfikator, wykonana jest kopia dla identyfikatora w funkcji, podczas gdy jeśli jest przekazywana przez odniesienie, ta sama lokalizacja jest używana w obrębie w obrębie w obrębie tego samego lokalizacji funkcjonować. Tak więc przekazywanie identyfikatora kończy się dwoma lokalizacjami, a przekazywanie przez referencje kończy.

Odniesienie LVALUE i odniesienie RVALUE

Normalny sposób utworzenia odniesienia jest następujący:

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

Pamięć (zasób) jest położony i zidentyfikowany najpierw (o nazwie takiej jak identyfikator), a następnie odniesienie (z nazwą taką jak ref). Po przekazaniu jako argumentu do funkcji, w funkcji zostanie wykonana kopia identyfikatora, podczas gdy w przypadku odniesienia zostanie użyta oryginalna lokalizacja (określana) w funkcji.

Dziś możliwe jest po prostu odniesienie bez identyfikacji. Oznacza to, że możliwe jest najpierw utworzenie odniesienia bez posiadania identyfikatora dla lokalizacji. Używa tego &&, jak pokazano w poniższym stwierdzeniu:

int && ref = 4;

Tutaj nie ma poprzedniej identyfikacji. Aby uzyskać dostęp do wartości obiektu, po prostu użyj Ref, ponieważ użyjesz powyższego identyfikatora.

Z deklaracją && nie ma możliwości przekazania argumentu do funkcji przez identyfikator. Jedynym wyborem jest przejście przez odniesienie. W takim przypadku w funkcji jest tylko jedna lokalizacja, a nie druga skopiowana lokalizacja jak w przypadku identyfikatora.

Deklaracja odniesienia z i nazywa się odniesieniem LValue. Deklaracja odniesienia z && nazywana jest odniesieniem RValue, które jest również odniesieniem pralelu (patrz poniżej).

Wskaźnik

Rozważ następujący kod:

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

Wyjście jest 5.

Tutaj PTDINT jest identyfikatorem, takim jak powyższy identyfikator. Istnieją tutaj dwa obiekty (lokalizacje) zamiast jednego: spiczasty obiekt, Ptdint zidentyfikowane przez Ptdint i obiekt wskaźnika, ptrint zidentyfikowane przez ptrint. & Ptdint zwraca adres spiczastego obiektu i stawia go jako wartość w obiekcie wskaźnika ptrint. Aby zwrócić (uzyskać) wartość spiczastego obiektu, użyj identyfikatora dla obiektu wskaźnika, jak w „*ptrint”.

Notatka: PTDINT jest identyfikatorem, a nie odniesieniem, podczas gdy nazwa, wspomniana wcześniej, jest odniesieniem.

Drugie i trzecie wiersze powyższego kodu można zmniejszyć do jednego wiersza, co prowadzi do następującego kodu:

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

Notatka: Po zwiększeniu wskaźnika wskazuje na następną lokalizację, która nie jest dodawaniem wartości 1. Po zmniejszeniu wskaźnika wskazuje na poprzednią lokalizację, która nie jest odejmowaniem wartości 1.

Darmowy sklep

System operacyjny przydziela pamięć dla każdego uruchomionego programu. Pamięć, która nie jest przydzielona do żadnego programu, jest znana jako bezpłatny sklep. Wyrażenie, które zwraca lokalizację liczby całkowitej z bezpłatnego sklepu, to:

nowy int

To zwraca lokalizację liczby całkowitej, która nie jest zidentyfikowana. Poniższy kod ilustruje, jak korzystać z wskaźnika w bezpłatnym sklepie:

int *ptrint = new int;
*ptrint = 12;
Cout<< *ptrInt <<'\n';

Wyjście jest 12.

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

usuń ptrint;

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

int *ptrint = new int;
*ptrint = 12;
usuń ptrint;
Cout<< *ptrInt <<'\n';

Wyjście jest 0, I nie ma nic takiego jak NULL lub niezdefiniowany. Usuń zastępuje wartość lokalizacji domyślną wartością określonego typu lokalizacji, a następnie umożliwia lokalizację do ponownego użycia. Wartość domyślna dla lokalizacji INT to 0.

Ponowne wykorzystanie zasobu

W kategorii wyrażenia taksonomia ponowne wykorzystanie zasobu jest takie samo, jak ponowne wykorzystanie lokalizacji lub pamięci dla obiektu. Poniższy kod ilustruje, w jaki sposób można ponownie wykorzystać lokalizację z bezpłatnego sklepu:

int *ptrint = new int;
*ptrint = 12;
Cout<< *ptrInt <<'\n';
usuń ptrint;
Cout<< *ptrInt <<'\n';
*ptrint = 24;
Cout<< *ptrInt <<'\n';

Wyjście to:

12
0
24

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

Poniższy program pokazuje, w jaki sposób odniesienie całkowitowe zwrócone przez funkcję jest ponownie wykorzystywane:

#włączać
za pomocą przestrzeni nazw Std;
int & fn ()
int i = 5;
int & j = i;
powrót J;

int main ()
int & myint = fn ();
Cout<< myInt <<'\n';
myint = 17;
Cout<< myInt <<'\n';
powrót 0;

Wyjście to:

5
17

Obiekt taki jak I, zadeklarowany w zakresie lokalnym (zakres funkcji), przestaje istnieć na końcu lokalnego zakresu. Jednak funkcja fn () powyżej zwraca odniesienie i. Dzięki temu zwróconym odniesieniu nazwa, MyINT w funkcji Main (), ponownie wykorzystuje lokalizację zidentyfikowaną przez I dla wartości 17.

lvalue

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

int myint = 512;
int & myref = myint;
int* ptr = &myInt;
int fn ()
++ptr; --Ptr;
zwróć myint;

Tutaj Myint jest lValue; Myref jest wyrażeniem odniesienia LValue; *PTR jest wyrażeniem LValue, ponieważ jego wynik można zidentyfikować z PTR; ++ ptr lub -ptr jest wyrażeniem LVALUE, ponieważ jego wynik jest możliwy do zidentyfikowania z nowym stanem (adresem) PTR, a FN jest lValue (wyrażenie).

Rozważ następujący segment kodu:

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

W drugim stwierdzeniu lokalizacja „A” ma 2 i jest możliwe do zidentyfikowania przez „A”, podobnie jak LValue. Lokalizacja B ma 8 i jest identyfikowalna przez B, podobnie jak LValue. Lokalizacja dla C będzie miała sumę i jest możliwe do zidentyfikowania przez C, podobnie jak LValue. W drugim stwierdzeniu wyrażenia lub wartości 16 i 64 są RVALES (patrz poniżej).

Rozważ następujący segment kodu:

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

Wyjście to 'v';;

SEQ to tablica. Lokalizacja „V” lub dowolnej podobnej wartości w tablicy jest identyfikowana przez SEQ [i], gdzie i jest indeksem. Tak więc wyrażenie, seq [i], jest wyrażeniem LValue. SEQ, który jest identyfikatorem dla całej tablicy, jest również lValue.

pralue

PrValue jest wyrażeniem, którego ocena inicjuje obiekt lub pole bitowe lub oblicza wartość operandu operatora, jak określono w kontekście, w którym się pojawia.

W oświadczeniu,

int myint = 256;

256 to pralue (wyrażenie pralue), który inicjuje obiekt zidentyfikowany przez MyINT. Ten obiekt nie jest odwoływany.

W oświadczeniu,

int && ref = 4;

4 to pralue (wyrażenie pralue), który inicjuje obiekt odwołany przez ref. Ten obiekt nie jest oficjalnie identyfikowany. Ref jest przykładem wyrażenia odniesienia RValue lub wyrażenia odniesienia pralera; To nazwa, ale nie oficjalny identyfikator.

Rozważ następujący segment kodu:

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

6 to pralue, który inicjuje obiekt zidentyfikowany przez identyfikator; Do obiektu jest również odwoływany przez Ref. Tutaj sędzia jest odniesieniem LVALUE, a nie odniesieniem do pralki.

Rozważ następujący segment kodu:

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

15 i 63 to stała, która oblicza się dla siebie, wytwarzając operand (u bitów) dla operatora dodatku. Zatem 15 lub 63 jest wyrażeniem pralue.

Wszelkie dosłowne, z wyjątkiem dosłownego sznurka, jest pralue (i.mi., wyrażenie pralera). Tak więc dosłowność, taka jak 58 lub 58.53, czyli prawda lub fałsz, jest pralue. Literał może być użyty do zainicjowania obiektu lub obliczałby dla siebie (w innej formie 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 string jest literał, a nie pralela? Rozważ następujący kod:

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

Wyjście to:

Miłość nie nienawiść
N

STR identyfikuje cały ciąg. A więc wyrażenie, str, a nie to, co identyfikuje, jest lValue. Każdy znak w ciągu może być zidentyfikowany przez Str [i], gdzie i jest indeksem. Wyrażenie, STR [5], a nie znak, który identyfikuje, jest lValue. Literał sznurka jest lValue, a nie pralelem.

W poniższym stwierdzeniu literał tablicy inicjuje obiekt, ARR:

ptrint ++ lub ptrint--

Tutaj Ptrint jest wskaźnikiem do lokalizacji całkowitej. Całe wyrażenie, a nie końcową wartość lokalizacji, do której wskazuje, jest pralue (wyrażenie). Wynika to z faktu, że wyrażenie, ptrint ++ lub ptrint-, identyfikuje pierwszą pierwszą wartość jej lokalizacji, a nie drugą ostateczną wartość tej samej lokalizacji. Z drugiej strony -ptrint lub -ptrint jest lValue, ponieważ identyfikuje jedyną wartość zainteresowania lokalizacją. Innym sposobem patrzenia na to jest to, że pierwotna wartość oblicza drugą wartość końcową.

W drugim stwierdzeniu następującego kodu A lub B można nadal uznać za pralue:

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

Tak więc A lub B w drugiej instrukcji jest LValue, ponieważ identyfikuje obiekt. Jest to również pralege, ponieważ oblicza się do liczby całkowitej operandu dla operatora dodatku.

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

int *ptrint = new int

Tutaj *ptrint to lValue, podczas gdy (nowy int) to pralue. Pamiętaj, że lValue lub pralue jest wyrażeniem. (nowy int) nie identyfikuje żadnego obiektu. Zwrócenie adresu nie oznacza identyfikacji obiektu o nazwie (np. Ident, powyżej). W *ptrint nazwa, ptrint, jest to, co naprawdę identyfikuje obiekt, więc *ptrint jest lvalue. Z drugiej strony (New Int) to Prwari, ponieważ oblicza nową lokalizację pod adresem wartości operandowej dla operatora przypisania =.

XValue

Dzisiaj LVALUE oznacza wartość lokalizacji; PrValue oznacza „czystą” RValue (zobacz, co oznacza RValue poniżej). Dzisiaj XValue oznacza „wygasając” LVALUE.

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

„XValue to grewacja, która oznacza obiekt lub pole bitowe, którego zasoby można ponownie wykorzystać (zwykle dlatego, że znajduje się pod koniec swojego życia). [Przykład: Niektóre rodzaje wyrażeń obejmujących odniesienia RVALUE dają xalues, takie jak wywołanie funkcji, której typ zwrócony jest odniesienie RVALUE lub rzucanie na przykład referencyjny RVALUE- End End] ”

Oznacza to, że zarówno LValue, jak i PrValue mogą wygasnąć. Poniższy kod (skopiowany z góry) pokazuje, w jaki sposób pamięć (zasób) lValue, *ptrint jest ponownie wykorzystywany po usunięciu.

int *ptrint = new int;
*ptrint = 12;
Cout<< *ptrInt <<'\n';
usuń ptrint;
Cout<< *ptrInt <<'\n';
*ptrint = 24;
Cout<< *ptrInt <<'\n';

Wyjście to:

12
0
24

Poniższy program (skopiowany z góry) pokazuje, w jaki sposób przechowywanie odniesienia całkowitego, które jest odniesieniem LVALUE zwrócone przez funkcję, jest ponownie używany w funkcji Main ():

#włączać
za pomocą przestrzeni nazw Std;
int & fn ()
int i = 5;
int & j = i;
powrót J;

int main ()
int & myint = fn ();
Cout<< myInt <<'\n';
myint = 17;
Cout<< myInt <<'\n';
powrót 0;

Wyjście to:

5
17

Kiedy obiekt taki jak I w funkcji FN () wychodzi z zakresu, naturalnie zostaje zniszczony. W takim przypadku przechowywanie I nadal zostało ponownie używane w funkcji Main ().

Powyższe dwie próbki kodów ilustrują ponowne wykorzystanie przechowywania LVALUES. Możliwe jest ponowne użycie przechowywania PrALUE (RVALUES) (patrz później).

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

„Ogólnie rzecz biorąc, efektem tej zasady jest to, że nazwane odniesienia RVALUE są traktowane jako LVALEES, a bezimienne odniesienia RVALUE do obiektów są traktowane jako XValues. Odniesienia RVALUE do funkcji są traktowane jako LVALES." (Zobaczymy później).

Zatem XValue to LVALE lub PrAlue, której zasoby (pamięć) można ponownie wykorzystać. XVALUES to zestaw przecięcia LVALUES i PRALUES.

XValue ma coś więcej niż to, co zostało rozwiązane w tym artykule. Jednak XValue zasługuje na sam cały artykuł, więc dodatkowe specyfikacje dla XValue nie są adresowane w tym artykule.

Zestaw taksonomii kategorii wyrażeń

Kolejny cytat ze specyfikacji C ++:

"Notatka: Historycznie, LVALUES i RVAUES były tak zwane, ponieważ mogły pojawić się po lewej i prawej stronie zadania (chociaż nie jest to już ogólnie prawdziwe); GLAVELES to „uogólnione” Lalues, praledy to „czyste” RValues, a ksvalues ​​„wygasają”. Pomimo ich nazwisk te terminy klasyfikują wyrażenia, a nie wartości. - Notatka końcowa ”

Tak więc GLAVELUES ​​jest zbiorem Unii LVALUES i XVALUES I RAVEUES. XVALUES to zestaw przecięcia LVALUES i PRALUES.

Na razie taksonomia kategorii wyrażenia jest lepiej zilustrowana diagramem Venna w następujący sposób:

Wniosek

Lowca jest wyrażeniem, którego ocena określa tożsamość obiektu, pola bitowego lub funkcji.

PrValue jest wyrażeniem, którego ocena inicjuje obiekt lub pole bitowe lub oblicza wartość operandu operatora, jak określono w kontekście, w którym się pojawia.

XValue to lValue lub pralue, z dodatkową właściwością, że jej zasoby (pamięć) można ponownie wykorzystać.

Specyfikacja C ++ ilustruje taksonomię kategorii wyrażeń ze schematem drzewa, wskazując, że w taksonomii istnieje pewna hierarchia. Na razie nie ma hierarchii w taksonomii, więc niektórzy autorzy używają schematu Venna, ponieważ ilustruje taksonomię lepiej niż diagram drzewa.