Jakie problemy z programowaniem najlepiej rozwiązać za pomocą wskaźników? [Zamknięte]


58

Zasadniczo rozumiem, jak używać wskaźników, ale nie wiem, jak najlepiej ich używać w celu lepszego programowania.

Jakie są dobre projekty lub problemy do rozwiązania przy użyciu wskaźników, dzięki czemu mogę je lepiej zrozumieć?


Zapytaj, jakie problemy programistyczne najlepiej rozwiązać za pomocą wskaźników, a nie jakich projektów / programów. Możesz napisać program albo za pomocą wskaźników, albo nie - one (projekty) są po prostu zbyt dużym konstruktem, aby dać sensowną odpowiedź.
Oded

37
Problemy z programowaniem są tworzone za pomocą wskaźników, a nie rozwiązane. :)
xpda,

4
Właściwie zastanawiam się, które problemy można rozwiązać bez wskaźnika. Na pewno bardzo mało.
deadalnix

4
@deadalnix, to zależy, czy masz na myśli wskaźniki jawne czy pośrednie. Jeśli masz na myśli niejawne wskaźniki (tzn. Gdzieś są używane wskaźniki), nie byłoby możliwe napisanie żadnego programu, który używa stosu lub stosu. Jeśli masz na myśli jawne wskaźniki, to jesteś nieograniczony, możesz dokonać dowolnej wielkości alokacji, tworząc nowe ramki stosu (w zasadzie za pomocą wywołania funkcji) i dezalokacji za pomocą wywołań ogona, zakładając, że są one zoptymalizowane przez kompilator.
dan_waterworth

3
niektóre z tych odpowiedzi są okropne.

Odpowiedzi:


68

Manipulowanie dużymi ilościami danych w pamięci to miejsce, w którym wskaźniki naprawdę świecą.

Przekazywanie dużego obiektu przez odniesienie jest równoznaczne z przekazaniem zwykłej starej liczby. Możesz manipulować potrzebnymi częściami w przeciwieństwie do kopiowania obiektu, zmieniania go, a następnie przekazywania kopii w celu umieszczenia na miejscu oryginału.


12
To świetna odpowiedź i dodam, że nie potrzebujesz dużych obiektów do manipulowania dużymi ilościami danych; czasami robią to duże obiekty, ale jeszcze częstszym problemem jest bardzo częste manipulowanie grupą małych obiektów. Co, jeśli się nad tym zastanowić, dotyczy tylko wszystkich programów komputerowych.
jhocking

44

Koncepcja wskaźnika pozwala odwoływać się do danych według adresu bez powielania przechowywania danych. Takie podejście pozwala na pisanie wydajnych algorytmów, takich jak:

  1. Sortowanie
    Podczas przenoszenia danych w algorytmie sortowania możesz przesunąć wskaźnik zamiast samych danych - pomyśl o posortowaniu milionów wierszy na ciągu 100 znaków; oszczędzasz wiele niepotrzebnych ruchów danych.

  2. Listy połączone
    Możesz przechowywać lokalizację następnego i / lub poprzedniego elementu, a nie całe dane powiązane z rekordem.

  3. Przekazywanie parametrów
    W tym przypadku podajesz adres danych zamiast samych danych. Ponownie pomyśl o algorytmie kompresji nazwy, który działa na milionach wierszy.

Pojęcie to można rozszerzyć na struktury danych, takie jak relacyjne bazy danych, w których wskaźnik przypomina klucz obcy . Niektóre języki nie zachęcają do używania wskaźników, takich jak C # i COBOL.

Przykłady można znaleźć w wielu miejscach, takich jak:

Poniższy post może być w jakiś sposób istotny:


14
Nie powiedziałbym, że C # nie zachęca do używania wskaźników; zamiast tego nie zachęca do używania niezarządzanych wskaźników - w C # jest mnóstwo rzeczy, które są przekazywane przez odniesienie zamiast wartości.
Blakomen,

Ładne, dobre wyjaśnienie
Sabby

5
Musiałem głosować za komentarzem C #. „Referencje” w języku C # są równoważne wskaźnikom, po prostu C # odciąga cię od wiedzy, że tak naprawdę masz do czynienia ze wskaźnikiem do obiektu w zarządzanej stercie.
MattDavey,

