C++

Tassonomia delle categorie di espressioni in C++

Tassonomia delle categorie di espressioni in C++

Un calcolo è qualsiasi tipo di calcolo che segue un algoritmo ben definito. Un'espressione è una sequenza di operatori e operandi che specifica un calcolo. In altre parole, un'espressione è un identificatore o un letterale, o una sequenza di entrambi, uniti da operatori.Nella programmazione, un'espressione può dare come risultato un valore e/o causare qualche evento. Quando risulta in un valore, l'espressione è un glvalue, rvalue, lvalue, xvalue o prvalue. Ognuna di queste categorie è un insieme di espressioni. Ogni insieme ha una definizione e situazioni particolari in cui prevale il suo significato, differenziandolo da un altro insieme. Ogni set è chiamato una categoria di valore.

Nota: Un valore o letterale è ancora un'espressione, quindi questi termini classificano le espressioni e non i veri valori really.

glvalue e rvalue sono i due sottoinsiemi dell'espressione big set. glvalue esiste in due ulteriori sottoinsiemi: lvalue e xvalue. rvalue, l'altro sottoinsieme per l'espressione, esiste anche in altri due sottoinsiemi: xvalue e prvalue. Quindi, xvalue è un sottoinsieme di glvalue e rvalue: cioè, xvalue è l'intersezione di glvalue e rvalue. Il seguente diagramma di tassonomia, tratto dalla specifica C++, illustra la relazione di tutti gli insiemi:

prvalue, xvalue e lvalue sono i valori della categoria principale. glvalue è l'unione di lvalue e xvalue, mentre rvalue è l'unione di xvalue e prvalue.

Hai bisogno di conoscenze di base in C++ per capire questo articolo; è necessaria anche la conoscenza di Scope in C++.

Contenuto dell'articolo

Nozioni di base

Per comprendere veramente la tassonomia delle categorie di espressioni, è necessario ricordare o conoscere prima le seguenti caratteristiche di base: posizione e oggetto, archiviazione e risorsa, inizializzazione, identificatore e riferimento, riferimenti lvalue e rvalue, puntatore, archivio gratuito e riutilizzo di risorsa.

Posizione e oggetto

Considera la seguente dichiarazione:

int ident;

Questa è una dichiarazione che identifica una posizione in memoria. Una locazione è un particolare insieme di byte consecutivi in ​​memoria. Una posizione può essere composta da un byte, due byte, quattro byte, sessantaquattro byte, ecc. La posizione di un numero intero per una macchina a 32 bit è quattro byte. Inoltre, la posizione può essere identificata da un identificatore.

Nella dichiarazione di cui sopra, la posizione non ha alcun contenuto. Vuol dire che non ha alcun valore, poiché il contenuto è il valore. Quindi, un identificatore identifica una posizione (piccolo spazio continuo). Quando alla posizione viene assegnato un particolare contenuto, l'identificatore identifica quindi sia la posizione che il contenuto; cioè, l'identificatore identifica quindi sia la posizione che il valore.

Considera le seguenti affermazioni:

int ident1 = 5;
int ident2 = 100;

Ognuna di queste affermazioni è una dichiarazione e una definizione. Il primo identificatore ha il valore (contenuto) 5 e il secondo identificatore ha il valore 100. In una macchina a 32 bit, ciascuna di queste posizioni è lunga quattro byte. Il primo identificatore identifica sia una posizione che un valore. Anche il secondo identificatore identifica entrambi.

Un oggetto è una regione denominata di memoria in memoria. Quindi, un oggetto è una posizione senza un valore o una posizione con un valore.

Archiviazione di oggetti e risorse

La posizione di un oggetto è anche chiamata memoria o risorsa dell'oggetto.

Inizializzazione

Considera il seguente segmento di codice:

int ident;
ident = 8;

La prima riga dichiara un identificatore. Questa dichiarazione fornisce una posizione (memoria o risorsa) per un oggetto intero, identificandolo con il nome, ident. La riga successiva inserisce il valore 8 (in bit) nella posizione identificata da ident. L'inserimento di questo valore è l'inizializzazione.

La seguente istruzione definisce un vettore con contenuto, 1, 2, 3, 4, 5, identificato da vtr:

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

