Wskaźniki
Transkrypt
Wskaźniki
Wskaźniki Na podstawie http://pl.wikibooks.org/wiki/C/Wskaźniki Informacje ogólne Wskaźnik (ang. pointer) to specjalny rodzaj zmiennej, w której zapisany jest adres w pamięci komputera, tzn. wskaźnik wskazuje miejsce, gdzie zapisana jest jakaś informacja. Adres w pamięci to pewna liczba całkowita, jednoznacznie definiująca położenie pewnego obiektu (czyli np. znaku czy liczby) w pamięci komputera. Przykładowo, patrząc na schemat poniżej, zmienna b przechowuje liczbę całkowitą 17. Znajduje się ona w pamięci komputera pod adresem 1462. By stworzyć wskaźnik do zmiennej i móc się nim posługiwać należy przypisać mu odpowiednią wartość (adres obiektu, na jaki ma wskazywać). Adres ten można uzyskać przy pomocy operatora & (operatora pobrania adresu).: #include <stdio.h> int main (void) { int liczba = 80; printf("Zmienna znajduje sie pod adresem: %p,: %d\n", (void*)&liczba, liczba); return 0; } Aby móc zapisać gdzieś taki adres należy zadeklarować zmienną wskaźnikową. Robi się to poprzez dodanie * (gwiazdki) po typie na jaki zmienna ma wskazywać, np.: int *wskaznik1; Aby dobrać się do wartości wskazywanej przez wskaźnik należy użyć unarnego operatora * (gwiazdka), zwanego operatorem wyłuskania: #include <stdio.h> int main (void) { int liczba = 80; int *wskaznik = &liczba; *wskaznik = 42; printf("Wartosc zmiennej: %d, wartosc wskazywana przez wskaznik: %d\n", liczba, *wskaznik); liczba = 0x42; printf("Wartosc zmiennej: %d, wartosc wskazywana przez wskaznik: %d\n", liczba, *wskaznik); return 0; } Przekazywanie argumentów do funkcji przez wskaźnik Argumentem (lub argumentami) funkcji mogą być wskaźniki. W przypadku "normalnych" zmiennych nasza funkcja działa tylko na lokalnych kopiach tychże argumentów, natomiast nie zmienia zmiennych, które zostały podane jako argument. Natomiast w przypadku wskaźnika, każda operacja na wartości wskazywanej powoduje zmianę wartości zmiennej zewnętrznej. Przykład: #include <stdio.h> void func (int *zmienna) { *zmienna = 5; } void main () { int z=3; printf ("z=%d\n", z); /* wypisze 3 */ func(&z); printf ("z=%d\n", z); /* wypisze 5 */ } Dynamiczna alokacja pamięci „Standardowe” zmienne programu przechowywane są na tzw. stosie (ang. stack) - powstają, gdy program wchodzi do bloku, w którym zmienne są zadeklarowane a zwalniane w momencie, kiedy program opuszcza ten blok. Jeśli deklaruje się w ten sposób tablice, to ich rozmiar musi być znany w momencie kompilacji - żeby kompilator wygenerował kod rezerwujący odpowiednią ilość pamięci. Dostępny jest jednak drugi rodzaj rezerwacji pamięci. Jest to alokacja na stercie (ang. heap). Sterta to obszar pamięci wspólny dla całego programu, przechowywane są w nim zmienne, których czas życia nie jest związany z poszczególnymi blokami. Należy samodzielnie rezerwować dla nich miejsce i to miejsce zwalniać, ale dzięki temu można to zrobić w dowolnym momencie działania programu. Należy pamiętać, że rezerwowanie i zwalnianie pamięci na stercie zajmuje więcej czasu niż analogiczne działania na stosie. Dodatkowo, zmienna zajmuje na stercie więcej miejsca niż na stosie - sterta utrzymuje specjalną strukturę, w której trzymane są wolne partie (może to być np. lista). Tak więc używajmy dynamicznej alokacji tam, gdzie jest potrzebna - dla danych, których rozmiaru nie jesteśmy w stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z blokiem, w którym zostały zaalokowane. Funkcje związane z dynamiczną alokacją pamięci (stdlib.h) void *malloc(size_t n) – zwraca wskaźnik do n bajtów niezainicjowanej pamięci lub NULL void *calloc(size_t n, size_t size) – zwraca wskaźnik do obszar mogącego pomieścić tablicę n elementów podanego rozmiaru size void *realloc(void *ptr, size_t size) – zmienia rozmiar bloku wskazanego przez ptr na size void free(void *ptr) - zwalnia obszar pamięci wskazywany przez ptr, ptr powinien być wartością zwróconą wcześniej Przykład (dynamiczna alokacja pamięci dla tablicy jednowymiarowej) #include <stdio.h> #include <stdlib.h> void main () { int n; int *tab; printf ("Podaj wielkosc tablicy do zarezerwowania: "); scanf("%d",&n); //rezerwacja pamięci tab = (int*)malloc(n*sizeof(int)); tab[n-2] = 133; printf("%d",tab[n-2]); //koniecznie należy pamietac o zwalnianiu zaalokowanej pamieci! free(tab); } Wskaźniki a tablice Tablica jest również rodzajem zmiennej wskaźnikowej. „Nazwa” tablicy jest wskaźnikiem do bloku pamięci, w którym są przechowywane jej poszczególne elementy. Przykład: int tab[] { 1,2,3,4,5,6 }; printf(”Adres początku tablicy: %p”,tab); Dzięki operacjom arytmetycznym na wskaźnikach można uzyskać dostęp do tablicy bez stosowania operatora indeksowania []. Przykład: int tab[] { 1,2,3,4,5,6 }; printf(”%d”,*(tab+3)); //to samo przy pomocy operatora indeksowania printf(”%d”,tab[3]); scanf(”%d”,(tab+4); //analogicznie scanf(”%d”,&tab[4]); Wskaźniki do funkcji http://pl.wikibooks.org/wiki/C/Wskaźniki_-_więcej#Deklaracja_wska.C5.BAnika_na_funkcj.C4.99 Zadania 1. Napisz program zawierający definicje dwóch zmiennych całkowitoliczbowych (wczytywane z klawiatury i wskaźnika mogącego pokazywać na tego typu zmienne. Przetestuj operacje na zmiennych przy pomocy wskaźnika: ustaw jego wartość na pierwszą zmienną, zmodyfikuj jej wartość poprzez nazwę zmiennej i poprzez wskaźnik. Na każdym etapie wypisuj wartość zmiennej odczytaną poprzez jej nazwę i poprzez wskaźnik. 2. Napisz funkcję zamieniającą wartości dwóch zmiennych przekazywanych jako argumenty, jeżeli pierwsza wartość jest większa od drugiej. Wywołaj ją w programie głównym. Prototyp funkcji (miejsca oznaczone /*... */ należy uzupełnić). void zamien(/*... */ zmienna1, /*... */ zmienna2) Fragment programu głównego wywołującego funkcję //... int a=10; b=5; printf(”Przed wywolaniem funkcji zamien: a=%d, b=%d”,a,b); zamien(/*... */ a, /*... */ b); //po wywolaniu funkcji a=5, b=10! 3. Napisz program, który pobiera z klawiatury ilość liczb do wczytania, następnie wczytuje poszczególne liczby i wypisuje je w kolejności odwrotnej. Program powinien działać dla dowolnej ilości liczb do wczytania (ograniczonej zasobami pamięciowymi przydzielonymi 4. 5. 6. 7. 8. przez system operacyjny dla programu). Podpowiedź: wykorzystaj dynamiczny przydział pamięci dla tablicy, która będzie przechowywała wczytane liczby. Napisz bezargumentową funkcję, która rezerwuje pamięć dla pojedynczej zmiennej typu int i zwraca jako wartość wskaźnik do niej. Napisz program, który wczytuje liczby z klawiatury aż do napotkania 0 i wypisuje na ekranie co drugą liczbę w kolejności odwrotnej niż wczytano. Program powinien działać dla dowolnej ilości liczb do wczytania (ograniczonej zasobami pamięciowymi przydzielonymi przez system operacyjny dla programu). Podpowiedź: wykorzystaj dynamiczny przydział pamięci dla tablicy, która będzie przechowywała wczytane liczby, z realokacją (funkcja realloc). Zmodyfikuj program z zadania 5 tak, aby odwołania do tablicy odbywały się przy pomocy wskaźników (nie operatora indeksowania []). Napisz program alokujący dynamicznie pamięć dla macierzy dwuwymiarowej NxM (wymiary N, M podane z klawiatury), wczytujący tę macierz ze standardowego wejścia i zwracający maksymalne elementy w wierszach. Poszczególne zadania wydziel do funkcji. Napisz funkcję wypisującą łańcuch znakowy na ekran, przyjmującą jako parametry łańcuch do wypisania oraz wskaźnik do funkcji wypisującej pojedynczą literę. Przygotuj klika wariantów funkcji do wypisywania pojedynczego znaku – konwertujący znak na dużą literę, wypisujący podwójnie itp.. Wywołaj funkcję wypisującą łańcuch kilkakrotnie, podając różne funkcje wypisywania znaku.