P-Programowanie

Wskaźniki

Ostatania modyfikacja: 28 września 2017, kategoria: C++

Wskaźniki są nieodłącznym elementem języka C. W języku C++ także są przydatne i korzystanie z nich ułatwia pracę, jednak w odróżnieniu do C wiele rzeczy da się osiągnąć bez ich użycia. Poprawne operowanie wskaźnikami znacznie przyśpiesza wydajność czasową programu, zmniejsza zużycie pamięci oraz skraca kod źródłowy.

Czym są wskaźniki?

Wskaźnik (ang. pointer) – typ zmiennej odpowiedzialnej za przechowywanie adresu do innej zmiennej (innego miejsca w pamięci) w obrębie naszej aplikacji.

Wskaźnik może wskazywać na jakąś zmienną, strukturę, tablicę a nawet funkcję. Oto podstawowe operatory niezbędne do operowania wskaźnikami:

*operator wyłuskania wartości zmiennej, na którą wskazuje wskaźnik (wyciąga wartość ze wskaźnika)
&operator pobrania adresu danej zmiennej, tablicy, struktury itp (pobiera adres zmiennej)

Po co są wskaźniki?

Wiele początkujących osób zastanawia się – po co istnieją wskaźniki C++? Zanim przeczytasz resztę artykułu, postaram Ci się to przybliżyć.

W języku C++ możemy do funkcji przekazać dowolną ilość parametrów. Modyfikując parametry w obrębie ciała funkcji oryginalne zmienne nie zmienią się. Przekazując zmienne do funkcji poprzez wartość (czyli w sposób standardowy), tworzymy wewnątrz funkcji ich kopię! Co z tego faktu wynika? Każda funkcja w C++ może zmodyfikować maksymalnie jedną zmienną, za pomocą wartości zwracanej return.

Korzystając z mojego talentu graficznego zobrazowałem Ci przekazywanie parametrów do funkcji poprzez wartość. Jesteśmy w stanie zmodyfikować wartość zmiennej a, ale nie jesteśmy w stanie zmienić innych zmiennych.

wskazniki C++

Co zrobić, aby jedną funkcją zmodyfikować 3 zmienne na raz? Nie można użyć rozkazu return 3 razy, ponieważ każda funkcja zwraca tylko jedną wartość. W tej sytuacji trzeba skorzystać ze wskaźników. Jeżeli przekażemy do funkcji jako jej argument wskaźnik, wtedy operacje na wskaźniku zmieniają zmienną oryginalną z poza ciała funkcji – nie operujemy na kopii zmiennej. Dzięki temu, nawet jeżeli funkcja jest typu void i nic nie zwraca, możemy modyfikować wiele zmiennych z poza ciała funkcji:

wskazniki C++

Jak używać wskaźników

Zmienna wskaźnikowa (czyli wskaźnik) poprzedzona jest gwiazdką (*) i przechowuje adres pamięci (a nie wartość) zmiennej , na którą wskazuje.

Deklarując wskaźnik postępujemy tak jak ze zwykłymi zmiennymi, jednak nazwę wskaźnika poprzedzamy gwiazdką. Uwaga! Gwiazdka przed nazwą wskaźnika nie ma związku z operatorem wyłuskania!

Tak wygląda tworzenie wskaźnika. Utworzone przez nas zmienne są puste. Zmienna telefon nie zawiera żadnej wartości a wskaźnik wsk nie wskazuje żadnej wartości.

Bardzo ważne jest aby nie korzystać ze wskaźnika który nie wskazuje na żadną zmienną! Prowadzi to zawsze do błędów i  niesie ze sobą nieprzewidziane konsekwencje!

Rozbudujmy powyższy przykład:

Za pomocą operatora pobrania adresu (&) pobraliśmy adres zmiennej telefon. Adres zmiennej został przypisany wskaźnikowi wsk. Pamiętaj że gwiazdka przed nazwą wskaźnika to nie operator wyłuskania! Chcąc wyświetlić wartość wskaźnika posłużymy się operatorem wyłuskania czyli gwiazdką (*).

