Catégorie d'expression Taxonomie en C ++

Catégorie d'expression Taxonomie en C ++
Un calcul est tout type de calcul qui suit un algorithme bien défini. Une expression est une séquence d'opérateurs et d'opérandes qui spécifie un calcul. En d'autres termes, une expression est un identifiant ou un littéral, ou une séquence des deux, rejoints par des opérateurs.En programmation, une expression peut entraîner une valeur et / ou provoquer un certain temps. Lorsqu'il entraîne une valeur, l'expression est une glvalue, une valeur, un lvalue, un xvalue ou un prvalue. Chacune de ces catégories est un ensemble d'expressions. Chaque ensemble a une définition et des situations particulières où sa signification prévaut, la différenciant d'un autre ensemble. Chaque ensemble est appelé une catégorie de valeur.

Note: Une valeur ou un littéral est toujours une expression, donc ces termes classent les expressions et ne sont pas vraiment des valeurs.

GlValue et Rvalue sont les deux sous-ensembles de l'expression de Big Set. Glvalue existe dans deux autres sous-ensembles: Lvalue et Xvalue. RValue, l'autre sous-ensemble d'expression, existe également dans deux autres sous-ensembles: xvalue et prvalue. Ainsi, XValue est un sous-ensemble de Glvalue et de Rvalue: c'est-à-dire que XValue est l'intersection de Glvalue et Rvalue. Le diagramme de taxonomie suivant, tiré de la spécification C ++, illustre la relation de tous les ensembles:

PRVALUE, XVALUE et LVALUE sont les principales valeurs de catégorie. Glvalue est l'union des lvalues ​​et des xvalues, tandis que les valeurs sont l'union des xvalues ​​et des prvalues.

Vous avez besoin de connaissances de base en C ++ afin de comprendre cet article; Vous avez également besoin de connaissances sur la portée en C++.

Contenu de l'article

  • Bases
  • se répercuter
  • faire une préparation
  • xvalue
  • Ensemble de taxonomie de catégorie d'expression
  • Conclusion

Bases

Pour vraiment comprendre la taxonomie de la catégorie d'expression, vous devez vous rappeler ou connaître les fonctionnalités de base suivantes d'abord: emplacement et objet, stockage et ressources, initialisation, identifiant et référence, références LVALUe et référence, pointeur, magasin gratuit et réutilisation d'un Ressource.

Emplacement et objet

Considérez la déclaration suivante:

int identif;

Ceci est une déclaration qui identifie un emplacement en mémoire. Un emplacement est un ensemble particulier d'octets consécutifs en mémoire. Un emplacement peut comprendre un octet, deux octets, quatre octets, soixante-quatre octets, etc. L'emplacement pour un entier pour une machine 32 bits est de quatre octets. De plus, l'emplacement peut être identifié par un identifiant.

Dans la déclaration ci-dessus, l'emplacement n'a aucun contenu. Cela signifie qu'il n'a pas de valeur, car le contenu est la valeur. Ainsi, un identifiant identifie un emplacement (petit espace continu). Lorsque l'emplacement reçoit un contenu particulier, l'identifiant identifie alors à la fois l'emplacement et le contenu; c'est-à-dire que l'identifiant identifie alors à la fois l'emplacement et la valeur.

Considérez les affirmations suivantes:

int identi1 = 5;
int identif2 = 100;

Chacune de ces déclarations est une déclaration et une définition. Le premier identifiant a la valeur (contenu) 5, et le deuxième identifiant a la valeur 100. Dans une machine à 32 bits, chacun de ces emplacements fait quatre octets de long. Le premier identifiant identifie à la fois un emplacement et une valeur. Le deuxième identifiant identifie également les deux.

Un objet est une région de stockage nommée en mémoire. Ainsi, un objet est soit un emplacement sans valeur ou un emplacement avec une valeur.

Stockage et ressource d'objet

L'emplacement d'un objet est également appelé stockage ou ressource de l'objet.

Initialisation

Considérez le segment de code suivant:

int identif;
identifère = 8;

La première ligne déclare un identifiant. Cette déclaration fournit un emplacement (stockage ou ressource) pour un objet entier, en l'identifiant avec le nom, identifiant. La ligne suivante place la valeur 8 (en bits) dans l'emplacement identifié par identi. La mise en place de cette valeur est l'initialisation.

