Dlaczego warto korzystać ze wskaźników? [Zamknięte]


356

Wiem, że to naprawdę podstawowe pytanie, ale zacząłem od podstawowego programowania w C ++ po kodowaniu kilku projektów w językach wysokiego poziomu.

Zasadniczo mam trzy pytania:

  1. Po co używać wskaźników zamiast normalnych zmiennych?
  2. Kiedy i gdzie powinienem używać wskaźników?
  3. Jak korzystasz ze wskaźników z tablicami?

5
Omówione wcześniej na to pytanie Nadzieja, która pomaga!
Doug T.

4
Aby uzyskać listę książek, patrz stackoverflow.com/questions/388242/... . Po Javie uznałem, że Accelerated C ++ jest bardzo przydatny.
Jabba

163
Używamy wskaźników, ponieważ łatwiej jest podać komuś adres do domu niż każdemu dać kopię domu.
Rishi Dua,

14
@RishiDua To jest najlepsze wyjaśnienie wskaźnika, z jakim się kiedykolwiek spotkałem. Dziękuję za to, zwiększyło moje zrozumienie :)
Josh

2
Wskaźników można również użyć, jeśli chcesz zwrócić więcej niż jedną wartość / obiekt.
Gaurav

Odpowiedzi:


172
  • Po co używać wskaźników zamiast normalnych zmiennych?

Krótka odpowiedź brzmi: nie. ;-) Wskaźników należy używać tam, gdzie nie można użyć niczego innego. Jest to spowodowane brakiem odpowiedniej funkcjonalności, brakiem typów danych lub czystą wydajnością. Więcej poniżej ...

  • Kiedy i gdzie powinienem używać wskaźników?

Krótka odpowiedź tutaj: tam, gdzie nie można użyć niczego innego. W C nie masz obsługi złożonych typów danych, takich jak ciąg. Nie ma również możliwości przekazania zmiennej „przez odwołanie” do funkcji. Tam musisz użyć wskaźników. Możesz także poprosić ich o wskazanie praktycznie wszystkiego, połączonych list, członków struktur i tak dalej. Ale nie wchodźmy w to tutaj.

  • Jak korzystasz ze wskaźników z tablicami?

Przy niewielkim wysiłku i dużym zamieszaniu. ;-) Jeśli mówimy o prostych typach danych, takich jak int i char, istnieje niewielka różnica między tablicą a wskaźnikiem. Te deklaracje są bardzo podobne (ale nie takie same - np. sizeofZwracają różne wartości):

char* a = "Hello";
char a[] = "Hello";

Możesz dotrzeć do dowolnego elementu w tablicy w ten sposób

printf("Second char is: %c", a[1]);

Indeks 1, ponieważ tablica zaczyna się od elementu 0. :-)

Lub możesz równie dobrze to zrobić

printf("Second char is: %c", *(a+1));

Operator wskaźnika (*) jest potrzebny, ponieważ mówimy printf, że chcemy wydrukować znak. Bez znaku * zostanie wydrukowana reprezentacja znakowa samego adresu pamięci. Teraz zamiast tego używamy samej postaci. Gdybyśmy użyli% s zamiast% c, poprosilibyśmy printf o wydrukowanie zawartości adresu pamięci wskazanego przez „a” plus jeden (w tym przykładzie powyżej), i nie musielibyśmy wstawiać * z przodu:

printf("Second char is: %s", (a+1)); /* WRONG */

Ale to nie tylko wydrukowałoby drugi znak, ale zamiast tego wszystkie znaki w kolejnych adresach pamięci, dopóki nie zostanie znaleziony znak pusty (\ 0). I tu zaczyna się robić niebezpiecznie. Co się stanie, jeśli przypadkowo spróbujesz wydrukować zmienną typu liczba całkowita zamiast wskaźnika char za pomocą formatyzatora% s?

char* a = "Hello";
int b = 120;
printf("Second char is: %s", b);

