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 pcome puntatore a char. Quando diciamo "puntatore a char", cosa significa? Significa che il valore di pè l'indirizzo di a char; pci dice dove c'è nella memoria uno spazio riservato per contenere a char.
L'istruzione inoltre si inizializza pper indicare il primo carattere nella stringa letterale "Hello". Per il bene di questo esercizio, è importante capire pche 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 iprima 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 intcodice:
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 , isaranno 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 ++pha 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 ptrverrà 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 ++ptrparte.
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 *ptrparte.
Quindi se abbiamo:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
l'output sorprendentemente egotistico sarà:
I
Che cosa?! Va bene, quindi la *pparte 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 charcon 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 *pparte 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 ptrpunta 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++