Powyższy przykład wyświetli na ekranie wartość zmiennej telefon. Przed wyświetleniem wskaźnika został użyty operator wyłuskania. Pobiera on wartość zmiennej spod adresu ze zmiennej wskaźnikowej. Bez użycia operatora wyłuskania, została by wyświetlona wartość zmiennej wskaźnikowej wsk czyli adres zmiennej telefon:

Używając sizeof na wskaźniku nie mozna zapomnieć o operatorze wyłuskania. W przeciwnym wypadku wynikiem zawsze będzie ta sama wartość, ponieważ każda zmienna wskaźnikowa ma ten sam rozmiar w pamięci.

Rzeczą normalną są wskaźniki do wskaźników. Przy ich wyświetlaniu ważna jest ilość operatorów wyłuskania. Jeżeli do wskaźnika drugiego stopnia użyjemy jednego operatora wyłuskania wtedy otrzymamy adres wskaźnika a nie wartość zmiennej na którą wskazują.

Wskaźniki poddają się rzutowaniu typów. W dowolnym momencie możemy zmienić typ zmiennej wskaźnikowej np. z int na long.

Wartość wskaźnika (zmiennej na którą wskazuje) możemy modyfikować bez uzycia nazwy zmiennej:

Totalnie błędna byłaby sytuacja, kiedy chcemy nadać zmiennej wskaźnikowej określoną wartość, bez wcześniejszego przypisania wskaźnika do określonej zmiennej:

Wskaźniki generyczne

Zwykłe wskaźniki używane są aby wskazywać na określony obiekt określonego typu, np zmienna tekstowa, zmienna liczbowa czy struktura. Wskaźniki generyczne dają nam możliwość wskazywania na określony obszar pamięci aplikacji bez ustalania typu wskaźnika. Oznacza to że wskaźnik generyczny będzie miał typ void.

Przed wykonaniem operacji na wskaźniku generycznym i przed jego wyświetleniem, musimy określić wielkość na jaką wskazuje. W powyższym przykładzie jest to int:

Wskaźniki i funkcje

W języku C++ przekazujemy argumenty do funkcji poprzez tzw. przekazywanie przez wartość. W języku C oraz C++ możemy przekazywać wartości do funkcji poprzez przekazywanie przez wskaźnik. Wskaźnik będzie wtedy argumentem funkcji.

Należy zwrócić uwagę, że do funkcji której argumentem jest wskaźnik, przekazujemy adres zmiennej za pomocą operatora pobrania adresu (&) a nie samą wartość.

Więcej o przekazywaniu wskaźników do funkcji dowiesz się w artykule: przekazywanie argumentów do funkcji C++.

Wskaźniki i tablice

Tablice są sciśle związane ze wskaźnikami. Nazwa tablicy to wskaźnik na jej pierwszy element. Oznacza to że możemy wyświetlić pierwszy element tablicy umieszczając operator wyłuskania przed jej nazwą:

Możemy także tworzyć wskaźniki do określonych elementów tablicy:

Stringi w C to tablica charów. Tworząc wskaźnik będzie to już wskaźnik do wskaźnika a nie zwykły wskaźnik.

Często spotykane są także tablice wskaźników. Ich tworzenie jest proste i analogiczne do innych typów zmiennych:

Dynamiczna alokacja pamięci

Często podczas działania programu pojawia się problem dynamicznego zarządzania pamięcią. Problem fajnie można zaprezentować na przykładzie tablicy o różnej ilości elementów. Z pomocą przychodzą wskaźniki oraz operatory new i delete.

Najpierw tworzymy wskaźnik określonego typu a następnie zmienną o dowolnej wielkości. Potem przydzielamy wskaźnikowi adres nowej zmiennej utworzonej na stercie:

W C odpowiednikami new i delete są operatory malloc i free. Można używać operatorów C jak i C++, należy jednak pamiętać aby ich nie mieszać! (malloc + delete oraz new + free)!