Spowoduje to wydrukowanie wszystkiego, co zostanie znalezione pod adresem pamięci 120 i kontynuowanie drukowania, dopóki nie zostanie znaleziony znak zerowy. Wykonanie tej instrukcji printf jest niewłaściwe i nielegalne, ale prawdopodobnie i tak by działało, ponieważ wskaźnik jest w rzeczywistości typu int w wielu środowiskach. Wyobraź sobie problemy, które możesz spowodować, gdybyś zamiast tego użył sprintf () i przypisał w ten sposób zbyt długą „tablicę znaków” innej zmiennej, która ma przydzielone tylko ograniczone miejsce. Najprawdopodobniej skończysz na zapisywaniu czegoś innego w pamięci i spowodujesz awarię programu (jeśli masz szczęście).

Aha, a jeśli nie przypiszesz wartości ciągu do tablicy / wskaźnika char podczas deklarowania, MUSISZ przydzielić mu wystarczającą ilość pamięci, zanim nadasz jej wartość. Korzystanie z malloc, calloc lub podobnego. To dlatego, że zadeklarowałeś tylko jeden element w tablicy / jeden adres pamięci do wskazania. Oto kilka przykładów:

char* x;
/* Allocate 6 bytes of memory for me and point x to the first of them. */
x = (char*) malloc(6);
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* Delete the allocation (reservation) of the memory. */
/* The char pointer x is still pointing to this address in memory though! */
free(x);
/* Same as malloc but here the allocated space is filled with null characters!*/
x = (char *) calloc(6, sizeof(x));
x[0] = 'H';
x[1] = 'e';
x[2] = 'l';
x[3] = 'l';
x[4] = 'o';
x[5] = '\0';
printf("String \"%s\" at address: %d\n", x, x);
/* And delete the allocation again... */
free(x);
/* We can set the size at declaration time as well */
char xx[6];
xx[0] = 'H';
xx[1] = 'e';
xx[2] = 'l';
xx[3] = 'l';
xx[4] = 'o';
xx[5] = '\0';
printf("String \"%s\" at address: %d\n", xx, xx);

Pamiętaj, że nadal możesz używać zmiennej x po wykonaniu wolnej () przydzielonej pamięci, ale nie wiesz, co tam jest. Zauważ też, że dwa printf () mogą dać ci różne adresy, ponieważ nie ma gwarancji, że drugi przydział pamięci zostanie wykonany w tej samej przestrzeni, co pierwszy.


8
Twój przykład ze 120 jest błędny. Używa% c, a nie% s, więc nie ma błędu; drukuje jedynie małą literę x. Co więcej, twoje późniejsze twierdzenie, że wskaźnik jest typu int, jest błędne i bardzo zła rada dla programisty C niedoświadczonego ze wskaźnikami.
R .. GitHub ZATRZYMAJ LÓD

13
-1 Zaczynasz dobrze, ale pierwszy przykład jest zły. Nie, to nie to samo. W pierwszym przypadku ajest wskaźnikiem, aw drugim przypadku ajest tablicą. Czy wspominałem już o tym? To nie to samo! Sprawdź sam: porównaj sizeof (a), spróbuj przypisać nowy adres do tablicy. To nie zadziała.
sellibitze

42
char* a = "Hello"; i char a[] = "Hello"; nie są takie same, są zupełnie inne. Jeden deklaruje wskaźnik, a drugi tablicę. Spróbuj a, sizeofa zobaczysz różnicę.
Patrick Schlüter,

12
„Wykonanie tej instrukcji printf nie jest złe ani nielegalne”. To jest po prostu źle. Ta instrukcja printf ma nieokreślone zachowanie. W wielu implementacjach spowoduje to naruszenie dostępu. Wskaźniki w rzeczywistości nie są typu int, są w rzeczywistości wskaźnikami (i niczym więcej).
Mankarse

19
-1, pomyliłeś się tutaj tak wiele faktów, a ja po prostu mówię wprost, że nie zasługujesz na liczbę głosów pozytywnych lub status akceptowanych odpowiedzi.
asveikau

50

Jednym z powodów używania wskaźników jest możliwość modyfikacji zmiennej lub obiektu w wywoływanej funkcji.

W C ++ lepszym rozwiązaniem jest używanie referencji niż wskaźników. Chociaż referencje są w zasadzie wskaźnikami, C ++ w pewnym stopniu ukrywa ten fakt i sprawia wrażenie, jakbyś przekazywał wartość. Ułatwia to zmianę sposobu, w jaki funkcja wywołująca odbiera wartość, bez konieczności modyfikowania semantyki jej przekazywania.

