Voici une explication détaillée qui, je l'espère, vous sera utile. Commençons par votre programme, car c'est le plus simple à expliquer.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
La première déclaration:
const char* p = "Hello";
déclare p
comme un pointeur vers char
. Lorsque nous disons "pointeur vers un char
", qu'est-ce que cela signifie? Cela signifie que la valeur de p
est l'adresse de a char
; p
nous indique où dans la mémoire il y a un espace réservé pour contenir un fichier char
.
L'instruction s'initialise également p
pour pointer sur le premier caractère de la chaîne littérale "Hello"
. Pour les besoins de cet exercice, il est important de comprendre p
qu'il ne pointe pas vers la chaîne entière, mais uniquement vers le premier caractère 'H'
,. Après tout, p
est un pointeur vers un char
, pas vers la chaîne entière. La valeur de p
est l'adresse du 'H'
in "Hello"
.
Ensuite, vous configurez une boucle:
while (*p++)
Que signifie la condition de boucle *p++
? Trois choses sont à l'œuvre ici qui rendent cela déroutant (du moins jusqu'à ce que la familiarité s'installe):
- La priorité des deux opérateurs, postfix
++
et indirection*
- La valeur d'une expression d'incrémentation de suffixe
- L'effet secondaire d'une expression d'incrément postfix
1. Préséance . Un rapide coup d'œil à la table de priorité des opérateurs vous indiquera que l'incrément de suffixe a une priorité plus élevée (16) que la déréférence / indirection (15). Cela signifie que l'expression complexe *p++
va être regroupés comme suit: *(p++)
. C'est-à-dire que la *
pièce sera appliquée à la valeur de la p++
pièce. Alors commençons par le p++
rôle.
2. Valeur de l'expression Postfix . La valeur de p++
est la valeur d' p
avant l'incrément . Si tu as:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
la sortie sera:
7
8
car i++
évalue i
avant l'incrément. De même p++
va évaluer la valeur actuelle de p
. Comme nous le savons, la valeur actuelle de p
est l'adresse de 'H'
.
Alors maintenant, la p++
partie de *p++
a été évaluée; c'est la valeur actuelle de p
. Ensuite, la *
partie se produit. *(current value of p)
signifie: accéder à la valeur à l'adresse détenue par p
. Nous savons que la valeur à cette adresse est 'H'
. Ainsi, l'expression est *p++
évaluée à 'H'
.
Maintenant, attendez une minute, dites-vous. Si *p++
évalue à 'H'
, pourquoi cela ne 'H'
s'imprime pas dans le code ci-dessus? C'est là qu'interviennent les effets secondaires .
3. Effets secondaires d'expression de Postfix . Le suffixe ++
a la valeur de l'opérande actuel, mais il a pour effet secondaire d'incrémenter cet opérande. Hein? Jetez à nouveau un coup d'œil à ce int
code:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Comme indiqué précédemment, la sortie sera:
7
8
Quand i++
est évalué dans le premier printf()
, il évalue à 7. Mais le standard C garantit qu'à un moment donné avant que le second ne printf()
commence à s'exécuter, l' effet secondaire de l' ++
opérateur aura eu lieu. C'est-à-dire, avant que le second ne printf()
se produise, i
aura été incrémenté du fait de l' ++
opérateur dans le premier printf()
. C'est d'ailleurs l'une des rares garanties que la norme donne sur le moment des effets secondaires.
Dans votre code, lorsque l'expression *p++
est évaluée, elle est évaluée à 'H'
. Mais au moment où vous arrivez à ceci:
printf ("%c", *p)
cet effet secondaire embêtant s'est produit. p
a été incrémenté. Whoa! Il ne pointe plus vers 'H'
, mais vers un personnage passé 'H'
: vers le 'e'
, en d'autres termes. Cela explique votre sortie cockneyfied:
ello
D'où le refrain de suggestions utiles (et précises) dans les autres réponses: pour imprimer la prononciation reçue "Hello"
et non son homologue cockney, vous avez besoin de quelque chose comme
while (*p)
printf ("%c", *p++);
Tellement pour ça. Qu'en est-il du reste? Vous demandez la signification de ces derniers:
*ptr++
*++ptr
++*ptr
Nous venons de parler de la première, alors regardons la seconde: *++ptr
.
Nous avons vu dans notre explication précédente que l'incrément de suffixe p++
a une certaine priorité , une valeur et un effet secondaire . L'incrément de préfixe ++p
a le même effet secondaire que son homologue postfixe: il incrémente son opérande de 1. Cependant, il a une priorité et une valeur différentes .
L'incrément de préfixe a une priorité inférieure à celle du suffixe; il a la priorité 15. En d'autres termes, il a la même priorité que l'opérateur de déréférence / indirection *
. Dans une expression comme
*++ptr
ce qui compte, ce n'est pas la priorité: les deux opérateurs sont identiques en priorité. L' associativité entre donc en jeu. L'incrément de préfixe et l'opérateur d'indirection ont une associativité droite-gauche. En raison de cette associativité, l'opérande ptr
va être groupé avec l'opérateur ++
le plus à droite avant l'opérateur le plus à gauche *
,. En d'autres termes, l'expression va être groupée *(++ptr)
. Donc, comme pour *ptr++
mais pour une raison différente, ici aussi la *
pièce va être appliquée à la valeur de la ++ptr
pièce.
Alors, quelle est cette valeur? La valeur de l'expression d'incrément de préfixe est la valeur de l'opérande après l'incrément . Cela en fait une bête très différente de l'opérateur d'incrémentation postfix. Disons que vous avez:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
La sortie sera:
8
8
... différent de ce que nous avons vu avec l'opérateur postfix. De même, si vous avez:
const char* p = "Hello";
printf ("%c ", *p); // note space in format string
printf ("%c ", *++p); // value of ++p is p after the increment
printf ("%c ", *p++); // value of p++ is p before the increment
printf ("%c ", *p); // value of p has been incremented as a side effect of p++
la sortie sera:
H e e l // good dog
Voyez-vous pourquoi?
Maintenant , nous arrivons à la troisième expression que vous avez parlé, ++*ptr
. C'est le plus délicat du lot, en fait. Les deux opérateurs ont la même priorité et la même associativité droite-gauche. Cela signifie que l'expression sera groupée ++(*ptr)
. La ++
pièce sera appliquée à la valeur de la *ptr
pièce.
Donc, si nous avons:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
le résultat étonnamment égoïste va être:
I
Quoi?! Ok, donc la *p
partie va être évaluée 'H'
. Ensuite, le ++
entre en jeu, à quel point, il va être appliqué au 'H'
pointeur, pas du tout! Que se passe-t-il lorsque vous ajoutez 1 à 'H'
? Vous obtenez 1 plus la valeur ASCII de 'H'
72; vous obtenez 73. certifiez que comme char
, et vous obtenez la char
avec la valeur ASCII de 73: 'I'
.
Cela prend en compte les trois expressions que vous avez posées dans votre question. En voici une autre, mentionnée dans le premier commentaire de votre question:
(*ptr)++
Celui-là est intéressant aussi. Si tu as:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
cela vous donnera cette sortie enthousiaste:
HI
Que se passe-t-il? Encore une fois, c'est une question de priorité , de valeur d'expression et d' effets secondaires . En raison des parenthèses, la *p
pièce est traitée comme une expression principale. Les expressions primaires l'emportent sur tout le reste; ils sont évalués en premier. Et *p
, comme vous le savez, évalue à 'H'
. Le reste de l'expression, la ++
partie, est appliqué à cette valeur. Donc, dans ce cas, (*p)++
devient 'H'++
.
Quelle est la valeur de 'H'++
? Si vous avez dit 'I'
, vous avez oublié (déjà!) Notre discussion sur la valeur par rapport aux effets secondaires avec incrément de suffixe. N'oubliez pas, 'H'++
évalue à la valeur actuelle de 'H'
. Donc, ce premier printf()
va être imprimé 'H'
. Ensuite, comme effet secondaire , cela 'H'
va être incrémenté 'I'
. Le second l' printf()
imprime 'I'
. Et vous avez votre joyeux salut.
D'accord, mais dans ces deux derniers cas, pourquoi ai-je besoin
char q[] = "Hello";
char* p = q;
Pourquoi ne puis-je pas avoir quelque chose comme
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Parce que "Hello"
est une chaîne littérale. Si vous essayez ++*p
, vous essayez de changer le 'H'
dans la chaîne en 'I'
, créant ainsi toute la chaîne "Iello"
. En C, les littéraux de chaîne sont en lecture seule; tenter de les modifier appelle un comportement indéfini. "Iello"
n'est pas défini en anglais également, mais ce n'est qu'une coïncidence.
Inversement, vous ne pouvez pas avoir
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Pourquoi pas? Parce que dans ce cas, p
est un tableau. Un tableau n'est pas une valeur l modifiable; vous ne pouvez pas changer les p
points par pré- ou post-incrémentation ou décrémentation, car le nom du tableau fonctionne comme s'il s'agissait d'un pointeur constant. (Ce n'est pas ce que c'est réellement; c'est juste un moyen pratique de le regarder.)
Pour résumer, voici les trois choses que vous avez posées:
*ptr++ // effectively dereferences the pointer, then increments the pointer
*++ptr // effectively increments the pointer, then dereferences the pointer
++*ptr // effectively dereferences the pointer, then increments dereferenced value
Et en voici un quatrième, tout aussi amusant que les trois autres:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
Le premier et le second planteront s'il ptr
s'agit en fait d'un identifiant de tableau. Les troisième et quatrième planteront si elles ptr
pointent vers une chaîne littérale.
Voilà. J'espère que tout est en cristal maintenant. Vous avez été un public formidable et je serai ici toute la semaine.
(*ptr)++
(les parenthèses sont nécessaires pour*ptr++