Dynamiczne tworzenie zmiennych daje nam duże możliwości. Ważne jest aby usuwać je kiedy są niepotrzebne. W przeciwnym wypadku istnieje szansa wystąpienia błędu memory leak.

Użytkownik król napisał:

25 stycznia 2013


Witam
Jeden z najlepszych artykulow o wskaznikach jakie znalazlem- skrupulatnie pokazane zaleznosci male szczegoliki pokazane w ktorych mozna by sie bylo pogubic

Użytkownik Ignac napisał:

23 kwietnia 2013


Bardzo przydatna stroka wszystko skrupulatnie i z szczegółami wyjaśnine napewno polecę ją moim znajomym programistom gdyż jest ona wykonana perfekcyjnie i naprawde bardzo dobrze ponieważ bardzo dobrze mi się z niej korzysta i jest naprawde dobra poleecam ją wszystkim i w głównej mierze namawiam do tego aby polecić ją swoim znajomym tak jak ja to zrobie. pozdrawiam i zycze miłego programowania z tą świetnie wykonana stronką!

Użytkownik Damis napisał:

13 maja 2013


Bardzo świetna strona!!! Moja pani od programowania aż mnie pochwaliła że świetnie przedstawiłem wskaźniki na lekcji. Polecam z całego serca wszystkim programistą którzy szukają informacji o wskaźnikach. Jeśli jesteś programista i nie wiesz nic o wskaźnikach i trafiłeś na tą stronę to masz już 5 u nauczycielki! POLECAM DAMIŚ

Użytkownik Tomek napisał:

06 lipca 2013


Fajny artykuł.

Użytkownik Bartek napisał:

09 września 2013


int *wsk[3];
wsk[1] = &liczba1;
wsk[2] = &liczba2;
wsk[3] = &liczba3;

na pewno dobrze? ;]

Użytkownik Karol napisał:

09 września 2013


Na 100% :)

Użytkownik Paweł napisał:

12 września 2013


A co z numerowaniem indeksu tablicy od 0 ?

Użytkownik Jakub napisał:

12 września 2013


Pytanie poczatkujacego:

char *tekst = „programowanie” ;

dlaczego przy:

cout << *tekst ;

zwraca mi jako wartosc 1 literke stringu zamiast calego ciagu

a przy:

cout << tekst ;

zwraca caly ciag zamiast adresu?

Użytkownik Karol napisał:

13 września 2013


Haha! Bartek i Paweł macie racje, totalna gafa. Dzięki za uwagę do wpisu.

@Jakub
W jakim języku programujesz? Jeżeli jest to C++ to zacznij używać biblioteki „std::string”. Używanie C-stringów w języku C++ niepotrzebnie utrudnia sprawę.

„Char*” to nie jest typ przechowujący napisy, ani też żadna tablica na znaki. Jest to wskaźnik wskazujący na pierwszą literę w pamięci. Dlatego też normalne jest wypisywanie przez program pierwszej litery.

Dla wskaźnika „char*” operator „<<" został przeciążony (inaczej zaimplementowany niż dla pozostałych typów wskaźnikowych). "Char*" jest C-stringiem więc strumień wyjścia spodziewa się ciągu znaków. Dlatego w drugim wypadku nie wyświetla się adres. Jeżeli chcesz wyświetlić adres, rzutuj na (void*) lub chociaż (int). Drugim rozwiązaniem będzie używanie 'sprintf', czyli funkcji, która została zaprojektowana dla C-stringów. Znów warto wspomnieć, nie ma sensu w C++ bawić się C-stringami, lepiej operować na std::string. Poczytaj o wskaźnikach oraz C-stringach. Tworząc wskaźniki "char*" powinno się używać "const", aby nie pogubić napisów. Wtedy stały będzie adres wskaźnika, a nie tekst na który wskazuje. W niedługim czasie postaram się rozbudować ten artykuł opisując C-stringa.

Użytkownik Jakub napisał:

13 września 2013


Karol – dzieki za szybka i wyczerpujaca odpowiedz, powoli ukladam sobie to w glowie, to mi pomoglo, dzieki :)