1
@MattDavey: odniesienia C # nie są wskaźnikami. Nie możesz ich dodawać, używać ich do indeksowania tablicy, używać wielu pośrednich itp. Nie możesz na przykład mieć odniesień do odniesień.
Billy ONeal,

5
@Billy ONeal pogląd, że „jest to tylko wskaźnik, jeśli potrafisz na nim wykonywać arytmetykę”, jest szczerze absurdalny, ale nie mam cierpliwości, aby się z tobą kłócić.
MattDavey,

29

Dziwi mnie, że żadna inna odpowiedź nie wspomniała o tym: wskaźniki pozwalają na tworzenie nieciągłych i nieliniowych struktur danych, w których element może być powiązany z wieloma innymi w złożony sposób.

Połączone listy (pojedynczo, podwójnie i kołowo), drzewa (czerwono-czarny, AVL, trie, binarny, partycjonowanie przestrzeni…) i wykresy to przykłady struktur, które można zbudować w sposób najbardziej naturalny pod względem referencji, a nie tylko wartości .



@dan_waterworth: Nie. Zbyt dobrze znam tę czarną magię.
Jon Purdy,

8

Jednym prostym sposobem jest polimorfizm. Polimorfizm działa tylko ze wskaźnikami.

Ponadto używasz wskaźników za każdym razem, gdy potrzebujesz dynamicznego przydziału pamięci. W C zwykle dzieje się tak, gdy trzeba przechowywać dane w tablicy, ale nie znasz rozmiaru w czasie kompilacji. Następnie zadzwonisz do malloc, aby przydzielić pamięć i wskaźnik, aby uzyskać do niej dostęp. Niezależnie od tego, czy wiesz, czy nie, kiedy używasz tablicy, używasz wskaźników.

for(int i = 0; i < size; i++)
   std::cout << array[i];

jest odpowiednikiem

for(int i = 0; i < size; i++)
   std::cout << *(array + i);

Ta wiedza pozwala robić naprawdę fajne rzeczy, takie jak kopiowanie całej tablicy w jednym wierszu:

while( (*array1++ = *array2++) != '\0')

W c ++ używasz new do przydzielania pamięci dla obiektu i przechowywania go we wskaźniku. Robisz to za każdym razem, gdy musisz utworzyć obiekt w czasie wykonywania zamiast w czasie kompilacji (tj. Metoda tworzy nowy obiekt i zapisuje go na liście).

Aby lepiej zrozumieć wskaźniki:

  1. Znajdź kilka projektów, które bawią się tradycyjnymi strunami i tablicami.
  2. Znajdź niektóre projekty wykorzystujące dziedziczenie.

  3. Oto projekt, na którym wyciąłem zęby:

Wczytaj z pliku dwie macierze nxn i wykonaj na nich podstawowe operacje w przestrzeni wektorowej i wydrukuj ich wynik na ekranie.

Aby to zrobić, musisz użyć dynamicznych tablic i odwołać się do swoich tablic za pomocą wskaźników, ponieważ będziesz mieć dwie tablice tablic (wielowymiarowe tablice dynamiczne). Po zakończeniu tego projektu będziesz miał całkiem dobry pomysł na użycie wskaźników.


2
„Polimorfizm działa tylko ze wskaźnikami”. Niedokładne: polimorfizm działa również z referencjami. Które ostatecznie są wskaźnikami, ale myślę, że to rozróżnienie jest ważne, szczególnie dla początkujących.
Laurent Pireyn,

twój przykład kopiowania tablicy w jednym wierszu jest tak naprawdę kopiowaniem łańcucha zakończonego zerem w jednym wierszu, a nie jakiejkolwiek ogólnej tablicy.
tcrosley

8

Aby naprawdę zrozumieć, dlaczego wskaźniki są ważne, musisz zrozumieć różnicę między alokacją sterty a alokacją stosu.

Oto przykład przydziału stosu:

struct Foo {
  int bar, baz
};

void foo(void) {
  struct Foo f;
}

Obiekty przydzielone na stosie istnieją tylko przez czas wykonywania bieżącej funkcji. Kiedy wezwanie do foowykracza poza zakres, zmienia się również zmienna f.