L'instruction suivante définit un vecteur avec du contenu, 1, 2, 3, 4, 5, identifié par Vtr:

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

Ici, l'initialisation avec 1, 2, 3, 4, 5 se fait dans la même déclaration de la définition (déclaration). L'opérateur d'affectation n'est pas utilisé. L'instruction suivante définit un tableau avec du contenu 1, 2, 3, 4, 5:

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

Cette fois, un opérateur d'affectation a été utilisé pour l'initialisation.

Identifiant et référence

Considérez le segment de code suivant:

int identif = 4;
Int & Ref1 = Identity;
int & Ref2 = identifier;
couter<< ident <<"<< ref1 <<"<< ref2 << '\n';

La sortie est:

4 4 4

Identify est un identifiant, tandis que Ref1 et Ref2 sont des références; Ils font référence au même emplacement. Une référence est un synonyme d'un identifiant. Conventionnellement, REF1 et Ref2 sont des noms différents d'un objet, tandis que l'identifiant est l'identifiant du même objet. Cependant, l'identin peut toujours être appelé le nom de l'objet, ce qui signifie, identi, ref1, et ref2 nommez le même emplacement.

La principale différence entre un identifiant et une référence est que, lorsqu'il est passé comme argument à une fonction, s'il est passé par identifiant, une copie est faite pour l'identifiant dans la fonction, tandis que s'il est passé par référence, le même emplacement est utilisé dans le fonction. Donc, passer par identifiant se retrouve avec deux emplacements, tandis que le passage par référence se retrouve avec le même seul emplacement.

référence lvalue et référence

La façon normale de créer une référence est la suivante:

int identif;
identifère = 4;
int & ref = identif;

Le stockage (ressource) est situé et identifié d'abord (avec un nom tel que identique), puis une référence (avec un nom tel qu'une réf). Lorsque vous passez en tant qu'argument à une fonction, une copie de l'identifiant sera faite dans la fonction, tandis que pour le cas d'une référence, l'emplacement d'origine sera utilisé (mentionné) dans la fonction.

Aujourd'hui, il est possible d'avoir une référence sans l'identifier. Cela signifie qu'il est possible de créer une référence d'abord sans avoir d'identifiant pour l'emplacement. Ceci utilise &&, comme indiqué dans l'instruction suivante:

int && ref = 4;

Ici, il n'y a pas d'identification précédente. Pour accéder à la valeur de l'objet, utilisez simplement REF comme vous utiliseriez l'identité ci-dessus.

Avec la déclaration &&, il n'y a aucune possibilité de transmettre un argument à une fonction par identifiant. Le seul choix est de passer par référence. Dans ce cas, il n'y a qu'un seul emplacement utilisé dans la fonction et non le deuxième emplacement copié comme avec un identifiant.

Une déclaration de référence avec et est appelée référence lvalue. Une déclaration de référence avec && est appelée référence de Rvalue, qui est également une référence de PRValue (voir ci-dessous).

Aiguille

Considérez le code suivant:

int ptdint = 5;
int * ptrint;
ptrint = &ptdInt;
couter<< *ptrInt <<'\n';

La sortie est 5.

Ici, Ptdint est un identifiant comme l'identité ci-dessus. Il y a deux objets (emplacements) ici au lieu d'un: l'objet pointu, le ptdint identifié par Ptdint, et l'objet de pointeur, ptrint identifié par ptrint. & Ptdint renvoie l'adresse de l'objet pointu et le met comme la valeur de l'objet Poiner Ptrint. Pour retourner (obtenir) la valeur de l'objet pointu, utilisez l'identifiant pour l'objet Pointer, comme dans «* ptrint».

Note: Ptdint est un identifiant et non une référence, tandis que le nom, ref, mentionné précédemment, est une référence.

Les deuxième et troisième lignes du code ci-dessus peuvent être réduites à une ligne, conduisant au code suivant:

int ptdint = 5;
int * ptrint = &ptdInt;
couter<< *ptrInt <<'\n';