Rozważ następujące przykłady:

Za pomocą referencji:

public void doSomething()
{
    int i = 10;
    doSomethingElse(i);  // passes i by references since doSomethingElse() receives it
                         // by reference, but the syntax makes it appear as if i is passed
                         // by value
}

public void doSomethingElse(int& i)  // receives i as a reference
{
    cout << i << endl;
}

Korzystanie ze wskaźników:

public void doSomething()
{
    int i = 10;
    doSomethingElse(&i);
}

public void doSomethingElse(int* i)
{
    cout << *i << endl;
}

16
Prawdopodobnie dobrym pomysłem jest wspomnienie, że referencje są bezpieczniejsze, ponieważ nie można przekazać referencji o wartości zerowej.
SpoonMeiser

26
Tak, to prawdopodobnie największa zaleta korzystania z referencji. Dzięki za zwrócenie na to uwagi.
Gra

2
Z pewnością możesz przekazać odwołanie zerowe. To po prostu nie jest tak łatwe jak przekazanie wskaźnika zerowego.
n0th

1
zgodzić się z @ n0rd. Jeśli uważasz, że nie możesz przekazać zerowego numeru referencyjnego, jesteś w błędzie. Łatwiej jest przekazać zwisające odwołanie niż odwołanie zerowe, ale ostatecznie można to zrobić dość łatwo. Odniesienia nie są srebrną kulą chroniącą inżyniera przed wystrzeleniem się w stopę. Zobacz na żywo .
WhozCraig

2
@ n0rd: Takie zachowanie jest jawnie niezdefiniowane.
Daniel Kamil Kozar,

42
  1. Wskaźniki pozwalają odwoływać się do tego samego miejsca w pamięci z wielu lokalizacji. Oznacza to, że możesz zaktualizować pamięć w jednej lokalizacji, a zmianę można zobaczyć z innej lokalizacji w programie. Zaoszczędzisz także miejsce, umożliwiając współdzielenie komponentów w swoich strukturach danych.
  2. Wskaźników należy używać w każdym miejscu, w którym należy uzyskać adres i przekazać go do określonego miejsca w pamięci. Możesz także używać wskaźników do nawigacji po tablicach:
  3. Tablica to blok ciągłej pamięci, która została przypisana do określonego typu. Nazwa tablicy zawiera wartość początkowego miejsca tablicy. Gdy dodasz 1, zabierze Cię to do drugiego miejsca. Umożliwia to pisanie pętli zwiększających wskaźnik, który przesuwa się po tablicy bez wyraźnego licznika do użycia podczas uzyskiwania dostępu do tablicy.

Oto przykład w C:

char hello[] = "hello";

char *p = hello;

while (*p)
{
    *p += 1; // increase the character by one

    p += 1; // move to the next spot
}

printf(hello);

odciski

ifmmp

ponieważ pobiera wartość dla każdego znaku i zwiększa go o jeden.


because it takes the value for each character and increments it by one. Czy w reprezentacji ascii czy jak?
Eduardo S.

28

Wskaźniki są jednym ze sposobów uzyskania pośredniego odniesienia do innej zmiennej. Zamiast trzymać wartość zmiennej, podają jej adres . Jest to szczególnie przydatne w przypadku tablic, ponieważ za pomocą wskaźnika do pierwszego elementu w tablicy (jego adres) można szybko znaleźć następny element, zwiększając wskaźnik (do następnej lokalizacji adresu).

Najlepsze wyjaśnienie wskaźników i arytmetyki wskaźników, które przeczytałem, znajduje się w The C Programming Language . Dobrą książką dla początkujących do nauki C ++ jest C ++ Primer .


1
Dziękuję Ci! wreszcie praktyczne wyjaśnienie korzyści płynących z używania stosu! czy wskaźnik do pozycji w tablicy również poprawia wydajność uzyskując dostęp do wartości @ i względem wskaźnika?

To chyba najlepsze wytłumaczenie, jakie przeczytałem. ludzie mają sposób na „nadmierne komplikowanie” :)
Detilium