Jednym z przypadków, w których staje się to problemem, jest potrzeba zwrócenia czegoś innego niż typ całkowy z funkcji (na przykład struktura Foo z powyższego przykładu).

Na przykład następująca funkcja spowodowałaby tak zwane „niezdefiniowane zachowanie”.

struct Foo {
  int bar, baz
};

struct Foo *foo(void) {
  struct Foo f;
  return &f;
}

Jeśli chcesz zwrócić coś struct Foo *z funkcji, tak naprawdę potrzebujesz alokacji sterty:

struct Foo {
  int bar, baz
};

struct Foo *foo(void) {
  return malloc(sizeof(struct Foo));
}

Funkcja mallocprzydziela obiekt na stercie i zwraca wskaźnik do tego obiektu. Zauważ, że termin „obiekt” jest tutaj używany luźno, co oznacza „coś”, a nie obiekt w znaczeniu programowania obiektowego.

Żywotność obiektów przydzielonych na stercie jest kontrolowana przez programistę. Pamięć dla tego obiektu będzie zarezerwowana do momentu zwolnienia go przez programistę, tj. Przez wywołanie free()lub do zakończenia programu.

Edycja : Nie zauważyłem, że to pytanie jest oznaczone jako pytanie C ++. Operatory C ++ newi new[]wykonują tę samą funkcję, co malloc. Operatory deletei delete[]są analogiczne do free. Choć newi deletepowinny być wykorzystywane wyłącznie do celów alokacji i wolne C ++ obiektów, zastosowanie malloci freejest całkowicie legalne w kodu C ++.


1
(+1) znany również jako „statyczne zmienne alokowane” i „dynamiczne zmienne alokowane” ;-)
umlcat,

4

Napisz dowolny nietrywialny projekt w C, a będziesz musiał dowiedzieć się, jak / kiedy używać wskaźników. W C ++ będziesz używać głównie obiektów z obsługą RAII, które wewnętrznie zarządzają wskaźnikami, ale w C surowe wskaźniki mają znacznie większą rolę. Jeśli chodzi o rodzaj projektu, który powinieneś zrobić, może to być coś nietrywialnego:

  • Mały i prosty serwer WWW
  • Narzędzia wiersza poleceń systemu Unix (mniej, cat, sortuj itp.)
  • Rzeczywisty projekt, który chcesz wykonać dla własnego dobra, a nie tylko dla nauki

Polecam ostatni.


Po prostu wybieram projekt, który chcę wykonać (jak gra 2D) i przypuszczam, że będę potrzebować i uczyć się wskazówek?
dysoco

3
Z mojego doświadczenia wynika, że ​​robienie prawdziwych projektów, które są nieco poza twoim zasięgiem umiejętności, jest najlepszym sposobem na naukę. Pojęć można się nauczyć, czytając i pisząc małe programy „zabawkowe”. Jednak, aby naprawdę nauczyć się, jak używać tych pojęć do pisania lepszych programów, wystarczy napisać programy inne niż zabawki.
Viktor Dahl,

3

Niemal wszystkie problemy programistyczne, które można rozwiązać za pomocą wskaźników, można rozwiązać za pomocą innych bezpieczniejszych typów referencji (nie odnosząc się do referencji C ++ , ale ogólna koncepcja CS posiadania zmiennej odnosi się do wartości danych przechowywanych gdzie indziej).

Wskaźniki , będące specyficzną implementacją referencji na niskim poziomie, w których można bezpośrednio manipulować adresami pamięci, są bardzo potężne, ale ich użycie może być nieco niebezpieczne (np. Wskazywać lokalizacje pamięci poza programem).

Zaletą bezpośredniego używania wskaźników jest to, że będą one nieco szybsze, bez konieczności przeprowadzania kontroli bezpieczeństwa. Języki takie jak Java, które nie implementują bezpośrednio wskaźników w stylu C, odczują niewielki spadek wydajności, ale zmniejszą wiele rodzajów trudnych do debugowania sytuacji.

