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
- lvaleur
- prvalue
- valeur x
- Ensemble de taxonomie de catégorie d'expression
- Conclusion
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 4ident 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 internationalCela 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 :
120
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 :
#inclureen 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 :
517
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étesterm
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 entierIci, *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 :
120
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() :
#inclureen 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 :
517
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.