23

Pozwól mi też spróbować odpowiedzieć na to pytanie.

Wskaźniki są podobne do referencji. Innymi słowy, nie są to kopie, ale raczej sposób na odniesienie do oryginalnej wartości.

Przede wszystkim jednym z miejsc, w których zwykle będziesz musiał często korzystać ze wskaźników, jest, gdy masz do czynienia z wbudowanym sprzętem . Być może musisz zmienić stan cyfrowego PIN-u IO. Być może przetwarzasz przerwanie i musisz zapisać wartość w określonej lokalizacji. Dostajesz obraz. Jeśli jednak nie masz do czynienia ze sprzętem i zastanawiasz się, jakiego rodzaju użyć, czytaj dalej.

Po co używać wskaźników zamiast normalnych zmiennych? Odpowiedź staje się jaśniejsza, gdy masz do czynienia ze złożonymi typami, takimi jak klasy, struktury i tablice. Jeśli użyjesz normalnej zmiennej, możesz zrobić kopię (kompilatory są wystarczająco inteligentne, aby temu zapobiec w niektórych sytuacjach, a C ++ 11 też pomaga, ale na razie nie będziemy omijać tej dyskusji).

Co się stanie, jeśli chcesz zmodyfikować oryginalną wartość? Możesz użyć czegoś takiego:

MyType a; //let's ignore what MyType actually is right now.
a = modify(a); 

To zadziała dobrze, a jeśli nie wiesz dokładnie, dlaczego używasz wskaźników, nie powinieneś ich używać. Uważaj na powód „prawdopodobnie szybszy”. Uruchom własne testy, a jeśli faktycznie są szybsze, użyj ich.

Powiedzmy jednak, że rozwiązujesz problem, w którym musisz przydzielić pamięć. Przydzielając pamięć, musisz ją cofnąć. Przydział pamięci może, ale nie musi, być udany. Tutaj przydają się wskaźniki - pozwalają przetestować istnienie przydzielonego obiektu i pozwalają uzyskać dostęp do obiektu, dla którego przydzielono pamięć, usuwając odwołanie ze wskaźnika.

MyType *p = NULL; //empty pointer
if(p)
{
    //we never reach here, because the pointer points to nothing
}
//now, let's allocate some memory
p = new MyType[50000];
if(p) //if the memory was allocated, this test will pass
{
    //we can do something with our allocated array
    for(size_t i=0; i!=50000; i++)
    {
        MyType &v = *(p+i); //get a reference to the ith object
        //do something with it
        //...
    }
    delete[] p; //we're done. de-allocate the memory
}

To jest klucz do tego, dlaczego miałbyś używać wskaźników - referencje zakładają, że element, do którego się odwołujesz, już istnieje . Wskaźnik nie.

Innym powodem, dla którego używałbyś wskaźników (a przynajmniej musiałbyś sobie z nimi poradzić) jest to, że są one typem danych, który istniał przed referencjami. Dlatego jeśli w końcu użyjesz bibliotek do robienia rzeczy, w których wiesz, że są lepsze, przekonasz się, że wiele z tych bibliotek używa wskaźników w dowolnym miejscu, po prostu ze względu na to, jak długo są w pobliżu (dużo z nich zostały napisane przed C ++).

Jeśli nie korzystałeś z żadnych bibliotek, możesz zaprojektować swój kod w taki sposób, aby trzymać się z dala od wskaźników, ale biorąc pod uwagę, że wskaźniki są jednym z podstawowych typów języka, im szybciej będziesz z nich wygodnie korzystać, tym bardziej przenośne byłyby twoje umiejętności C ++.

Z punktu widzenia łatwości konserwacji, powinienem również wspomnieć, że kiedy korzystasz ze wskaźników, musisz albo sprawdzić ich ważność i zająć się sprawą, gdy są one nieważne, lub po prostu założyć, że są one prawidłowe i zaakceptować fakt, że twój program ulegnie awarii lub gorzej, GDY założenie zostanie złamane. Innymi słowy, wybór wskaźników polega na wprowadzeniu złożoności kodu lub większego nakładu prac konserwacyjnych, gdy coś się zepsuje i próbujesz wyśledzić błąd, który należy do całej klasy błędów wprowadzanych przez wskaźniki, takich jak uszkodzenie pamięci.

