Co jest trudne w przypadku wskaźników C? [Zamknięte]


173

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?


54
Wstrętna, przesadzona, niepotrzebnie kontrowersyjna, odpowiada z ziarnem prawdy: Zostali okaleczeni - mówię ci - psychicznie okaleczeni - najpierw ucząc się jednego z tych „ekspresyjnych” języków wysokiego poziomu. Powinni byli zacząć od programowania na gołym metalu, tak jak zamierzył to Bóg, a Daniel Boone!
dmckee --- ex-moderator kitten

3
... a Programista będzie lepiej, ponieważ będzie rozwijać się w dyskusję, mimo najlepszych starań was.
dmckee --- ex-moderator kitten

To jest dyskusyjne i subiektywne, generuje N wyników, moje trudne jest twoje łatwe. Prawdopodobnie będzie działać na programistach, ale nie przenosimy tam jeszcze tych pytań, ponieważ strona nie jest poza wersją beta.
Sam Saffron

2
@Sam Saffron: Chociaż ogólnie zgadzam się, że jest to bardziej pytanie dotyczące programistów. Pytanie typu E, szczerze mówiąc, nie byłoby źle, gdyby ludzie chcieli oznaczyć „Myślę, że to łatwe” i „Nienawidzę patrzeć na wskazówki” jako spam oni są.
jkerian

3
Ktoś musi poruszyć ten temat: „To jest jak palec wskazujący na księżyc. Nie koncentruj się na palcu, bo stracisz całą tę niebiańską chwałę” - Bruce Lee
mu jest za krótkie

Odpowiedzi:


86

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:

  1. Magazyn Heap vs Stack. To po prostu oszałamiające, jak wielu ludzi tego nie rozumie, nawet w ogólnym sensie.
  2. Ramki stosu. Tylko ogólna koncepcja dedykowanej sekcji stosu dla zmiennych lokalnych, wraz z powodem, dla którego jest to `` stos '' ... szczegóły, takie jak przechowywanie lokalizacji powrotu, szczegóły obsługi wyjątków i poprzednie rejestry można bezpiecznie pozostawić, dopóki ktoś nie spróbuje zbudować kompilator.
  3. „Pamięć to pamięć to pamięć” Casting zmienia tylko wersje operatorów lub ilość miejsca, które kompilator daje na określony fragment pamięci. Wiesz, że masz do czynienia z tym problemem, kiedy ludzie mówią o tym, „czym naprawdę jest (prymitywna) zmienna X ”.

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.


13
IMHO, zrozumienie stosu i sterty jest tak samo niepotrzebne, jak szczegóły procesora niskiego poziomu. Stos i sterta są szczegółami implementacji. Specyfikacja ISO C nie zawiera ani jednej wzmianki o słowie „stos”, podobnie jak K&R.
sigjuice

4
@sigjuice: Twoje zastrzeżenia pomijają sens pytania i odpowiedzi. A) K&R C jest anachronizmem B) ISO C nie jest jedynym językiem ze wskaźnikami, moje punkty 1 i 2 zostały opracowane w oparciu o język nie oparty na C C) 95% architektur (nie języków) używa stosu / stack system, jest dość powszechne, że wyjątki są wyjaśniane względem niego. D) Pytanie brzmiało: „dlaczego ludzie nie rozumieją wskaźników”, a nie „jak wyjaśnić ISO C”
jkerian,

9
@John Marchetti: Jeszcze bardziej ... biorąc pod uwagę, że pytanie brzmiało „Jaki jest główny problem z ludzkim problemem ze wskaźnikami”, nie sądzę, by osoby zadające pytania związane ze wskaźnikami były pod wielkim wrażeniem „Ty nie” naprawdę muszę wiedzieć „jako odpowiedź. Oczywiście nie zgadzają się. :)
jkerian

3
@jkerian Może być nieaktualny, ale trzy lub cztery strony K&R, które wyjaśniają wskaźniki, robią to bez potrzeby szczegółowego wdrażania. Znajomość szczegółów implementacji jest przydatna z różnych powodów, ale IMHO nie powinna być warunkiem wstępnym do zrozumienia kluczowych konstrukcji języka.
sigjuice

