O Wskaźnikach.

Transkrypt

O Wskaźnikach.
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?
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.
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:
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!
int telefon; //zmienna liczbowa
int *wsk;
//zmienna wskaźnikowa typu liczbowego
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:
int telefon = 12345; //zmienna liczbowa
int *wsk = &telefon; //wskaźnik wsk zawiera adres zmiennej telefon
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ą (*).
#include <iostream>
using namespace std;
int main()
{
int telefon = 12345; //zmienna liczbowa
int *wsk = &telefon; //wskaźnik wsk zawiera adres zmiennej telefon
cout << *wsk << endl;
return 0;
}
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:
int telefon = 12345;
//zmienna liczbowa
int *wsk = &telefon;
//przypisanie wskaźnikowi adresu zmiennej telefon
cout << *wsk << endl;
//wyświetlenie wyłuskanej wartości wskaźnika (12345)
cout << wsk << endl;
//wyświetlenie adresu zmiennej telefon
cout << &wsk << endl;
//wyświetlenie adresu wskaźnika
cout << &telefon << endl; //wyświetlenie adresu zmiennej telefon
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ą.
int telefon = 12345; //zmienna liczbowa
int *wsk = &telefon; //wskaźnik wsk zawiera adres zmiennej telefon
int **wsk2 = &wsk;
//wskaźnik wsk2 zawiera adres wskaźnika wsk
cout << **wsk2 << endl;
int telefon = 12345; //zmienna liczbowa
int *wsk = &telefon; //wskaźnik wsk zawiera adres zmiennej telefon
int **wsk2 = &wsk;
//wskaźnik wsk2 zawiera adres wskaźnika wsk
cout << **wsk2 << endl;
Wartość wskaźnika (zmiennej na którą wskazuje) możemy modyfikować bez użycia nazwy
zmiennej:
int telefon = 12345; //zmienna liczbowa
int *wsk = &telefon; //wskaźnik wsk zawiera adres zmiennej telefon
*wsk = 999;
cout << *wsk << endl;
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:
int *wsk;
*wsk = 999; //źle!!
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.
#include <iostream>
using namespace std;
// funkcja przyjmuje jako argument wskaźnik
void zwieksz_liczbe (int *liczba);
int main()
{
int numerek = 5;
int *wsk = &numerek;
zwieksz_liczbe(wsk); //przekazujemy wskaźnik (bez operatorów)
cout << numerek << endl;
zwieksz_liczbe(&numerek); //przekazujemy bezpośrednio adres zmiennej (operator &)
cout << numerek << endl;
return 0;
}
void zwieksz_liczbe (int *liczba)
{
*liczba+= 5;
}
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ść.
Wskaźniki i tablice
Tablice są ściś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ą:
int tablica[5] = {1, 2, 3, 4, 5};
cout << *tablica << endl;
Możemy także tworzyć wskaźniki do określonych elementów tablicy:
int tablica[5] = {1, 2, 3, 4, 5};
int *wsk = &tablica[3];
cout << *wsk << endl;
int tablica[5] = {1, 2, 3, 4, 5};
int *wsk = &tablica[3];
cout << *wsk << endl;
Często spotykane są także tablice wskaźników. Ich tworzenie jest proste i analogiczne do innych
typów zmiennych:
#include <iostream>
using namespace std;
int main()
{
int liczba1 = 1, liczba2 = 2, liczba3 = 3;
int *wsk[3];
wsk[0] = &liczba1;
wsk[1] = &liczba2;
wsk[2] = &liczba3;
cout << *wsk[1] << endl;
return 0;
}
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:
#include <iostream>
using namespace std;
int main()
{
int *zmienna;
zmienna = new int[3];
zmienna[0] = 111;
cout << *zmienna << endl;
delete zmienna;
return 0;
}
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.

Podobne dokumenty