Oto szczegółowe wyjaśnienie, które, mam nadzieję, będzie pomocne. Zacznijmy od twojego programu, ponieważ jest on najłatwiejszy do wyjaśnienia.
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
Pierwsze stwierdzenie:
const char* p = "Hello";
deklaruje pjako wskaźnik do char. Kiedy mówimy „wskaźnik do a char”, co to oznacza? Oznacza to, że wartością pjest adres a char; pmówi nam, gdzie w pamięci jest trochę miejsca na przechowywanie char.
Instrukcja jest również inicjowana, paby wskazać pierwszy znak w literale ciągu "Hello". Dla dobra tego ćwiczenia, ważne jest, aby zrozumieć, pjak nie wskazując na cały ciąg, ale tylko do pierwszego znaku, 'H'. W końcu pjest wskaźnikiem do jednego char, a nie do całego ciągu. Wartością pjest adres 'H'in "Hello".
Następnie konfigurujesz pętlę:
while (*p++)
Co oznacza stan pętli *p++? Działają tu trzy rzeczy, które sprawiają, że jest to zagadkowe (przynajmniej do czasu, gdy się zażywa):
- Pierwszeństwo dwóch operatorów, przyrostka
++i pośrednia*
- Wartość wyrażenia przyrostu przyrostka
- Efekt uboczny wyrażenia przyrostu przyrostka
1. Pierwszeństwo . Szybkie spojrzenie na tabelę pierwszeństwa dla operatorów powie, że przyrost przyrostka ma wyższy priorytet (16) niż wyłuskiwanie / pośrednictwo (15). Oznacza to, że złożone wyrażenie *p++ma być grupowane jako: *(p++). Oznacza to, że *część zostanie zastosowana do wartości p++części. Więc weźmy p++najpierw część.
2. Wartość wyrażenia Postfix . Wartość p++jest wartością p przed przyrostem . Jeśli masz:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
wynik będzie następujący:
7
8
ponieważ i++szacuje się do iprzed przyrostem. Podobnie p++będzie oceniać bieżącą wartość p. Jak wiemy, aktualna wartość pto adres 'H'.
Więc teraz p++część *p++została oszacowana; to aktualna wartość p. Wtedy *część się dzieje. *(current value of p)oznacza: dostęp do wartości pod adresem przechowywanym przez p. Wiemy, że wartość pod tym adresem to 'H'. Więc wyrażenie *p++oblicza 'H'.
Teraz poczekaj chwilę, mówisz. Jeśli ma *p++wartość 'H', dlaczego nie pojawia się to 'H'w powyższym kodzie? Tutaj pojawiają się efekty uboczne .
3. Efekty uboczne wyrażenia Postfix . Postfix ++ma wartość bieżącego operandu, ale ma efekt uboczny zwiększania wartości tego operandu. Co? Spójrz intponownie na ten kod:
int i = 7;
printf ("%d\n", i++);
printf ("%d\n", i);
Jak wspomniano wcześniej, wynik będzie następujący:
7
8
Gdy i++ocenia się w pierwszym printf(), to ocenia się 7. Ale C Standard gwarantuje, że w pewnym momencie przed drugim printf()zaczyna wykonującego efektem ubocznym tego ++operatora będą miały miejsce. Oznacza to, że zanim printf()nastąpi drugie , izostanie zwiększone w wyniku działania ++operatora w pierwszym printf(). Nawiasem mówiąc, jest to jedna z nielicznych gwarancji, jakie norma daje co do czasu wystąpienia skutków ubocznych.
Następnie w kodzie *p++obliczone wyrażenie oblicza 'H'. Ale zanim do tego dojdziesz:
printf ("%c", *p)
pojawił się ten nieznośny efekt uboczny. pzostał zwiększony. Whoa! Nie wskazuje już 'H'na jedną postać z przeszłości 'H': 'e'innymi słowy. To wyjaśnia twoje cockneyfied wyjście:
ello
Stąd chór pomocnych (i dokładnych) sugestii w innych odpowiedziach: aby wydrukować wymowę otrzymaną, "Hello"a nie jej odpowiednik cockney, potrzebujesz czegoś takiego jak
while (*p)
printf ("%c", *p++);
Tyle na ten temat. Co z resztą? Pytasz o znaczenie tych:
*ptr++
*++ptr
++*ptr
Po prostu rozmawialiśmy o pierwszej, więc spójrzmy na sekundę: *++ptr.
Widzieliśmy w naszym wcześniejszym wyjaśnieniu, że przyrost przyrostka p++ma pewien priorytet , wartość i efekt uboczny . Inkrementacja przedrostka ++pma ten sam efekt uboczny, co jego odpowiednik po przyrostku: zwiększa swój operand o 1. Jednakże ma inny priorytet i inną wartość .
Przyrost przedrostka ma niższy priorytet niż przyrostek; ma pierwszeństwo 15. Innymi słowy, ma taki sam priorytet jak operator wyłuskiwania / pośredniczenia *. W wyrażeniu takim jak
*++ptr
nie ma znaczenia pierwszeństwo: te dwa operatory mają takie same pierwszeństwo. Tak więc asocjatywność zaczyna działać. Przyrost przedrostka i operator pośredni mają łączność prawa-lewa. Z powodu tego skojarzeń, operand ptrma być zgrupowane ze skrajnej prawej operatora ++przed operatorem bardziej na lewo *. Innymi słowy, wyrażenie zostanie zgrupowane *(++ptr). Tak więc, jak w przypadku, *ptr++ale z innego powodu, również tutaj *część zostanie zastosowana do wartości ++ptrczęści.
Więc co to za wartość? Wartość wyrażenia zwiększania przedrostka jest wartością argumentu po inkrementacji . To sprawia, że jest to zupełnie inna bestia od operatora przyrostu przyrostka. Powiedzmy, że masz:
int i = 7;
printf ("%d\n", ++i);
printf ("%d\n", i);
Wynik będzie:
8
8
... różni się od tego, co widzieliśmy z operatorem postfiksowym. Podobnie, jeśli masz:
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++
wynik będzie następujący:
H e e l // good dog
Czy rozumiesz, dlaczego?
Teraz mamy do trzeciej wypowiedzi pytał pan o, ++*ptr. Właściwie to najtrudniejsza z wielu. Oba operatory mają ten sam priorytet i łączność prawa-lewa. Oznacza to, że wyrażenie zostanie zgrupowane ++(*ptr). ++Część zostanie zastosowana do wartości *ptrczęści.
Więc jeśli mamy:
char q[] = "Hello";
char* p = q;
printf ("%c", ++*p);
zaskakująco egoistyczny wynik będzie następujący:
I
Co?! OK, więc *pczęść będzie oceniana do 'H'. Wtedy ++wchodzi do gry, w którym momencie zostanie zastosowany do wskaźnika 'H', a nie do wskaźnika! Co się stanie, gdy dodasz 1 do 'H'? Otrzymasz 1 plus wartość ASCII 'H', 72; masz 73. Oświadczamy, że jako char, i masz charo wartości ASCII 73: 'I'.
To rozwiązuje problem z trzema wyrażeniami, o które zapytałeś w swoim pytaniu. Oto kolejny, wspomniany w pierwszym komentarzu do Twojego pytania:
(*ptr)++
Ten też jest interesujący. Jeśli masz:
char q[] = "Hello";
char* p = q;
printf ("%c", (*p)++);
printf ("%c\n", *p);
da ci to entuzjastyczne wyjście:
HI
Co się dzieje? Ponownie, jest to kwestia pierwszeństwa , wartości wyrażenia i skutków ubocznych . Ze względu na nawiasy *pczęść jest traktowana jako wyrażenie podstawowe. Podstawowe wyrażenia są ważniejsze od wszystkiego innego; są oceniane jako pierwsze. I *p, jak wiesz, ocenia 'H'. Reszta wyrażenia, czyli ++część, jest stosowana do tej wartości. Tak więc w tym przypadku (*p)++staje się 'H'++.
Jaka jest wartość 'H'++? Jeśli powiedziałeś 'I', zapomniałeś (już!) Naszej dyskusji o wartości vs. efekt uboczny z przyrostem przyrostka. Pamiętaj, 'H'++zwraca bieżącą wartość 'H' . Więc to najpierw printf()zostanie wydrukowane 'H'. Następnie, jako efekt uboczny , 'H'zostanie zwiększony do 'I'. Drugi to printf()drukuje 'I'. I masz twoje wesołe powitanie.
W porządku, ale w tych dwóch ostatnich przypadkach, dlaczego tego potrzebuję
char q[] = "Hello";
char* p = q;
Dlaczego nie mogę po prostu mieć czegoś takiego
/*const*/ char* p = "Hello";
printf ("%c", ++*p); // attempting to change string literal!
Ponieważ "Hello"jest to literał ciągu. Jeśli spróbujesz ++*p, próbujesz zmienić 'H'ciąg na 'I', tworząc cały ciąg "Iello". W języku C literały łańcuchowe są tylko do odczytu; próba ich zmodyfikowania wywołuje niezdefiniowane zachowanie. "Iello"jest niezdefiniowany również w języku angielskim, ale to tylko zbieg okoliczności.
I odwrotnie, nie możesz tego mieć
char p[] = "Hello";
printf ("%c", *++p); // attempting to modify value of array identifier!
Dlaczego nie? Ponieważ w tym przypadku pjest tablicą. Tablica nie jest modyfikowalną wartością l; nie możesz zmienić pmiejsca, w którym wskazuje przed lub po inkrementacji lub dekrementacji, ponieważ nazwa tablicy działa tak, jakby była stałym wskaźnikiem. (Tak naprawdę nie jest; to po prostu wygodny sposób patrzenia na to).
Podsumowując, oto trzy rzeczy, o które pytałeś:
*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
A oto czwarta, równie zabawna jak pozostałe trzy:
(*ptr)++ // effectively forces a dereference, then increments dereferenced value
Pierwsza i druga ulegną awarii, jeśli w ptrrzeczywistości jest to identyfikator tablicy. Trzeci i czwarty ulegną awarii, jeśli ptrwskażą literał ciągu.
Masz to. Mam nadzieję, że teraz wszystko jest kryształowe. Byłeś świetną publicznością i będę tu przez cały tydzień.
(*ptr)++(nawiasy potrzebne do ujednoznacznienia*ptr++)