Wskaźniki nie są konieczne, ale dają językowi siłę i elastyczność są języki w których nie używa się wskaźników typ wskaźnikowy typ pochodny: typ nw; /* definicja zmiennej nw typu typ */ typ *w_nw; /* definicja wskaźnika w_nw do typu typ */ wskaźnik zmienna która zawiera adres innej zmiennej wskaźnik niesie dwojakiego rodzaju informację: o adres obiektu, który wskazuje (gdzie) o rodzaj obiektu, który wskazuje (co) rola operatora odwołania pośredniego (wyłuskiwania) Główne obszary zastosowanie wskaźników w funkcjach, które mogą zmieniać wartość wskazywanych argumentów usprawnienie pracy z tablicami dostęp do specjalnych komórek pamięci rezerwacja obszarów pamięci (dynamiczna alokacja pamięci)
Funkcje zmieniające wartość wskazywanych argumentów zwykłe przekazanie argumentów przez wartość przekazujemy kopię zmiennej; zmiany dokonane wewnątrz funkcji nie przenoszą się na oryginał inne rozwiązanie zmienne globalne; problem niejawne (nieoczywiste) powiązania między danymi najlepsze rozwiązanie użycie wskaźników zasada: parametr aktualny adres obiektu; parametr formalny wskaźnik; zmiany poprzez operator odwołania pośredniego Tablice a wskaźniki bardzo bliski związek między tablicami a wskaźnikami każda operacja wykonana przez indeksowanie tablicy można wykonać przez działanie na wskaźnikach (wersja wskaźnikowa zwykle szybsza) int a[10]; /* rezerwacja tablicy o 10 elem. typu int */ int *pa; /* pa wskaźnik do pokazywania int */ pa a[0] a[1] a[2] a[9]
Po przypisaniu: pa=&a[0]; /* lub pa=a */ pa a[0] a[1] a[2] a[9] kluczowy punkt: jeżeli pa wskazuje na pewien element tablicy, to pa+1 wskazuje na następny element tablicy (bez względu na rodzaj tablicy) pa pa+1 pa+2 a[0] a[1] a[2] a[9] pa wskazuje na a[0] pa+k wskazuje na a[k] *pa == a[0] *(pa+k) == a[k] Wniosek: wyrażenie postaci tablica i indeks jest równoważne wyrażeniu postaci: wskaźnik i przesunięcie a stała, adres początku tablicy; dlatego a[k] == *(a+k) różnica pomiędzy wskaźnikiem a nazwą tablicy a to nie jest l-wartość; wyrażenia a++ niedozwolone
#include <iostream> using namespace std; int main() int a[5]=1, 2, 3, 4, 5; int w1, w2, w3, s, k, *pa; /* metoda tablica + indeks */ for (s=0, k=0; k<5; k++) s += a[k]; w1=s; /* metoda nazwa tablicy + przesuniecie */ for (s=0, k=0; k<5; k++) s += *(a+k); w2=s; /* metoda wskaźnik + przesuniecie */ pa=a; for (s=0, k=0; k<5; k++) s += *pa++; w3=s; cout<< w1= << w1 << w2= << w2 << w3= << w3 << endl; w1=15 w2=15 w3=15 Arytmetyka wskaźników dodawanie/odejmowanie liczb całkowitych do/od wskaźnika o wskaźnik + liczba całkowita k przesuwa wskaźnik o k pozycji wzdłuż tablicy o wskaźnik - liczba całkowita k cofa wskaźnik o k pozycji wzdłuż tablicy gdy p1, p2 dwa wskaźniki pokazujące elementy tej samej tablicy to p1-p2 ilość pozycji w tablicy o które różnią się elementy wskazywane przez oba wskaźniki
inne operacje na wskaźnikach o porównywanie wskaźników między sobą; dozwolone operacje: ==,!= czy obiekty wskazywane są tożsame, czy też różne <, >, <=, >= - dla wskaźników wskazujących elementy tej samej tablicy <, >, <=, >= - dla wskaźników nie wskazujących elementów tej samej tablicy, ale tego samego typu sens: porównanie położenia obiektów w pamięci porównanie wskaźnika z adresem 0 o oparte na założeniu, że adres 0 nie może być adresem żadnego obiektu w C/C++ o gdy dostaniemy 0 jako wartość wskaźnika zwracanego przez pewien proces, to oznaczało to, że ten proces zakończył się niepowodzeniem metoda sygnalizacji błędu o konwencja gdy wskaźnik 0 nic z nim nie rób Równoważność wskaźnik tablica: jak to działa wewnątrz funkcji? Jak przekazywać i odbierać tablice jako argumenty funkcji?
Przykład funkcja konwertująca temperatury: void konwertuj_t(float t[ ], int ile) /* wersja tablicowa */ int k; for (k=0; k<ile; k++) t[k]=5.0*(t[k] 32.0)/9.0; main() int m; float temp[101]; for (m=0; m<101; m++) temp[m]=m; konwertuj_t(temp, 101); Wersja wskaźnikowa funkcji konwertuj_t: void konwertuj_t(float *t, int ile) /* wersja wskaźnikowa */ int k; for (k=0; k<ile; k++, t++) *t =5.0*(*t 32.0)/9.0; adres tablicy odbieramy jako wskaźnik przesuwamy wskaźnik wzdłuż obszaru pamięci zajmowanego przez tablicę tak, by w obliczeniach wziąć odpowiedni element przewaga wersji wskaźnikowej program nie oblicza adresów odpowiadających poszczególnym indeksom (tylko przesuwa wskaźnik) możliwa wersja mieszana odbieramy adres jako wskaźnik, używamy jak tablicy
void konwertuj_t(float *t, int ile) /* wersja mieszana */ int k; for (k=0; k<ile; k++) t[k]=5.0*(t[k] 32.0)/9.0; Funkcje znakowe wersje wskaźnikowe Przykład 1 kopiowanie stringów: a. wersja tablicowa: void strcpy(char kopia[ ], char wzor[ ]) int k=0; while (kopia[k]=wzor[k]) k++; b. wersja wskaźnikowa void strcpy(char *kopia, char *wzor) while (*kopia++ = *wzor++) ; Przykład 2 sumowanie stringów (strcat) Do końca stringu s1 dodaje string s2, zwraca wskaźnik do stringu zawierającego sumę obu.
#include <iostream> using namespace std; char *strcat(char *suma, char* zrodlo) char *start=suma; /* znalezenie konca stringu suma */ while (*suma++) ; suma--; /* skopiowanie stringu zrodlo */ while (*suma++ = *zrodlo++) ; return start; int main() char sss[80]="ala ma "; cout << sss << strcat(sss,"kota!!!\n"); return 0; Ala ma kota!!! Ala ma kota!!! Dostęp do wyróżnionych obszarów pamięci stosowane, gdy chcemy sięgnąć do konkretnej komórki pamięci (np. o adresie 93960) ważne dla komórek wyróżnionych (np. sprzężonych z zewnętrznym miernikiem temperatury przy normalnej definicji zmiennej nie mamy wpływ na alokację pamięci rozwiązanie użycie wskaźnika: float *p; p= (float*) 93960; cout << Aktualna temperatura wynosi: << *p << endl;
Dynamiczna alokacja pamięci normalna (statyczna) definicja tablicy nie możemy określić rozmiaru tablicy po rozpoczęciu pracy programu konieczność deklaracji nadmiarowej (nieefektywność) inna możliwość: dynamiczny przydział pamięci Przykład 1: utworzenie pojedynczego obiektu typu int: int *wint; wint= new int; *wint = 1000; cout << *wint; delete wint; // powołanie do życia // nadanie wartości // wypisanie wartości // likwidacja obiektu Przykład 2: utworzenie tablicy o rozmiarze określonym przez użytkownika: #include <iostream> #include <cstdlib> using namespace std; int main() float *wf, *wfb; int rozmiar,k; cout << "Podaj rozmiar tablicy: "; cin >> rozmiar; cout << rozmiar << endl; wf=new float[rozmiar]; // utworzenie tablicy wfb=wf; for (k=0; k<rozmiar; k++) *wfb++=k/100.0; cout << "Ostatni element = " // obsługa tablicy << *(wf+rozmiar-1) << endl; delete [ ] wf; // usunięcie tablicy
tworzenie obiektów operatory new oraz new [ ] zdefiniowane w C++ obiekty tworzone na stercie własności różne od zmiennych automatycznych obszar sterty nie jest nieograniczony konieczność likwidacji obiektów już niepotrzebnych: operatory delete oraz delete [] Cechy obiektów tworzonych dynamicznie: istnieją od momentu utworzenia do momentu ich skasowania my decydujemy o ich czasie życia tak utworzone obiekty nie mają nazwy są dostępne tylko przez wskaźnik obiektów tych nie obowiązują zwykłe zasady dotyczące zakresu ważności jeżeli tylko dostępny jest wskaźnik wskazujący na dany obiekt, to mamy do niego pełny dostęp po utworzeniu zawiera śmiecie Uwaga: w C do dynamicznej alokacji pamięci stosuje się funkcje biblioteki standardowej: malloc, calloc, realloc, free. void* malloc(size_t size) -- zwraca wskaźnik do obszaru pamięci przeznaczonego dla obiektu o razmiarze size, lub NULL gdy alokacja jest niemożliwa void *calloc(size_t ile, size_t size) - zwraca wskaźnik do obszaru pamięci przeznaczonego dla tablicy o ile elementów o długości size każdy (NULL gdy alokacja niemożliwa)
void *realloc(void *p, size_t size) zmienia rozmiar obiektu wskazywanego przez p na wartość określoną przez size. Zawartość początkowej części obiektu o rozmiarze równym min(nowy rozmiar, stary rozmiar). Zwraca wskaźnik do nowego obszaru (NULL gdy polecenie nie może być wykonane wtedy zawartość *p się nie zmienia). void free(void *p) zwalnia obszar pamięci wskazywany przez p. Nie robi nic, gdy p=null. Argument p musi być wskaźnikiem zwróconym wcześniej przez malloc, calloc lub realloc. Użycie tych funkcji konieczność rzutowania wskaźników oraz podawania rozmiarów obiektów Przykład 2a #include <iostream> #include <cstdlib> using namespace std; int main() float *wf, *wfb; int rozmiar,k; cout << "Podaj rozmiar tablicy: "; cin >> rozmiar; cout << rozmiar << endl; if ((wf=(float*)malloc(rozmiar*sizeof(float)))==null) printf("blad alokacji pamieci!!!\n"); return 1; wfb=wf; for (k=0; k<rozmiar; k++) // obsługa tablicy *wfb++=k/100.0; cout << "Ostatni element = " << *(wf+rozmiar-1) << endl; free(wf) // usunięcie tablicy