3
„Ogólnie pomogło podawanie wyraźnych, fikcyjnych adresów różnych miejsc”. -> +1
fredoverflow

146

Kiedy zacząłem z nimi pracować, największym problemem, jaki miałem, była składnia.

int* ip;
int * ip;
int *ip;

są takie same.

ale:

int* ip1, ip2;  //second one isn't a pointer!
int *ip1, *ip2;

Czemu? ponieważ „wskaźnikowa” część deklaracji należy do zmiennej, a nie do typu.

A następnie dereferencja rzeczy używa bardzo podobnej notacji:

*ip = 4;  //sets the value of the thing pointed to by ip to '4'
x = ip;   //hey, that's not '4'!
x = *ip;  //ahh... there's that '4'

Z wyjątkiem sytuacji, gdy faktycznie potrzebujesz wskaźnika ... wtedy używasz znaku ampersand!

int *ip = &x;

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 .

void foo(****ipppArr);

aby to nazwać, potrzebuję adresu tablicy wskaźników do wskaźników do wskaźników ints:

foo(&(***ipppArr));

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ą)


21
Twój komentarz do pierwszego, >> * ip = 4; // ustawia wartość ip na „4”. << jest nieprawidłowa. Powinno być >> // ustawia wartość rzeczy wskazywanej przez ip na '4'
aaaa bbbb

8
W każdym języku umieszczanie zbyt wielu typów jeden na drugim jest złym pomysłem. Możesz znaleźć napis "foo (& (*** ipppArr));" dziwne w C, ale piszemy coś w stylu "std :: map <std :: pair <int, int>, std :: pair <std :: vector <int>, std :: tuple <int, double, std :: list <int> >>> ”w C ++ jest również bardzo złożony. Nie oznacza to, że wskaźniki w kontenerach C lub STL w C ++ są złożone. Oznacza to po prostu, że musisz używać lepszych definicji typów, aby były zrozumiałe dla czytelnika twojego kodu.
Patrick,

20
Naprawdę nie mogę uwierzyć, że niezrozumienie składni jest odpowiedzią, na którą głosowano najwięcej. To najłatwiejsza część dotycząca wskaźników.
jason

4
Nawet czytając tę ​​odpowiedź, kusiło mnie, żeby wziąć kartkę papieru i narysować obrazek. W C zawsze rysowałem obrazki.
Michael Easter

19
@Jason Jaka obiektywna miara trudności jest inna niż ta, którą większość ludzi uważa za trudną?
Rupert Madden-Abbott

52

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.


18
@dmckee: Cóż, czy się mylę? Ilu programistów Java mogłoby poradzić sobie z błędem segfault?
Robert Harvey

5
Czy segfault ma coś wspólnego z drążkiem zmiany biegów? - Programista Java
Tom Anderson

6
@Robert: miał być prawdziwym uzupełnieniem. To trudny temat do omówienia bez ranienia ludzkich uczuć. I obawiam się, że mój komentarz przyspieszył konflikt, o którym myślałem, że udało ci się uniknąć. Mea cupla.
dmckee --- ex-moderator kitten

30
Nie zgadzam się; nie musisz rozumieć podstawowej architektury, aby uzyskać wskaźniki (i tak są one abstrakcją).
jason

11
@Jason: W C wskaźnik jest zasadniczo adresem pamięci. Bezpieczna praca z nimi nie jest możliwa bez zrozumienia architektury maszyny. Zobacz en.wikipedia.org/wiki/Pointer_(computing) i boredzo.org/pointers/#definition
Robert Harvey

42

Gdy mamy do czynienia ze wskazówkami, zdezorientowani ludzie przebywają w jednym z dwóch obozów. Byłem (jestem?) W obu.

array[]tłum

To 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:

  1. notacja tablicy (indeksowanie) z nazwą tablicy
  2. notacja tablicowa (indeksowanie) z nazwą wskaźnika
  3. notacja wskaźnika (*) z nazwą wskaźnika
  4. notacja wskaźnika (*) z nazwą tablicy

 

