1 Wykład 1: Wskaźniki Podstawy programowania Programowanie w CDr inż. Zdzisława Rowińska III p. KIS, pokój 314,
2 Gdzie wykorzystywane są wskaźniki?Praca z tablicami Funkcje – zmiana wartości przesyłanych argumentów Dostęp do specjalnych komórek pamięci Rezerwacja obszarów pamięci
3 Definiowanie wskaźnikówtyp_obiektu *nazwa_wskaźnika; nazwa_wskaźnika wskazuje na obiektu typu typ_obiektu. Przykłady Można definiować wskaźniki do obiektów różnych typów: int *wi; char *wch; float *wf; Uwaga: Z definicji wskaźnika wynika, że wskaźnik pokazuje na obiekt. Referencja nie jest obiektem, dlatego nie można definiować wskaźników do referencji. Wskaźnik, który pokazuje na obiekt jednego typu, nie może być wykorzystany do pokazywania na obiekt innego typu.
4 Wskaźniki Sama definicja wskaźnika nie powoduje, że wskaźnik wskazuje na konkretny obiekt. Nadanie wartości obiektowi (wskaźnik wskazuje na istniejący obiekt): int *wi; int i; wi=&i; //ustawienie wskaźnika w na obiekt i Jeśli wskaźnik wskazuje na konkretny obiekt, to można odnosić się do tego obiektu za pomocą wskaźnika. Do operacji tej służy jednoargumentowy operator odniesienia (wyłuskania, dereferencji) * *nazwa_wskaźnika; Przykład: int *wi; //definicja wskaźnika int i; //definicja zmiennej i wi=&i; //ustawienie wskaźnika *wi=5; //przypisanie wartości 5 zmiennej i
5 Wskaźniki – cd Przykłady: int *wi,*wj; //definicja wskaźnikaint i,j; //definicja zmiennych i,j wi=&i; i=5; *wi=5; j=7; j=*wi; wi=&j; wj=wi;
6 Wskaźniki typu void void *wv; Definicja wskaźnika bez podania typu obiektu, na jaki wskazuje. Może być użyty do wskazywania na obiekty dowolnego typu. Przykłady: void *wv; int *wi; float *wf; wv=wi; //teraz wskaźnik wv wskazuje na ten sam obiekt (typu int), //na który wskazuje wskaźnik wi wv=wf; wi=wf //kompilator zasygnalizuje błąd!! wi=(int *)wf; //wykorzystanie rzutowania wf=(float *)wv; //wykorzystanie rzutowania Wskaźnikowi typu void można przypisać wskaźnik dowolnego (niestałego) typu. Działanie odwrotne wymaga operatora rzutowania.
7 Zastosowanie wskaźników do tablicint *wsk; //definicja wskaźnika int tab[10]; //definicja tablicy wsk=&tab[indeks]; //ustawienie wskaźnika na elemencie tablicy //o indeksie indeks wsk=&tab[0]; wsk=tab; wsk=&tab[indeks]; wsk=wsk+ind; wsk += ind; //przesunięcie wskaźnika o ind pozycji instrukcje równoważne Dodanie do wskaźnika liczby całkowitej ind powoduje, że wskaźnik pokazuje o ind elementów dalej w tablicy; niezależnie od tego, jakiego typu są elementy tablicy.
8 Zastosowanie wskaźników do tablicPrzykład: #include
9 Nazwa tablicy i wskaźnikNazwa tablicy jest jednocześnie adresem jej zerowego elementu. Nazwa tablicy jest stałym wskaźnikiem do jej zerowego elementu. float *wsk; float tab[10]; wsk=tab; //wsk=&tab[0]; //możliwe jest przypisanie wsk++; //niemożliwa jest instrukcja tab++; tab[ind]; *(tab+ind); Wskaźnik jest pewnym obiektem w pamięci (to znaczy posiada swój adres). Nazwa (również tablicy) nie jest obiektem (nie ma więc adresu). float *wsk; &wsk; //adres wskaźnika – odwołanie poprawne
10 Wskaźniki w argumentach funkcjivoid fun(float f) { f /= 3.3; } float fun(float *f) { *f /= 3.3; Przesyłanie argumentów przez wartość. Funkcja nie zmienia wartości przesyłanego argumentu. void main() { float fvar = 13.3; fun(fvar); printf(”%f\n”, fvar); // 13.3 fun(&fvar); printf(”%f\n”, fvar); // 4.03 } Przesyłanie argumentów przez wskaźnik. Zmiana wewnątrz funkcji wartości przesyłanego argumentu. 10
11 Przesyłanie tablic do funkcji#include
12 Przesyłanie tablic do funkcji – cdOdebranie tablicy jako tablicy – czytelność funkcji Odebranie tablicy jako adresu inicjującego wskaźnik – funkcja działa szybciej Odebranie tablicy jako adresu – łatwiejsze przekazywanie tablic wielowymiarowych (rozmiary tablicy nie muszą być znane w momencie wywołania funkcji) 12
13 Wskaźniki do stałych Wskaźniki do stałych mogą zawierać adresy dowolnych zmiennych i mogą być modyfikowane w programie, ale nie można za ich pomocą modyfikować zmiennych wskazywanych. const int stala = 10; // definicja stałej typu int const int *wsk_st; // wskaźnik do stałej typu int void main() { int i = 5; const int *w = &i; // wskaźnik zainicjowany wsk_st = &i; // inicjacja adresem zmiennej automatycznej i cout << *wsk_st << endl; // 5 cout << *w << endl; // 5 // *w = *w + 5; // błąd kompilatora ! // *wsk_st+= 7; // nie można modyfikować stałej wsk_st = &stala; // wskaźnik inicjowany adresem stałej w = &stala; // wskaźnik inicjowany adresem stałej cout << *wsk_st << endl; // 10 cout << *w << endl; // 10 } 13
14 Stałe wskaźniki Stały wskaźnik to wskaźnik, który zawsze pokazuje na to samo. Wskaźnik tego typu musi być zainicjowany w miejscu definicji - tak jak każda stała. W programie nie można już modyfikować jego wartości. Za pomocą wskaźnika stałego można jednak modyfikować zawartość zmiennej wskazywanej, ale tylko tej, której adresem wskaźnik został zainicjowany. const int st = 4; // stała int zm = 10; // definicja zmiennej int * const w = &zm; // stały wskaźnik do zmiennej zm // int * const x = &st; // błąd – wskaźnik musi wskazywać na zmienną const int * const x = &st; // dobrze - stały wskaźnik do stałej int i = 7; int * const wi = &i; // stały wskaźnik do zmiennej lokalnej i int * const pz = &zm; // stały wskaźnik do zmiennej zm cout << *wi << endl; // i=7 = i cout << *pz << *w << endl; // 10 i 10; zm=10 *wi +=8; // i = = 15 cout << i << endl; // i =15 // wi = &zm; // błąd – nie wolno zmieniać stałej wi *pz += 2; // zm = cout << zm << endl; // zm = 12 14
15 Wskaźniki do funkcji typ_zwracany (*wsk_do_funkcji)([argumenty_funkcji]); #include
16 Wskaźniki do funkcji Nazwa funkcji jest adresem jej początku (adresem miejsca w pamięci, gdzie zaczyna się kod tej funkcji). Zastosowanie wskaźników do funkcji: Przesyłanie argumentów do funkcji. Adres funkcji można wysłać jako argument. Tworzenie tablic ze wskaźników do funkcji. 16
17 Przesyłanie argumentów do funkcji#include
18 Tablice wskaźników typ_wskazywany *tablica[rozmiar];//typ_wskazywany *(tablica[rozmiar]); zapis równoważny double *tablica[10]; 18
19 Tablice wskaźników - cd#include
20 Pamięć a zmienne w programieZe względu na czas życia wyróżnia się: - obiekty statyczne - istniejące od chwili rozpoczęcia działania programu aż do jego zakończenia - obiekty dynamiczne - tworzone i usuwane z pamięci w trakcie wykonania programu: - automatycznie, czyli bez udziału programisty - kontrolowane przez programistę
21 Pamięć a zmienne w programieO tym czy obiekt jest statyczny czy dynamiczny decyduje miejsce deklaracji oraz klasa pamięci (przypisywana jawnie w deklaracji lub przyjmowana domyślnie). Klasę pamięci określają słowa kluczowe: auto, static, register, umieszczane przed deklaracją zmiennej, np. static int x; wszystkie zmienne globalne są statyczne wszystkie zmienne lokalne zadeklarowane bez jawnego specyfikowania klasy pamięci są automatyczne (auto). Zmienną lokalną można uczynić statyczną dodając przed deklaracją static (czasami stosowane w funkcjach) zmienne klasy register są automatyczne
22 Funkcje malloc, calloc, freeDynamiczna alokacja pamięci Funkcje malloc, calloc, free W języku C/C++ istnieją standardowe funkcje umożliwiające dynamiczną alokację pamięci (malloc i calloc) oraz funkcja zwalniająca przydzieloną pamięć (free). Prototypy funkcji: void *malloc(size_t K); // alokacja K bajtów void *calloc(size_t N, size_t K); // alokacja N razy po K bajtów void free(void *x); // zwolnienie obszaru Funkcje malloc i calloc przydzielają spójne obszary pamięci, których rozmiary nie przekraczają 64 KB (size_t jest zdefiniowany jako unsigned) i zwracają wskazanie do przydzielonego obszaru. Jeżeli alokacja nie jest możliwa, to zwracany jest wskaźnik NULL. Funkcja calloc dodatkowo zeruje przydzieloną pamięć. Funkcja free zwraca do systemu przydzieloną pamięć. Funkcje malloc i calloc zwracają wskaźniki do typu void dlatego niezbędne są konwersje typu przy podstawieniach do wskaźników innych typów. Należy uważać, aby za pomocą funkcji free nie zwalniać pamięci, która nie została przydzielona. 22
23 Funkcje malloc, calloc, freeDynamiczna alokacja pamięci Funkcje malloc, calloc, free Przykład: void main() { int i; char* ps = NULL; //wskaźnik do char ps = (char*) malloc(5 * sizeof(char)); //alokacja pamięci na //elementy typu char if (ps) strcpy(ps, "Ola"); for (i = 0; i < strlen(ps); i++) printf("%c\n", *(ps + i)); //wypisanie i-tego //elementu tablicy free(ps); //zwolnienie pamięci ps = NULL; } 23
24 Dynamiczny przydział pamięci - przykład Przydział pamięci dla n-liczb typu int, wczytanie liczb, obliczenie średniej #include
25 Dynamiczny przydział pamięci na tablicę dwuwymiarową (macierz):Zamiast standardowego odwołania do elementów macierzy: tab[i][j] stosujemy odwołania do odpowiednich elementów wektora: *(tab+M*i+j) lub tab[M*i+j], gdzie M –liczba kolumn Przykład: tablica tab, dla której N=3, M=4 odwołujemy się do elementu tab[2][2]: *(tab+4*2+2)
26 Przydział pamięci na tablicę NxM, wygenerowanie i wyświetlenie liczb#include
27 Metoda 2 (wskaźnik na tablicę wskaźników):Przydzielamy pamięć na N-elementowy wektor wskaźników na typ int, a następnie do kolejnych elementów tego wektora zapisujemy adresy M-elementowych wektorów liczb typu int (pamięć na wektory jest także przydzielana dynamicznie) int **tab; tab = (int**) calloc(N,sizeof(int *)); for (i=0; i
28 Przydział pamięci na tablicę NxM, wygenerowanie i wyświetlenie liczb#include