Note: Lorsqu'un pointeur est incrémenté, il pointe vers l'emplacement suivant, qui n'est pas un ajout de la valeur 1. Lorsqu'un pointeur est décrémenté, il pointe vers l'emplacement précédent, qui n'est pas une soustraction de la valeur 1.

Magasin gratuit

Un système d'exploitation alloue la mémoire pour chaque programme qui s'exécute. Une mémoire qui n'a été allouée à aucun programme est connue sous le nom de magasin gratuit. L'expression qui renvoie un emplacement pour un entier du magasin gratuit est:

Nouveau Int

Cela renvoie un emplacement pour un entier qui n'est pas identifié. Le code suivant illustre comment utiliser le pointeur avec le magasin gratuit:

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

La sortie est 12.

Pour détruire l'objet, utilisez l'expression de suppression comme suit:

supprimer des ptrints;

L'argument à l'expression de suppression est un pointeur. Le code suivant illustre son utilisation:

int * ptrint = new int;
* ptrint = 12;
supprimer des ptrints;
couter<< *ptrInt <<'\n';

La sortie est 0, et rien de tel que null ou indéfini. Supprimer remplace la valeur de l'emplacement par la valeur par défaut du type particulier de l'emplacement, puis permet l'emplacement de réutilisation. La valeur par défaut pour un emplacement int est 0.

Réutiliser une ressource

Dans la taxonomie de catégorie d'expression, la réutilisation d'une ressource est la même que la réutilisation d'un emplacement ou d'un stockage pour un objet. Le code suivant illustre comment un emplacement du magasin gratuit peut être réutilisé:

int * ptrint = new int;
* ptrint = 12;
couter<< *ptrInt <<'\n';
supprimer des ptrints;
couter<< *ptrInt <<'\n';
* ptrint = 24;
couter<< *ptrInt <<'\n';

La sortie est:

12
0
24

Une valeur de 12 est d'abord affectée à l'emplacement non identifié. Ensuite, le contenu de l'emplacement est supprimé (en théorie, l'objet est supprimé). La valeur de 24 est réaffectée au même endroit.

Le programme suivant montre comment une référence entière renvoyée par une fonction est réutilisée:

#inclure
Utilisation de Namespace Std;
int & fn ()
int i = 5;
int & j = i;
retour j;

int main()
int & myInt = fn ();
couter<< myInt <<'\n';
myInt = 17;
couter<< myInt <<'\n';
retour 0;

La sortie est:

5
17

Un objet tel que I, déclaré dans une portée locale (portée de la fonction), cesse d'exister à la fin de la portée locale. Cependant, la fonction fn () ci-dessus, renvoie la référence de i. Grâce à cette référence retournée, le nom, MyInt dans la fonction Main (), réutilise l'emplacement identifié par I pour la valeur 17.

se répercuter

Une lvalue est une expression dont l'évaluation détermine l'identité d'un objet, d'un champ de bit ou d'une fonction. L'identité est une identité officielle comme IDD ci-dessus, ou un nom de référence Lvalue, un pointeur ou le nom d'une fonction. Considérez le code suivant qui fonctionne:

int myInt = 512;
int & myref = myInt;
int * ptr = &myInt;
int fn ()
++ptr; --PTR;
Renvoie monint;

Ici, MyInt est un lvalue; MyRef est une expression de référence lvalue; * PTR est une expression Lvalue car son résultat est identifiable avec PTR; ++ PTR ou -PTR est une expression LVALUE car son résultat est identifiable avec le nouvel état (adresse) de PTR, et FN est un LVALUE (expression).

Considérez le segment de code suivant:

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

Dans la deuxième déclaration, l'emplacement de «a» a 2 et est identifiable par «a», et c'est ainsi qu'un lvalue. L'emplacement de B a 8 et est identifiable par B, tout comme un lvalue. L'emplacement pour C aura la somme, et est identifiable par C, tout comme un lvalue. Dans la deuxième déclaration, les expressions ou les valeurs de 16 et 64 sont des values ​​(voir ci-dessous).

Considérez le segment de code suivant:

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

La sortie est 'V';;

Seq est un tableau. L'emplacement pour «V» ou toute valeur similaire dans le tableau est identifié par SEQ [i], où I est un index. Donc, l'expression, seq [i], est une expression lvalue. SEQ, qui est l'identifiant de l'ensemble du tableau, est également un lvalue.

