Z liczby zamieszczonych tutaj pytań jasno wynika, że ludzie mają dość fundamentalne problemy, gdy zastanawiają się nad wskaźnikami i arytmetyką wskaźników.
Ciekaw jestem, dlaczego. Nigdy tak naprawdę nie sprawiały mi większych problemów (chociaż po raz pierwszy dowiedziałem się o nich w neolicie). Aby napisać lepsze odpowiedzi na te pytania, chciałbym wiedzieć, co ludzie uważają za trudne.
Tak więc, jeśli zmagasz się ze wskazówkami, lub ostatnio nagle „zrozumiałeś”, jakie aspekty wskaźników spowodowały problemy?
Odpowiedzi:
Podejrzewam, że w swoich odpowiedziach ludzie zagłębiają się. Zrozumienie planowania, rzeczywistych operacji procesora lub zarządzania pamięcią na poziomie zestawu nie jest tak naprawdę wymagane.
Kiedy uczyłem, najczęstszym źródłem problemów były następujące luki w zrozumieniu uczniów:
Większość moich uczniów była w stanie zrozumieć uproszczony rysunek fragmentu pamięci, generalnie sekcji zmiennych lokalnych stosu w bieżącym zakresie. Ogólnie pomagało podawanie wyraźnych, fikcyjnych adresów różnych miejsc.
Podsumowując, mówię, że jeśli chcesz zrozumieć wskaźniki, musisz zrozumieć zmienne i czym one są w rzeczywistości we współczesnych architekturach.
źródło
Kiedy zacząłem z nimi pracować, największym problemem, jaki miałem, była składnia.
są takie same.
ale:
Czemu? ponieważ „wskaźnikowa” część deklaracji należy do zmiennej, a nie do typu.
A następnie dereferencja rzeczy używa bardzo podobnej notacji:
Z wyjątkiem sytuacji, gdy faktycznie potrzebujesz wskaźnika ... wtedy używasz znaku ampersand!
Brawo za spójność!
Następnie, najwyraźniej tylko po to, by być szarpnięciami i udowodnić, jak sprytni są, wielu programistów bibliotek używa wskaźników do wskaźników, a jeśli spodziewają się tablicy tych rzeczy, to dlaczego nie po prostu przekazać do tego wskaźnika .
aby to nazwać, potrzebuję adresu tablicy wskaźników do wskaźników do wskaźników ints:
Za sześć miesięcy, kiedy będę musiał utrzymywać ten kod, spędzę więcej czasu próbując dowiedzieć się, co to wszystko oznacza, niż przepisywanie od podstaw. (tak, prawdopodobnie źle zrozumiałem tę składnię - minęło trochę czasu, odkąd zrobiłem cokolwiek w C. Trochę za tym tęsknię, ale jestem trochę masochistą)
źródło
Prawidłowe zrozumienie wskaźników wymaga znajomości architektury bazowej maszyny.
Wielu programistów nie wie dziś, jak działa ich maszyna, podobnie jak większość ludzi, którzy wiedzą, jak prowadzić samochód, nie wie nic o silniku.
źródło
Gdy mamy do czynienia ze wskazówkami, zdezorientowani ludzie przebywają w jednym z dwóch obozów. Byłem (jestem?) W obu.
array[]
tłumTo jest tłum, który od razu nie wie, jak przetłumaczyć z notacji wskaźnika na notację tablicową (lub nawet nie wie, że są nawet powiązane). Oto cztery sposoby uzyskania dostępu do elementów tablicy:
Chodzi o to, że dostęp do tablic za pomocą wskaźników wydaje się dość prosty i prosty, ale w ten sposób można zrobić mnóstwo bardzo skomplikowanych i sprytnych rzeczy. Niektóre z nich mogą wprawić w zakłopotanie doświadczonych programistów C / C ++, nie mówiąc już o niedoświadczonych nowicjuszach.
reference to a pointer
Ipointer to a pointer
tłumTo świetny artykuł, który wyjaśnia różnicę i który będę cytować i wykradać kod :)
Jako mały przykład, bardzo trudno jest dokładnie zrozumieć, co chciał zrobić autor, jeśli natkniesz się na coś takiego:
Lub w mniejszym stopniu coś takiego:
Więc na koniec dnia, co tak naprawdę rozwiązujemy z całym tym bełkotem? Nic.
Ta złożoność i pozorna (odważna) wymienność z odniesieniami, która często jest kolejnym zastrzeżeniem wskazówek i błędem nowicjuszy, utrudnia zrozumienie wskaźników. Ważne jest również, aby zrozumieć, ze względu na zakończenie, że wskaźniki odwołań są nielegalne w C i C ++ z mylących powodów, które prowadzą do
lvalue
-rvalue
semantyki.Jak zauważyła poprzednia odpowiedź, wiele razy będziesz mieć po prostu tych gorących programistów, którzy myślą, że są sprytni, używając
******awesome_var->lol_im_so_clever()
i większość z nas jest prawdopodobnie winna czasami pisania takich okrucieństw, ale to po prostu nie jest dobry kod iz pewnością nie da się go utrzymać .Cóż, ta odpowiedź okazała się dłuższa, niż się spodziewałem ...
źródło
Osobiście obwiniam jakość materiałów referencyjnych i ludzi prowadzących nauczanie; większość pojęć w C (ale zwłaszcza wskaźniki) jest po prostu źle nauczona . Wciąż grożę, że napiszę własną książkę w języku C (zatytułowaną Ostatnia rzecz, której świat potrzebuje, to kolejna książka o języku programowania C ), ale nie mam na to czasu ani cierpliwości. Więc kręcę się tutaj i rzucam ludziom przypadkowe cytaty ze Standardu.
Jest też fakt, że kiedy początkowo projektowano C, zakładano, że rozumiesz architekturę maszyny na dość szczegółowym poziomie tylko dlatego, że nie było sposobu, aby tego uniknąć w codziennej pracy (pamięć była tak napięta, a procesory były tak wolne trzeba było zrozumieć, jak to, co napisałeś, wpływa na wydajność).
źródło
Na stronie Joela Spolsky'ego znajduje się świetny artykuł potwierdzający pogląd, że wskazówki są trudne - The Perils of JavaSchools .
[Zastrzeżone - Nie jestem Java-hater per se .]
źródło
Większość rzeczy jest trudniejsza do zrozumienia, jeśli nie jesteś ugruntowany w wiedzy, która jest „pod spodem”. Kiedy uczyłem CS, stało się o wiele łatwiejsze, gdy zacząłem moim studentom programować bardzo prostą "maszynę", symulowany komputer dziesiętny z opkodami dziesiętnymi, których pamięć składała się z rejestrów dziesiętnych i adresów dziesiętnych. Włożyliby bardzo krótkie programy, na przykład, aby dodać serię liczb, aby uzyskać wynik. Potem stąpali po nim, by obserwować, co się dzieje. Mogli przytrzymać klawisz „enter” i patrzeć, jak biegnie „szybko”.
Jestem pewien, że prawie wszyscy na SO zastanawiają się, dlaczego warto uzyskać tak podstawowy poziom. Zapominamy, jak to jest nie wiedzieć, jak programować. Zabawa takim zabawkowym komputerem wprowadza koncepcje, bez których nie można programować, takie jak pomysły, że obliczenia są procesem krok po kroku, wykorzystując niewielką liczbę podstawowych prymitywów do budowania programów oraz pojęcie pamięci. zmienne jako miejsca przechowywania liczb, w których adres lub nazwa zmiennej różni się od numeru, który zawiera. Istnieje różnica między momentem wejścia do programu a czasem jego „uruchomienia”. Uczenie się programowania porównuję do przekraczania serii „progów zwalniających”, takich jak bardzo proste programy, potem pętle i podprogramy, potem tablice, potem sekwencyjne operacje we / wy, wskaźniki i strukturę danych.
Wreszcie, gdy docierasz do C, wskazówki są mylące, chociaż K&R wykonał bardzo dobrą robotę, wyjaśniając je. Sposób, w jaki nauczyłem się ich w C, to umiejętność ich czytania - od prawej do lewej. Na przykład kiedy widzę
int *p
w swojej głowie, mówię „p
wskazuje naint
”. C został wymyślony jako krok naprzód w stosunku do języka asemblera i to właśnie w nim lubię - jest blisko tej „podstawy”. Wskaźniki, jak wszystko inne, są trudniejsze do zrozumienia, jeśli nie masz takiego uziemienia.źródło
Nie otrzymałem wskazówek, dopóki nie przeczytałem opisu w K&R. Do tego momentu wskazówki nie miały sensu. Czytałem całą masę rzeczy, w których ludzie mówili: „Nie ucz się wskazówek, są one mylące i zranią twoją głowę i wywołują tętniaki”, więc unikałem tego przez długi czas i stworzyłem niepotrzebną atmosferę trudnej koncepcji .
W przeciwnym razie, głównie myślałem, dlaczego u licha miałbyś chcieć zmiennej, którą musisz przejść przez obręcze, aby uzyskać wartość, a jeśli chcesz przypisać do niej coś, musisz robić dziwne rzeczy, aby uzyskać wartości w nich. Pomyślałem, że celem zmiennej jest coś, co służy do przechowywania wartości, więc dlaczego ktoś chciał to komplikować, było poza mną. „Więc za pomocą wskaźnika musisz użyć
*
operatora, aby uzyskać jego wartość ??? Co to za głupia zmienna?” , Myślałem. Bezcelowe, gra słów nie zamierzona.Było to skomplikowane, ponieważ nie rozumiałem, że wskaźnik jest adresem do czegoś. Jeśli wyjaśnisz, że jest to adres, że jest to coś, co zawiera adres do czegoś innego i że możesz manipulować tym adresem, aby robić użyteczne rzeczy, myślę, że może to wyjaśnić zamieszanie.
Klasa, która wymagała używania wskaźników do uzyskiwania dostępu / modyfikowania portów na komputerze PC, używania arytmetyki wskaźników do adresowania różnych lokalizacji pamięci i patrzenia na bardziej skomplikowany kod C, który modyfikował ich argumenty, wyprowadził mnie z przekonania, że wskaźniki są, no cóż, bezcelowe.
źródło
Oto przykład wskaźnika / tablicy, który dał mi przerwę. Załóżmy, że masz dwie tablice:
Twoim celem jest skopiowanie zawartości uint8_t z miejsca docelowego za pomocą memcpy (). Zgadnij, które z poniższych działań pozwalają osiągnąć ten cel:
Odpowiedź (Alert Spoilera!) Brzmi: WSZYSTKIE. „Miejsce docelowe”, „i miejsce docelowe” oraz „i miejsce docelowe [0]” mają tę samą wartość. „& miejsce docelowe” to inny typ niż pozostałe dwa, ale nadal ma tę samą wartość. To samo dotyczy permutacji „źródła”.
Na marginesie, osobiście wolę pierwszą wersję.
źródło
sizeof(source)
, ponieważ jeślisource
jest wskaźnikiem,sizeof
nie będzie tym, czego chcesz. Czasami (nie zawsze) piszęsizeof(source[0]) * number_of_elements_of_source
tylko po to, żeby trzymać się z daleka od tego błędu.Powinienem zacząć od stwierdzenia, że C i C ++ były pierwszymi językami programowania, których się nauczyłem. Zacząłem od C, potem dużo C ++ w szkole, a potem wróciłem do C, aby nabrać w nim biegłości.
Pierwszą rzeczą, która zdezorientowała mnie we wskaźnikach podczas nauki C, była prosta:
To zamieszanie wynikało głównie z tego, że wprowadzono mnie w używanie odwołań do zmiennej w argumentach OUT, zanim wskaźniki zostały mi prawidłowo wprowadzone. Pamiętam, że pominąłem napisanie pierwszych kilku przykładów w C for Dummies, ponieważ były one zbyt proste tylko, aby nigdy nie uzyskać do działania pierwszego programu, który napisałem (najprawdopodobniej z tego powodu).
Mylące w tym było to, co
&ch
tak naprawdę oznaczało i dlaczego tegostr
nie potrzebowaliśmy.Kiedy się z tym zapoznałem, później pamiętam, jak pomyliłem się co do alokacji dynamicznej. W pewnym momencie zdałem sobie sprawę, że posiadanie wskaźników do danych nie jest zbyt przydatne bez pewnego typu dynamicznej alokacji, więc napisałem coś takiego:
spróbować dynamicznie przydzielić trochę miejsca. To nie zadziałało. Nie byłem pewien, czy to zadziała, ale nie wiedziałem, jak inaczej mogłoby działać.
Później dowiedziałem się o
malloc
inew
, ale naprawdę wydawały mi się magicznymi generatorami pamięci. Nie wiedziałem nic o tym, jak mogą działać.Jakiś czas później ponownie uczono mnie rekurencji (wcześniej nauczyłem się jej samodzielnie, ale teraz byłem na zajęciach) i zapytałem, jak to działa pod maską - gdzie są przechowywane oddzielne zmienne. Mój profesor powiedział „na stosie” i wiele rzeczy stało się dla mnie jasnych. Słyszałem ten termin już wcześniej i wcześniej wdrażałem stosy oprogramowania. Słyszałem, jak inni mówili o „stosie” już dawno, ale zapomniałem o tym.
W tym czasie zdałem sobie również sprawę, że używanie tablic wielowymiarowych w C może być bardzo zagmatwane. Wiedziałem, jak działały, ale tak łatwo je zaplątać, że postanowiłem spróbować obejść ich użycie, kiedy tylko będę mógł. Myślę, że tutaj problem dotyczył głównie składni (zwłaszcza przekazywania lub zwracania ich z funkcji).
Odkąd pisałem C ++ dla szkoły przez następny rok lub dwa, zdobyłem duże doświadczenie w używaniu wskaźników do struktur danych. Tutaj miałem nowy zestaw kłopotów - pomieszanie wskaźników. Miałbym wiele poziomów wskaźników (takich jak
node ***ptr;
) potykanie mnie. Wyłuskałem wskaźnik niewłaściwą liczbę razy i ostatecznie uciekłem się do ustalenia, ile*
potrzebuję, metodą prób i błędów.W pewnym momencie dowiedziałem się, jak działa sterta programu (w pewnym sensie, ale na tyle dobrze, że nie utrzymuje mnie już w nocy). Pamiętam, że czytałem, że jeśli spojrzysz na kilka bajtów przed wskaźnikiem, który
malloc
w pewnym systemie zwraca, możesz zobaczyć, ile danych zostało faktycznie przydzielonych. Zdałem sobie sprawę, że kod w programiemalloc
może poprosić o więcej pamięci z systemu operacyjnego i ta pamięć nie była częścią moich plików wykonywalnych. Posiadanie przyzwoitego pomysłu na to, jakmalloc
działa, jest naprawdę przydatne.Niedługo potem wziąłem udział w zajęciach z asemblera, które nie nauczyły mnie tyle wskazówek, jak prawdopodobnie większość programistów myśli. Zmusiło mnie to do zastanowienia się, na jaki asembler może zostać przetłumaczony mój kod. Zawsze starałem się pisać wydajny kod, ale teraz miałem lepszy pomysł, jak to zrobić.
Wziąłem też kilka zajęć, na których musiałem napisać trochę seplenienia . Pisząc lisp, nie przejmowałem się tak wydajnością, jak byłem w C.Miałem bardzo mało pojęcia, na co ten kod może zostać przetłumaczony po skompilowaniu, ale wiedziałem, że wydawało się, że używa się wielu lokalnych nazwanych symboli (zmiennych) utworzonych o wiele łatwiejsze. W pewnym momencie napisałem trochę kodu rotacji drzewa AVL w odrobinie seplenienia, że miałem bardzo ciężko pisać w C ++ z powodu problemów ze wskaźnikami. Zdałem sobie sprawę, że moja niechęć do tego, co uważałem za nadmiar zmiennych lokalnych, utrudniła mi napisanie tego i kilku innych programów w C ++.
Wziąłem również zajęcia z kompilatorów. Podczas tych zajęć przerzuciłem się do zaawansowanego materiału i nauczyłem się statycznego przypisania pojedynczego (SSA) i martwych zmiennych, co nie jest takie ważne, z wyjątkiem tego, że nauczył mnie, że każdy przyzwoity kompilator poradzi sobie dobrze ze zmiennymi, które są nieużywany. Wiedziałem już, że więcej zmiennych (w tym wskaźników) z poprawnymi typami i dobrymi nazwami pomoże mi zachować porządek w głowie, ale teraz wiedziałem również, że unikanie ich ze względów wydajnościowych jest jeszcze głupsze niż mówili moi mniej nastawieni na mikro-optymalizację profesorowie mnie.
Więc dla mnie bardzo pomogła wiedza o układzie pamięci programu. Pomaga mi myślenie o tym, co oznacza mój kod, zarówno symbolicznie, jak i na sprzęcie. Używanie lokalnych wskaźników, które mają właściwy typ, bardzo pomaga. Często piszę kod, który wygląda następująco:
więc jeśli schrzanię typ wskaźnika, błąd kompilatora będzie bardzo jasny, na czym polega problem. Gdybym to zrobił:
i dostanie tam nieprawidłowy typ wskaźnika, błąd kompilatora byłby znacznie trudniejszy do ustalenia. Kusiłoby mnie, aby uciekać się do prób i błędów w mojej frustracji i prawdopodobnie pogorszyć sytuację.
źródło
Patrząc wstecz, były cztery rzeczy, które naprawdę pomogły mi w końcu zrozumieć wskazówki. Wcześniej mogłem ich używać, ale nie rozumiałem ich w pełni. Oznacza to, że wiedziałem, że postępując zgodnie z formularzami, osiągnę pożądane rezultaty, ale nie w pełni rozumiałem „dlaczego” formularze. Zdaję sobie sprawę, że nie jest to dokładnie to, o co prosiłeś, ale myślę, że jest to przydatne następstwo.
Pisanie procedury, która pobrała wskaźnik do liczby całkowitej i zmodyfikowała tę liczbę. To dało mi niezbędne formy, na podstawie których mogłem zbudować mentalne modele działania wskaźników.
Jednowymiarowa dynamiczna alokacja pamięci. Ustalenie alokacji pamięci 1-D pozwoliło mi zrozumieć pojęcie wskaźnika.
Dwuwymiarowa dynamiczna alokacja pamięci. Ustalenie alokacji pamięci 2-D wzmocniło tę koncepcję, ale także nauczyło mnie, że sam wskaźnik wymaga pamięci i należy go wziąć pod uwagę.
Różnice między zmiennymi stosu, zmiennymi globalnymi i pamięcią sterty. Zrozumienie tych różnic nauczyło mnie typów pamięci, na które wskazują wskaźniki.
Każdy z tych elementów wymagał wyobrażenia sobie, co się dzieje na niższym poziomie - zbudowania modelu mentalnego, który zadowalałby każdy przypadek, o którym mogłem pomyśleć. Wymagało to czasu i wysiłku, ale było warto. Jestem przekonany, że aby zrozumieć wskaźniki, trzeba zbudować ten model myślowy na tym, jak działają i jak są wdrażane.
Wróćmy teraz do pierwotnego pytania. Opierając się na poprzedniej liście, było kilka elementów, które początkowo miałem trudności z uchwyceniem.
źródło
Miałem swój „moment wskaźnika” pracując nad niektórymi programami telefonicznymi w C. Musiałem napisać emulator centrali AXE10 używając analizatora protokołów, który rozumiał tylko klasyczne C. Wszystko zależało od znajomości wskaźników. Próbowałem napisać swój kod bez nich (hej, byłem „przed wskaźnikiem”, trochę mi odpuścił) i całkowicie się nie udało.
Kluczem do ich zrozumienia był dla mnie operator & (adres). Kiedy zrozumiałem, że
&i
oznacza to „adres i”, to zrozumienie, które*i
oznacza „zawartość adresu wskazywanego przez i” przyszło nieco później. Za każdym razem, gdy pisałem lub czytałem swój kod, zawsze powtarzałem, co oznacza „&” i co oznacza „*” i ostatecznie zacząłem używać ich intuicyjnie.Ku mojemu wstydowi zostałem zmuszony do nauki języka VB, a potem języka Java, więc moja znajomość wskaźnika nie jest tak wyostrzona jak kiedyś, ale cieszę się, że jestem „post-pointer”. Nie proś mnie jednak o korzystanie z biblioteki, która wymaga zrozumienia * * p.
źródło
&i
jest adres i*i
zawartość, to co to jesti
?Główna trudność ze wskazówkami, przynajmniej dla mnie, polega na tym, że nie zacząłem od C. Zacząłem od Javy. Całe pojęcie wskaźników było naprawdę obce aż do kilku zajęć w college'u, na których oczekiwano, że będę znać C. Więc wtedy nauczyłem się podstaw języka C i tego, jak używać wskaźników w ich podstawowym znaczeniu. Nawet wtedy za każdym razem, gdy czytam kod C, muszę sprawdzić składnię wskaźnika.
Tak więc w moim bardzo ograniczonym doświadczeniu (1 rok w prawdziwym świecie + 4 w college'u) wskazówki mnie dezorientują, ponieważ nigdy nie musiałem naprawdę używać go w niczym innym niż w klasie. Mogę współczuć studentom, którzy zaczynają teraz CS z JAVĄ zamiast C czy C ++. Jak powiedziałeś, nauczyłeś się wskaźników w erze „neolitu” i prawdopodobnie używasz ich od tamtej pory. Dla nas, nowszych ludzi, pojęcie przydzielania pamięci i wykonywania arytmetyki wskaźnikowej jest naprawdę obce, ponieważ wszystkie te języki to wyabstrahowały.
PS Po przeczytaniu eseju Spolsky'ego jego opis „JavaSchools” nie przypominał tego, przez co przeszedłem na studiach w Cornell ('05 -'09). Uczyłem się struktur i programowania funkcjonalnego (sml), systemów operacyjnych (C), algorytmów (długopis i papier) i całej masy innych zajęć, których nie uczyłem w Javie. Jednak wszystkie zajęcia wprowadzające i obieralne zostały wykonane w Javie, ponieważ nie trzeba wymyślać koła na nowo, gdy próbujesz zrobić coś wyższego niż zaimplementowanie tablicy haszującej ze wskaźnikami.
źródło
void foo(Clazz obj) { obj = new Clazz(); }
działa, a jednocześnievoid bar(Clazz obj) { obj.quux = new Quux(); }
mutuje argument ...Oto brak odpowiedzi: użyj cdecl (lub c ++ decl), aby to rozgryźć:
źródło
Dodają dodatkowy wymiar do kodu bez znaczącej zmiany składni. Pomyśl o tym:
Jest tylko jedna rzecz do zmiany:
a
. Możesz pisać,a = 6
a wyniki są oczywiste dla większości ludzi. Ale teraz rozważ:Są dwie rzeczy,
a
które mają znaczenie w różnych momentach: rzeczywista wartośća
wskaźnika i wartość „za” wskaźnikiem. Możesz zmienića
:... i
some_int
nadal jest gdzieś z tą samą wartością. Ale możesz też zmienić to, na co wskazuje:Istnieje luka pojęciowa
a = 6
, która ma tylko lokalne skutki uboczne, a*a = 6
która może wpływać na wiele innych rzeczy w innych miejscach. Chodzi mi o to nie że pojęcie zadnie jest z natury trudne, ale to dlatego, że można zrobić zarówno natychmiastowe, lokalne rzeczy za
lub coś pośredniego z*a
... że może być to, co dezorientuje ludzi.źródło
Programowałem w c ++ przez jakieś 2 lata, a potem przeszedłem na Javę (5 lat) i nigdy nie oglądałem się za siebie. Jednak kiedy ostatnio musiałem używać rodzimych rzeczy, odkryłem (ze zdumieniem), że nie zapomniałem o wskaźnikach i nawet uważam je za łatwe w użyciu. To ostry kontrast z tym, czego doświadczyłem 7 lat temu, kiedy po raz pierwszy próbowałem uchwycić tę koncepcję. Więc myślę, że zrozumienie i sympatia to kwestia dojrzałości programowania? :)
LUB
Wskaźniki są jak jazda na rowerze, kiedy już nauczysz się, jak z nimi pracować, nie można o tym zapomnieć.
Podsumowując, trudno pojąć lub nie, cała idea wskaźnika jest BARDZO edukacyjna i uważam, że powinna być zrozumiała dla każdego programisty, niezależnie od tego, czy programuje w języku ze wskaźnikami, czy nie.
źródło
Wskaźniki są trudne z powodu pośrednictwa.
źródło
Wskaźniki (wraz z niektórymi innymi aspektami pracy na niskim poziomie) wymagają od użytkownika odebrania magii.
Większość programistów wysokiego poziomu lubi magię.
źródło
Wskaźniki są sposobem radzenia sobie z różnicą między uchwytem do obiektu a samym obiektem. (ok, niekoniecznie przedmioty, ale wiesz, co mam na myśli i gdzie jest mój umysł)
W pewnym momencie prawdopodobnie będziesz musiał uporać się z różnicą między nimi. We współczesnym języku wysokiego poziomu staje się to rozróżnieniem między kopiowaniem według wartości a kopiowaniem przez odniesienie. Tak czy inaczej, jest to koncepcja, która jest często trudna do zrozumienia dla programistów.
Jednak, jak już wskazano, składnia obsługi tego problemu w C jest brzydka, niespójna i zagmatwana. W końcu, jeśli naprawdę spróbujesz to zrozumieć, wskaźnik będzie miał sens. Ale kiedy zaczynasz zajmować się wskazówkami do wskaźników i tak dalej do znudzenia, staje się to naprawdę zagmatwane zarówno dla mnie, jak i dla innych ludzi.
Inną ważną rzeczą do zapamiętania w przypadku wskaźników jest to, że są one niebezpieczne. C jest językiem mistrza programisty. Zakłada, że wiesz, co robisz, a tym samym daje ci moc, by naprawdę zepsuć rzeczy. Chociaż niektóre typy programów nadal muszą być napisane w języku C, większość programów tego nie robi, a jeśli masz język, który zapewnia lepszą abstrakcję dla różnicy między obiektem a jego uchwytem, sugeruję, abyś go użył.
Rzeczywiście, w wielu nowoczesnych aplikacjach C ++ często jest tak, że każda wymagana arytmetyka wskaźników jest hermetyzowana i abstrakcyjna. Nie chcemy, aby programiści zajmowali się arytmetyką wskaźników w każdym miejscu. Chcemy scentralizowanego, dobrze przetestowanego interfejsu API, który wykonuje obliczenia wskaźników na najniższym poziomie. Wprowadzanie zmian w tym kodzie musi być wykonywane z wielką starannością i obszernymi testami.
źródło
Myślę, że jednym z powodów, dla których wskaźniki C są trudne, jest to, że łączą kilka pojęć, które nie są w rzeczywistości równoważne; jednak, ponieważ wszystkie są realizowane za pomocą wskaźników, ludziom może być trudno rozplątać te pojęcia.
W C przyzwyczajeni są wskaźniki, między innymi:
W C można zdefiniować połączoną listę liczb całkowitych w następujący sposób:
Wskaźnik jest tam tylko dlatego, że jest to jedyny sposób na zdefiniowanie rekurencyjnej struktury danych w C, kiedy koncepcja naprawdę nie ma nic wspólnego z tak niskopoziomowymi szczegółami, jak adresy pamięci. Rozważ następujący odpowiednik w Haskell, który nie wymaga użycia wskaźników:
Całkiem proste - lista jest albo pusta, albo utworzona z wartości i reszty listy.
Oto, jak możesz zastosować funkcję
foo
do każdego znaku ciągu w C:Pomimo wykorzystania wskaźnika jako iteratora, ten przykład ma niewiele wspólnego z poprzednim. Tworzenie iteratora, który można zwiększać, jest inną koncepcją niż definiowanie rekurencyjnej struktury danych. Żadna z tych koncepcji nie jest szczególnie związana z ideą adresu pamięci.
Oto rzeczywista sygnatura funkcji znaleziona w glib :
Whoa! To całkiem niezłe kęs
void*
. A wszystko to po to, aby zadeklarować funkcję, która iteruje po liście, która może zawierać dowolne rzeczy, stosując funkcję do każdego elementu członkowskiego. Porównaj to z tym, jakmap
jest deklarowane w Haskell:To jest o wiele prostsze:
map
jest funkcją, która pobiera funkcję, która konwertuje ana
na ab
i stosuje ją do listya
, aby otrzymać listęb
's. Podobnie jak w przypadku funkcji Cg_list_foreach
,map
nie musi wiedzieć nic w swojej definicji o typach, do których będzie stosowana.Podsumowując:
Myślę, że wskaźniki C byłyby dużo mniej zagmatwane, gdyby ludzie najpierw dowiedzieli się o rekurencyjnych strukturach danych, iteratorach, polimorfizmie itp. Jako osobnych koncepcjach, a następnie dowiedzieli się, jak można użyć wskaźników do implementacji tych pomysłów w C , zamiast mieszać je wszystkie koncepcje razem w jeden temat „wskaźników”.
źródło
c != NULL
w Twoim przykładzie „Witaj, świecie” ... masz na myśli*c != '\0'
.Myślę, że wymaga to solidnych podstaw, prawdopodobnie z poziomu maszyny, z wprowadzeniem do kodu maszynowego, asemblacji oraz tego, jak reprezentować elementy i strukturę danych w pamięci RAM. Potrzeba trochę czasu, trochę pracy domowej lub praktyki rozwiązywania problemów i trochę myślenia.
Ale jeśli ktoś na początku zna języki wysokiego poziomu (co nie jest niczym złym - stolarz używa siekiery. Osoba, która potrzebuje rozłupać atom, używa czegoś innego. Potrzebujemy ludzi, którzy są stolarzami, a mamy ludzi, którzy badają atomy) i osoba, która zna język wysokiego poziomu, otrzymuje 2-minutowe wprowadzenie do wskaźników, a potem trudno oczekiwać, że zrozumie arytmetykę wskaźników, wskaźniki do wskaźników, tablicę wskaźników do łańcuchów o zmiennej wielkości, tablicę znaków itp. Niski, solidny fundament może bardzo pomóc.
źródło
Problem, który zawsze miałem (głównie samouk), to „kiedy” używać wskaźnika. Mogę owinąć głowę wokół składni konstruowania wskaźnika, ale muszę wiedzieć, w jakich okolicznościach wskaźnik powinien być używany.
Czy tylko ja mam taki sposób myślenia? ;-)
źródło
Kiedyś ... Mieliśmy mikroprocesory 8-bitowe i wszyscy pisali w asemblerze. Większość procesorów zawierała jakiś rodzaj adresowania pośredniego używanego dla tablic skoków i jąder. Kiedy pojawiły się języki wyższego poziomu, dodajemy cienką warstwę abstrakcji i nazywamy je wskaźnikami. Z biegiem lat coraz bardziej oddalaliśmy się od sprzętu. To niekoniecznie jest złe. Nie bez powodu nazywane są językami wyższego poziomu. Im bardziej mogę się skoncentrować na tym, co chcę robić, zamiast na szczegółach tego, jak to się robi, tym lepiej.
źródło
Wydaje się, że wielu uczniów ma problem z pojęciem pośrednictwa, zwłaszcza gdy po raz pierwszy spotykają się z pojęciem pośrednictwa. Pamiętam z czasów, gdy byłem studentem, że spośród ponad 100 studentów mojego kursu tylko garstka ludzi naprawdę rozumiała wskazówki.
Pojęcie pośrednictwa nie jest czymś, czego często używamy w prawdziwym życiu, dlatego początkowo trudno jest go zrozumieć.
źródło
Niedawno miałem moment kliknięcia wskaźnika i byłem zaskoczony, że było to dla mnie mylące. Chodziło raczej o to, że wszyscy tak dużo o tym mówili, że przypuszczałem, że działa jakaś mroczna magia.
Sposób, w jaki to zrozumiałem, był następujący. Wyobraź sobie, że wszystkie zdefiniowane zmienne otrzymują przestrzeń pamięci w czasie kompilacji (na stosie). Jeśli potrzebujesz programu, który mógłby obsługiwać duże pliki danych, takie jak dźwięk lub obrazy, nie chciałbyś mieć stałej ilości pamięci dla tych potencjalnych struktur. Więc czekasz do czasu wykonania, aby przypisać określoną ilość pamięci do przechowywania tych danych (na stercie).
Gdy masz już dane w pamięci, nie chcesz kopiować tych danych z całej szyny pamięci za każdym razem, gdy chcesz uruchomić na niej operację. Załóżmy, że chcesz zastosować filtr do danych obrazu. Masz wskaźnik, który zaczyna się na początku danych przypisanych do obrazu, a funkcja przebiega przez te dane, zmieniając je w miejscu. Gdybyś nie wiedział, co robimy, prawdopodobnie skończyłbyś na tworzeniu duplikatów danych podczas przeprowadzania operacji.
Przynajmniej tak to widzę w tej chwili!
źródło
Mówiąc jako nowicjusz C ++ tutaj:
Przetrawienie systemu wskaźników zajęło mi trochę czasu, niekoniecznie z powodu koncepcji, ale ze względu na składnię C ++ w stosunku do Javy. Kilka rzeczy, które uznałem za mylące, to:
(1) Deklaracja zmiennej:
vs.
vs.
i najwyraźniej
jest deklaracją funkcji, a nie deklaracją zmiennej. W innych językach istnieje tylko jeden sposób zadeklarowania zmiennej.
(2) Znak ampersand jest używany na kilka różnych sposobów. Jeśli to jest
wtedy & a jest adresem pamięci.
OTOH, jeśli tak
wtedy & a jest parametrem przekazywanym przez odwołanie.
Chociaż może się to wydawać trywialne, może być mylące dla nowych użytkowników - pochodzę z Javy, a Java jest językiem o bardziej jednolitym użyciu operatorów
(3) Relacja tablica-wskaźnik
Jedna rzecz, która jest nieco frustrująca do zrozumienia, to wskaźnik
może być wskaźnikiem do int
lub
może być tablicą int
Aby jeszcze bardziej pogorszyć sytuację, wskaźniki i tablice nie są wymienne we wszystkich przypadkach, a wskaźniki nie mogą być przekazywane jako parametry tablicowe.
To podsumowuje niektóre z podstawowych frustracji, jakie miałem z C / C ++ i jego wskaźnikami, którymi jest IMO, jest znacznie potęgowane przez fakt, że C / C ++ ma wszystkie te dziwactwa specyficzne dla języka.
źródło
Osobiście nie rozumiałem wskaźnika nawet po ukończeniu studiów podyplomowych i po pierwszej pracy. Jedyne, co wiedziałem, to to, że potrzebujesz go do listy połączonej, drzew binarnych i do przekazywania tablic do funkcji. Tak było nawet w mojej pierwszej pracy. Dopiero gdy zacząłem udzielać wywiadów, rozumiem, że koncepcja wskaźnika jest głęboka i ma ogromne zastosowanie i potencjał. Potem zacząłem czytać K&R i pisać własny program testowy. Moim celem była praca.
W tym czasie odkryłem, że wskazówki nie są naprawdę złe ani trudne, jeśli są nauczane w dobry sposób. Niestety, kiedy uczę się C na maturze, nasz nauczyciel nie był świadomy wskaźnika, a nawet przy zadaniach używano mniej wskaźników. Na poziomie magisterskim użycie wskaźnika jest tak naprawdę tylko do tworzenia drzew binarnych i połączonej listy. Myśląc, że nie potrzebujesz odpowiedniego zrozumienia wskaźników, aby z nimi pracować, zabijaj pomysł ich uczenia się.
źródło
Wskaźniki ... hah ... wszystko o wskaźniku w mojej głowie polega na tym, że podaje adres pamięci, gdzie rzeczywiste wartości niezależnie od jego odniesienia .. więc nie ma w tym żadnej magii ... jeśli nauczysz się jakiegoś asemblera, nie będziesz miał problemów z nauką jak działają wskaźniki ... no, chłopaki ... nawet w Javie wszystko jest odniesieniem ..
źródło
Główny problem ludzie nie rozumieją, dlaczego potrzebują wskazówek. Ponieważ nie mają jasności co do stosu i stosu. Dobrze jest zacząć od 16-bitowego asemblera dla x86 z małym trybem pamięci. Pomogło to wielu osobom zorientować się w stosie, stosie i „adresie”. I bajt :) Współcześni programiści czasami nie potrafią powiedzieć, ile bajtów potrzeba, aby zaadresować przestrzeń 32-bitową. Skąd mają pojęcie o wskazówkach?
Drugi moment to notacja: deklarujesz wskaźnik jako *, otrzymujesz adres jako &, co dla niektórych osób nie jest łatwe do zrozumienia.
Ostatnią rzeczą, jaką widziałem, był problem z pamięcią masową: rozumieją stertę i stos, ale nie mogą wpaść w pojęcie „statycznego”.
źródło