Professional Documents
Culture Documents
Wskazniki
Wskazniki
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:
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!
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;
}
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;
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.