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 p
jako wskaźnik do char
. Kiedy mówimy „wskaźnik do a char
”, co to oznacza? Oznacza to, że wartością p
jest adres a char
; p
mówi nam, gdzie w pamięci jest trochę miejsca na przechowywanie char
.
Instrukcja jest również inicjowana, p
aby wskazać pierwszy znak w literale ciągu "Hello"
. Dla dobra tego ćwiczenia, ważne jest, aby zrozumieć, p
jak nie wskazując na cały ciąg, ale tylko do pierwszego znaku, 'H'
. W końcu p
jest wskaźnikiem do jednego char
, a nie do całego ciągu. Wartością p
jest 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 i
przed przyrostem. Podobnie p++
będzie oceniać bieżącą wartość p
. Jak wiemy, aktualna wartość p
to 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 int
ponownie 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 , i
zostanie 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. p
został 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 ++p
ma 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 ptr
ma 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 ++ptr
częś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 *ptr
częś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 *p
część 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 char
o 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 *p
część 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 p
jest tablicą. Tablica nie jest modyfikowalną wartością l; nie możesz zmienić p
miejsca, 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 ptr
rzeczywistości jest to identyfikator tablicy. Trzeci i czwarty ulegną awarii, jeśli ptr
wskażą 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++
)