Qui, l'inizializzazione con 1, 2, 3, 4, 5 viene eseguita nella stessa istruzione della definizione (dichiarazione). L'operatore di assegnazione non viene utilizzato. La seguente istruzione definisce un array con contenuto 1, 2, 3, 4, 5:

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

Questa volta, per l'inizializzazione è stato utilizzato un operatore di assegnazione.

Identificatore e riferimento

Considera il seguente segmento di codice:

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

L'uscita è:

4 4 4

ident è un identificatore, mentre ref1 e ref2 sono riferimenti; fanno riferimento alla stessa posizione. Un riferimento è sinonimo di un identificatore. Convenzionalmente, ref1 e ref2 sono nomi diversi di un oggetto, mentre ident è l'identificatore dello stesso oggetto. Tuttavia, ident può ancora essere chiamato il nome dell'oggetto, il che significa che ident, ref1 e ref2 chiamano la stessa posizione.

La differenza principale tra un identificatore e un riferimento è che, se passato come argomento a una funzione, se passato per identificatore, viene fatta una copia per l'identificatore nella funzione, mentre se passato per riferimento, viene utilizzata la stessa posizione all'interno della funzione. Quindi, passando per identificatore finisce con due posizioni, mentre passando per riferimento finisce con la stessa posizione same.

Riferimento lvalue e Riferimento rvalue

Il modo normale per creare un riferimento è il seguente:

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

La memoria (risorsa) viene prima localizzata e identificata (con un nome come ident), quindi viene fatto un riferimento (con un nome come ref). Quando si passa come argomento ad una funzione, verrà fatta una copia dell'identificatore nella funzione, mentre nel caso di un riferimento, nella funzione verrà utilizzata (riferita) la posizione originale.

Oggi è possibile avere solo un riferimento senza identificarlo. Ciò significa che è possibile creare prima un riferimento senza avere un identificatore per la posizione. Questo utilizza &&, come mostrato nella seguente dichiarazione:

int&& ref = 4;

Qui, non c'è alcuna identificazione precedente. Per accedere al valore dell'oggetto, usa semplicemente ref come useresti l'ident sopra.

Con la dichiarazione &&, non è possibile passare un argomento a una funzione tramite identificatore. L'unica scelta è passare per riferimento. In questo caso, c'è solo una posizione utilizzata all'interno della funzione e non la seconda posizione copiata come con un identificatore.

Una dichiarazione di riferimento con & è chiamata lvalue reference. Una dichiarazione di riferimento con && è chiamata riferimento rvalue, che è anche un riferimento prvalue (vedi sotto).

puntatore

Considera il seguente codice:

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

L'uscita è 5.

Qui, ptdInt è un identificatore come l'ident sopra. Ci sono due oggetti (posizioni) qui invece di uno: l'oggetto puntato, ptdInt identificato da ptdInt, e l'oggetto puntatore, ptrInt identificato da ptrInt. &ptdInt restituisce l'indirizzo dell'oggetto puntato e lo inserisce come valore nell'oggetto puntatore ptrInt. Per restituire (ottenere) il valore dell'oggetto puntato, utilizzare l'identificatore per l'oggetto puntatore, come in "*ptrInt".

Nota: ptdInt è un identificatore e non un riferimento, mentre il nome, ref, menzionato in precedenza, è un riferimento.

La seconda e la terza riga del codice sopra possono essere ridotte a una riga, portando al seguente codice:

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

Nota: Quando un puntatore viene incrementato, punta alla posizione successiva, che non è un'aggiunta del valore 1. Quando un puntatore viene decrementato, punta alla posizione precedente, che non è una sottrazione del valore 1.

Negozio gratuito

Un sistema operativo alloca memoria per ogni programma in esecuzione. Una memoria che non è allocata ad alcun programma è nota come free store. L'espressione che restituisce una posizione per un numero intero dall'archivio gratuito è:

nuovo int

Restituisce una posizione per un numero intero non identificato. Il codice seguente illustra come utilizzare il puntatore con l'archivio gratuito:

int *ptrInt = nuovo int;
*ptrInt = 12;
cout<< *ptrInt  <<'\n';