Jeśli chodzi o powód, dla którego potrzebujesz pośrednictwa, lista jest dość długa, ale zasadniczo dwie kluczowe idee to:

  1. Kopiowanie wartości dużych obiektów jest powolne i spowoduje, że obiekt będzie przechowywany dwukrotnie w pamięci RAM (potencjalnie bardzo kosztowny), ale kopiowanie przez odniesienie jest prawie natychmiastowe przy użyciu tylko kilku bajtów pamięci RAM (na adres). Na przykład, powiedzmy, że masz ~ 1000 dużych obiektów (każdy około 1 MB pamięci RAM) w pamięci, a użytkownik musi mieć możliwość wybrania bieżącego obiektu (na który użytkownik będzie reagował). Posiadanie zmiennej, selected_objectktóra jest odniesieniem do jednego z obiektów, jest znacznie bardziej wydajne niż kopiowanie wartości bieżącego obiektu do nowej zmiennej.
  2. Posiadanie skomplikowanych struktur danych, które odnoszą się do innych obiektów, takich jak połączone listy lub drzewa, przy czym każdy element w strukturze danych odnosi się do innych elementów w strukturze danych. Korzyść z odwoływania się do innych elementów w strukturze danych oznacza, że ​​nie musisz przenosić wszystkich pozycji na liście w pamięci tylko dlatego, że wstawiłeś nowy element na środku listy (może mieć wstawki o stałym czasie).

2

Manipulowanie obrazem na poziomie pikseli jest prawie zawsze łatwiejsze i szybsze przy użyciu wskaźników. Czasami jest to możliwe tylko przy użyciu wskaźników.


1
Nie sądzę, aby stwierdzenie „czasami można używać tylko wskaźników” jest prawdziwe. Istnieją języki, które nie ujawniają wskaźników programistom, ale można ich używać do rozwiązywania problemów w tej samej przestrzeni.
Thomas Owens

Być może ... Nie znam wszystkich dostępnych języków, ale na pewno nie chciałbym napisać procedury, aby zastosować filtr splotowy do obrazu przy użyciu języka, który nie ma wskaźników.
Dave Nay

@ Thomas, podczas gdy masz rację, pytanie dotyczy c ++, który udostępnia wskaźniki programistom.
Jonathan Henson,

@Jathanathan Nawet jeśli możesz rozwiązać problem w języku, który nie ujawnia wskaźników, możesz także rozwiązać ten problem w języku, który ujawnia wskaźniki bez użycia wskaźników. To sprawia, że ​​pierwsze zdanie jest prawdziwe, ale drugie zdanie jest fałszywe - według mojej najlepszej wiedzy nie ma problemów, których nie można rozwiązać bez użycia wskaźników.
Thomas Owens

1
Przeciwnie, przetwarzanie obrazu często wymaga „arytmetyki współrzędnych” - powiedzmy, biorąc sinus i cosinus pod kątem i mnożąc go przez współrzędne xi y. W innych przypadkach potrzebna jest replikacja pikseli z zawijaniem lub poza granicami. W tym sensie arytmetyka wskaźników jest raczej nieodpowiednia.
rwong

1

Wskaźniki są używane w wielu językach programowania pod powierzchnią, nie denerwując użytkownika. C / C ++ po prostu daje ci do nich dostęp.

Kiedy ich używać: tak często, jak to możliwe, ponieważ kopiowanie danych jest nieefektywne. Kiedy ich nie używać: Gdy chcesz dwie kopie, które można zmienić osobno. (Co w zasadzie zakończy się kopiowaniem zawartości obiektu_1 do innego miejsca w pamięci i zwracaniem wskaźnika - tym razem wskazując na obiekt_2)


1

Wskaźniki są istotną częścią każdej implementacji struktury danych w C, a struktury danych są istotną częścią każdego nietrywialnego programu.

Jeśli chcesz dowiedzieć się, dlaczego wskaźniki są tak ważne, sugerowałbym zapoznanie się z listą połączoną i spróbowanie jej napisania bez użycia wskaźników. Nie postawiłem ci niemożliwego wyzwania (WSKAZÓWKA: wskaźniki są używane do wskazywania lokalizacji w pamięci, jak odwołujesz się do elementów w tablicach?).


+1 za wzmiankę o strukturach danych, użycie w algorytmach jest naturalną konsekwencją.
Matthieu M.,

0

Przykładem może być budowanie portfela zamówień z limitem.

