1 Wskaźniki Wskaźnik (ang. pointer) jest obiektem (zmienną) przechowującym adres pamięci. Definiowanie wskaźników: typ *nazwa wskaznika; np.: int *wsk na x;, double *xxx;, char *znak;. Aby można było pracować ze wskaźnikiem, trzeba najpierw go ustawić, tak aby pokazywał na coś sensownego, np.: int *wsk; //definicja wskaźnika do obiektów typu int int x = 4; //definicja zwykłego obiektu typu int wsk = &x; //ustawienie wskaźnika o nazwie wsk tak, //by pokazywał na obiekt x cout << *wsk; //wyświetlony zostaje obiekt, na który pokazuje wskaźnik wsk Oprócz tego, że wskaźnik zawiera adres miejsca w pamięci, gdzie znajduje się jakiś konkretny obiekt (na który został ustawiony wcześniej wskaźnik), to dodatkowo wskaźnik zawiera wiedzę jakiego typu jest ten obiekt, na który pokazuje. Dlatego wskaźnik służący do pokazywania na obiekty jednego typu nie nadaje się do pokazywania na obiekty innego typu. Wobec tego, podczas kompilacji takiego kawałka kody, pojawi się komunikat o błędzie: double *wsk; int x = 4; wsk = &x; //błąd!!! Próba wstawienia do wskaźnika do pokazywania //na obiekty double, adresu obiektu typu int Wiedza, jakiego typu jest obiekt, na który wskazuje (pokazuje) wskaźnik, jest niezbędna do tego aby móc poprawnie zinterpretować ten obiekt oraz (w przypadku, gdy mamy do czynienia z tablicą) ewentualnie poruszać się po sąsiednich obiektach. Zdarzają się jednak przypadki, choć bardzo rzadko, że wiedza ta nie jest nam potrzebna, a wręcz uniemożliwia nam działanie (np. gdy do jakiś starszych funkcji bibliotecznych trzeba wysłać adres jakiegoś obiektu w pamięci. Funkcję taką interesuje jedynie gdzie ten obiekt jest, a nie co w nim jest). W takich przypadkach możemy użyć wskaźnika, który pozbawiony jest wiedzy o typie, czyli wskaźnik bez typu, a mianowicie void. Taki wskaźnik co prawda może pokazywać na obiekty dowolnego typu (czyli zawierać adresy miejsc gdzie znajdują się obiekty dowolnego typu). Ale logiczne jest, że skoro taki wskaźnik jest pozbawiony wiedzy o tym na co pokazuje, nie może wyświetlać tego na co pokazuje i nie może ewentualnie poruszać sie po sąsiednich obiektach. 1.1 Główne zastosowania wskaźników ulepszenie pracy z tablicami funkcje mogące zmieniać wartości przesyłanych do nich argumentów dostęp do specjalnych komórek w pamięci rezerwacja obszarów w pamięci 1
1.1.1 Ulepszenie pracy z tablicami Przykład stosowania wskaźników do pokazywania na elementy tablic: int *wskaznik; //definicja wskaźnika int tablica[20]; //definicja tablicy 20-elementowej wskaznik = &tablica[4]; //ustawienie wskaźnika na element tablicy z indeksem 4 W instrukcji powyżej ustawiliśmy wskaźnik na element tablicy z indeksem 4 (piąty element licząc od zera, bo pamiętam, że elementy tablicy numerowane sa od zera!). W przypadku gdy chcemy ustawić wskaźnik na zerowy element tj. element początkowy tablicy, to przez analogię piszemy: wskaznik = &tablica[0]; //ustawienie wskaźnika na początkowy element tablicy Ale pamiętając, że: NAZWA TABLICY JEST RÓWNOCZEŚNIE ADRESEM ZEROWEGO JEJ ELE- MENTU. Stąd wynika prosty wniosek, że to samo co powyżej możemy uzyskać pisząc po prostu: wskaznik = tablica; //ustawienie wskaźnika na początkowy element tablicy Załóżmy teraz, ze po ustawieniu wskaźnika na element początkowy, chcemy go przesunąć o jedno miejsce w prawo, czyli na element o indeksie 1. Oczywiście można to napisać po prostu tak: wskaznik = &tablica[1]; //ustawienie wskaźnika na element tablicy o indeksie 1 ale skoro wiemy, że nazwa tablicy jest jednocześnie adresem jej zerowego (początkowego) elementu, to możemy to przesunięcie napisać następująco: wskaznik = tablica + 1; //ustawienie wskaźnika na element tablicy o indeksie 1 Pamiętając, że początkowo ustawiliśmy sobie wskażnik na zerowy element wskaznik = tablica;, to ostatnie wyrażenie jest w tym przypadku różnoznaczne z takim zapisem: wskaznik = wskaznik + 1; //ustawienie wskaźnika na element tablicy o indeksie 1 A to z kolei, jak już wiemy, jest zawsze równoznaczne, takiemu zapisowi: wskaznik++; Naturalnie, nie zawsze pojawia się potrzeba przesuwania wskaźnika o jedno miejsce w prawo. W sytuacji gdy chcemy przesuwać wskaźnikiem o n elementów w prawo wystarczy napisać: wskaznik += n; co jest równoznaczne z: wskaznik = wskaznik + n; Oczywiście, mozemy się poruszać w każdą stronę po elemenach tablicy, żeby przesuwać się w lewą str (czyli w stronę obniżania się wartości indeksów) znak + zastępujemy znakiem. Przy przesuwaniu się w taki sposób po elementach tablicy istnieje jednak pewna pułapka, możemy np. ustawić sobie wskaźnik na element tablicy, który tak naprawdę nie istnieje. W takiej sytuacji kompilator najczęściej nie pokaże nam błędu! Na przykład: int *wskaznik; //definicja wskaźnika int tablica[5]; //definicja tablicy pięcioelementowej wskaznik = &tablica[4]; //ustawienie wskaźnika na element tablicy z indeksem 4 wskaznik += 2; //przesunięcie wskaźnika o 2 pozycje w prawo, //czyli ustawienie go na element o indeksie 6 //a element taki nie istnieje!!!! 2
Kolejny przykład: wskaznik = tablica; //ustawienie wskaźnika na początkowy element tablicy wskaznik--; //przesuniecie wskaźnika o 1 element w lewo, //czyli na element o indeksie -1, a element taki nie istnieje!!!! Dodanie do wskaźnika jakieś liczby całkowitej powoduje, że pokazuje on tyleż elementów tablicy dalej. Niezależnie od tego, jakie są te elementy. Przykład zastosowania wskaźnika w pracy z tablicą: # include <iostream> # include <cmath> using namespace std; int main() int *wi; double *wd; int tabint[10] = 0, 1, 2, 3, 4, 5, 6, 7, 8, 9; double tabdoub[10]; wd = tabdoub; for(int i = 0 ; i < 10 ; i++) *(wd++) = i / 100.0; wi = tabint; wd = tabdoub; for(int i = 0 ; i < 10 ; i++) cout << i << "-> \t" << *wi << "\t\t" << *wd << endl; 3
wi++; wd++; wi = &tabint[3]; wd = tabdoub + 5; for(int i = 0 ; i < 4 ; i++) *(wi++) = -111; *(wd++) = -555.5; cout << "tresc tablicy po wstawieniu nowych wartosci: \n"; wi = tabint; wd = tabdoub; for(int i = 0 ; i < 10 ; i++) cout << i << "-> \t" << *(wi++) << "\t\t" << *(wd++) << endl; system ("Pause"); return 0; 0-> 0------>0 1-> 1------>0.01 2-> 2------>0.02 3-> 3------>0.03 4-> 4------>0.04 5-> 5------>0.05 6-> 6------>0.06 7-> 7------>0.07 8-> 8------>0.08 9-> 9------>0.09 tresc tablicy po wstawieniu nowych wartosci: 0-> 0------>0 1-> 1------>0.01 2-> 2------>0.02 3-> -111------>0.03 4-> -111------>0.04 5-> -111------>-555.5 6-> -111------>-555.5 7-> 7------>-555.5 8-> 8------>-555.5 9-> 9------>0.09 Dozwolone operacje arytmetyczne na wskaźnikach to: 4
dodawanie i odejmowanie od nich liczb naturalnych - przesuwanie wskaźników odejmowanie dwóch wskaźników pokazujących na tę samą tablicę Odejmowanie od siebie dwóch wskaźników pokazujących na różne elementy tej samej tablicy, daje w rezultacie liczbę dzielących je elementów tablicy. Liczba ta może byc dodatnia lub ujemna. Wskaźniki można ze sobą porównywać, używając operatorów: ==! = < > <= >= Jeżeli dwa wskaźniki pokazuja na jakieś elementy tej samej tablicy, t wskaźnik, który jest mniejszy, pokazuje na obiekt o mniejszym indeksie. 1.1.2 Zastosowanie wskaźników w argumentach funkcji Przykład działania wskaźników w argumentach funkcji: # include <iostream> # include <cmath> using namespace std; float zwiekszacz (float *wsk); float funk_zwykla (float argument); //*************************************************************** int main () float m1, m2, n1, n2, *wskazn; cout << "podaj dwie liczby \n"; cin >> m1 >> m2; wskazn = &m1; n1 = zwiekszacz(&m1); n2 = funk_zwykla(m2); cout << "teraz pierwsza liczba wynosi: " << m1 << " a wartosc funkcji zwiekszacz wynosi: " << n1 << endl; cout << "po kolejnym wywolaniu funkcji jej wartosc wynosi: " << zwiekszacz(&m1) << endl; cout << "teraz druga liczba wynosi: " << m2 << " a wartosc funkcji zwyklej wynosi: " << n2 << endl; cout << "po kolejnym wywolaniu funkcji jej wartosc wynosi: " << funk_zwykla(m2) << endl; system ("pause"); return 0; float zwiekszacz (float *wsk) *wsk += 100; 5
return *wsk/100; float funk_zwykla (float argument) argument += 100; return argument/100; 6