Cześć, chcę zapytać o wskaźniki. Wiem jak się je tworzy, znam ich ogóle założenia jednak nie bardzo wiem, kiedy dokładnie należy ich użyć? Po co ich w ogóle używać? Może mi ktoś to wyjaśnić? Nie bardzo mogę zrozumieć funkcję, jaką pełnią wskaźniki w programowaniu.
Jak masz np. tablice 2-megabajtowa to nie bedziesz wszystkiego kopiowal tylko w argumencie podasz jej adres.
Jak chcesz zmodyfikowac cos zeby na zewnatrz to bylo widoczne no to bez wskaznika tez moze byc ciezko.
No i czesto jest tak, ze to co pod wskaznikiem to zyje na stercie a nie stosie.
To, co napisał @stivens to kwestia optymalizacji. Wskaźniki są po to, żeby programista decydował jak korzystać z pamięci. Przykładem jest odczytywanie plików wave. Są to duże pliki, więc:
- kopiowanie ich między funkcjami zajmowałoby dużo czasu,
- w momencie pisania kodu nie wiemy ile bitów ma próbka, więc musimy odczytać do z nagłówka i odpowiednio potraktować dane - zrobić brzydką konwersję z
void*
na typ np.short*
albochar*
.
W nowszych standardach C++ nie ma potrzeby pisania typów z gwiazdką, bo są od tego klasy. Dlatego w C++ kiedyś może zabraknąć jawnej deklaracji wskaźników na rzecz klas w stylu unique_ptr
.
Są 4 podstawowe przyczyny dla używania wskaźników. Po pierwsze, kiedy chcemy zmienić wartość poza funkcją. Jeśli masz funkcję:
int foo(int x) {
x+=4;
return x;
}
To wywołując funkcję: foo(z)
to wartość zmiennej z
się nie zmieni. Jesli chcemy coś zmienić nazewnątrz funkcji, musimy użyć wskaźnika. Nie jest to szczególnie dobra praktyka, ale często, ze względów wydajnościowych tak się robi. W szczególności gdy mamy funkcję, która wypełnia jakąś strukturę, to zaalokowanie pamięci jest po stronie wywołującego, a funkcja tylko zapisuje wartości. Weźmy na przykład wywołanie:
int x;
scanf("%d", &x);
Żeby ustawić wartość x
, scanf
musi dostać wskaźnik. inny przykład z POSIXowego API:
struct stat sb;
if(stat(argv[i], &sb) == -1) {
printf("nie mozna sprawdzić pliku %s\n", argv[i]);
exit(1);
}
Teoretycznie funkcja stat
mogłaby zwrócić wartość typu stat sb
i w nowoczesnym C, nawet byłoby to wydajne. Jednak w czasach gdy to API było tworzone nie koniecznie. Bez optymalizacji, ten sam obszar pamięci byłby zaalokowany 2 razy (w funkcji stat i poza nią) i raz skopiowany z jednego do drugiego. Natomiast alokowanie dynamiczne wewnątrz byłoby mniej wydajne i podatne na błędy. Dlatego stosuje się zwracanie wartości przez wskaźnik. Przy okazji, dzięki temu można zwrócić dwie wartości na raz, bo funkcja sama w sobie zwraca kod błedu, po którym określamy czy funkcja się powiodła.
Po drugie, czasem, jak @stivens pisze, chcemy odnieść się do kawałka pamięci bez kopiowania go. W szczególności, bez wskaźników nie dałoby się zaimplementować listy (podobnie z drzewami), bo jej istotą jest to, że każdy element jest odrębnym kawałkiem pamięci i wszystkie są tylko powiązane wskaźnikami.
Po trzecie, tablice to w gruncie rzeczy wskaźniki do większych kawałków pamięci.
Wreszcie po czwarte, Każda zmienna ma swoje miejsce w pamięci. Gdy alokujemy na stosie (zmienne lokalne) czy w sekcji danych (zmienne globalne) adres jest znany już w czasie linkowania. Jednak nie zawsze one wystarczą. Często musimy alokować pamięć dynamicznie (funkcja malloc()
). Wówczas nie ma innego sposobu, żeby odnieść się do zawartości pamięci, jak tylko przez wskaźnik.