na przykład kanał ITCH 4.1 ma pojęcie „zamień” zamówienia, w których ceny (a zatem i pierwszeństwo) mogą ulec zmianie. Chcesz móc odbierać zamówienia i przenosić je gdzie indziej. Wdrożenie podwójnie zakończonej kolejki ze wskaźnikami sprawia, że ​​operacja jest bardzo łatwa.


0

Przeglądając różne strony StackExchange, zdaję sobie sprawę, że zadawanie takich pytań jest raczej modne. Będę narażony na krytykę i negatywne opinie. To nie ma na celu trollowania ani płomienia, chcę jedynie pomóc, udzielając uczciwej oceny pytania.

Ta ocena jest następująca: To bardzo dziwne pytanie, które należy zadać programistowi C. Prawie wszystko, co mówi, to „nie znam C.” Jeśli przeanalizuję trochę bardziej i bardziej cynicznie, podpowiedź w następstwie tego „pytania”: „Czy jest jakiś skrót, który mogę wykorzystać, aby szybko i nagle zdobyć wiedzę doświadczonego programisty C, bez poświęcania czasu? do samodzielnego badania? ” Jakakolwiek „odpowiedź”, którą ktoś może udzielić, nie zastąpi zrobienia i wykonania zadania polegającego na zrozumieniu podstawowej koncepcji i jej wykorzystania.

Wydaje mi się, że bardziej konstruktywne jest uczenie się języka C z pierwszej ręki, niż korzystanie z Internetu i zadawanie tego ludziom pytań. Kiedy dobrze znasz C, nie będziesz zadawać sobie trudu z zadawaniem takich pytań, to będzie jak pytanie „jakie problemy najlepiej rozwiązać za pomocą szczoteczki do zębów?”


0

Mimo że wskaźniki naprawdę świecą podczas pracy z dużymi obiektami pamięci, nadal istnieje sposób, aby zrobić to samo bez nich.

Wskaźnik jest absolutnie niezbędny do tak zwanego programowania dynamicznego , kiedy nie wiesz, ile pamięci potrzebujesz, zanim program się uruchomi. W programowaniu dynamicznym możesz żądać fragmentów pamięci podczas wykonywania i umieszczać w nich własne dane - więc potrzebujesz wskaźnika lub referencji (tutaj różnica nie jest ważna), aby móc pracować z tymi fragmentami danych.

Tak długo, jak możesz zająć określoną pamięć w czasie wykonywania i umieścić swoje dane w nowo pozyskanej pamięci, możesz wykonać następujące czynności:

  1. Możesz mieć samorozszerzające się struktury danych. Są to struktury, które mogą się rozszerzać, żądając dodatkowej pamięci, o ile wyczerpią się ich pojemności. Kluczową właściwością każdej samorozszerzającej się struktury jest to, że składa się ona z małych bloków pamięci (zwanych węzłami, elementów listy itp. W zależności od struktury), a każdy blok zawiera odniesienia do innych bloków. Te „powiązane” struktury budują większość współczesnych typów danych: wykresy, drzewa, listy itp.

  2. Możesz programować za pomocą paradygmatu OOP (Object-Oriented Programming). Cały OOP opiera się na wykorzystaniu zmiennych nie bezpośrednich, ale odniesień do instancji klas (zwanych obiektami) i manipulowaniu nimi. Żadna pojedyncza instancja nie może istnieć bez wskaźników (nawet jeśli możliwe jest użycie klas tylko statycznych, nawet bez wskaźników, jest to raczej wyjątek).


0

Zabawne, właśnie odpowiedziałem na pytanie dotyczące C ++ i mówiłem o wskaźnikach.

Krótka wersja jest taka, że ​​NIGDY nie potrzebujesz wskaźników, chyba że 1) używana biblioteka zmusza cię 2) Potrzebujesz referencji zerowej.

Jeśli potrzebujesz tablicy, listy, łańcucha itp., Po prostu umieść ją na stosie i użyj obiektu stl. Zwracanie lub przekazywanie obiektów stl jest szybkie (niezaznaczony fakt), ponieważ mają wewnętrzny kod, który kopiuje wskaźnik zamiast obiektu i kopiuje dane tylko, jeśli do niego napiszesz. Jest to zwykły C ++, nawet nowy C ++ 11, który ułatwi pisarzom bibliotek.