faire une préparation

Un prvalue est une expression dont l'évaluation initialise un objet ou un champ de bit ou calcule la valeur de l'opérande d'un opérateur, comme spécifié par le contexte dans lequel il apparaît.

Dans la déclaration,

int myInt = 256;

256 est un prvalue (expression prvalue) qui initialise l'objet identifié par myInt. Cet objet n'est pas référencé.

Dans la déclaration,

int && ref = 4;

4 est un prvalue (expression de PRValue) qui initialise l'objet référencé par Ref. Cet objet n'est pas identifié officiellement. REF est un exemple d'une expression de référence de la valeur ou d'une expression de référence de PRValue; c'est un nom, mais pas un identifiant officiel.

Considérez le segment de code suivant:

int identif;
identifère = 6;
int & ref = identif;

6 est un prvalue qui initialise l'objet identifié par Identify; L'objet est également référencé par Ref. Ici, l'arbitre est une référence LVALUE et non une référence de PRValue.

Considérez le segment de code suivant:

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

15 et 63 sont chacun une constante qui calcule pour lui-même, produisant un opérande (en bits) pour l'opérateur d'addition. Ainsi, 15 ou 63 est une expression de PRValue.

Tout littéral, à l'exception du littéral des cordes, est un prvalue (i.e., une expression de prvalue). Ainsi, un littéral tel que 58 ou 58.53, ou vrai ou faux, est un prvalue. Un littéral peut être utilisé pour initialiser un objet ou calculer à lui-même (dans une autre forme en bits) comme valeur d'un opérande pour un opérateur. Dans le code ci-dessus, le littéral 2 initialise l'objet, un. Il se calcule également en tant qu'opérande pour l'opérateur d'affectation.

Pourquoi une chaîne littérale n'est-elle pas un prvalue? Considérez le code suivant:

Char Str [] = "Love Not Hate";
couter << str <<'\n';
couter << str[5] <<'\n';

La sortie est:

L'amour ne déteste pas
n

Str identifie toute la chaîne. Ainsi, l'expression, Str, et non ce qu'elle identifie, est un lvalue. Chaque caractère de la chaîne peut être identifié par Str [i], où je suis un index. L'expression, Str [5], et non le caractère qu'il identifie, est une lvalue. Le littéral des cordes est un lvalue et non un prvalue.

Dans l'instruction suivante, un littéral du tableau initialise l'objet, arr:

ptrint ++ ou ptrint--

Ici, Ptrint est un pointeur vers un emplacement entier. L'expression entière, et non la valeur finale de l'emplacement à laquelle il pointe, est un prvalue (expression). En effet. À l'autre main, -Ptrint ou -Ptrint est un LVALUE car il identifie la seule valeur de l'intérêt pour l'emplacement. Une autre façon de le voir est que la valeur d'origine calcule la deuxième valeur finale.

Dans la deuxième déclaration du code suivant, A ou B peut toujours être considéré comme un prvalue:

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

Ainsi, A ou B dans la deuxième instruction est un lvalue car il identifie un objet. C'est aussi un prvalue car il calcule à l'entier d'un opérande pour l'opérateur d'addition.

(Nouveau Int), et non l'emplacement qu'il établit est un prvalue. Dans l'instruction suivante, l'adresse de retour de l'emplacement est attribuée à un objet de pointeur:

int * ptrint = new int

Ici, * Ptrint est un lvalue, tandis que (New Int) est un prvalue. N'oubliez pas qu'une lvalue ou un prvalue est une expression. (Nouveau int) n'identifie aucun objet. Le retour de l'adresse ne signifie pas d'identifier l'objet avec un nom (tel que identifient ci-dessus). Dans * ptrint, le nom, ptrint, est ce qui identifie vraiment l'objet, donc * ptrint est un lvalue. D'un autre côté, (New INT) est un PRValue, car il calcule un nouvel emplacement à une adresse de valeur d'opérande pour l'opérateur d'affectation =.

xvalue

Aujourd'hui, LVALUE signifie la valeur de l'emplacement; PRValue signifie «pur» rvalue (voir ce que représente la réalité ci-dessous). Aujourd'hui, XValue signifie «expiration» LVALUE.