L'uscita è 12.

Per distruggere l'oggetto, utilizzare l'espressione delete come segue:

elimina ptrInt;

L'argomento dell'espressione delete è un puntatore. Il codice seguente ne illustra l'utilizzo:

int *ptrInt = nuovo int;
*ptrInt = 12;
elimina ptrInt;
cout<< *ptrInt <<'\n';

L'uscita è 0, e niente come null o undefined. elimina sostituisce il valore per la posizione con il valore predefinito del particolare tipo di posizione, quindi consente il riutilizzo della posizione. Il valore predefinito per una posizione int è 0.

Riutilizzare una risorsa

Nella tassonomia delle categorie di espressioni, riutilizzare una risorsa equivale a riutilizzare una posizione o un archivio per un oggetto. Il codice seguente illustra come è possibile riutilizzare una posizione dal negozio gratuito:

int *ptrInt = nuovo int;
*ptrInt = 12;
cout<< *ptrInt <<'\n';
elimina ptrInt;
cout<< *ptrInt <<'\n';
*ptrInt = 24;
cout<< *ptrInt <<'\n';

L'uscita è:

12
0
24

Un valore di 12 viene prima assegnato alla posizione non identificata. Quindi il contenuto della posizione viene eliminato (in teoria l'oggetto viene eliminato). Il valore di 24 viene riassegnato alla stessa posizione.

Il seguente programma mostra come viene riutilizzato un riferimento intero restituito da una funzione:

#includere
usando lo spazio dei nomi std;
int&fn()

int i = 5;
int& j = io;
restituire j;

intero principale()

int& mioInt = fn();
cout<< myInt <<'\n';
mioInt = 17;
cout<< myInt <<'\n';
restituisce 0;

L'uscita è:

5
17

Un oggetto come i, dichiarato in un ambito locale (scopo della funzione), cessa di esistere alla fine dell'ambito locale. Tuttavia, la funzione fn() sopra, restituisce il riferimento di i. Attraverso questo riferimento restituito, il nome, myInt nella funzione main(), riutilizza la posizione identificata da i per il valore 17.

lvalue

Un lvalue è un'espressione la cui valutazione determina l'identità di un oggetto, un campo di bit o una funzione. L'identità è un'identità ufficiale come ident sopra, o un nome di riferimento lvalue, un puntatore o il nome di una funzione. Considera il seguente codice che funziona:

int mioInt = 512;
int& mioRif = mioInt;
int* ptr = &myInt;
int fn()

++ptr; --ptr;
restituire mioInt;

Qui, myInt è un lvalue; myRef è un'espressione di riferimento lvalue; *ptr è un'espressione lvalue perché il suo risultato è identificabile con ptr; ++ptr o -ptr è un'espressione lvalue perché il suo risultato è identificabile con il nuovo stato (indirizzo) di ptr e fn è un lvalue (espressione).

Considera il seguente segmento di codice:

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

Nella seconda istruzione, la posizione per 'a' ha 2 ed è identificabile da 'a', e così è un lvalue. La posizione di b ha 8 ed è identificabile da b, così come un lvalue. La posizione per c avrà la somma ed è identificabile da c, e così è un lvalue. Nella seconda affermazione, le espressioni o i valori di 16 e 64 sono valori r (vedi sotto).

Considera il seguente segmento di codice:

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

L'uscita è 'v';

seq è un array. La posizione per 'v' o qualsiasi valore simile nell'array è identificata da seq[i], dove i è un indice. Quindi, l'espressione, seq[i], è un'espressione lvalue. seq, che è l'identificatore per l'intero array, è anche un lvalue.

valore

Un prvalue è un'espressione la cui valutazione inizializza un oggetto o un campo di bit o calcola il valore dell'operando di un operatore, come specificato dal contesto in cui appare.

Nella dichiarazione,

int mioInt = 256;

256 è un prvalue (espressione prvalue) che inizializza l'oggetto identificato da myInt. Questo oggetto non è referenziato.

Nella dichiarazione,

int&& ref = 4;

4 è un prvalue (espressione prvalue) che inizializza l'oggetto a cui fa riferimento ref. Questo oggetto non è identificato ufficialmente. ref è un esempio di espressione di riferimento rvalue o espressione di riferimento prvalue; è un nome, ma non un identificatore ufficiale.

Considera il seguente segmento di codice:

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

6 è un prvalue che inizializza l'oggetto identificato da ident; l'oggetto è referenziato anche da ref. Qui, il ref è un riferimento lvalue e non un riferimento prvalue.

Considera il seguente segmento di codice:

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

15 e 63 sono ciascuna una costante che calcola se stessa, producendo un operando (in bit) per l'operatore di addizione. Quindi, 15 o 63 è un'espressione di valore.

Qualsiasi letterale, eccetto il letterale stringa, è un prvalue (i.e., un'espressione di valore). Quindi, un letterale come 58 o 58.53, o vero o falso, è un prvalue. Un letterale può essere utilizzato per inizializzare un oggetto o calcola da solo (in qualche altra forma in bit) come valore di un operando per un operatore. Nel codice sopra, il letterale 2 inizializza l'oggetto, a. Si calcola anche come operando per l'operatore di assegnazione.

Perché una stringa letterale non è un prvalue?? Considera il seguente codice:

char str[] = "ama non odia";
cout << str <<'\n';
cout << str[5] <<'\n';

L'uscita è:

amare non odiare
n

str identifica l'intera stringa. Quindi, l'espressione, str, e non ciò che identifica, è un lvalue. Ogni carattere nella stringa può essere identificato da str[i], dove i è un indice. L'espressione, str[5], e non il carattere che identifica, è un lvalue. Il letterale stringa è un lvalue e non un prvalue.

Nella seguente istruzione, un letterale array inizializza l'oggetto, arr:

ptrInt++ o ptrInt-- 

Qui, ptrInt è un puntatore a una posizione intera. L'intera espressione, e non il valore finale della posizione a cui punta, è un prvalue (espressione). Questo perché l'espressione, ptrInt++ o ptrInt-, identifica il primo valore originale della sua posizione e non il secondo valore finale della stessa posizione. D'altra parte, -ptrInt o  -ptrInt è un lvalue perché identifica l'unico valore dell'interesse nella posizione. Un altro modo di vederlo è che il valore originale calcola il secondo valore finale.

Nella seconda istruzione del codice seguente, a o b può ancora essere considerato come un prvalue:

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

Quindi, aob nella seconda istruzione è un lvalue perché identifica un oggetto. È anche un prvalue poiché calcola l'intero di un operando per l'operatore di addizione.

(new int), e non la posizione che stabilisce è un prvalue. Nella seguente istruzione, l'indirizzo di ritorno della posizione è assegnato a un oggetto puntatore:

int *ptrInt = nuovo int

Qui, *ptrInt è un lvalue, mentre (new int) è un prvalue. Ricorda, un lvalue o un prvalue è un'espressione. (new int) non identifica alcun oggetto. Restituire l'indirizzo non significa identificare l'oggetto con un nome (come ident, sopra). In *ptrInt, il nome, ptrInt, è ciò che identifica realmente l'oggetto, quindi *ptrInt è un lvalue. D'altra parte, (new int) è un prvalue, poiché calcola una nuova posizione a un indirizzo di valore operando per l'operatore di assegnazione =.

xvalue

Oggi, lvalue sta per Location Value; prvalue sta per rvalue "puro" (vedi sotto cosa significa rvalue). Oggi, xvalue sta per "eXpiring" lvalue.

La definizione di xvalue, citata dalla specifica C++, è la seguente:

“Un xvalue è un glvalue che denota un oggetto o un campo di bit le cui risorse possono essere riutilizzate (di solito perché è vicino alla fine della sua vita). [Esempio: alcuni tipi di espressioni che coinvolgono riferimenti rvalue producono xvalue, come una chiamata a una funzione il cui tipo restituito è un riferimento rvalue o un cast a un tipo di riferimento rvalue - esempio finale]”

Ciò significa che sia lvalue che prvalue possono scadere. Il codice seguente (copiato dall'alto) mostra come viene riutilizzata la memoria (risorsa) di lvalue, *ptrInt dopo che è stata eliminata.

int *ptrInt = nuovo int;
*ptrInt = 12;
cout<< *ptrInt <<'\n';
elimina ptrInt;
cout<< *ptrInt <<'\n';
*ptrInt = 24;
cout<< *ptrInt <<'\n';

L'uscita è:

12
0
24

Il seguente programma (copiato dall'alto) mostra come la memorizzazione di un riferimento intero, che è un riferimento lvalue restituito da una funzione, viene riutilizzata nella funzione main():

#includere
usando lo spazio dei nomi std;
int&fn()

int i = 5;
int& j = io;
restituire j;

intero principale()

int& mioInt = fn();
cout<< myInt <<'\n';
mioInt = 17;
cout<< myInt <<'\n';
restituisce 0;

L'uscita è:

5
17

Quando un oggetto come i nella funzione fn() esce dall'ambito, viene naturalmente distrutto. In questo caso, la memoria di i è stata ancora riutilizzata nella funzione main().

I due esempi di codice precedenti illustrano il riutilizzo della memorizzazione di lvalues. È possibile avere un riutilizzo della memoria di prvalues ​​(rvalues) (vedi oltre).

La seguente citazione relativa a xvalue proviene dalla specifica C++:

“In generale, l'effetto di questa regola è che i riferimenti rvalue con nome sono trattati come lvalue e i riferimenti rvalue senza nome agli oggetti sono trattati come xvalue. i riferimenti rvalue alle funzioni sono trattati come lvalue, denominati o meno.” (vedi più avanti).

Quindi, un xvalue è un lvalue o un prvalue le cui risorse (archiviazione) possono essere riutilizzate. xvalues ​​è l'insieme di intersezione di lvalue e prvalue.

C'è di più in xvalue di quello che è stato affrontato in questo articolo. Tuttavia, xvalue merita un intero articolo da solo, quindi le specifiche extra per xvalue non sono trattate in questo articolo.

Set di tassonomia delle categorie di espressioni

Un'altra citazione dalla specifica C++:

Nota: Storicamente, lvalue e rvalue erano così chiamati perché potevano apparire sul lato sinistro e destro di un'assegnazione (sebbene questo non sia più generalmente vero); glvalues ​​sono lvalue “generalizzati”, prvalues ​​sono rvalue “puri” e xvalues ​​sono lvalue “eXpiring”. Nonostante i loro nomi, questi termini classificano le espressioni, non i valori. - nota finale”

Quindi, glvalues ​​è l'insieme di unione di lvalue e xvalue e rvalues ​​sono l'insieme di unione di xvalue e prvalue. xvalues ​​è l'insieme di intersezione di lvalue e prvalue.

A partire da ora, la tassonomia della categoria dell'espressione è illustrata meglio con un diagramma di Venn come segue:

Conclusione

Un lvalue è un'espressione la cui valutazione determina l'identità di un oggetto, un campo di bit o una funzione.

Un prvalue è un'espressione la cui valutazione inizializza un oggetto o un campo di bit o calcola il valore dell'operando di un operatore, come specificato dal contesto in cui appare.

Un xvalue è un lvalue o un prvalue, con la proprietà aggiuntiva che le sue risorse (storage) possono essere riutilizzate.

La specifica C++ illustra la tassonomia delle categorie di espressioni con un diagramma ad albero, indicando che esiste una gerarchia nella tassonomia. A partire da ora, non esiste una gerarchia nella tassonomia, quindi un diagramma di Venn viene utilizzato da alcuni autori, poiché illustra la tassonomia meglio del diagramma ad albero.

Le migliori app di mappatura del gamepad per Linux
Se ti piace giocare su Linux con un gamepad invece di un tipico sistema di input di tastiera e mouse, ci sono alcune app utili per te. Molti giochi pe...
Strumenti utili per i giocatori Linux
Se ti piace giocare su Linux, è probabile che tu abbia utilizzato app e utilità come Wine, Lutris e OBS Studio per migliorare l'esperienza di gioco. O...
Giochi rimasterizzati in HD per Linux che non hanno mai avuto una versione Linux prima
Molti sviluppatori ed editori di giochi stanno realizzando remaster HD di vecchi giochi per prolungare la vita del franchise, per favore i fan richied...