Odpowiedź na twoje pytanie może być w tej części

Jeśli używasz wskaźnika, upewnij się, że spełnia jeden z tych dwóch warunków. 1) Podajesz dane wejściowe, które mogą być zerowalne. Przykładem jest opcjonalna nazwa pliku. 2) Jeśli chcesz oddać własność. Tak jak w przypadku przekazania lub zwrócenia wskaźnika, nie masz ŻADNYCH jego kopii ani użyć wskazanego wskaźnika

ptr=blah; func(ptr); //never use ptr again for here on out.

Ale nie używałem wskaźników ani inteligentnych wskaźników od bardzo dawna i profilowałem swoją aplikację. Działa bardzo szybko.

DODATKOWA UWAGA: Zauważam, że piszę własne struktury i przekazuję je. Jak to zrobić bez użycia wskaźników? To nie jest kontener STL, więc przechodzenie przez ref jest powolne. Zawsze ładuję moją listę danych / deques / map i tym podobne. Nie pamiętam zwracania żadnych obiektów, chyba że była to jakaś lista / mapa. Nawet sznurka. Spojrzałem na kod dla pojedynczych obiektów i zauważyłem, że robię coś takiego, { MyStruct v; func(v, someinput); ... } void func(MyStruct&v, const D&someinput) { fillV; }więc w zasadzie zwracam obiekty (wiele) lub wstępnie przydzielam / przekazuję referencję do wypełnienia (pojedynczy).

Teraz, jeśli piszesz, jesteś własnym deque, mapą itp., Musisz użyć wskaźników. Ale nie musisz. Pozwól STL i być może zwiększyć martwienie się o to. Musisz tylko napisać dane i rozwiązania. Nie pojemniki do przechowywania;)

Mam nadzieję, że teraz nigdy nie używasz wskaźników: D. Powodzenia w radzeniu sobie z bibliotekami, które do tego zmuszają


0

Wskaźniki są bardzo przydatne do pracy z urządzeniami zamapowanymi w pamięci. Możesz zdefiniować strukturę, która odzwierciedla (powiedzmy) rejestr kontrolny, a następnie przypisać go do adresu rzeczywistego rejestru kontrolnego w pamięci i bezpośrednio nim manipulować. Możesz także wskazać bezpośrednio bufor transferu na karcie lub układzie scalonym, jeśli MMU zamapowało go w przestrzeni pamięci systemowej.


0

Widzę wskaźniki jako mój palec wskazujący, używamy kilku rzeczy:

  1. kiedy ktoś prosi cię o coś, czego nie możesz zabrać, na przykład gdzie jest ulica, i tak, „wskażę” tę ulicę, i to właśnie robimy w przypadku argumentów przez odniesienie
  2. podczas liczenia lub przemierzania czegoś używalibyśmy tego samego palca i tak właśnie robimy w tablicach

proszę wybacz tę złą odpowiedź


0

Ponieważ twoje pytanie jest oznaczone jako C ++, odpowiem na twoje pytanie dotyczące tego języka.

W C ++ istnieje rozróżnienie między wskaźnikami a referencjami, dlatego istnieją dwa scenariusze, w których wskaźniki (lub inteligentne wskaźniki) są niezbędne do ułatwienia określonych zachowań. Mogą być używane w innych okolicznościach, jednak pytasz, jak „najlepiej z nich korzystać”, a we wszystkich innych okolicznościach istnieją lepsze alternatywy.

1. Polimorfizm

Wskaźnik klasy bazowej umożliwia wywołanie metody wirtualnej zależnej od typu obiektu, na który wskazuje wskaźnik.

2. Tworzenie trwałych obiektów

Wskaźniki są niezbędne podczas dynamicznego tworzenia obiektu (na stercie zamiast na stosie). Jest to konieczne, jeśli chcesz, aby żywotność tych obiektów była dłuższa niż zakres, w którym zostały utworzone.

Jeśli chodzi o „dobre projekty lub problemy do rozwiązania”, jak już powiedzieli inni, każdy nietrywialny projekt będzie wykorzystywał wskaźniki.


Może ktoś wolałby pokroić obiekt niż użyć wskaźnika. Szczęśliwego debugowania: D
deadalnix,
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.