Ecco una spiegazione dettagliata che spero possa essere utile. Cominciamo con il tuo programma, in quanto è il più semplice da spiegare.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
La prima affermazione:
const char* p = "Hello";
dichiara p
come puntatore a char
. Quando diciamo "puntatore a char
", cosa significa? Significa che il valore di p
è l'indirizzo di a char
; p
ci dice dove c'è nella memoria uno spazio riservato per contenere a char
.
L'istruzione inoltre si inizializza p
per indicare il primo carattere nella stringa letterale "Hello"
. Per il bene di questo esercizio, è importante capire p
che punta non all'intera stringa, ma solo al primo carattere 'H'
,. Dopotutto, p
è un puntatore a uno char
, non all'intera stringa. Il valore di p
è l'indirizzo di 'H'
in "Hello"
.
Quindi imposta un loop:
while (*p++)
Cosa significa la condizione del loop *p++
? Qui sono al lavoro tre cose che rendono questo enigma (almeno fino a quando non inizia la familiarità):
- La precedenza dei due operatori, postfix
++
e indirection*
- Il valore di un'espressione di incremento postfix
- L'effetto collaterale di un'espressione di incremento postfix
1. Precedenza . Una rapida occhiata alla tabella delle precedenza per gli operatori ti dirà che l'incremento postfisso ha una precedenza più alta (16) rispetto a dereference / indirection (15). Ciò significa che il complesso espressione *p++
sta per essere raggruppati come: *(p++)
. Vale a dire, la *
parte verrà applicata al valore della p++
parte. Quindi prendiamo p++
prima la parte.
2. Valore dell'espressione Postfix . Il valore di p++
è il valore di p
prima dell'incremento . Se hai:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
l'output sarà:
7
8
perché i++
valuta i
prima dell'incremento. Allo stesso modo p++
sta per valutare il valore corrente di p
. Come sappiamo, il valore corrente di p
è l'indirizzo di 'H'
.
Quindi ora la p++
parte di *p++
è stata valutata; è il valore corrente di p
. Quindi la *
parte accade. *(current value of p)
significa: accedere al valore all'indirizzo di proprietà di p
. Sappiamo che il valore a quell'indirizzo è 'H'
. Quindi l'espressione *p++
valuta 'H'
.
Ora aspetta un minuto, stai dicendo. Se viene *p++
valutato 'H'
, perché non viene 'H'
stampato nel codice sopra? Ecco dove entrano in gioco gli effetti collaterali .
3. Effetti collaterali di espressione postfissa . Il postfisso ++
ha il valore dell'operando corrente, ma ha l' effetto collaterale di incrementare quell'operando. Eh? Dai di nuovo un'occhiata a quel int
codice:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Come notato in precedenza, l'output sarà:
7
8
Quando i++
viene valutato nella prima printf()
, esso restituisce 7. Ma le garanzie C standard che ad un certo punto prima del secondo printf()
inizia l'esecuzione, l' effetto collaterale del ++
operatore avrà avuto luogo. Vale a dire, prima che printf()
accada il secondo , i
saranno stati incrementati come risultato ++
dell'operatore nel primo printf()
. Questo, tra l'altro, è una delle poche garanzie che lo standard offre sulla tempistica degli effetti collaterali.
Nel tuo codice, quindi, quando *p++
viene valutata l'espressione , viene valutata 'H'
. Ma quando arrivi a questo:
printf ("%c", *p)
quel fastidioso effetto collaterale si è verificato. p
è stato incrementato. Whoa! Non punta più a 'H'
, ma al passato di un personaggio 'H'
: 'e'
in altre parole. Questo spiega la tua produzione sconcertata:
ello
Da qui il coro di suggerimenti utili (e accurati) nelle altre risposte: per stampare la pronuncia ricevuta "Hello"
e non la sua controparte cockney, hai bisogno di qualcosa di simile
while (*p)
printf ("%c", *p++);
Questo per quello. E il resto? Chiedete i significati di questi:
*ptr++
*++ptr
++*ptr
Abbiamo appena parlato del primo, quindi cerchiamo di guardare al secondo: *++ptr
.
Abbiamo visto nella nostra precedente spiegazione che l'incremento postfisso p++
ha una certa precedenza , un valore e un effetto collaterale . L'incremento del prefisso ++p
ha lo stesso effetto collaterale della sua controparte postfisso: incrementa il suo operando di 1. Tuttavia, ha una precedenza diversa e un valore diverso .
L'incremento del prefisso ha una precedenza inferiore rispetto al postfisso; ha la precedenza 15. In altre parole, ha la stessa precedenza dell'operatore dereference / indirection *
. In un'espressione simile
*++ptr
ciò che conta non è la precedenza: i due operatori sono identici in precedenza. Quindi entra in gioco l' associatività . L'incremento del prefisso e l'operatore indiretto hanno associatività destra-sinistra. A causa di tale associatività, l'operando ptr
verrà raggruppato con ++
l'operatore più a destra prima dell'operatore più a sinistra *
,. In altre parole, l'espressione verrà raggruppata *(++ptr)
. Quindi, come con *ptr++
ma per un motivo diverso, anche qui la *
parte verrà applicata al valore della ++ptr
parte.
Quindi qual è quel valore? Il valore dell'espressione dell'incremento del prefisso è il valore dell'operando dopo l'incremento . Questo lo rende una bestia molto diversa dall'operatore di incremento postfix. Diciamo che hai:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
L'output sarà:
8
8
... diverso da quello che abbiamo visto con l'operatore postfix. Allo stesso modo, se hai:
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++
l'output sarà:
H e e l // good dog
Capisci perché?
Ora arriviamo alla terza espressione hai chiesto, ++*ptr
. Questo è il più complicato del lotto, in realtà. Entrambi gli operatori hanno la stessa precedenza e associatività destra-sinistra. Ciò significa che l'espressione verrà raggruppata ++(*ptr)
. La ++
parte verrà applicata al valore della *ptr
parte.
Quindi se abbiamo:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
l'output sorprendentemente egotistico sarà:
I
Che cosa?! Va bene, quindi la *p
parte sta per valutare 'H'
. Quindi ++
entra in gioco, a quel punto, verrà applicato al 'H'
, non al puntatore! Cosa succede quando aggiungi 1 a 'H'
? Ottieni 1 più il valore ASCII di 'H'
, 72; ottieni 73. Rappresentalo come achar
, e si ottiene il char
con il valore ASCII 73: 'I'
.
Questo si prende cura delle tre espressioni che hai chiesto nella tua domanda. Eccone un altro, menzionato nel primo commento alla tua domanda:
(*ptr)++
Anche quello è interessante. Se hai:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
ti darà questo risultato entusiasta:
HI
Cosa sta succedendo? Ancora una volta, è una questione di precedenza , valore di espressione ed effetti collaterali . A causa delle parentesi, la *p
parte viene trattata come un'espressione primaria. Le espressioni primarie vincono tutto il resto; vengono valutati per primi. E *p
, come sai, valuta 'H'
. Il resto dell'espressione, la ++
parte, viene applicato a quel valore. Quindi, in questo caso,(*p)++
diventa 'H'++
.
Qual è il valore di 'H'++
? Se hai detto 'I'
, hai dimenticato (già!) La nostra discussione sul valore rispetto all'effetto collaterale con incremento postfix. Ricorda, 'H'++
valuta il valore corrente di 'H'
. In modo che prima printf()
verrà stampato 'H'
. Quindi, come effetto collaterale , 'H'
verrà incrementato 'I'
. Il secondo lo printf()
stampa 'I'
. E hai il tuo saluto allegro.
Va bene, ma in questi ultimi due casi, perché ne ho bisogno
char q[] = "Hello";
char* p = q;
Perché non posso semplicemente avere qualcosa del genere
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Perché "Hello"
è una stringa letterale. Se ci provi ++*p
, stai cercando di cambiare 'H'
la stringa in 'I'
, creando l'intera stringa"Iello"
. In C, i letterali stringa sono di sola lettura; il tentativo di modificarli invoca un comportamento indefinito. "Iello"
è indefinito anche in inglese, ma è solo una coincidenza.
Al contrario, non puoi averlo
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Perchè no? Perché in questo caso, p
è un array. Un array non è un valore l modificabile; non puoi cambiare dovep
punti in pre o post-incremento o decremento, poiché il nome dell'array funziona come se fosse un puntatore costante. (Non è quello che è in realtà; è solo un modo conveniente per vederlo.)
Per riassumere, ecco le tre cose che hai chiesto:
*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
Ed eccone un quarto, altrettanto divertente degli altri tre:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
Il primo e il secondo andranno in crash se ptr
realtà è un identificatore di array. Il terzo e il quarto andranno in crash se ptr
punta a una stringa letterale.
Ecco qua. Spero sia tutto cristallo adesso. Sei stato un grande pubblico e sarò qui tutta la settimana.
(*ptr)++
(le parentesi dovevano essere*ptr++