int vals[5] = {10, 20, 30, 40, 50};
int *ptr;
ptr = vals;

array       element            pointer
notation    number     vals    notation

vals[0]     0          10      *(ptr + 0)
ptr[0]                         *(vals + 0)

vals[1]     1          20      *(ptr + 1)
ptr[1]                         *(vals + 1)

vals[2]     2          30      *(ptr + 2)
ptr[2]                         *(vals + 2)

vals[3]     3          40      *(ptr + 3)
ptr[3]                         *(vals + 3)

vals[4]     4          50      *(ptr + 4)
ptr[4]                         *(vals + 4)

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 pointerI pointer to a pointertłum

To ś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:

//function prototype
void func(int*& rpInt); // I mean, seriously, int*& ??

int main()
{
  int nvar=2;
  int* pvar=&nvar;
  func(pvar);
  ....
  return 0;
}

Lub w mniejszym stopniu coś takiego:

//function prototype
void func(int** ppInt);

int main()
{
  int nvar=2;
  int* pvar=&nvar;
  func(&pvar);
  ....
  return 0;
}

Więc na koniec dnia, co tak naprawdę rozwiązujemy z całym tym bełkotem? Nic.

Teraz widzieliśmy składnię ptr-to-ptr i ref-to-ptr. Czy są jakieś zalety jednego nad drugim? Obawiam się, że nie. Użycie jednego z nich dla niektórych programistów to tylko osobiste preferencje. Niektórzy, którzy używają ref-to-ptr, mówią, że składnia jest „czystsza”, podczas gdy niektórzy, którzy używają ptr-to-ptr, mówią, że składnia ptr-to-ptr sprawia, że ​​jest ona bardziej przejrzysta dla tych, którzy czytają to, co robisz.

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- rvaluesemantyki.

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 ...


5
Myślę, że mogłeś udzielić odpowiedzi w C ++ na pytanie w C ... przynajmniej druga część.
detly

Wskaźniki do wskaźników odnoszą się również do C: p
David Titarenco

1
Heh? Widzę tylko wskaźniki do wskaźników podczas przekazywania tablic - twój drugi przykład nie ma zastosowania do większości przyzwoitego kodu C. Ponadto przeciągasz C do bałaganu C ++ - odwołania w C nie istnieją.
nowy123456

Musisz mieć do czynienia ze wskazówkami w wielu uzasadnionych przypadkach. Na przykład, gdy mamy do czynienia ze wskaźnikiem funkcji wskazującym na funkcję, która zwraca wskaźnik. Inny przykład: struktura, która może pomieścić zmienną liczbę innych struktur. I wiele więcej ...
David Titarenco

2
„wskaźniki do odniesień są niedozwolone w języku C” - bardziej jak „nieobecne” :)
Kos

29

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ść).


3
Tak. 'int foo = 5; int * pfoo = & foo; Widzisz, jakie to przydatne? OK, idąc dalej ... „Tak naprawdę nie korzystałem ze wskaźników, dopóki nie napisałem własnej biblioteki z podwójnymi linkami.
John Lopez

2
+1. Udzielam korepetycji uczniom CS100 i tak wiele ich problemów zostało rozwiązanych poprzez przejrzenie wskazówek w zrozumiały sposób.
benzado

1
+1 za kontekst historyczny. Zaczęło się długo po tym czasie i nigdy nie przyszło mi to do głowy.
Lumi

26

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 .]


2
@Jason - to prawda, ale nie neguje argumentu.
Steve Townsend

4
Spolsky nie mówi, że JavaSchools są powodem, dla którego ludzie mają trudności ze wskazówkami. Mówi, że skutkują niepiśmiennymi osobami z dyplomami informatyki.
benzado

1
@benzado - słuszna uwaga - mój krótki post byłby lepszy, gdyby zawierał „świetny artykuł potwierdzający pogląd, że wskazówki są trudne”. Artykuł sugeruje, że „posiadanie tytułu CS z„ dobrej szkoły ”” nie jest tak dobrym prognostykiem sukcesu jak programista, jak kiedyś, podczas gdy „rozumie wskaźniki” (i rekurencję) nadal nim jest.
Steve Townsend