Więc jeśli kontrolujesz cały swój kod, trzymaj się z dala od wskaźników i zamiast tego używaj referencji, utrzymując je na stałe, kiedy możesz. Zmusi cię to do zastanowienia się nad czasem życia twoich obiektów i sprawi, że twój kod będzie łatwiejszy do zrozumienia.

Pamiętaj tylko o tej różnicy: Odwołanie jest zasadniczo poprawnym wskaźnikiem. Wskaźnik nie zawsze jest prawidłowy.

Czy więc mówię, że niemożliwe jest utworzenie nieprawidłowego odwołania? Nie. Jest to całkowicie możliwe, ponieważ C ++ pozwala robić prawie wszystko. Trudniej jest robić to niezamierzenie, a będziesz zaskoczony, jak wiele błędów jest niezamierzonych :)


Możesz pisać ładne klasy otoki dla IO zamapowanego w pamięci, dzięki którym możesz zasadniczo uniknąć używania wskaźników.
einpoklum

13

Oto nieco inne, ale wnikliwe spojrzenie na to, dlaczego wiele funkcji języka C ma sens: http://steve.yegge.googlepages.com/tour-de-babel#C

Zasadniczo standardowa architektura procesora to architektura Von Neumanna i niezwykle przydatne jest odniesienie się do położenia elementu danych w pamięci i wykonywanie arytmetyki z nim na takim komputerze. Jeśli znasz jakiś wariant języka asemblera, szybko zobaczysz, jak ważne jest to na niskim poziomie.

C ++ sprawia, że ​​wskaźniki są nieco mylące, ponieważ czasami zarządza nimi dla Ciebie i ukrywa ich efekt w postaci „referencji”. Jeśli używasz prostego C, potrzeba wskaźników jest o wiele bardziej oczywista: nie ma innego sposobu na wywołanie przez referencję, jest to najlepszy sposób na przechowywanie łańcucha, to najlepszy sposób na iterację przez tablicę itp.


12

Jednym ze sposobów użycia wskaźników (nie będę wspominać rzeczy, które zostały już omówione w postach innych osób) jest dostęp do pamięci, której nie przydzieliłeś. Nie jest to zbyt przydatne w programowaniu na PC, ale jest używane w programowaniu wbudowanym, aby uzyskać dostęp do urządzeń mapowanych w pamięci.

W dawnych czasach DOS-a mogłeś mieć bezpośredni dostęp do pamięci karty graficznej, deklarując wskaźnik do:

unsigned char *pVideoMemory = (unsigned char *)0xA0000000;

Wiele urządzeń osadzonych nadal korzysta z tej techniki.


Nie powód do używania wskaźnika - struktury podobnej do przęsła - która również utrzymuje długość - jest o wiele bardziej odpowiedni. Teraz jest gsl::spani wkrótce będzie std::span.
einpoklum

10

W dużej mierze wskaźniki są tablicami (w C / C ++) - są to adresy w pamięci i mogą być dostępne jak tablica w razie potrzeby (w „normalnych” przypadkach).

Ponieważ są adresem przedmiotu, są małe: zajmują tylko miejsce na adres. Ponieważ są małe, wysyłanie ich do funkcji jest tanie. A potem pozwalają tej funkcji działać na rzeczywistym elemencie, a nie na kopii.

Jeśli chcesz wykonać dynamiczny przydział pamięci (na przykład dla listy połączonej), musisz użyć wskaźników, ponieważ są one jedynym sposobem na pobranie pamięci ze sterty.


8
Myślę, że mylące jest twierdzenie, że wskaźniki to tablice. Naprawdę, nazwy tablic są stałymi wskaźnikami do pierwszego elementu tablicy. Tylko dlatego, że możesz uzyskać dostęp do dowolnego punktu, tak jakby jego tablica nie oznacza, że ​​jest ... możesz dostać naruszenie dostępu :)
rmeador

2
Wskaźniki nie są tablicami. Przeczytaj rozdział 6 comp.lang.c FAQ .
Keith Thompson