La définition de xvalue, citée à partir de la spécification C ++, est la suivante:

«Un xvalue est une glvalue qui désigne un objet ou un champ de bit dont les ressources peuvent être réutilisées (généralement parce qu'elle est vers la fin de sa vie). [Exemple: certains types d'expressions impliquant des références de référence de rendement des xvalues, tels qu'un appel à une fonction dont le type de retour est une référence de référence ou un casting à un type de référence de référence - Exemple de fin] »

Cela signifie que Lvalue et Prvalue peuvent expirer. Le code suivant (copié d'en haut) montre comment le stockage (ressource) de la Lvalue, * Ptrint est réutilisé après sa suppression.

int * ptrint = new int;
* ptrint = 12;
couter<< *ptrInt <<'\n';
supprimer des ptrints;
couter<< *ptrInt <<'\n';
* ptrint = 24;
couter<< *ptrInt <<'\n';

La sortie est:

12
0
24

Le programme suivant (copié d'en haut) montre comment le stockage d'une référence entière, qui est une référence Lvalue renvoyée par une fonction, est réutilisée dans la fonction principale ():

#inclure
Utilisation de Namespace Std;
int & fn ()
int i = 5;
int & j = i;
retour j;

int main()
int & myInt = fn ();
couter<< myInt <<'\n';
myInt = 17;
couter<< myInt <<'\n';
retour 0;

La sortie est:

5
17

Lorsqu'un objet comme moi dans la fonction fn () sort de portée, il est naturellement détruit. Dans ce cas, le stockage de I a toujours été réutilisé dans la fonction principale ().

Les deux échantillons de code ci-dessus illustrent la réutilisation du stockage des LVAlues. Il est possible d'avoir une réutilisation de stockage des PRValues ​​(RValues) (voir plus tard).

La citation suivante concernant XValue provient de la spécification C ++:

«En général, l'effet de cette règle est que les références nommées nommées sont traitées comme des LVAlues et des références de valeur de la valeur sans nom aux objets sont traités comme des xvalues. Les références de référence aux fonctions sont traitées comme des LVALUes qu'ils soient nommés ou non." (voir plus tard).

Ainsi, un. XValues ​​est l'ensemble d'intersection des LVALUES et PRVALUES.

Il y a plus à xvalue que ce qui a été abordé dans cet article. Cependant, XValue mérite un article entier en soi, et donc les spécifications supplémentaires pour XValue ne sont pas abordées dans cet article.

Ensemble de taxonomie de catégorie d'expression

Une autre citation de la spécification C ++:

"Note: Historiquement, les LVAlues et les values ​​étaient soi-disant parce qu'ils pouvaient apparaître du côté gauche et droit d'une affectation (bien que ce ne soit plus généralement vrai); Les glvalues ​​sont des lvalies «généralisées», les prvalues ​​sont des values ​​«pures» et les xvalues ​​sont des lvalues ​​«expirés». Malgré leurs noms, ces termes classent les expressions, et non les valeurs. - Note de fin "

Ainsi, Glvalues ​​est l'ensemble syndical de Lvalues ​​et Xvalues ​​et Rvalues ​​est l'ensemble syndical de xvalues ​​et de prvalues. XValues ​​est l'ensemble d'intersection des LVALUES et PRVALUES.

À partir de maintenant, la taxonomie de catégorie d'expression est mieux illustrée d'un diagramme de Venn comme suit:

Conclusion

Une lvalue est une expression dont l'évaluation détermine l'identité d'un objet, d'un champ de bit ou d'une fonction.

Un prvalue est une expression dont l'évaluation initialise un objet ou un champ de bit ou calcule la valeur de l'opérande d'un opérateur, comme spécifié par le contexte dans lequel il apparaît.

Un xvalue est un lvalue ou un prvalue, avec la propriété supplémentaire que ses ressources (stockage) peuvent être réutilisées.

La spécification C ++ illustre la taxonomie de catégorie d'expression avec un diagramme d'arbres, indiquant qu'il y a une hiérarchie dans la taxonomie. Pour l'instant, il n'y a pas de hiérarchie dans la taxonomie, donc un diagramme de Venn est utilisé par certains auteurs, car il illustre la taxonomie mieux que le diagramme des arbres.