Niedawno natknąłem się na ten problem, którego sam nie potrafię zrozumieć.
Co naprawdę oznaczają te trzy wyrażenia ?
*ptr++
*++ptr
++*ptr
Próbowałem Ritchie. Ale niestety nie był w stanie nadążyć za tym, co powiedział o tych 3 operacjach.
Wiem, że wszystkie są wykonywane w celu zwiększenia wskaźnika / wskazanej wartości. Mogę również zgadywać, że może być wiele rzeczy dotyczących pierwszeństwa i kolejności ocen. Podobnie jak inkrementuje się wskaźnik, a potem pobiera zawartość tego wskaźnika, po prostu pobiera się zawartość, a następnie zwiększa wskaźnik itd. Itd. Jak widać, nie mam jasnego zrozumienia ich faktycznych operacji, które chciałbym jak najszybciej usunąć. Ale jestem naprawdę zagubiony, kiedy mam szansę zastosować je w programach. Na przykład:
int main()
{
const char *p = "Hello";
while(*p++)
printf("%c",*p);
return 0;
}
daje mi to wyjście:
ello
Ale spodziewałem się, że to się wydrukuje Hello
. Ostatnia prośba - proszę podać przykłady działania każdego wyrażenia w danym fragmencie kodu. Ponieważ przez większość czasu nad moją głową przelatuje tylko jeden akapit teorii.
(*ptr)++
(nawiasy potrzebne do ujednoznacznienia*ptr++
)char* p
, wskazujący na poprawny zakończony ciąg unikalnych znaków. Następnie mają funkcjęfn(char ch)
, która drukuje zarównoch
parametr i prąd wskazywany przez charp
. Teraz wywołajfn(*p++);
P: Czy wyświetlafn
ten sam znak dwa razy ? Zdziwiłbyś się, ilu profesorów źle zrozumiało to pytanie.const char* p = "Hello";
Odpowiedzi:
Oto szczegółowe wyjaśnienie, które, mam nadzieję, będzie pomocne. Zacznijmy od twojego programu, ponieważ jest on najłatwiejszy do wyjaśnienia.
Pierwsze stwierdzenie:
deklaruje
p
jako wskaźnik dochar
. Kiedy mówimy „wskaźnik do achar
”, co to oznacza? Oznacza to, że wartościąp
jest adres achar
;p
mówi nam, gdzie w pamięci jest trochę miejsca na przechowywaniechar
.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ńcup
jest wskaźnikiem do jednegochar
, a nie do całego ciągu. Wartościąp
jest adres'H'
in"Hello"
.Następnie konfigurujesz pętlę:
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):++
i pośrednia*
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ścip++
części. Więc weźmyp++
najpierw część.2. Wartość wyrażenia Postfix . Wartość
p++
jest wartościąp
przed przyrostem . Jeśli masz:wynik będzie następujący:
ponieważ
i++
szacuje się doi
przed przyrostem. Podobniep++
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 przezp
. 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ójrzint
ponownie na ten kod:Jak wspomniano wcześniej, wynik będzie następujący:
Gdy
i++
ocenia się w pierwszymprintf()
, to ocenia się 7. Ale C Standard gwarantuje, że w pewnym momencie przed drugimprintf()
zaczyna wykonującego efektem ubocznym tego++
operatora będą miały miejsce. Oznacza to, że zanimprintf()
nastąpi drugie ,i
zostanie zwiększone w wyniku działania++
operatora w pierwszymprintf()
. 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: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: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 jakTyle na ten temat. Co z resztą? Pytasz o znaczenie tych:
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 jaknie 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:
Wynik będzie:
... różni się od tego, co widzieliśmy z operatorem postfiksowym. Podobnie, jeśli masz:
wynik będzie następujący:
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:
zaskakująco egoistyczny wynik będzie następujący:
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 jakochar
, i maszchar
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:
Ten też jest interesujący. Jeśli masz:
da ci to entuzjastyczne wyjście:
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 najpierwprintf()
zostanie wydrukowane'H'
. Następnie, jako efekt uboczny ,'H'
zostanie zwiększony do'I'
. Drugi toprintf()
drukuje'I'
. I masz twoje wesołe powitanie.W porządku, ale w tych dwóch ostatnich przypadkach, dlaczego tego potrzebuję
Dlaczego nie mogę po prostu mieć czegoś takiego
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ć
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ś:
A oto czwarta, równie zabawna jak pozostałe trzy:
Pierwsza i druga ulegną awarii, jeśli w
ptr
rzeczywistości jest to identyfikator tablicy. Trzeci i czwarty ulegną awarii, jeśliptr
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ń.
źródło
Załóżmy, że
ptr
wskazuje i-ty element tablicyarr
.*ptr++
obliczaarr[i]
i ustawiaptr
wskazanie na (i + 1) -ty elementarr
. Jest odpowiednikiem*(ptr++)
.*++ptr
ustawiaptr
wskazanie (i + 1) -tego elementuarr
i oblicza doarr[i+1]
. Jest odpowiednikiem*(++ptr)
.++*ptr
wzrastaarr[i]
o jeden i ocenia się do swojej zwiększonej wartości; wskaźnikptr
pozostaje nietknięty. Jest odpowiednikiem++(*ptr)
.Jest jeszcze jeden, ale aby go napisać, potrzebujesz nawiasów:
(*ptr)++
rośniearr[i]
o jeden i szacuje się do swojej wartości przed zwiększeniem; wskaźnikptr
ponownie pozostaje nietknięty.Resztę możesz dowiedzieć się sam; odpowiedział na nie również @Jaguar.
źródło
*ptr++ : post increment a pointer ptr
*++ptr : Pre Increment a pointer ptr
++*ptr : preincrement the value at ptr location
Przeczytaj tutaj o operatorach preinkrementacji i postinkrementacji
To da
Hello
jako wyjścieźródło
Hello
Stan twojej pętli jest zły:
Jest taki sam jak
I to źle, powinno to być:
*ptr++
jest taki sam jak*(ptr++)
, czyli:*++ptr
jest taki sam jak*(++ptr)
, czyli:++*ptr
jest taki sam jak++(*ptr)
, czyli:źródło
Masz rację co do pierwszeństwa, pamiętaj, że
*
ma pierwszeństwo przed przyrostem przedrostka, ale nie przed przyrostem przyrostka. Oto jak ten podział:*ptr++
- przechodzenie od lewej do prawej, wyłuskiwanie wskaźnika, a następnie zwiększanie wartości wskaźnika (nie tego, na co wskazuje, ze względu na pierwszeństwo przyrostka nad dereferencją)*++ptr
- zwiększ wskaźnik, a następnie wyłuskuj go, dzieje się tak, ponieważ przedrostek i wyłuskiwanie mają ten sam priorytet i są oceniane w kolejności od prawej do lewej++*ptr
- podobny do powyższego pod względem pierwszeństwa, ponownie przechodząc od prawej do lewej w celu wyłuskiwania wskaźnika, a następnie zwiększania tego, na co wskazuje wskaźnik. Zwróć uwagę, że w twoim przypadku spowoduje to niezdefiniowane zachowanie, ponieważ próbujesz zmodyfikować zmienną tylko do odczytu (char* p = "Hello";
).źródło
Dodam moje zdanie, ponieważ chociaż inne odpowiedzi są poprawne, myślę, że czegoś brakuje.
znaczy
Natomiast
znaczy
Ważne jest, aby zrozumieć, że po zwiększeniu (i po zmniejszeniu) oznacza
Dlaczego to ma znaczenie? Cóż, w C to nie jest takie ważne. Jednak w C ++
ptr
może to być typ złożony, taki jak iterator. Na przykładW tym przypadku, ponieważ
it
jest to złożony typ,it++
może mieć skutki uboczne z powodutemp
tworzenia. Oczywiście, jeśli masz szczęście, kompilator spróbuje wyrzucić kod, który nie jest potrzebny, ale jeśli konstruktor lub destruktor iteratora cośit++
zrobi, pokaże te efekty, gdy utworzytemp
.Krótko o tym, co próbuję powiedzieć, brzmi: Napisz, co masz na myśli . Jeśli masz na myśli przyrost ptr, to
++ptr
nie piszptr++
. Jeśli masz na myślitemp = ptr, ptr += 1, temp
to napiszptr++
źródło
To jest to samo, co:
Zatem
ptr
pobierana jest wartość obiektu wskazywanego przez , a następnieptr
jest zwiększana.To jest to samo, co:
Tak więc wskaźnik
ptr
jest zwiększany, a następnieptr
odczytywany jest obiekt wskazywany przez .To jest to samo, co:
Zatem obiekt wskazywany przez
ptr
jest zwiększany;ptr
sama w sobie jest niezmieniona.źródło
Postfix i prefix mają wyższy priorytet niż dereference, więc
* ptr ++ w tym miejscu podaje przyrost ptr, a następnie wskazuje na nową wartość ptr
* ++ ptr tutaj PreInkrementacja pięści, a następnie wskazanie nowej wartości ptr
++ * ptr tutaj najpierw pobierz wartość ptr wskazującą na i zwiększ tę wartość
źródło
Wyrażenia wskaźników: * ptr ++, * ++ ptr i ++ * ptr:
Uwaga : wskaźniki muszą zostać zainicjowane i muszą mieć prawidłowy adres. Ponieważ w pamięci RAM oprócz naszego programu (a.out) jest dużo więcej programów uruchomionych jednocześnie, tj. Jeśli spróbujesz uzyskać dostęp do pamięci, która nie została zarezerwowana dla Ciebie, system operacyjny przejdzie przez błąd Segmentacji.
Zanim to wyjaśnimy, rozważmy prosty przykład.
przeanalizuj dane wyjściowe powyższego kodu, mam nadzieję, że otrzymałeś dane wyjściowe powyższego kodu. Z powyższego kodu jasno wynika, że nazwa wskaźnika ( ptr ) oznacza, że mówimy o adresie, a * ptr oznacza, że mówimy o wartości / danych.
PRZYPADEK 1 : * ptr ++, * ++ ptr, * (ptr ++) i * (++ ptr):
wyżej wspomniana składnia wszystkich 4 jest podobna,
address gets incremented
ale sposób , w jaki adres jest zwiększany, jest inny.Uwaga : aby rozwiązać dowolne wyrażenie, dowiedz się, ile operatorów występuje w wyrażeniu, a następnie znajdź priorytety operatora. I wielu operatorów mających ten sam priorytet, następnie sprawdź kolejność ewolucji lub asocjatywności, która może przebiegać od prawej (R) do lewej (L) lub od lewej do prawej.
* ptr ++ : Tutaj są 2 operatory, mianowicie de-reference (*) i ++ (inkrementacja). Oba mają ten sam priorytet, a następnie sprawdź skojarzenie, które jest od R do L. Więc rozpoczyna rozwiązywanie od prawej do lewej, niezależnie od operatorów, które pojawią się jako pierwsze.
* ptr ++ : pierwsza ++ pojawiła się podczas rozwiązywania z R do L, więc adres jest zwiększany, ale przyrost postu.
* ++ ptr : Tak samo jak pierwszy tutaj również adres jest zwiększany, ale jego wartość wstępna.
* (ptr ++) : Tutaj są 3 operatory, wśród nich grouping () o najwyższym priorytecie, więc pierwszy ptr ++ rozwiązany, tj. adres jest zwiększany ale post.
* (++ ptr) : Tak samo jak w powyższym przypadku adres również jest zwiększany, ale wstępnie inkrementowany.
PRZYPADEK 2 : ++ * ptr, ++ (* ptr), (* ptr) ++:
wyżej wspomniana składnia wszystkich 4 jest podobna, we wszystkich wartościach / danych są zwiększane, ale sposób zmiany wartości jest inny.
++ * ptr : first * pojawił się podczas rozwiązywania od R do L, więc wartość ulega zmianie, ale jej początkowy inkrement.
++ (* ptr) : Tak samo jak powyżej, wartość zostanie zmodyfikowana.
(* ptr) ++ : Tutaj są 3 operatory, wśród nich grouping () o najwyższym priorytecie, Inside () * ptr, więc pierwszy * ptr jest rozwiązany, tj. wartość jest zwiększana, ale post.
Uwaga : ++ * ptr i * ptr = * ptr + 1 są takie same, w obu przypadkach wartość ulega zmianie. ++ * ptr: używana jest tylko 1 instrukcja (INC), bezpośrednio wartość jest zmieniana w jednym strzale. * ptr = * ptr + 1: tutaj pierwsza wartość jest zwiększana (INC), a następnie przypisywana (MOV).
Aby zrozumieć powyższą różną składnię inkrementacji wskaźnika, rozważmy prosty kod:
W powyższym kodzie spróbuj komentować / usuwać komentarze i analizować wyniki.
Wskaźniki jako stałe : nie ma sposobów, za pomocą których można uczynić wskaźniki stałymi, o kilku wspominam tutaj.
1) const int * p OR int const * p : Tutaj
value
jest stała , adres nie jest stały, tj. Gdzie wskazuje p? Jakiś adres? Jaka jest wartość pod tym adresem? Jakaś wartość, prawda? Ta wartość jest stała, nie możesz jej modyfikować, ale gdzie wskazuje wskaźnik? Jakiś adres, prawda? Może również wskazywać na inny adres.Aby to zrozumieć, rozważmy poniższy kod:
Spróbuj przeanalizować wynik powyższego kodu
2) int const * p : nazywa się '
**constant pointe**r
' ieaddress is constant but value is not constant
. Tutaj nie możesz zmienić adresu, ale możesz zmodyfikować wartość.Uwaga : stały wskaźnik (powyższy przypadek) musi zostać zainicjowany podczas deklaracji.
Aby to zrozumieć, sprawdźmy prosty kod.
W powyższym kodzie, jeśli zauważysz, że nie ma ++ * p lub * p ++, więc możesz pomyśleć, że jest to prosty przypadek, ponieważ nie zmieniamy adresu ani wartości, ale spowoduje to błąd. Czemu ? Powód, o którym wspominam w komentarzach.
Więc jakie jest rozwiązanie tego problemu?
aby uzyskać więcej informacji na ten temat, rozważmy poniższy przykład.
3) const int * const p : Tutaj zarówno adres, jak i wartość są stałe .
Aby to zrozumieć, sprawdźmy poniższy kod
źródło
++*p
środki, które starają się zwiększyć wartość ASCII*p
, którenie możesz zwiększyć wartości, ponieważ jest ona stała, więc wystąpiłby błąd
tak jak w przypadku pętli while, pętla działa aż
*p++
do końca łańcucha, w którym znajduje się znak'\0'
(NULL).Odkąd
*p++
pomija pierwszy znak, wynik będzie wyświetlany tylko od drugiego znaku.Poniższy kod nie wyświetli niczego, ponieważ pętla while ma
'\0'
Poniższy kod daje takie same wyniki, jak następny kod, tj. Ello.
...................................
źródło