Wskaźniki tak naprawdę nie są tablicami. Poza tym nie ma wiele zastosowania w surowych tablicach - na pewno nie po C ++ 11 i std::array.
einpoklum

@einpoklum - wskaźniki są wystarczająco blisko tablic, aby były równoważne w operacjach referencyjnych i iterowaniu przez tablice :) .... także - C ++ 11 był 3 lata od wydania, kiedy ta odpowiedź została napisana w 2008 roku
warren

@warren: Wskaźniki nie są tablicami ani nie są blisko tablic, po prostu rozpadają się na tablice. Pedagogicznie niewłaściwe jest mówienie ludziom, że są do siebie podobni. Ponadto z pewnością możesz mieć odwołania do tablic, które nie są tego samego typu co wskaźniki, i utrzymywać informacje o rozmiarze; patrz tutaj
einpoklum

9

Wskaźniki są ważne w wielu strukturach danych, których projektowanie wymaga możliwości skutecznego łączenia lub łączenia jednego „węzła” z drugim. Nie „wybierzesz” wskaźnika zamiast powiedzieć normalny typ danych, taki jak zmiennoprzecinkowy, mają one po prostu inne cele.

Wskaźniki są przydatne tam, gdzie wymagana jest wysoka wydajność i / lub niewielki rozmiar pamięci.

Adres pierwszego elementu w tablicy można przypisać do wskaźnika. Umożliwia to bezpośredni dostęp do przydzielonych bajtów. Cały sens macierzy polega na tym, abyś nie musiał tego robić.


9

Jednym ze sposobów używania wskaźników zamiast zmiennych jest wyeliminowanie wymaganej duplikacji pamięci. Na przykład, jeśli masz jakiś duży złożony obiekt, możesz użyć wskaźnika, aby wskazać tę zmienną dla każdego utworzonego odwołania. W przypadku zmiennej musisz zduplikować pamięć dla każdej kopii.


To jest powód, aby używać referencji (lub co najwyżej - opakowań referencji), a nie wskaźników.
einpoklum

6

W C ++, jeśli chcesz korzystać z podtypu polimorfizm , ty masz do wykorzystania wskazówek. Zobacz ten post: Polimorfizm C ++ bez wskaźników .

Naprawdę, kiedy się nad tym zastanowić, ma to sens. Kiedy korzystasz z polimorfizmu podtypu, ostatecznie nie wiesz z góry, która metoda lub metoda podklasy zostanie zaimplementowana, ponieważ nie wiesz, jaka jest rzeczywista klasa.

Pomysł posiadania zmiennej, która przechowuje obiekt nieznanej klasy, jest niezgodny z domyślnym (niepointerowalnym) trybem przechowywania obiektów na stosie w C ++, gdzie ilość przydzielonego miejsca bezpośrednio odpowiada klasie. Uwaga: jeśli klasa ma 5 pól instancji w porównaniu do 3, konieczne będzie przydzielenie większej ilości miejsca.


Zauważ, że jeśli używasz „&” do przekazywania argumentów przez referencję, pośrednictwo (tj. Wskaźniki) jest nadal zaangażowane za sceną. „&” To po prostu cukier składniowy, który (1) pozwala zaoszczędzić kłopotów ze stosowaniem składni wskaźnika, a (2) pozwala kompilatorowi być bardziej rygorystycznym (np. Zakazując wskaźników zerowych).


Nie, nie musisz używać wskaźników - możesz używać referencji. A kiedy piszesz „wskaźniki są za kulisami” - to nie ma znaczenia. gotoinstrukcje są również używane poza sceną - w instrukcjach maszyny docelowej. Nadal nie twierdzimy, że ich używamy.
einpoklum

@ einpoklum-reinstateMonica Jeśli masz zestaw obiektów, na których chcesz działać, przypisując kolejno każdy element do zmiennej tymczasowej i wywołując metodę polimorficzną na tej zmiennej, to POTRZEBUJESZ wskaźnika, ponieważ nie możesz ponownie powiązać referencji.
Sildoreth,

Jeśli masz odwołanie do klasy pochodnej do klasy pochodnej i wywołujesz metodę wirtualną na tym odwołaniu, wówczas zostanie wywołane przesłonięcie klasy pochodnej. Czy się mylę?
einpoklum,

