C++

Taxonomie des catégories d'expressions en C++

Taxonomie des catégories d'expressions 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, joint par des opérateurs.En programmation, une expression peut aboutir à une valeur et/ou provoquer des événements. Lorsqu'elle aboutit à une valeur, l'expression est une glvalue, rvalue, lvalue, xvalue ou prvalue. Chacune de ces catégories est un ensemble d'expressions. Chaque ensemble a une définition et des situations particulières où son sens prévaut, le différenciant d'un autre ensemble. Chaque ensemble est appelé une catégorie de valeur.

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

glvalue et rvalue sont les deux sous-ensembles de l'expression big set. glvalue existe dans deux autres sous-ensembles : lvalue et xvalue. rvalue, l'autre sous-ensemble pour l'expression, existe également dans deux autres sous-ensembles : xvalue et prvalue. Ainsi, xvalue est un sous-ensemble de glvalue et rvalue : c'est-à-dire que xvalue est l'intersection de glvalue et de 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 valeurs de la catégorie principale. glvalue est l'union de lvalues ​​et xvalues, tandis que rvalues ​​est l'union de xvalues ​​et prvalues.

Vous avez besoin de connaissances de base en C++ pour comprendre cet article ; vous devez également connaître Scope en C++.

Contenu de l'article

Notions de base

Pour vraiment comprendre la taxonomie des catégories d'expressions, vous devez d'abord rappeler ou connaître les fonctionnalités de base suivantes : emplacement et objet, stockage et ressource, initialisation, identifiant et référence, références lvalue et rvalue, pointeur, stockage gratuit et réutilisation d'un Ressource.

Emplacement et objet

Considérez la déclaration suivante :

int ident;

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 consister en un octet, deux octets, quatre octets, soixante-quatre octets, etc. L'emplacement d'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 aucune valeur, car le contenu est la valeur. Ainsi, un identifiant identifie un emplacement (petit espace continu). Lorsque la localisation se voit attribuer un contenu particulier, l'identifiant identifie alors à la fois la localisation et le contenu ; c'est-à-dire que l'identifiant identifie alors à la fois l'emplacement et la valeur.

Considérez les déclarations suivantes :

int ident1 = 5;
int ident2 = 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 à la fois.

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

Stockage d'objets et ressources

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

Initialisation

Considérez le segment de code suivant :

int ident;
identifiant = 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, ident. La ligne suivante met la valeur 8 (en bits) dans l'emplacement identifié par ident. La mise de cette valeur est l'initialisation.

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

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

Ici, l'initialisation avec 1, 2, 3, 4, 5 se fait dans la même instruction de la définition (déclaration). L'opérateur d'affectation n'est pas utilisé. L'instruction suivante définit un tableau avec le 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 ident = 4;
int& ref1 = ident;
int& ref2 = ident;
cout<< ident <<"<< ref1 <<"<< ref2 << '\n';

La sortie est :

4 4 4

ident est un identifiant, tandis que ref1 et ref2 sont des références ; ils font référence au même endroit. Une référence est synonyme d'un identifiant. Classiquement, ref1 et ref2 sont des noms différents d'un objet, tandis que ident est l'identifiant du même objet. Cependant, ident peut toujours être appelé le nom de l'objet, ce qui signifie que ident, ref1 et ref2 nomment le même emplacement.

La principale différence entre un identifiant et une référence est que, lorsqu'il est passé en 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 une fonction. Ainsi, le passage par identifiant aboutit à deux emplacements, tandis que le passage par référence aboutit au même emplacement.

Référence lvalue et référence rvalue

La manière normale de créer une référence est la suivante :

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