Użytkownik Marcin napisał:

27 stycznia 2014


Dobre przykłady, pomogłeś zrozumieć wskaźniki lepiej niż dotychczasowe wykłady, które przeczytałem. Mam propozycję rozwinięcia wyświetlanych kombinacji, tak jak to sobie zrobiłem na własny użytek na podstawie tutejszego, czyli wrzuciłem na cout: wsk, &wsk, *wsk, wsk2, *wsk2, **wsk2, &wsk2, &telefon. Kiedy rozrysowałem graficznie trzy komórki pamięci z podziałem na blok adresu i zawierający się w nim blok przechowywanej wartości, dodałem operatory wydobycia w postaci dodatkowych bloczków, dalej połączyłem wszystko strzałkami na podstawie wydruku cout na konsoli to wreszcie wszystko stało się jasne i czytelne. Największą trudnością jest tutaj dobre skojarzenie operatorów czyli &, *, ** (i więcej), no i tego przypadku bez operatora z fizyczną strukturą pamięci.

Rozumiem, że tylko przy deklaracji gwiazdka jest informacją dla kompilatora i dla człowieka, żeby zmienną traktować jako typ wskaźnikowy. W pozostałych przypadkach jest to już zawsze wydobycie wartości wskazywanej komórki pamięci (np. zmiennej). Pytam bo jest to najtrudniejsze do zrozumienia w tym całym znacznikowym porządku i powiem szczerze, że bez rysunku i tutejszych przykładów nie byłem w stanie tego zrozumieć. Pewnie można było to bardziej rozróżnić ale ze względu na prostotę składni tak to rozwiązano?

Użytkownik Ola napisał:

18 marca 2015


Wreszcie porządnie wyjaśnione wskaźniki !

Użytkownik Fanka napisał:

26 listopada 2015


Napisz funkcję, która przyjmuje jako parametr wejściowy wskaźnik typu char i zwraca go.
Pooomoocyyyyy!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
Jęstem zdesperowaną nastolatką, ktura ma wielkiii problem ; ;
Słyszałam, że jest pan fajniusim programistą.
Proszę o przeekspresowe napisanie programu :*

Użytkownik WOJTEK napisał:

09 lutego 2016


Zwalniając pamięć z tablicy robimy to nie:
delete zmienna;

tylko

delete [] zmienna;

taka drobna korekta ;-)

Użytkownik Monika napisał:

14 stycznia 2018


Witam, chciałabym się dowiedzieć czym różni się zapis danych do tablicy od zapisu danych do pliku w języku c++?

Użytkownik Karol napisał:

12 lutego 2018


Świetnie wszystko wytłumaczone :) Nawet tak stary człowiek jak ja (23 lata) uczący się dopiero programowania wszystko rozumie :)
Pozdrawiam imiennika

Użytkownik Paweł napisał:

15 lutego 2018


Super wytłumaczone. Lepiej niż w niejednym podręczniku. Dzięki i pozdrawiam!

Użytkownik miroslaw napisał:

16 kwietnia 2018


witam.
fajnie to wytlumaczone ale mam dylemat przy przerabianiu ksiazki c++ Pana Grebosza.

zadanie
Napisz funkcje ktora zarezerwuje 10 elementowa tablice, a jej adres przekaze wskaznikowi o nazwie linijka .
Ma to byc tablica, w ktorej mozna przechowywac wskazniki do tablic znakow.
nastepnie za pomoca petli for dla kazdego z 10 elementow tej tablicy linijka:
-zarezerwoj operatorem new tablice 80 znakow
-jej adres wpisz do kolejnego elementu tablicy linijka
-w tej 80 znakowej tablicy znakow umiesc c string „to jest linijka nr n”- gdzie n jest kolejnym numerem linijki(0-9)…

problem w tym ze program mi nie dziala jak przekazuje tablice do wskaznika linijka

Prosze o podpowiedz jezeli znajdziesz chwile
Dzikuje i Pozdrawiam

Zachęcam Cię do zostawienia komentarza!

Ilość znaków: 0