@ einpoklum-reinstateMonica To prawda. Ale nie można zmienić tego, do którego obiektu się odwołuje. Jeśli więc zapętlasz listę / zestaw / tablicę tych obiektów, zmienna referencyjna nie będzie działać.
Sildoreth,

5

Ponieważ kopiowanie dużych obiektów w różnych miejscach marnuje czas i pamięć.


4
W jaki sposób wskaźniki pomagają w tym? Myślę, że osoby pochodzące z Java lub .NET nie zna różnicę między stosu i sterty, więc ta odpowiedź jest całkiem bezużyteczny ..
Mats Fredriksson

Możesz przekazać przez referencję. Zapobiegnie to kopiowaniu.
Martin York

1
@MatsFredriksson - Zamiast przekazywać (kopiować) dużą strukturę danych i ponownie kopiować wynik, wystarczy wskazać, gdzie jest on w pamięci RAM, a następnie bezpośrednio go zmodyfikować.
John U

Więc nie kopiuj dużych obiektów. Nikt nie powiedział, że potrzebujesz do tego wskaźników.
einpoklum

5

Oto moja odpowiedź i nie obiecuję, że będę ekspertem, ale znalazłem wskazówki, które będą świetne w jednej z moich bibliotek, które próbuję napisać. W tej bibliotece (Jest to graficzny interfejs API z OpenGL :-)) możesz stworzyć trójkąt z obiektami wierzchołków, które zostaną do nich wprowadzone. Metoda rysowania bierze te trójkątne obiekty i ... rysuje je na podstawie utworzonych przeze mnie wierzchołków. Cóż, jest w porządku.

Ale co jeśli zmienię współrzędną wierzchołka? Przenieś to czy coś za pomocą moveX () w klasie wierzchołków? Cóż, ok, teraz muszę zaktualizować trójkąt, dodając więcej metod i wydajność jest marnowana, ponieważ muszę aktualizować trójkąt za każdym razem, gdy porusza się wierzchołek. Nadal nie jest to wielka sprawa, ale to nie jest takie świetne.

Co jeśli mam siatkę z tonami wierzchołków i tonami trójkątów, a siatka się obraca i porusza, i tak dalej. Będę musiał zaktualizować każdy trójkąt, który używa tych wierzchołków, i prawdopodobnie każdy trójkąt w scenie, ponieważ nie wiedziałbym, które z nich używają. To ogromnie wymaga komputera, a jeśli mam kilka oczek na krajobrazie, o Boże! Mam kłopoty, ponieważ aktualizuję każdy trójkąt prawie każdą klatkę, ponieważ te wierzchołki zmieniają się cały czas!

Dzięki wskaźnikom nie musisz aktualizować trójkątów.

Gdybym miał trzy * obiekty wierzchołków na klasę trójkątów, nie tylko oszczędzam miejsce, ponieważ zillionowe trójkąty nie mają trzech obiektów wierzchołków, które same są duże, ale także te wskaźniki zawsze wskazują na wierzchołki, do których są przeznaczone, bez względu na to, jak często zmieniają się wierzchołki. Ponieważ wskaźniki nadal wskazują na ten sam wierzchołek, trójkąty się nie zmieniają, a proces aktualizacji jest łatwiejszy w obsłudze. Gdybym cię pomylił, nie wątpiłbym w to, nie udaję, że jestem ekspertem, po prostu wrzucam moje dwa centy do dyskusji.


4

Potrzebę wskaźników w języku C opisano tutaj

Podstawową ideą jest to, że wiele ograniczeń w języku (takich jak używanie tablic, ciągów i modyfikowanie wielu zmiennych w funkcjach) można usunąć, manipulując lokalizacjami pamięci danych. Aby pokonać te ograniczenia, wskaźniki zostały wprowadzone w C.

Ponadto widać również, że za pomocą wskaźników możesz szybciej uruchamiać kod i oszczędzać pamięć w przypadkach, gdy przekazujesz duże typy danych (takie jak struktura z wieloma polami) do funkcji. Wykonanie kopii takich typów danych przed upływem czasu zajęłoby dużo czasu i pochłonęło pamięć. To kolejny powód, dla którego programiści preferują wskaźniki dla dużych typów danych.

