Il est dit ici que
Le terme lvalue modifiable est utilisé pour souligner que la lvalue permet à l’object désigné d’être modifié et examiné. Les types d’object suivants sont des lvalues, mais non des lvalues modifiables:
- Un type de tableau
- Un type incomplet
- Un type qualifié de const
- Une structure ou un type d’union avec l’un de ses membres qualifié de type const
Comme ces valeurs ne sont pas modifiables, elles ne peuvent pas apparaître à gauche d’une instruction d’affectation.
Pourquoi l’object de type tableau n’est pas modifiable? N’est-il pas correct d’écrire
int i = 5, a[10] = {0}; a[i] = 1;
?
Et aussi, qu’est-ce qu’un type incomplet?
Assumer la déclaration
int a[10];
alors tout ce qui suit est vrai:
a
est “tableau de 10 éléments de int
“; sauf si a
est l’opérande de sizeof
ou unary &
opérateurs, l’expression sera convertie en une expression de type “pointeur sur int
” et sa valeur sera l’adresse du premier élément du tableau; a[i]
est int
; il fait référence à l’object entier stocké en tant que ième élément du tableau; a
peut ne pas être la cible d’une affectation, car C ne traite pas les tableaux comme d’autres variables. Vous ne pouvez donc pas écrire quelque chose comme a = b
ou a = malloc(n * sizeof *a)
ou quelque chose du genre. Vous remarquerez que je continue d’insister sur le mot “expression”. Il y a une différence entre le bloc de mémoire que nous avons mis de côté pour contenir 10 nombres entiers et les symboles (expressions) que nous utilisons pour faire référence à ce bloc de mémoire. On peut s’y référer avec l’expression a
. Nous pouvons également créer un pointeur sur ce tableau:
int (*ptr)[10] = &a;
L’expression *ptr
également le type “tableau de 10 éléments d’ int
” et fait référence au même bloc de mémoire que a
.
C ne traite pas les expressions de tableau ( a
, *ptr
) comme les expressions d’autres types, et l’une des différences est qu’une expression de type tableau peut ne pas être la cible d’une affectation. Vous ne pouvez pas réaffecter a
pour faire référence à un object de tableau différent (identique pour l’expression *ptr
). Vous pouvez atsortingbuer une nouvelle valeur à a[i]
ou à (*ptr)[i]
(changer la valeur de chaque élément du tableau), et vous pouvez affecter ptr
à pointer sur un tableau différent:
int b[10], c[10]; ..... ptr = &b; ..... ptr = &c;
Quant à la deuxième question …
Un type incomplet manque d’informations de taille; des déclarations comme
struct foo; int bar[]; union bletch;
tous créent des types incomplets car le compilateur ne dispose pas d’assez d’informations pour déterminer la quantité de mémoire à mettre de côté pour un object de ce type. Vous ne pouvez pas créer d’objects de type incomplet; par exemple, vous ne pouvez pas déclarer
struct foo myFoo;
à moins que vous ne complétiez la définition de struct foo
. Cependant, vous pouvez créer des pointeurs sur des types incomplets. par exemple, vous pouvez déclarer
struct foo *myFooPtr;
sans compléter la définition de struct foo
car un pointeur stocke simplement l’adresse de l’object et vous n’avez pas besoin de connaître la taille du type pour cela. Cela permet de définir des types auto-référentiels tels que
struct node { T key; // for any type T Q val; // for any type Q struct node *left; struct node *right; };
La définition de type pour le struct node
n’est pas complète tant que nous n’avons pas atteint cette fermeture }
. Puisque nous pouvons déclarer un pointeur sur un type incomplet, tout va bien. Cependant, nous n’avons pas pu définir la structure comme
struct node { ... // same as above struct node left; struct node right; };
parce que le type n’est pas complet lorsque nous déclarons les membres left
et right
, et aussi parce que chaque membre left
et right
contient chacun des membres left
et right
, chacun contenant des membres left
et right
, et ainsi de suite. et ainsi de suite.
C’est génial pour les structures et les unions, mais qu’en est-il de
int bar[];
???
Nous avons déclaré la bar
symboles et indiqué qu’il s’agirait d’un type de tableau, mais la taille est inconnue à ce stade. Finalement, nous devrons le définir avec une taille, mais de cette façon, le symbole peut être utilisé dans des contextes où la taille du tableau n’est ni significative ni nécessaire. Cependant, je n’ai pas d’exemple concret et sans artifices pour illustrer cela.
MODIFIER
Répondez aux commentaires ici, puisqu’il n’y aura pas de place dans la section commentaires pour ce que je veux écrire (je suis d’humeur verbeuse ce soir). Tu as demandé:
Cela signifie-t-il que toutes les variables sont des expressions?
Cela signifie que toute variable peut être une expression ou une partie d’une expression. Voici comment la norme de langue définit le terme expression :
6.5 Expressions
1 Une expression est une séquence d’opérateurs et d’opérandes qui spécifie le calcul d’une valeur, ou qui désigne un object ou une fonction, ou qui génère des effets secondaires ou qui en effectue une combinaison.
Par exemple, la variable a
tout seul compte comme une expression; il désigne l’object tableau que nous avons défini comme contenant 10 valeurs entières. Il évalue également à l’adresse du premier élément du tableau. La variable a
peut également faire partie d’une expression plus grande, telle a[i]
; l’opérateur est l’opérateur en indice []
et les opérandes sont les variables a
et i
. Cette expression désigne un seul membre du tableau et correspond à la valeur actuellement stockée dans ce membre. Cette expression peut à son tour faire partie d’une expression plus grande comme a[i] = 0
.
Et aussi, permettez-moi de préciser que, dans la déclaration int a [10], un [] signifie-t-il un type de tableau
Oui, exactement.
En C, les déclarations sont basées sur les types d’expressions plutôt que sur les types d’objects. Si vous avez une simple variable nommée y
qui stocke une valeur int
et que vous souhaitez accéder à cette valeur, vous utilisez simplement y
dans une expression, comme
x = y;
Le type de l’ expression y
est int
, la déclaration de y
est donc écrite
int y;
Par contre, si vous avez un tableau de valeurs int
et que vous souhaitez accéder à un élément spécifique, vous utiliserez le nom du tableau et un index avec l’opérateur indice pour accéder à cette valeur, comme
x = a[i];
Le type de l’ expression a[i]
est int
, la déclaration du tableau est donc écrite comme suit:
int arr[N]; // for some value N.
” int
-ness” de arr
est donné par le spécificateur de type int
; le “tableau” de arr
est donné par le déclarant arr[N]
. Le déclarant nous donne le nom de l’object déclaré ( arr
) ainsi que des informations de type supplémentaires non fournies par le spécificateur de type (“est un tableau à N éléments”). La déclaration “se lit” comme
a -- a a[N] -- is an N-element array int a[N]; -- of int
EDIT 2
Et après tout cela, je ne vous ai toujours pas expliqué la vraie raison pour laquelle les expressions de tableau sont des lvalues non modifiables. Alors, voici encore un autre chapitre de ce livre de réponse.
C n’a pas surgi complètement formé de l’esprit de Dennis Ritchie; il était dérivé d’un langage antérieur connu sous le nom de B (dérivé de BCPL). 1 B était une langue “sans type”; il n’y avait pas différents types pour les entiers, les flottants, le texte, les enregistrements, etc. Au lieu de cela, tout était simplement un mot de longueur fixe ou une “cellule” (essentiellement un entier non signé). La mémoire a été traitée comme un tableau linéaire de cellules. Lorsque vous avez alloué un tableau dans B, tel que
auto V[10];
le compilateur a alloué 11 cellules; 10 cellules contiguës pour le tableau lui-même, plus une cellule qui était liée à V contenant l’emplacement de la première cellule:
+----+ V: | | -----+ +----+ | ... | +----+ | | | <----+ +----+ | | +----+ | | +----+ | | +----+ ...
Lorsque Ritchie ajoutait des types de struct
à C, il réalisa que cet arrangement lui posait problème. Par exemple, il souhaitait créer un type de structure pour représenter une entrée dans une table de fichiers ou de répertoires:
struct { int inumber; char name[14]; };
Il souhaitait que la structure ne décrive pas simplement l'entrée de manière abstraite, mais qu'elle représente également les bits de l'entrée de la table de fichiers, qui ne comporte pas de cellule ni de mot supplémentaire pour stocker l'emplacement du premier élément du tableau. Alors il s'en est débarrassé - au lieu de mettre de côté un emplacement séparé pour stocker l'adresse du premier élément, il a écrit C de telle sorte que l'adresse du premier élément soit calculée lors de l'évaluation de l'expression du tableau.
C'est pourquoi vous ne pouvez pas faire quelque chose comme
int a[N], b[N]; a = b;
parce que a
et b
évaluent tous deux les valeurs de pointeur dans ce contexte; c'est équivalent à écrire 3 = 4
. Rien dans la mémoire ne stocke l'adresse du premier élément du tableau. le compilateur le calcule simplement pendant la phase de traduction.
1. Tout cela est tiré de l'étude Le développement du langage C
Le terme “lvalue de type tableau” désigne littéralement l’object tableau en tant que lvalue de type tableau, c’est-à-dire un object tableau dans son ensemble . Cette valeur n’est pas modifiable dans son ensemble, car aucune opération légale ne peut la modifier dans son ensemble. En fait, les seules opérations que vous pouvez effectuer sur une lvalue de type tableau sont les suivantes: unaire &
(adresse de), sizeof
et conversion implicite en type pointeur. Aucune de ces opérations ne modifie le tableau. C’est pourquoi les objects du tableau ne sont pas modifiables.
a[i]
ne fonctionne pas avec lvalue de type tableau. a[i]
désigne un object int
: le i-ème élément du tableau a
. La sémantique de cette expression (si elle est explicitement précisée) est la suivante: *((int *) a + i)
. La toute première étape – (int *) a
– convertit déjà la valeur de type tableau en une valeur de type int *
. À ce stade, la valeur de type tableau est définitivement hors de l’image.
Le type incomplet est un type dont la taille n’est pas [encore] connue. Par exemple: un type de structure déclaré mais non défini, un type de tableau avec une taille non spécifiée, le type de void
.
Un type incomplet est un type déclaré mais non défini, par exemple struct Foo;
.
Vous pouvez toujours affecter des éléments de tableau individuels (en supposant qu’ils ne soient pas const
). Mais vous ne pouvez pas affecter quelque chose à l’ensemble du tableau.
C et C ++ sont assez déroutants dans la mesure où quelque chose comme int a[10] = {0, 1, 2, 3};
n’est pas une tâche, mais une initialisation même s’il ressemble beaucoup à une tâche.
C’est OK (initialisation):
int a[10] = {0, 1, 2, 3};
Ceci ne fonctionne pas en C / C ++:
int a[10]; a = {0, 1, 2, 3};
En supposant a
soit un tableau d’ints, a[10]
n’est pas un tableau. C’est un int
.
a = {0}
serait illégal.
N’oubliez pas que la valeur d’un tableau est en réalité l’adresse (pointeur) de son premier élément. Cette adresse ne peut pas être modifiée. Alors
int a[10], b[10]; a = b
est illégal.
Cela n’a bien sûr rien à voir avec la modification du contenu du tableau comme dans a[1] = 3