Niedawno miałem przyjemność wyjaśnić wskazówki początkującym programistom w C i napotkałem następującą trudność. Może się to w ogóle nie wydawać problemem, jeśli już wiesz, jak używać wskaźników, ale spróbuj spojrzeć na poniższy przykład z czystym umysłem:
int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);
Dla absolutnie początkującego rezultat może być zaskakujący. W linii 2 właśnie zadeklarował * bar jako & foo, ale w linii 4 okazuje się, że * bar to w rzeczywistości foo zamiast & foo!
Można powiedzieć, że zamieszanie wynika z niejednoznaczności symbolu *: w linii 2 jest używany do zadeklarowania wskaźnika. W linii 4 jest używany jako operator jednoargumentowy, który pobiera wartość wskazywaną przez wskaźnik. Dwie różne rzeczy, prawda?
Jednak to „wyjaśnienie” wcale nie pomaga początkującym. Wprowadza nową koncepcję, wskazując na subtelną rozbieżność. To nie może być właściwy sposób nauczania.
Jak więc Kernighan i Ritchie to wyjaśnili?
Operator jednoargumentowy * jest operatorem pośrednim lub wyłuskiwaniem; po zastosowaniu do wskaźnika uzyskuje dostęp do obiektu wskazywanego przez wskaźnik. […]
Deklaracja wskaźnika ip
int *ip
jest traktowana jako mnemonik; mówi, że wyrażenie*ip
jest int. Składnia deklaracji zmiennej naśladuje składnię wyrażeń, w których zmienna może się pojawić .
int *ip
powinno być odczytywane jako „ *ip
zwróci int
”? Ale dlaczego w takim razie przypisanie po deklaracji nie jest zgodne z tym wzorcem? Co jeśli początkujący chce zainicjować zmienną? int *ip = 1
(czytaj: *ip
zwróci wartość int
i int
jest 1
) nie będzie działać zgodnie z oczekiwaniami. Model konceptualny po prostu nie wydaje się spójny. Czy coś mi umyka?
Edycja: próbowano podsumować odpowiedzi tutaj .
*
w deklaracji jest to token oznaczający „deklaruj wskaźnik”, w wyrażeniach jest to operator wyłuskiwania i te dwa reprezentują różne rzeczy, które mają ten sam symbol (to samo co operator mnożenia - ten sam symbol, inne znaczenie). To zagmatwane, ale wszystko inne niż rzeczywisty stan rzeczy będzie jeszcze gorsze.int* bar
było bardziej oczywiste, że gwiazda jest w rzeczywistości częścią typu, a nie częścią identyfikatora. Oczywiście powoduje to różne problemy z nieintuicyjnymi rzeczami, takimi jakint* a, b
.*
może mieć dwa różne znaczenia w zależności od kontekstu. Tak jak ta sama litera może być wymawiana różnie w zależności od słowa, w którym jest, przez co trudno jest nauczyć się mówić wieloma językami. Gdyby każda koncepcja / operacja miała swój własny symbol, potrzebowalibyśmy znacznie większych klawiatur, więc symbole są przetwarzane, gdy ma to sens.int* p
), ostrzegając ucznia przed używaniem wielu deklaracji w tym samym wierszu, gdy w grę wchodzą wskaźniki. Kiedy uczeń w pełni zrozumie pojęcie wskaźników, wyjaśnij mu, żeint *p
składnia is jest równoważna, a następnie wyjaśnij problem z wieloma deklaracjami.Odpowiedzi:
Aby Twój uczeń mógł zrozumieć znaczenie
*
symbolu w różnych kontekstach, musi najpierw zrozumieć, że konteksty są rzeczywiście różne. Kiedy zrozumieją, że konteksty są różne (tj. Różnica między lewą stroną zadania a ogólnym wyrażeniem), zrozumienie różnic nie jest zbyt wielkim skokiem poznawczym.Najpierw wyjaśnij, że deklaracja zmiennej nie może zawierać operatorów (zademonstruj to, pokazując, że umieszczenie symbolu
-
lub+
w deklaracji zmiennej po prostu powoduje błąd). Następnie pokaż, że wyrażenie (tj. Po prawej stronie przypisania) może zawierać operatory. Upewnij się, że uczestnik kursu rozumie, że wyrażenie i deklaracja zmiennej to dwa zupełnie różne konteksty.Kiedy zrozumieją, że konteksty są różne, możesz przejść do wyjaśnienia, że kiedy
*
symbol znajduje się w deklaracji zmiennej przed identyfikatorem zmiennej, oznacza to „zadeklaruj tę zmienną jako wskaźnik”. Następnie możesz wyjaśnić, że*
symbol używany w wyrażeniu (jako operator jednoargumentowy) jest „operatorem wyłuskiwania” i oznacza „wartość pod adresem”, a nie jego wcześniejsze znaczenie.Aby naprawdę przekonać ucznia, wyjaśnij, że twórcy C mogli użyć dowolnego symbolu na oznaczenie operatora wyłuskiwania (tj. Mogli
@
zamiast tego użyć ), ale z jakiegokolwiek powodu podjęli decyzję o użyciu*
.Podsumowując, nie da się wyjaśnić, że konteksty są różne. Jeśli uczeń nie rozumie różnych kontekstów, nie może zrozumieć, dlaczego
*
symbol może oznaczać różne rzeczy.źródło
Powód, dla którego skrót:
w twoim przykładzie może być mylące, ponieważ łatwo jest go błędnie odczytać jako równoważny z:
kiedy to faktycznie oznacza:
Napisany w ten sposób, z oddzielną deklaracją zmiennej i przypisaniem, nie ma takiego potencjału do zamieszania, a paralelizm użycia ↔ deklaracji opisany w cytacie K&R działa doskonale:
Pierwsza linia deklaruje zmienną
bar
, taką*bar
jakint
.Druga linia przypisuje adres
foo
dobar
, tworząc*bar
(aint
) alias dlafoo
(równieżint
).Wprowadzając składnię wskaźnika C dla początkujących, pomocne może być początkowo trzymanie się tego stylu oddzielania deklaracji wskaźnika od przypisań i wprowadzenie połączonej składni skróconej (z odpowiednimi ostrzeżeniami o potencjalnym pomyłce) dopiero po wprowadzeniu podstawowych pojęć dotyczących używania wskaźnika w C zostały odpowiednio zinternalizowane.
źródło
typedef
.typedef int *p_int;
oznacza, że zmienna typup_int
ma właściwość, która*p_int
jestint
. Wtedy mamyp_int bar = &foo;
. Zachęcanie kogokolwiek do tworzenia niezainicjowanych danych i późniejszego przypisywania ich do nich w ramach domyślnego nawyku wydaje się ... złym pomysłem.int a[2] = {47,11};
, że nie jest to inicjalizacja (nieistniejącego) elementua[2]
.*
powinno być częścią typu, a nie przypisanym do zmiennej, a wtedy byłbyś w stanie napisać,int* foo_ptr, bar_ptr
aby zadeklarować dwa wskaźniki. Ale w rzeczywistości deklaruje wskaźnik i liczbę całkowitą.Krótkie deklaracje
Dobrze jest poznać różnicę między deklaracją a inicjalizacją. Deklarujemy zmienne jako typy i inicjalizujemy je wartościami. Jeśli robimy jedno i drugie w tym samym czasie, często nazywamy to definicją.
1.
int a; a = 42;
Możemy zadeklarować o
int
nazwie A . Następnie inicjalizujemy go, nadając mu wartość42
.2.
int a = 42;
Możemy zadeklarować i
int
nazwany i nadać mu wartość 42. Jest on inicjowany . Definicja.42
3.
a = 43;
Kiedy używamy zmiennych, mówimy, że działamy na nich.
a = 43
jest operacją przypisania. Liczbę 43 przypisujemy zmiennej a.Mówiąc
deklarujemy, że bar jest wskaźnikiem do int. Mówiąc
deklarujemy bar i inicjalizujemy go adresem foo .
Po zainicjowaniu bara możemy użyć tego samego operatora, gwiazdki, aby uzyskać dostęp i operować na wartości foo . Bez operatora uzyskujemy dostęp i działamy na adresie wskazywanym przez wskaźnik.
Poza tym pozwoliłem przemówić obrazowi.
Co
Uproszczone WSPOMNIENIE o tym, co się dzieje. (A tutaj wersja odtwarzacza, jeśli chcesz pauzować itp.)
źródło
Drugie stwierdzenie
int *bar = &foo;
można zobaczyć obrazowo w pamięci jako:Teraz
bar
jest wskaźnikiem typuint
zawierającego adres&
zfoo
. Używając operatora jednoargumentowego*
, szanujemy, aby pobrać wartość zawartą w „foo” za pomocą wskaźnikabar
.EDYCJA : Moje podejście do początkujących polega na wyjaśnianiu
memory address
zmiennej, tjMemory Address:
Każda zmienna ma powiązany z nią adres dostarczony przez system operacyjny. Wint a;
,&a
jest adresem zmienneja
.Kontynuuj wyjaśnianie podstawowych typów zmiennych w
C
as,Types of variables:
Zmienne mogą zawierać wartości odpowiednich typów, ale nie adresy.Introducing pointers:
Jak wspomniano powyżej, na przykład zmienneMożliwe jest przypisanie,
b = a
ale nieb = &a
, ponieważ zmiennab
może mieć wartość, ale nie adres, dlatego wymagamy wskaźników .Pointer or Pointer variables :
Jeśli zmienna zawiera adres, nazywana jest zmienną wskaźnikową. Użyj*
w deklaracji, aby poinformować, że jest to wskaźnik.źródło
int *ip
jako „ip jest wskaźnikiem (*) typu int”, masz kłopoty podczas czytania czegoś takiegox = (int) *ip
.x = (int) *ip;
, pobierz wartość przez wyłuskiwanie wskaźnikaip
i rzuć wartość naint
dowolny typip
.int* bar = &foo;
sprawia ładuje więcej sensu. Tak, wiem, że zadeklarowanie wielu wskaźników w jednej deklaracji powoduje problemy. Nie, nie sądzę, żeby to miało w ogóle znaczenie.Patrząc na odpowiedzi i komentarze tutaj, wydaje się, że istnieje ogólna zgoda, że dana składnia może być myląca dla początkującego. Większość z nich proponuje coś podobnego:
Możesz
int* bar
zamiast tego napisać,int *bar
aby podkreślić różnicę. Oznacza to, że nie będziesz postępować zgodnie z podejściem K&R „deklaracja naśladuje użycie”, ale podejściem Stroustrup C ++ :Nie deklarujemy,
*bar
że jesteśmy liczbą całkowitą. Deklarujemy,bar
że jesteśmyint*
. Jeśli chcemy zainicjować nowo utworzoną zmienną w tej samej linii, jasne jest, że mamy do czynienia zbar
, a nie*bar
.int* bar = &foo;
Wady:
int* foo, bar
vsint *foo, *bar
).Edycja: inne podejście, które zostało zasugerowane, polega na "naśladowaniu" K&R, ale bez "skróconej składni" (zobacz tutaj ). Gdy tylko pominiesz wykonanie deklaracji i przypisania w tej samej linii , wszystko będzie wyglądało o wiele bardziej spójnie.
Jednak wcześniej czy później uczeń będzie musiał zajmować się wskaźnikami jako argumentami funkcji. I wskaźniki jako typy zwracane. I wskaźniki do funkcji. Będziesz musiał wyjaśnić różnicę między
int *func();
aint (*func)();
. Myślę, że prędzej czy później wszystko się rozpadnie. A może wcześniej jest lepiej niż później.źródło
Jest powód, dla którego preferuje styl K&R,
int *p
a styl Stroustrupint* p
; oba są ważne (i oznaczają to samo) w każdym języku, ale jak ujął to Stroustrup:Teraz, skoro próbujesz tutaj uczyć C, sugerowałoby to, że powinieneś bardziej podkreślać wyrażenia niż typy, ale niektórzy ludzie mogą łatwiej przyswoić jeden nacisk szybciej niż drugi, a to raczej o nich niż o języku.
Dlatego niektórym osobom łatwiej będzie zacząć od pomysłu, że a
int*
to coś innego niż anint
i od tego zacząć.Jeśli ktoś ma szybko grok sposób patrzenia na to, że zastosowanie
int* bar
mająbar
jako rzecz, która nie jest typu int, ale wskaźnik doint
, wtedy będziesz szybko zobaczyć, że*bar
jest robić coś nabar
, a reszta przyjdzie sama. Gdy już to zrobisz, możesz później wyjaśnić, dlaczego programiści C woląint *bar
.Albo nie. Gdyby istniał jeden sposób, w jaki wszyscy zrozumieliby tę koncepcję, od początku nie mielibyśmy żadnych problemów, a najlepszy sposób na wyjaśnienie go jednej osobie niekoniecznie będzie najlepszym sposobem na wyjaśnienie go drugiej.
źródło
int* p = &a
możemy to zrobićint* r = *p
. Jestem prawie pewien, że omówił to w The Design and Evolution of C ++ , ale minęło dużo czasu, odkąd to przeczytałem i głupio przekazałem komuś swoją kopię.int& r = *p
. I założę się, że pożyczkobiorca wciąż próbuje przetrawić książkę.Var A, B: ^Integer;
że wyjaśnia, że typ „wskaźnik do liczby całkowitej” odnosi się zarówno do, jakA
iB
. UżywanieK&R
styluint *a, *b
jest również wykonalne; ale taka deklaracja wyglądaint* a,b;
jednak tak, jakby byłaa
ib
obie są deklarowane jakoint*
, ale w rzeczywistości deklarujea
jakoint*
ib
jakoint
.tl; dr:
O: nie. Wyjaśnij wskaźniki początkującym i pokaż im, jak przedstawić koncepcje wskaźników w składni języka C po.
IMO składnia C nie jest okropna, ale też nie jest cudowna: nie jest to ani wielka przeszkoda, jeśli już rozumiesz wskaźniki, ani żadna pomoc w ich nauce.
Dlatego: zacznij od wyjaśnienia wskazówek i upewnij się, że naprawdę je rozumieją:
Wyjaśnij je na diagramach w kształcie prostokąta i strzałki. Możesz to zrobić bez adresów szesnastkowych, jeśli nie są one istotne, po prostu pokaż strzałki wskazujące inne pole lub jakiś symbol nul.
Wyjaśnij pseudokodem: po prostu wpisz adres foo i wartość przechowywaną w bar .
Następnie, kiedy nowicjusz zrozumie, czym są wskazówki, dlaczego i jak ich używać; następnie pokaż mapowanie na składnię C.
Podejrzewam, że powodem, dla którego tekst K&R nie dostarcza modelu koncepcyjnego, jest to, że oni już zrozumieli wskaźniki i prawdopodobnie założyli, że co inny kompetentny programista w tamtym czasie też to zrobił. Mnemonik jest tylko przypomnieniem odwzorowania dobrze zrozumiałej koncepcji na składnię.
źródło
Ten problem jest nieco zagmatwany, gdy zaczynasz uczyć się C.
Oto podstawowe zasady, które mogą Ci pomóc w rozpoczęciu:
W C jest tylko kilka podstawowych typów:
char
: wartość całkowita o rozmiarze 1 bajtu.short
: wartość całkowita o rozmiarze 2 bajtów.long
: wartość całkowita o rozmiarze 4 bajtów.long long
: wartość całkowita o rozmiarze 8 bajtów.float
: wartość niecałkowita o rozmiarze 4 bajtów.double
: wartość niecałkowita o rozmiarze 8 bajtów.Zauważ, że rozmiar każdego typu jest ogólnie definiowany przez kompilator, a nie przez standard.
Te liczby całkowite
short
,long
ilong long
są zwykle następujeint
.Nie jest to jednak konieczne i można ich używać bez
int
.Alternatywnie możesz po prostu stwierdzić
int
, ale może to być inaczej interpretowane przez różne kompilatory.Podsumowując:
short
jest taki sam jak,short int
ale niekoniecznie taki sam jakint
.long
jest taki sam jak,long int
ale niekoniecznie taki sam jakint
.long long
jest taki sam jak,long long int
ale niekoniecznie taki sam jakint
.W danym kompilatorze
int
jest alboshort int
albolong int
albolong long int
.Jeśli deklarujesz zmienną pewnego typu, możesz również zadeklarować inną zmienną wskazującą na nią.
Na przykład:
int a;
int* b = &a;
Zasadniczo dla każdego typu podstawowego mamy również odpowiedni typ wskaźnika.
Na przykład:
short
ishort*
.Istnieją dwa sposoby "spojrzenia" na zmienną
b
(to prawdopodobnie dezorientuje większość początkujących) :Możesz rozważyć
b
jako zmienną typuint*
.Możesz rozważyć
*b
jako zmienną typuint
.Dlatego niektórzy ludzie deklarowaliby
int* b
, podczas gdy inni deklarowaliint *b
.Ale faktem jest, że te dwie deklaracje są identyczne (spacje są bez znaczenia).
Można użyć
b
jako wskaźnika do wartości całkowitej lub*b
jako rzeczywistej wskazanej liczby całkowitej.Można dostać (odczyt) wartości spiczasty:
int c = *b
.I można ustawić (zapis) wartość spiczasty:
*b = 5
.Wskaźnik może wskazywać na dowolny adres pamięci, a nie tylko na adres jakiejś zmiennej, którą wcześniej zadeklarowałeś. Należy jednak zachować ostrożność podczas używania wskaźników, aby uzyskać lub ustawić wartość znajdującą się pod wskazanym adresem pamięci.
Na przykład:
int* a = (int*)0x8000000;
Tutaj mamy zmienną
a
wskazującą na adres pamięci 0x8000000.Jeśli ten adres pamięci nie jest zamapowany w przestrzeni pamięci twojego programu, to każda operacja odczytu lub zapisu
*a
najprawdopodobniej spowoduje awarię programu z powodu naruszenia zasad dostępu do pamięci.Możesz bezpiecznie zmienić wartość
a
, ale powinieneś być bardzo ostrożny, zmieniając wartość*a
.Typ
void*
jest wyjątkowy, ponieważ nie ma odpowiedniego „typu wartości”, którego można użyć (tj. Nie można zadeklarowaćvoid a
). Ten typ jest używany tylko jako ogólny wskaźnik do adresu pamięci, bez określania typu danych, które znajdują się w tym adresie.źródło
Być może przejście przez to trochę bardziej ułatwi to:
Poproś, aby powiedzieli ci, czego oczekują, że dane wyjściowe będą w każdej linii, a następnie niech uruchomią program i zobaczą, co się pojawi. Wyjaśnij ich pytania (naga wersja z pewnością podpowie kilka - ale możesz później martwić się o styl, surowość i przenośność). Następnie, zanim ich umysł zamieni się w papkę z przemyślenia lub staną się zombie po obiedzie, napisz funkcję, która przyjmuje wartość, i taką samą, która przyjmuje wskaźnik.
Z mojego doświadczenia wynika, że „dlaczego to jest drukowane w ten sposób?” garb, a następnie od razu pokazując, dlaczego jest to przydatne w parametrach funkcji, poprzez praktyczne zabawy (jako wstęp do niektórych podstawowych materiałów K&R, takich jak analiza ciągów / przetwarzanie tablic), co sprawia, że lekcja nie tylko ma sens, ale się trzyma.
Następnym krokiem jest przekonanie ich, aby wyjaśnili ci, jak się z tym
i[0]
wiąże&i
. Jeśli będą w stanie to zrobić, nie zapomną o tym i możesz zacząć mówić o strukturach, nawet trochę wcześniej, tak aby to się zapadło.Powyższe zalecenia dotyczące skrzynek i strzał są również dobre, ale mogą również zakończyć się dygresją do pełnej dyskusji o tym, jak działa pamięć - która jest rozmową, która musi się wydarzyć w pewnym momencie, ale może odwrócić uwagę od tego, co jest w zasięgu ręki. : jak interpretować notację wskaźnikową w C.
źródło
int foo = 1;
. Teraz jest OK:int *bar; *bar = foo;
. To nie jest w porządku:int *bar = foo;
Typ wyrażenia
*bar
toint
; w ten sposób typ zmiennej (i wyrażenia)bar
toint *
. Ponieważ zmienna ma typ wskaźnikowy, jej inicjator również musi mieć typ wskaźnika.Istnieje niespójność między inicjalizacją a przypisaniem zmiennej wskaźnikowej; to jest coś, czego trzeba się nauczyć na własnej skórze.
źródło
Wolałbym przeczytać to, ponieważ pierwszy
*
dotyczyint
więcej niżbar
.źródło
int* a, b
nie robi tego, co według nich robi.int* a,b
aby w ogóle tego używać. Dla lepszej czytelności, aktualizacji itp ... w każdym wierszu powinna znajdować się tylko jedna deklaracja zmiennej i nigdy więcej. Jest to również coś do wyjaśnienia początkującym, nawet jeśli kompilator sobie z tym poradzi.*
jako o typie i po prostu zniechęcaćint* a, b
. Chyba że wolisz mówić, że*a
toint
raczej typ niża
wskazówka doint
...int *a, b;
nie powinno być używane. Deklarowanie dwóch zmiennych o różnych typach w tym samym oświadczeniu jest dość kiepską praktyką i silnym kandydatem do problemów związanych z konserwacją. Być może jest inaczej dla tych z nas, którzy pracują w osadzonym polu, gdzie aint*
i aint
mają często różne rozmiary i czasami są przechowywane w zupełnie innych lokalizacjach pamięci. Jest to jeden z wielu aspektów języka C, którego najlepiej byłoby uczyć jako „wolno, ale nie rób tego”.Question 1
: Co to jestbar
?Ans
: Jest to zmienna wskaźnikowa (do wpisaniaint
). Wskaźnik powinien wskazywać na jakąś prawidłową lokalizację w pamięci, a później powinien zostać wyłuskany (* bar) przy użyciu operatora jednoargumentowego*
w celu odczytania wartości przechowywanej w tej lokalizacji.Question 2
: Co to jest&foo
?Ans
: foo jest zmienną typu., któraint
jest przechowywana w jakiejś poprawnej lokalizacji w pamięci i tej lokalizacji otrzymujemy od operatora,&
więc teraz mamy jakąś prawidłową lokalizację w pamięci&foo
.Tak więc oba razem wzięte, tj. To, czego potrzebował wskaźnik, było prawidłową lokalizacją w pamięci i zostało
&foo
to osiągnięte, więc inicjalizacja jest dobra.Teraz wskaźnik
bar
wskazuje na prawidłowe miejsce w pamięci, a wartość w nim przechowywana może zostać odczytana, tj*bar
źródło
Powinieneś zwrócić uwagę początkującego, że * ma inne znaczenie w deklaracji i wyrażeniu. Jak wiesz, * w wyrażeniu jest operatorem jednoargumentowym, a * w deklaracji nie jest operatorem, a jedynie rodzajem składni łączącej się z typem, aby kompilator wiedział, że jest to typ wskaźnikowy. lepiej powiedzieć początkującym, „* ma inne znaczenie. Aby zrozumieć znaczenie *, powinieneś znaleźć miejsce, w którym występuje *”
źródło
Myślę, że diabeł jest w kosmosie.
Napisałbym (nie tylko dla początkujących, ale także dla siebie): int * bar = & foo; zamiast int * bar = & foo;
Powinno to wyjaśnić, jaki jest związek między składnią a semantyką
źródło
Jak już wspomniano, * ma wiele ról.
Jest jeszcze jeden prosty pomysł, który może pomóc początkującym w zrozumieniu rzeczy:
Pomyśl, że „=” ma również wiele ról.
Gdy przypisanie jest używane w tym samym wierszu z deklaracją, należy traktować je jako wywołanie konstruktora, a nie dowolne przypisanie.
Kiedy widzisz:
Pomyśl, że jest to prawie równoważne z:
Nawiasy mają pierwszeństwo przed gwiazdką, więc „& foo” znacznie łatwiej intuicyjnie przypisać do „bar” niż „* bar”.
źródło
Widziałem to pytanie kilka dni temu, a potem akurat czytałem wyjaśnienie deklaracji typu Go na blogu Go . Zaczyna się od podania deklaracji typu C, co wydaje się przydatnym źródłem do dodania do tego wątku, chociaż myślę, że są już podane bardziej kompletne odpowiedzi.
(Dalej opisano, jak rozszerzyć to zrozumienie na wskaźniki funkcji itp.)
Jest to sposób, o którym wcześniej nie myślałem, ale wydaje się, że jest to całkiem prosty sposób uwzględnienia przeciążenia składni.
źródło
Jeśli problemem jest składnia, pomocne może być pokazanie równoważnego kodu z szablonem / using.
To może być następnie używane jako
Następnie porównaj normalną składnię / C z tym podejściem tylko w C ++. Jest to również przydatne przy wyjaśnianiu wskaźników stałych.
źródło
Źródło nieporozumień wynika z faktu, że
*
symbol może mieć różne znaczenia w języku C, w zależności od faktu, w jakim jest używany. Aby wytłumaczyć wskaźnik początkującemu,*
należy wyjaśnić znaczenie symbolu w innym kontekście.W deklaracji
*
symbol nie operatorowi wskazanie pośrednie . Zamiast tego pomaga określić typbar
informowania kompilatora, którybar
jest wskaźnikiem do plikuint
. Z drugiej strony, kiedy pojawia się w instrukcji,*
symbol (używany jako operator jednoargumentowy ) działa pośrednio. Dlatego oświadczeniebyłby błędny, ponieważ przypisuje adres
foo
obiektowi, którybar
wskazuje, a niebar
samemu sobie.źródło
„może zapisanie tego jako int * bar sprawia, że jest bardziej oczywiste, że gwiazda jest w rzeczywistości częścią typu, a nie częścią identyfikatora”. Ja również. I mówię, że to trochę jak Type, ale tylko dla jednej nazwy wskaźnika.
„Oczywiście powoduje to różne problemy związane z nieintuicyjnymi rzeczami, takimi jak int * a, b”.
źródło
Tutaj musisz użyć, zrozumieć i wyjaśnić logikę kompilatora, a nie logikę człowieka (wiem, jesteś człowiekiem, ale tutaj musisz naśladować komputer ...).
Kiedy piszesz
grupy kompilatorów, które jako
To znaczy: oto nowa zmienna, jej nazwa to
bar
, jej typ to wskaźnik do int, a jej wartość początkowa to&foo
.I trzeba dodać:
=
powyższe oznacza inicjalizację, a nie afektację, podczas gdy w kolejnych wyrażeniach*bar = 2;
tak jest affectationEdytuj według komentarza:
Uwaga: w przypadku wielokrotnych deklaracji
*
dotyczy tylko następującej zmiennej:bar jest wskaźnikiem do int zainicjalizowanym przez adres foo, b jest int zainicjowanym na 2, a in
bar w nieruchomym wskaźniku do int, a p jest wskaźnikiem do wskaźnika do int zainicjalizowanego na adres lub pasek.
źródło
int* a, b;
deklaruje a jako wskaźnik do anint
, ale b jakoint
.*
Symbol ma tylko dwa różne znaczenia: W deklaracji, oznacza to typ wskaźnika, a wyrazem jest to jednoskładnikowa operator dereference.*
in grzechotał się do typu, tak że wskaźnik jest inicjalizowany, podczas gdy w działaniu afektywnym ma to wpływ na wskazaną wartość. Ale przynajmniej dałeś mi fajną czapkę :-)Zasadniczo wskaźnik nie jest wskazaniem tablicy. Początkujący łatwo myśli, że wskaźnik wygląda jak tablica. większość przykładów ciągów przy użyciu rozszerzenia
"char * pstr" wygląda podobnie
„char str [80]”
Ale ważne rzeczy, Pointer jest traktowany jako zwykła liczba całkowita na niższym poziomie kompilatora.
Spójrzmy na przykłady:
Wyniki będą podobne do tego 0x2a6b7ed0 to adres str []
Zasadniczo pamiętaj, że wskaźnik jest rodzajem liczby całkowitej. przedstawienie adresu.
źródło
Wyjaśniłbym, że ints są obiektami, podobnie jak pływaki itp. Wskaźnik jest typem obiektu, którego wartość reprezentuje adres w pamięci (stąd dlaczego wskaźnik domyślnie ma wartość NULL).
Kiedy po raz pierwszy deklarujesz wskaźnik, używasz składni typu-wskaźnika-nazwy. Jest odczytywany jako „wskaźnik całkowity o nazwie nazwa, który może wskazywać na adres dowolnego obiektu będącego liczbą całkowitą”. Używamy tej składni tylko podczas dekleracji, podobnie jak w przypadku deklarowania int jako „int num1”, ale „num1” używamy tylko wtedy, gdy chcemy użyć tej zmiennej, a nie „int num1”.
int x = 5; // obiekt będący liczbą całkowitą o wartości 5
int * ptr; // liczba całkowita z wartością domyślną NULL
Aby wskaźnik wskazywał na adres obiektu, używamy symbolu „&”, który można odczytać jako „adres”.
ptr = & x; // teraz wartością jest adres 'x'
Ponieważ wskaźnik jest tylko adresem obiektu, aby uzyskać rzeczywistą wartość przechowywaną pod tym adresem, musimy użyć symbolu „*”, który użyty przed wskaźnikiem oznacza „wartość pod adresem wskazywanym przez”.
std :: cout << * ptr; // wypisuje wartość pod adresem
Możesz krótko wyjaśnić, że ' ” to „operator”, który zwraca różne wyniki z różnymi typami obiektów. Użyty ze wskaźnikiem operator „ ” nie oznacza już „mnożonego przez”.
Pomaga narysować diagram pokazujący, jak zmienna ma nazwę i wartość, a wskaźnik ma adres (nazwę) i wartość, i pokazać, że wartość wskaźnika będzie adresem int.
źródło
Wskaźnik to po prostu zmienna używana do przechowywania adresów.
Pamięć w komputerze składa się z bajtów (bajt składa się z 8 bitów) ułożonych w sposób sekwencyjny. Każdy bajt ma przypisaną liczbę, podobnie jak indeks lub indeks w tablicy, która jest nazywana adresem bajtu. Adres bajtu zaczyna się od 0 do jednego mniej niż rozmiar pamięci. Na przykład, powiedzmy, że w 64 MB pamięci RAM jest 64 * 2 ^ 20 = 67108864 bajtów. Dlatego adres tych bajtów będzie zaczynał się od 0 do 67108863.
Zobaczmy, co się stanie, gdy zadeklarujesz zmienną.
znaki int;
Jak wiemy, int zajmuje 4 bajty danych (zakładając, że używamy kompilatora 32-bitowego), więc kompilator rezerwuje 4 kolejne bajty z pamięci do przechowywania wartości całkowitej. Adres pierwszego bajtu z 4 przydzielonych bajtów jest znany jako adres znaków zmiennych. Powiedzmy, że adres 4 kolejnych bajtów to 5004, 5005, 5006 i 5007, to adres zmiennych znaczników będzie wynosił 5004.
Deklarowanie zmiennych wskaźnikowych
Jak już powiedziano, wskaźnik jest zmienną, która przechowuje adres pamięci. Podobnie jak w przypadku innych zmiennych, musisz najpierw zadeklarować zmienną wskaźnikową, zanim będziesz mógł jej użyć. Oto jak możesz zadeklarować zmienną wskaźnika.
Składnia:
data_type *pointer_name;
typ_danych to typ wskaźnika (znany również jako typ podstawowy wskaźnika). nazwa_wskaźnika to nazwa zmiennej, która może być dowolnym poprawnym identyfikatorem C.
Weźmy kilka przykładów:
int * ip oznacza, że ip jest zmienną wskaźnikową, która może wskazywać na zmienne typu int. Innymi słowy, zmienna wskaźnikowa ip może przechowywać tylko adresy zmiennych typu int. Podobnie, zmienna wskaźnikowa fp może przechowywać tylko adres zmiennej typu float. Typ zmiennej (znany również jako typ bazowy) ip jest wskaźnikiem do int, a typ fp jest wskaźnikiem do float. Zmienna wskaźnikowa typu wskaźnik do int może być reprezentowana symbolicznie jako (int *). Podobnie, zmienna wskaźnikowa typu pointer to float może być reprezentowana jako (float *)
Po zadeklarowaniu zmiennej wskaźnikowej następnym krokiem jest przypisanie jej prawidłowego adresu pamięci. Nigdy nie powinieneś używać zmiennej wskaźnikowej bez przypisania jej jakiegoś prawidłowego adresu pamięci, ponieważ tuż po deklaracji zawiera ona wartość śmieciową i może wskazywać na dowolne miejsce w pamięci. Użycie nieprzypisanego wskaźnika może dać nieprzewidywalny wynik. Może nawet spowodować awarię programu.
Źródło: thecguru jest zdecydowanie najprostszym, ale szczegółowym wyjaśnieniem, jakie kiedykolwiek znalazłem.
źródło