PS: Proszę zapoznać się z podanym linkiem, aby uzyskać szczegółowe wyjaśnienia z przykładowym kodem.


Pytanie dotyczy C ++, ta odpowiedź dotyczy C.
einpoklum

nie, pytanie jest oznaczone c AND c ++. Być może znacznik c jest nieistotny, ale jest tutaj od samego początku.
Jean-François Fabre

3

W Javie i C # wszystkie odwołania do obiektów są wskaźnikami, rzeczą w c ++ jest to, że masz większą kontrolę nad tym, gdzie wskazuje wskaźnik. Pamiętaj Z wielką mocą wiąże się wielka odpowiedzialność.


1

Jeśli chodzi o twoje drugie pytanie, generalnie nie musisz używać wskaźników podczas programowania, jest jednak jeden wyjątek i to wtedy, gdy tworzysz publiczny interfejs API.

Problem z konstruktorami C ++, których ludzie zwykle używają do zamiany wskaźników, jest bardzo zależny od używanego zestawu narzędzi, co jest w porządku, gdy masz pełną kontrolę nad kodem źródłowym, jednak jeśli na przykład skompilujesz bibliotekę statyczną za pomocą Visual Studio 2008 i spróbuj użyć go w Visual Studio 2010, otrzymasz mnóstwo błędów linkera, ponieważ nowy projekt jest powiązany z nowszą wersją STL, która nie jest kompatybilna wstecz. Sprawa staje się jeszcze bardziej nieprzyjemna, jeśli skompilujesz bibliotekę DLL i udostępnisz bibliotekę importu, z której ludzie korzystają w innym zestawie narzędzi, ponieważ w takim przypadku program zawiesi się wcześniej czy później bez wyraźnego powodu.

Aby więc przenieść duże zestawy danych z jednej biblioteki do drugiej, można rozważyć nadanie wskaźnika do tablicy funkcji, która ma kopiować dane, jeśli nie chcesz zmuszać innych do korzystania z tych samych narzędzi, których używasz . Zaletą tego jest to, że nie musi to być nawet tablica w stylu C, możesz użyć std :: vector i podać wskaźnik, podając na przykład adres pierwszego elementu i wektora [0], i użyj std :: vector do wewnętrznego zarządzania tablicą.

Kolejny dobry powód, aby ponownie używać wskaźników w C ++, dotyczy bibliotek, rozważ bibliotekę DLL, której nie można załadować podczas działania programu, więc jeśli używasz biblioteki importu, zależność nie jest spełniona, a program ulega awarii. Dzieje się tak na przykład, gdy podajesz publiczny interfejs API w bibliotece DLL obok aplikacji i chcesz uzyskać do niego dostęp z innych aplikacji. W takim przypadku, aby użyć interfejsu API, musisz załadować bibliotekę DLL z jej lokalizacji (zwykle jest to klucz rejestru), a następnie musisz użyć wskaźnika funkcji, aby móc wywoływać funkcje w bibliotece DLL. Czasami ludzie, którzy tworzą interfejs API, są wystarczająco mili, aby dać ci plik .h, który zawiera funkcje pomocnicze, aby zautomatyzować ten proces i dać ci wszystkie potrzebne wskaźniki funkcji,


1
  • W niektórych przypadkach wskaźniki funkcji są wymagane do korzystania z funkcji znajdujących się we wspólnej bibliotece (.DLL lub .so). Obejmuje to wykonywanie czynności w różnych językach, w których często zapewnia się interfejs DLL.
  • Tworzenie kompilatorów
  • Czy tworzysz naukowe kalkulatory, w których masz tablicę lub mapę wektorową lub łańcuchową wskaźników funkcji?
  • Próba bezpośredniej modyfikacji pamięci wideo - stworzenie własnego pakietu graficznego
  • Tworzenie interfejsu API!
  • Struktury danych - wskaźniki połączeń węzłów dla tworzonych drzew specjalnych

Istnieje wiele powodów dla wskaźników. Mangowanie nazwy C jest szczególnie ważne w bibliotekach DLL, jeśli chcesz zachować zgodność między językami.

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.