Le stockage (ressource) est localisé et identifié en premier (avec un nom tel que ident), puis une référence (avec un nom tel qu'une ref) est faite. Lors du passage en 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é (référencé) dans la fonction.

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

entier&& ref = 4;

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

Avec la déclaration &&, il n'y a aucune possibilité de passer 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é au sein de la fonction et non le deuxième emplacement copié comme avec un identifiant.

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

Aiguille

Considérez le code suivant :

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

La sortie est 5.

Ici, ptdInt est un identifiant comme l'identifiant ci-dessus. Il y a ici deux objets (emplacements) au lieu d'un : l'objet pointé, ptdInt identifié par ptdInt, et l'objet pointeur, ptrInt identifié par ptrInt. &ptdInt renvoie l'adresse de l'objet pointé et la met comme valeur dans l'objet pointeur ptrInt. Pour retourner (obtenir) la valeur de l'objet pointé, utilisez l'identifiant de l'objet pointeur, comme dans "*ptrInt".

Noter: 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 seule ligne, conduisant au code suivant :

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

Noter: Lorsqu'un pointeur est incrémenté, il pointe vers l'emplacement suivant, qui n'est pas une addition 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.

Boutique gratuite

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

nouvel international

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 = nouvel entier ;
*ptrInt = 12 ;
cout<< *ptrInt  <<'\n';

La sortie est 12.

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

supprimer ptrInt ;

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

int *ptrInt = nouvel entier ;
*ptrInt = 12 ;
supprimer ptrInt ;
cout<< *ptrInt <<'\n';

La sortie est 0, et pas quelque chose comme null ou undefined. delete remplace la valeur de l'emplacement par la valeur par défaut du type particulier de l'emplacement, puis autorise la réutilisation de l'emplacement. 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 revient à réutiliser un emplacement ou un stockage pour un objet. Le code suivant illustre comment un emplacement du magasin gratuit peut être réutilisé :

int *ptrInt = nouvel entier ;
*ptrInt = 12 ;
cout<< *ptrInt <<'\n';
supprimer ptrInt ;
cout<< *ptrInt <<'\n';
*ptrInt = 24 ;
cout<< *ptrInt <<'\n';

La sortie est :

12
0
24

Une valeur de 12 est d'abord attribué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 emplacement.

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

#inclure
en utilisant l'espace de noms std ;
entier& fn()

entier je = 5;
entier& j = i;
retour j;

int main()

int& monInt = fn();
cout<< myInt <<'\n';
monInt = 17 ;
cout<< myInt <<'\n';
renvoie 0 ;

La sortie est :

5
17

Un objet tel que i, déclaré dans une portée locale (portée de 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.

lvaleur

Une lvalue est une expression dont l'évaluation détermine l'identité d'un objet, d'un champ de bits ou d'une fonction. L'identité est une identité officielle comme ident 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 monInt = 512 ;
int& myRef = myInt;
int* ptr = &myInt;
entier fn()

++ptr; --ptr;
retourner monInt;

Ici, myInt est une 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 une lvalue (expression).

Considérez le segment de code suivant :

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

Dans la deuxième déclaration, l'emplacement de 'a' a 2 et est identifiable par 'a', de même qu'une lvalue. L'emplacement pour b a 8 et est identifiable par b, de même qu'une lvalue. L'emplacement pour c aura la somme, et est identifiable par c, de même qu'une lvalue. Dans la deuxième instruction, les expressions ou valeurs de 16 et 64 sont des rvalues ​​(voir ci-dessous).

Considérez le segment de code suivant :

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

La sortie est 'v';

seq est un tableau. L'emplacement de 'v' ou de toute valeur similaire dans le tableau est identifié par seq[i], où i est un indice. Ainsi, l'expression, seq[i], est une expression lvalue. seq, qui est l'identifiant de l'ensemble du tableau, est également une lvalue.

prvalue

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

Dans la déclaration,

int monInt = 256 ;

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

Dans la déclaration,

entier&& ref = 4;

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

Considérez le segment de code suivant :

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

6 est une prvalue qui initialise l'objet identifié par ident; l'objet est également référencé par ref. Ici, la ref est une référence lvalue et non une référence prvalue.

Considérez le segment de code suivant :

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

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

Tout littéral, à l'exception du littéral de chaîne, est une valeur pr (i.e., une expression prvalue). Ainsi, un littéral tel que 58 ou 58.53, ou vrai ou faux, est une valeur pr. Un littéral peut être utilisé pour initialiser un objet ou se calculerait lui-même (sous 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 comme un opérande pour l'opérateur d'affectation.

Pourquoi un littéral de chaîne n'est-il pas une valeur pr? Considérez le code suivant :

char str[] = "aimer pas détester";
cout << str <<'\n';
cout << str[5] <<'\n';

La sortie est :

aimer pas détester
m

str identifie la chaîne entière. Ainsi, l'expression str, et non ce qu'elle identifie, est une lvalue. Chaque caractère de la chaîne peut être identifié par str[i], où i est un indice. L'expression, str[5], et non le caractère qu'elle identifie, est une lvalue. Le littéral de chaîne est une lvalue et non une prvalue.

Dans l'instruction suivante, un littéral de 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 vers lequel elle pointe, est une prvalue (expression). C'est parce que l'expression, ptrInt++ ou ptrInt-, identifie la première valeur d'origine de son emplacement et non la deuxième valeur finale du même emplacement. D'autre part, -ptrInt ou  -ptrInt est une lvalue car elle identifie la seule valeur de l'intérêt dans l'emplacement. Une autre façon de voir les choses est que la valeur d'origine calcule la deuxième valeur finale.

Dans la deuxième instruction du code suivant, a ou b peut toujours être considéré comme une prvalue :

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

Ainsi, a ou b dans la deuxième instruction est une lvalue car elle identifie un objet. C'est aussi une prvalue car elle calcule l'entier d'un opérande pour l'opérateur d'addition.

(new int), et non l'emplacement qu'il établit est une prvalue. Dans l'instruction suivante, l'adresse de retour de l'emplacement est affectée à un objet pointeur :

entier *ptrInt = nouvel entier

Ici, *ptrInt est une lvalue, tandis que (new int) est une prvalue. Rappelez-vous, une lvalue ou une prvalue est une expression. (new int) n'identifie aucun objet. Renvoyer l'adresse ne signifie pas identifier l'objet avec un nom (comme ident, ci-dessus). Dans *ptrInt, le nom, ptrInt, est ce qui identifie vraiment l'objet, donc *ptrInt est une lvalue. D'autre part, (new int) est une prvalue, car il calcule un nouvel emplacement à une adresse de valeur d'opérande pour l'opérateur d'affectation =.

valeur x

Aujourd'hui, lvalue signifie Location Value ; prvalue signifie rvalue "pure" (voir ce que rvalue signifie ci-dessous). Aujourd'hui, xvalue signifie "eXpiring" lvalue.

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

"Une valeur x est une valeur gl qui désigne un objet ou un champ de bits dont les ressources peuvent être réutilisées (généralement parce qu'il est proche de la fin de sa durée de vie). [Exemple : Certains types d'expressions impliquant des références rvalue génèrent des valeurs x, telles qu'un appel à une fonction dont le type de retour est une référence rvalue ou un transtypage vers un type de référence rvalue - fin de l'exemple] »

Cela signifie que lvalue et prvalue peuvent expirer. Le code suivant (copié ci-dessus) montre comment le stockage (ressource) de la lvalue, *ptrInt est réutilisé après sa suppression.

int *ptrInt = nouvel entier ;
*ptrInt = 12 ;
cout<< *ptrInt <<'\n';
supprimer ptrInt ;
cout<< *ptrInt <<'\n';
*ptrInt = 24 ;
cout<< *ptrInt <<'\n';

La sortie est :

12
0
24

Le programme suivant (copié ci-dessus) 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é dans la fonction main() :

#inclure
en utilisant l'espace de noms std ;
entier& fn()

entier je = 5;
entier& j = i;
retour j;

int main()

int& monInt = fn();
cout<< myInt <<'\n';
monInt = 17 ;
cout<< myInt <<'\n';
renvoie 0 ;

La sortie est :

5
17

Lorsqu'un objet tel que i dans la fonction fn() sort de la portée, il est naturellement détruit. Dans ce cas, le stockage de i a tout de même été réutilisé dans la fonction main().

Les deux exemples de code ci-dessus illustrent la réutilisation du stockage de lvalues. Il est possible d'avoir une réutilisation de stockage des prvalues ​​(rvalues) (voir plus loin).

La citation suivante concernant xvalue est tirée de la spécification C++ :

"En général, l'effet de cette règle est que les références rvalue nommées sont traitées comme des lvalues ​​et les références rvalue non nommées aux objets sont traitées comme des xvalues. Les références rvalue aux fonctions sont traitées comme des lvalues, qu'elles soient nommées ou non." (voir plus tard).

Ainsi, une xvalue est une lvalue ou une prvalue dont les ressources (stockage) peuvent être réutilisées. xvalues ​​est l'ensemble d'intersection de lvalues ​​et prvalues.

Il y a plus à xvalue que ce qui a été abordé dans cet article. Cependant, xvalue mérite un article entier à lui seul, 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++ :

"Noter: Historiquement, les valeurs l et r étaient appelées ainsi parce qu'elles pouvaient apparaître à gauche et à droite d'une affectation (bien que ce ne soit plus généralement vrai) ; les glvalues ​​sont des lvalues ​​"généralisées", les prvalues ​​sont des rvalues ​​"pures" et les xvalues ​​sont des lvalues ​​"eXpirantes". Malgré leurs noms, ces termes classent des expressions, pas des valeurs. - note de fin"

Ainsi, glvalues ​​est l'ensemble d'union de lvalues ​​et xvalues ​​et rvalues ​​sont l'union de xvalues ​​et prvalues. xvalues ​​est l'ensemble d'intersection de lvalues ​​et prvalues.

À partir de maintenant, la taxonomie des catégories d'expression est mieux illustrée avec 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 bits ou d'une fonction.

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

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

La spécification C++ illustre la taxonomie des catégories d'expression avec un diagramme en arbre, indiquant qu'il existe une certaine 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 mieux la taxonomie que le diagramme en arbre.

Le bouton de clic gauche de la souris ne fonctionne pas sous Windows 10
Si vous utilisez une souris dédiée avec votre ordinateur portable ou de bureau mais que le le clic gauche de la souris ne fonctionne pas sur Windows 1...
Le curseur saute ou se déplace de manière aléatoire lors de la saisie dans Windows 10
Si vous constatez que le curseur de votre souris saute ou se déplace tout seul, automatiquement, au hasard lors de la saisie sur un ordinateur portabl...
Comment inverser le sens de défilement de la souris et des pavés tactiles dans Windows 10
Souris et Pavé tactiles rendent non seulement l'informatique facile, mais plus efficace et moins chronophage. Nous ne pouvons pas imaginer une vie san...