1
@Steve Townsend: Myślę, że nie rozumiesz argumentu pana Spolsky'ego.
jason

2
@Steve Townsend: Pan Spolsky argumentuje, że szkoły Java wychowują pokolenie programistów, którzy nie znają wskaźników i rekurencji, a nie, że wskaźniki są trudne ze względu na powszechność szkół Java. Jak stwierdziłeś „istnieje świetny artykuł o tym, dlaczego jest to trudne” i powiązany z nim, wydaje się, że masz tę drugą interpretację. Wybacz mi, jeśli się mylę.
jason

24

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 *pw swojej głowie, mówię „ pwskazuje na int”. 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.


1
Dobrym sposobem nauczenia się tego jest zaprogramowanie 8-bitowych mikrokontrolerów. Łatwo je zrozumieć. Weźmy kontrolery Atmel AVR; są nawet obsługiwane przez gcc.
Xenu

@Xenu: Zgadzam się. Dla mnie był to Intel 8008 i 8051 :-)
Mike Dunlavey

Dla mnie był to niestandardowy 8-bitowy komputer („Może”) na MIT w mrocznych mgiełkach czasu.
QuantumMechanic

Mike - powinieneś dać swoim uczniom CARDIAC :)
QuantumMechanic

1
@Quantum: CARDIAC - dobry, nie słyszałem o tym. „Może” - pozwól mi zgadnąć, czy było to, że kiedy Sussman (i inni) kazał ludziom czytać książkę Mead-Conway i tworzyć własne chipy LSI? To było trochę po moim tamtym czasie.
Mike Dunlavey

17

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.


4
Jeśli masz ograniczone zasoby do pracy (pamięć RAM, ROM, procesor), na przykład w aplikacjach wbudowanych, wskaźniki szybko nabierają znacznie więcej sensu.
Nick T

+1 za komentarz Nicka - szczególnie za przekazywanie struktur.
nowy123456

12

Oto przykład wskaźnika / tablicy, który dał mi przerwę. Załóżmy, że masz dwie tablice:

uint8_t source[16] = { /* some initialization values here */ };
uint8_t destination[16];

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:

memcpy(destination, source, sizeof(source));
memcpy(&destination, source, sizeof(source));
memcpy(&destination[0], source, sizeof(source));
memcpy(destination, &source, sizeof(source));
memcpy(&destination, &source, sizeof(source));
memcpy(&destination[0], &source, sizeof(source));
memcpy(destination, &source[0], sizeof(source));
memcpy(&destination, &source[0], sizeof(source));
memcpy(&destination[0], &source[0], sizeof(source));

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ę.


Wolę też pierwszą wersję (mniej interpunkcji).
sigjuice

++ Ja też, ale naprawdę musisz być ostrożny sizeof(source), ponieważ jeśli sourcejest wskaźnikiem, sizeofnie będzie tym, czego chcesz. Czasami (nie zawsze) piszę sizeof(source[0]) * number_of_elements_of_sourcetylko po to, żeby trzymać się z daleka od tego błędu.
Mike Dunlavey

miejsce docelowe, i miejsce docelowe, i miejsce docelowe [0] wcale nie są takie same - ale każdy z nich za pomocą innego mechanizmu zostanie przekonwertowany na tę samą pustkę *, gdy zostanie użyty w memcpy. Jednak gdy zostanie użyty jako argument sizeof, otrzymasz dwa różne wyniki i możliwe są trzy różne wyniki.
gnasher729

Myślałem, że adres operatora jest wymagany?
MarcusJ

7

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:

char ch;
char str[100];
scanf("%c %s", &ch, str);

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 &chtak naprawdę oznaczało i dlaczego tego strnie 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:

char * x = NULL;
if (y) {
     char z[100];
     x = z;
}

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 malloci new, 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 mallocw pewnym systemie zwraca, możesz zobaczyć, ile danych zostało faktycznie przydzielonych. Zdałem sobie sprawę, że kod w programie mallocmoż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, jak mallocdział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:

int foo(struct frog * f, int x, int y) {
    struct leg * g = f->left_leg;
    struct toe * t = g->big_toe;
    process(t);

więc jeśli schrzanię typ wskaźnika, błąd kompilatora będzie bardzo jasny, na czym polega problem. Gdybym to zrobił:

int foo(struct frog * f, int x, int y) {
    process(f->left_leg->big_toe);

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ę.


1
+1. Dokładne i wnikliwe. Zapomniałem o scanf, ale teraz, kiedy o tym wspomniałeś, pamiętam, że miałem to samo zamieszanie.
Joe White

6

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.

  1. 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.

  2. Jednowymiarowa dynamiczna alokacja pamięci. Ustalenie alokacji pamięci 1-D pozwoliło mi zrozumieć pojęcie wskaźnika.

  3. 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ę.

  4. 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.

  1. Jak i po co używać wskaźnika.
  2. Czym się różnią, a jednocześnie są podobne do tablic.
  3. Zrozumienie, gdzie przechowywane są informacje wskaźnika.
  4. Zrozumienie, na co i gdzie wskazuje wskaźnik.

Hej, czy możesz wskazać mi artykuł / książkę / rysunek / doodle / cokolwiek, czego mógłbym się nauczyć w podobny sposób, jak opisałeś w swojej odpowiedzi? Jestem głęboko przekonany, że jest to właściwy sposób uczenia się, w zasadzie czegokolwiek. Głębokie zrozumienie i dobre modele mentalne
Alexander Starbuck,

1
@AlexStarbuck - Nie chcę, żeby to zabrzmiało nonszalancko, ale metoda naukowa to świetne narzędzie. Narysuj sobie obraz tego, co myślisz, że może się wydarzyć w określonym scenariuszu. Zaprogramuj coś, aby to przetestować i przeanalizuj, co masz. Czy spełniło Twoje oczekiwania? Jeśli nie, określ, gdzie się różni? Powtarzaj w razie potrzeby, stopniowo zwiększając złożoność, aby przetestować zarówno rozumienie, jak i modele mentalne.
Sparky

6

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 &ioznacza to „adres i”, to zrozumienie, które *ioznacza „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.


Jeśli &ijest adres i *izawartość, to co to jest i?
Thomas Ahle

2
W pewnym sensie przeciążam użycie i. Dla dowolnej zmiennej i, & i oznacza "adres" i, i samo w sobie oznacza "zawartość & i", a * i oznacza "traktuj zawartość & i jako adres, przejdź do tego adresu i przekaż z powrotem zawartość".
Gary Rowe

5

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.


4
Szczerze mówiąc, biorąc pod uwagę, że nadal masz trudności ze wskazówkami, nie jestem pewien, czy twoje doświadczenie w Cornell zasadniczo zaprzecza artykułowi Joela. Oczywiście wystarczająco dużo twojego mózgu jest połączone z nastawieniem Java, aby wyrazić swoją opinię.
jkerian

5
Wat? Odniesienia w Javie (lub C #, Pythonie lub prawdopodobnie dziesiątkach innych języków) to po prostu wskaźniki bez arytmetyki. Zrozumienie wskaźników oznacza zrozumienie, dlaczego nie void foo(Clazz obj) { obj = new Clazz(); }działa, a jednocześnie void bar(Clazz obj) { obj.quux = new Quux(); }mutuje argument ...

1
Wiem, jakie odniesienia są w Javie, ale mówię tylko, że jeśli poprosisz mnie o zrobienie refleksji w Javie lub napisanie czegoś znaczącego w CI, nie mogę tego po prostu wyrzucić. Wymaga wielu badań, takich jak nauka za każdym razem.
pudełko na buty639

1
Jak to się dzieje, że przeszedłeś przez klasę systemów operacyjnych w C bez biegłości w C? Bez urazy, po prostu pamiętam, że musiałem opracować od podstaw prosty system operacyjny. Musiałem używać wskaźników tysiące razy ...
Gravity

5

Oto brak odpowiedzi: użyj cdecl (lub c ++ decl), aby to rozgryźć:

eisbaw@leno:~$ cdecl explain 'int (*(*foo)(const void *))[3]'
declare foo as pointer to function (pointer to const void) returning pointer to array 3 of int

4

Dodają dodatkowy wymiar do kodu bez znaczącej zmiany składni. Pomyśl o tym:

int a;
a = 5

Jest tylko jedna rzecz do zmiany: a. Możesz pisać, a = 6a wyniki są oczywiste dla większości ludzi. Ale teraz rozważ:

int *a;
a = &some_int;

Są dwie rzeczy, aktóre mają znaczenie w różnych momentach: rzeczywista wartość awskaźnika i wartość „za” wskaźnikiem. Możesz zmienić a:

a = &some_other_int;

... i some_intnadal jest gdzieś z tą samą wartością. Ale możesz też zmienić to, na co wskazuje:

*a = 6;

Istnieje luka pojęciowa a = 6, która ma tylko lokalne skutki uboczne, a *a = 6któ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 z alub coś pośredniego z *a... że może być to, co dezorientuje ludzi.


4

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.


3

Wskaźniki są trudne z powodu pośrednictwa.


„Mówi się, że nie ma problemu w informatyce, którego nie można rozwiązać za pomocą jeszcze jednego poziomu pośrednictwa” (nie ma jednak pojęcia, kto powiedział to pierwszy)
The Archetypal Paul

To jak magia, gdzie błędne ukierunkowanie jest tym, co dezorientuje ludzi (ale jest całkowicie niesamowite)
Nick T

3

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ę.


3

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.


3

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:

  • Zdefiniuj rekurencyjne struktury danych

W C można zdefiniować połączoną listę liczb całkowitych w następujący sposób:

struct node {
  int value;
  struct node* next;
}

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:

data List = List Int List | Null

Całkiem proste - lista jest albo pusta, albo utworzona z wartości i reszty listy.

  • Iteruj po łańcuchach i tablicach

Oto, jak możesz zastosować funkcję foodo każdego znaku ciągu w C:

char *c;
for (c = "hello, world!"; *c != '\0'; c++) { foo(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.

  • Osiągnij polimorfizm

Oto rzeczywista sygnatura funkcji znaleziona w glib :

typedef struct g_list GList;

void  g_list_foreach    (GList *list,
                 void (*func)(void *data, void *user_data),
                         void* user_data);

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, jak mapjest deklarowane w Haskell:

map::(a->b)->[a]->[b]

To jest o wiele prostsze: mapjest funkcją, która pobiera funkcję, która konwertuje an ana a bi stosuje ją do listy a, aby otrzymać listę b's. Podobnie jak w przypadku funkcji C g_list_foreach, mapnie 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”.


Niewłaściwe użycie c != NULLw Twoim przykładzie „Witaj, świecie” ... masz na myśli *c != '\0'.
Olaf Seibert

2

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.


2
Wskaźniki groking nie wymagają zrozumienia kodu maszynowego ani asemblera.
jason

Wymagaj, nie. Ale ludzie, którzy rozumieją montaż, prawdopodobnie znajdą wskazówki o wiele, dużo łatwiej, ponieważ wykonali już większość (jeśli nie wszystkie) niezbędnych połączeń mentalnych.
cHao

2

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? ;-)


Rozumiem. Moja odpowiedź trochę dotyczy tego.
J. Polfer

2

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.


2

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ć.


2

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!


Po przemyśleniu możesz zdefiniować stałą ilość pamięci do przechowywania obrazów / audio / wideo, powiedzmy na urządzeniu z ograniczoną pamięcią, ale wtedy musiałbyś poradzić sobie z pewnym rodzajem przesyłania strumieniowego do i z pamięci.
Chris Barry,

1

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:

A a(1);

vs.

A a = A(1);

vs.

A* a = new A(1); 

i najwyraźniej

A a(); 

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

int* i = &a;

wtedy & a jest adresem pamięci.

OTOH, jeśli tak

void f(int &a) {}

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

int* i

może być wskaźnikiem do int

int *i = &n; // 

lub

może być tablicą int

int* i = new int[5];

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.


C ++ 2011 poprawił trochę rzeczy, jeśli chodzi o deklaracje zmiennych.
gnasher729

0

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ę.


0

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 ..


0

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”.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.