Śledziłem ten samouczek o tym, jak działa wskaźnik do wskaźnika .
Zacytuję odpowiedni fragment:
int i = 5, j = 6, k = 7; int *ip1 = &i, *ip2 = &j;
Teraz możemy ustawić
int **ipp = &ip1;
i
ipp
wskazuje, naip1
które wskazujei
.*ipp
jestip1
, i**ipp
jesti
, lub 5. Możemy zilustrować sytuację za pomocą naszej znanej notacji prostokątnej, na przykład:Jeśli to powiemy
*ipp = ip2;
zmieniliśmy wskaźnik wskazywany przez
ipp
(to znaczyip1
), aby zawierał kopięip2
, tak że (ip1
) wskazuje teraz naj
:
Moje pytanie brzmi: dlaczego na drugim zdjęciu ipp
nadal wskazuje, ip1
ale nie ip2
?
ipp
przy definiowaniu, aby Twoje pytanie było kompletne ;-)ipp
wskazanie naip1
to*ipp = ip2
jest takie samo, jakip1 = ip2
int **ipp
jest znacznie mniej intuicyjny niżint** ipp
, zwłaszcza gdy**ipp
oznacza zupełnie inną rzecz poza deklaracjami typów.int **ipp
wydaje mi się dość intuicyjny. To znaczy, robię**ipp
plikint
. To prawda.ipp
jestint**
, więc po prostu piszint**
zamiast magicznego „imp
dereferencja jest int” zrozumieniem .Odpowiedzi:
Zapomnij na chwilę o analogii wskazującej. To, co naprawdę zawiera wskaźnik, to adres pamięci. Jest
&
to operator „adres” - tj. Zwraca adres w pamięci obiektu.*
Operator daje przedmiot wskaźnik dotyczy, czyli biorąc pod uwagę wskaźnik zawierający adres, to zwraca obiekt pod tym adresem pamięci. Więc kiedy to robisz*ipp = ip2
, to, co robisz, polega na*ipp
pobraniu obiektu pod adresem przechowywanym podipp
którym jest,ip1
a następnie przypisaniu doip1
wartości przechowywanej wip2
, czyli adresiej
.Po prostu
&
-> Adres*
-> Wartość atźródło
Ponieważ zmieniłeś wartość wskazywaną przez
ipp
nie wartośćipp
. Tak więc,ipp
nadal wskazuje naip1
(wartośćipp
),ip1
wartość jest teraz taka sama jakip2
wartość, więc oba wskazują naj
.To:
jest taki sam jak:
źródło
int *ip1 = &i
i*ipp = ip2;
, tj. Jeśli usunieszint
z pierwszej instrukcji, to przypisania będą wyglądać bardzo podobnie, ale*
w obu przypadkach robi się coś zupełnie innego.Podobnie jak w przypadku większości pytań dla początkujących w tagu C, na to pytanie można odpowiedzieć, wracając do pierwszych zasad:
&
Operator włącza zmienną do wskaźnika.*
Operator włącza wskaźnik do zmiennej.(Z technicznego punktu widzenia powinienem powiedzieć „lwartość” zamiast „zmienna”, ale wydaje mi się, że bardziej jasne jest opisanie zmiennych lokalizacji pamięci jako „zmiennych”).
Mamy więc zmienne:
Zmienna
ip1
zawiera wskaźnik.&
Operator włączai
się wskaźnik i że wartość wskaźnika jest przypisanyip1
. Więcip1
zawiera wskaźnik doi
.Zmienna
ip2
zawiera wskaźnik.&
Operator włączaj
się wskaźnik i że wskaźnik jest przypisanyip2
. Więcip2
zawiera wskaźnik doj
.Zmienna
ipp
zawiera wskaźnik.&
Operator włącza zmiennąip1
do wskaźnika i że wartość wskaźnika jest przypisanyipp
. Więcipp
zawiera wskaźnik doip1
.Podsumujmy dotychczasową historię:
i
zawiera 5j
zawiera 6ip1
zawiera „wskaźnik doi
”ip2
zawiera „wskaźnik doj
”ipp
zawiera „wskaźnik doip1
”Teraz mówimy
*
Operator włącza wskaźnik wstecz do zmiennej. Pobieramy wartośćipp
, czyli „wskaźnik doip1
i zamieniamy ją w zmienną. Jaka zmienna?ip1
Oczywiście!Dlatego jest to po prostu inny sposób powiedzenia
Więc pobieramy wartość
ip2
. Co to jest? „wskaźnik doj
”. Przypisujemy wartość wskaźnika doip1
, więcip1
teraz jest to „wskaźnik doj
”Zmieniliśmy tylko jedno: wartość
ip1
:i
zawiera 5j
zawiera 6ip1
zawiera „wskaźnik doj
”ip2
zawiera „wskaźnik doj
”ipp
zawiera „wskaźnik doip1
”Zmienna zmienia się po przypisaniu do niej. Policz zadania; nie może być więcej zmian w zmiennych niż przypisań! Zaczynasz poprzez przypisanie
i
,j
,ip1
,ip2
iipp
. Następnie przypisujesz do*ipp
, co, jak widzieliśmy, oznacza to samo, co „przypisuj doip1
”. Ponieważ nie przypisałeś goipp
po raz drugi, to się nie zmieniło!Jeśli chcesz zmienić
ipp
, musisz faktycznie przypisać doipp
:na przykład.
źródło
mam nadzieję, że ten fragment kodu może pomóc.
wyprowadza:
źródło
Osobiście uważam, że zdjęcia ze strzałkami skierowanymi w tę stronę lub takie, które utrudniają zrozumienie wskazówek. Sprawia, że wydają się abstrakcyjnymi, tajemniczymi istotami. Oni nie są.
Jak wszystko inne w twoim komputerze, wskaźniki są liczbami . Nazwa „wskaźnik” to po prostu fantazyjny sposób powiedzenia „zmiennej zawierającej adres”.
Dlatego pozwolę sobie poruszyć różne kwestie, wyjaśniając, jak właściwie działa komputer.
Mamy
int
, ma nazwęi
i wartość 5. To jest przechowywane w pamięci. Podobnie jak wszystko zapisane w pamięci, potrzebuje adresu, inaczej nie bylibyśmy w stanie go znaleźć. Powiedzmy, żei
kończy się pod adresem 0x12345678, a jego kolegaj
z wartością 6 kończy się tuż po nim. Zakładając 32-bitowy procesor, w którym int wynosi 4 bajty, a wskaźniki 4 bajty, wówczas zmienne są przechowywane w pamięci fizycznej w następujący sposób:Teraz chcemy wskazać na te zmienne. Tworzymy jeden wskaźnik do int
int* ip1
, i jedenint* ip2
. Podobnie jak wszystko w komputerze, te zmienne wskaźnikowe są również przydzielane gdzieś w pamięci. Załóżmy, że kończą się one pod następnymi sąsiednimi adresami w pamięci, zaraz poj
.ip1=&i;
Ustawiliśmy wskaźniki tak, aby zawierały adresy wcześniej przydzielonych zmiennych: („skopiuj adres i do ip1”) iip2=&j
. To, co dzieje się między wierszami, to:Więc to, co otrzymaliśmy, to jeszcze jakieś 4-bajtowe fragmenty pamięci zawierające liczby. Nigdzie w zasięgu wzroku nie ma mistycznych ani magicznych strzał.
W rzeczywistości, patrząc na zrzut pamięci, nie możemy stwierdzić, czy adres 0x12345680 zawiera znak
int
lubint*
. Różnica polega na tym, w jaki sposób nasz program korzysta z treści przechowywanych pod tym adresem. (Zadaniem naszego programu jest właściwie po prostu powiedzieć procesorowi, co ma zrobić z tymi liczbami.)Następnie dodajemy kolejny poziom pośrednictwa za pomocą
int** ipp = &ip1;
. Znowu otrzymujemy po prostu kawałek pamięci:Wzór wydaje się znajomy. Kolejny fragment 4 bajtów zawierający liczbę.
Teraz, gdybyśmy mieli zrzut pamięci powyższej fikcyjnej małej pamięci RAM, moglibyśmy ręcznie sprawdzić, gdzie wskazują te wskaźniki. Sprawdzamy, co jest przechowywane pod adresem
ipp
zmiennej i znajdujemy zawartość 0x12345680. Który jest oczywiście adresem, pod którymip1
jest przechowywany. Możemy udać się pod ten adres, sprawdzić tam zawartość i znaleźć adresi
, a na koniec udać się pod ten adres i znaleźć numer 5.Więc jeśli weźmiemy zawartość ipp,
*ipp
otrzymamy adres zmiennej wskaźnikowejip1
. Pisząc*ipp=ip2
, kopiujemy ip2 do ip1, jest to równoważneip1=ip2
. W obu przypadkach otrzymalibyśmy(Te przykłady zostały podane dla procesora typu big endian)
źródło
location, value, variable
lokalizacji1,2,3,4,5
i wartościA,1,B,C,3
, odpowiednią ideę wskaźników można łatwo wyjaśnić bez użycia strzałek, które są z natury mylące. Bez względu na wybraną implementację, w jakimś miejscu istnieje wartość i jest to element układanki, który zostaje zaciemniony podczas modelowania za pomocą strzałek.&
Operator o zmiennej daje monetę, która reprezentuje tę zmienną.*
Operator na tej monety daje kopię zmiennej. Nie potrzeba strzał!Zwróć uwagę na zadania:
wyniki
ipp
do wskazaniaip1
.więc do
ipp
do do punktuip2
, powinniśmy zmienić w podobny sposób,czego najwyraźniej nie robimy. Zamiast tego zmieniamy wartość pod adresem wskazanym przez
ipp
.Wykonując następujące czynności
po prostu zastępujemy przechowywaną wartość
ip1
.ipp = &ip1
, Środki*ipp = ip1 = &i
,Teraz
*ipp = ip2 = &j
.Więc
*ipp = ip2
jest zasadniczo taki sam jakip1 = ip2
.źródło
Żadne później przypisanie nie zmieniło wartości
ipp
. Dlatego nadal wskazujeip1
.To, co robisz
*ipp
, czyli zip1
, nie zmienia faktu, na któryipp
wskazujeip1
.źródło
umieściłeś ładne zdjęcia, spróbuję zrobić fajną sztukę ascii:
Jak @ Robert-S-Barnes powiedział w swojej odpowiedzi: zapomnij o wskazówkach io tym, co wskazuje na co, ale myśl w kategoriach pamięci. Zasadniczo
int*
oznacza to, że zawiera adres zmiennej, a anint**
zawiera adres zmiennej, która zawiera adres zmiennej. Następnie możesz użyć algebry wskaźnika, aby uzyskać dostęp do wartości lub adresów:&foo
średnichaddress of foo
i*foo
średnichvalue of the address contained in foo
.Tak więc, ponieważ wskaźniki dotyczą radzenia sobie z pamięcią, najlepszym sposobem na uczynienie tego „namacalnym” jest pokazanie, co algebra wskaźników robi z pamięcią.
Oto pamięć twojego programu (uproszczona na potrzeby przykładu):
kiedy robisz swój początkowy kod:
tak wygląda twoja pamięć:
tam możesz zobaczyć
ip1
i pobraćip2
adresyi
ij
iipp
jeszcze nie istnieje. Nie zapominaj, że adresy to po prostu liczby całkowite zapisane w specjalnym typie.Następnie deklarujesz i definiujesz
ipp
takie jak:więc oto twoja pamięć:
a następnie zmieniasz wartość wskazywaną przez przechowywany adres
ipp
, czyli adres przechowywany wip1
:pamięć programu jest
NB: ponieważ
int*
jest to typ specjalny, wolę zawsze unikać deklarowania wielu wskaźników w tej samej linii, ponieważ myślę, żeint *x;
lubint *x, *y;
notacja może wprowadzać w błąd. Wolę pisaćint* x; int* y;
HTH
źródło
ip2
powinna być3
nie4
.Ponieważ kiedy mówisz
mówisz „obiekt wskazany przez
ipp
”, aby wskazać kierunek pamięci, któryip2
wskazuje.Nie mówisz,
ipp
żeby wskazywaćip2
.źródło
Jeśli dodasz operator wyłuskiwania
*
do wskaźnika, przekierowujesz ze wskaźnika do wskazanego obiektu.Przykłady:
W związku z tym:
źródło
Jeśli chcesz
ipp
wskazaćip2
, musisz powiedziećipp = &ip2;
. Jednak toip1
nadal wskazywałobyi
.źródło
Na samym początku ustawiłeś,
Teraz wyłuskuj to jako
źródło
Rozważ każdą zmienną przedstawioną w ten sposób:
więc twoje zmienne powinny być reprezentowane w ten sposób
Ponieważ wartość
ipp
jest&ip1
taka instrukcja:zmienia wartość na adresie
&ip1
na wartość zip2
, co oznaczaip1
:Ale
ipp
nadal:Więc wartość
ipp
nadal&ip1
oznacza, że nadal wskazujeip1
.źródło
Ponieważ zmieniasz wskaźnik
*ipp
. To znaczyipp
(nazwa zmienna) ---- wejdź do środka.ipp
jest adresip1
.*ipp
idź do (adres od środka)ip1
.Teraz jesteśmy na
ip1
.*ipp
(tj.ip1
) =ip
2.ip2
zawiera adresj
.soip1
zawartość zostanie zastąpiona zawartością ip2 (tj. adres j), NIE ZMIENIAMYipp
TREŚCI. OTÓŻ TO.źródło
*ipp = ip2;
oznacza:Przypisz
ip2
do zmiennej wskazywanej przezipp
. Więc jest to równoważne z:Jeśli chcesz, aby adres
ip2
został zapisanyipp
, po prostu wykonaj:Teraz
ipp
wskazuje naip2
.źródło
ipp
może przechowywać wartość (tj. wskazywać) wskaźnik do obiektu typu wskaźnik . Kiedy to zrobiszwtedy
ipp
zawiera adres zmiennej (wskaźnik)ip2
, który jest (&ip2
) typu wskaźnik do wskaźnika . Teraz strzałkaipp
na drugim zdjęciu będzie wskazywać naip2
.Encyklopedia mówi: operator operator nieprawidłowego działa na zmienną wskaźnika oraz zwraca l wartość (zmienna), co odpowiada wartości na wskaźnik adresu. Nazywa się to wyłuskiwaniem wskaźnika.
*
Stosowanie
*
operatora przyipp
wyłuskiwaniu go do l-wartości wskaźnika doint
typu. Wyłuskana wartość l*ipp
jest wskaźnikiemint
typu do , może zawierać adresint
typu danych. Po oświadczeniuipp
posiada adresip1
i*ipp
posiada adres (wskazuje)i
. Możesz powiedzieć, że*ipp
jest to aliasip1
. Obie**ipp
i*ip1
są aliasami dlai
.Wykonując
*ipp
iip2
oba wskazują na tę samą lokalizację, aleipp
nadal wskazująip1
.W
*ipp = ip2;
rzeczywistości kopiuje zawartośćip2
(adresj
) doip1
(jak*ipp
jest aliasemip1
), w efekcie wykonując oba wskaźnikiip1
iip2
wskazując na ten sam obiekt (j
).Tak więc, na drugim rysunku, strzałka
ip1
iip2
wskazuje,j
podczas gdyipp
nadal wskazuje,ip1
ponieważ nie jest wykonywana żadna modyfikacja w celu zmiany wartościipp
.źródło