Wskaźniki
Transkrypt
Wskaźniki
Wskaźniki Programowanie Proceduralne 1 Adresy zmiennych Sterta # include<s t d i o . h> int a = 2 ; int main ( ) { int b = 3 ; Stos printf ( " adres zmiennej a %p\n" , &a ) ; printf ( " adres zmiennej b %p\n" , &b ) ; main() b 0x7fff0be8dccc return 0 ; 3 } Dane a adres zmiennej a 0x601040 adres zmiennej b 0x7fff0be8dccc Programowanie Proceduralne 0x601040 2 Kod adresy pamięci 1 2 3 4 5 6 7 8 9 10 11 12 13 2 Wskaźniki Wskaźnik (pointer ) int *wsk adres zmiennej w pamięci (np. &a) ws kaznik int a = 5 ; printf ( "%p\n" , &a ) ; Zmienna wskaźnikowa zmienna przechowująca adres int a 3 int ∗ wsk ; wsk = &a ; Typ wskaźnikowy typ zmiennej znajdujący się pod wskazanym adresem (np. int) Programowanie Proceduralne 3 Zmienna wskaźnikowa Deklaracja typ *identyfikator; Przykład int ∗ wa ; float ∗ wx ; char ∗ wz ; void ∗w ; int ∗t [ 1 0 ] ; int ∗∗ ww ; /∗ w s k a z n i k na zmienna t y p u i n t ∗/ /∗ w s k a z n i k na zmienna t y p u f l o a t ∗/ /∗ w s k a z n i k na zmienna d o w o l n e g o t y p u ∗/ /∗ t a b l i c a z m i e n n y c h w s k a z n i k o w y c h ∗/ /∗ w s k a z n i k na zmienna w s k a z n i k o w a ∗/ Programowanie Proceduralne 4 Operator pobrania adresu Operator referencji & zwraca adres zmiennej Przykład 1 2 3 4 5 6 7 8 9 int int int int a =10; b =13; ∗ wa ; ∗ wb ; wa = &a ; wb = &b ; wb = wa ; Programowanie Proceduralne 5 Operator pobrania adresu Operator referencji & zwraca adres zmiennej Przykład 1 2 3 4 5 6 7 8 9 int int int int a =10; b =13; ∗ wa ; ∗ wb ; wa = &a ; wb = &b ; wa wb ? ? a b 10 13 wb = wa ; Programowanie Proceduralne 6 Operator pobrania adresu Operator referencji & zwraca adres zmiennej Przykład 1 2 3 4 5 6 7 8 9 int int int int a =10; b =13; ∗ wa ; ∗ wb ; wa = &a ; wb = &b ; wa wb a b 10 13 wb = wa ; Programowanie Proceduralne 7 Operator pobrania adresu Operator referencji & zwraca adres zmiennej Przykład 1 2 3 4 5 6 7 8 9 int int int int a =10; b =13; ∗ wa ; ∗ wb ; wa = &a ; wb = &b ; wa wb a b 10 13 wb = wa ; Programowanie Proceduralne 8 Operator dostępu do adresu Operator dereferencji * daje dostęp do wskazanego adresu Przykład 1 2 3 4 5 6 7 8 9 10 11 int int int int a =10; b =13; ∗ wa = &a ; ∗ wb = &b ; ∗ wb = 5 ; ∗ wa = ∗ wb ; wa = wb ; ∗ wb = ∗ wb + 1 ; Programowanie Proceduralne 9 Operator dostępu do adresu Operator dereferencji * daje dostęp do wskazanego adresu Przykład 1 2 3 4 5 6 7 8 9 10 11 int int int int a =10; b =13; ∗ wa = &a ; ∗ wb = &b ; wa wb a b 10 13 ∗ wb = 5 ; ∗ wa = ∗ wb ; wa = wb ; ∗ wb = ∗ wb + 1 ; Programowanie Proceduralne 10 Operator dostępu do adresu Operator dereferencji * daje dostęp do wskazanego adresu Przykład 1 2 3 4 5 6 7 8 9 10 11 int int int int a =10; b =13; ∗ wa = &a ; ∗ wb = &b ; wa wb a b 10 5 ∗ wb = 5 ; ∗ wa = ∗ wb ; wa = wb ; ∗ wb = ∗ wb + 1 ; Programowanie Proceduralne 11 Operator dostępu do adresu Operator dereferencji * daje dostęp do wskazanego adresu Przykład 1 2 3 4 5 6 7 8 9 10 11 int int int int a =10; b =13; ∗ wa = &a ; ∗ wb = &b ; wa wb a b 5 5 ∗ wb = 5 ; ∗ wa = ∗ wb ; wa = wb ; ∗ wb = ∗ wb + 1 ; Programowanie Proceduralne 12 Operator dostępu do adresu Operator dereferencji * daje dostęp do wskazanego adresu Przykład 1 2 3 4 5 6 7 8 9 10 11 int int int int a =10; b =13; ∗ wa = &a ; ∗ wb = &b ; wa wb a b 6 5 ∗ wb = 5 ; ∗ wa = ∗ wb ; wa = wb ; ∗ wb = ∗ wb + 1 ; Programowanie Proceduralne 13 Uważaj na niezainicjowane zmienne wskaźnikowe. wa Przykład int ∗ wa ; ∗ wa = 5 ; NULL to adres 0 int ∗ wa ; wa = 0 ; wa = NULL ; BANG! /∗ s t d l i b . h ∗/ • Nigdy nie używaj operatora * na niezainicjowanej zmiennej. • Wskazanie puste, adres 0, NULL - informacja, że wskaźnik nic nie pokazuje Programowanie Proceduralne 14 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int a ) { a = a + 1; } Stos int main ( ) { int a = 3 ; zwieksz ( a ) ; printf ( "%d\n" , a ) ; Dane return 0 ; } Kod Programowanie Proceduralne 15 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int a ) { a = a + 1; } Stos int main ( ) { int a = 3 ; main() a zwieksz ( a ) ; printf ( "%d\n" , a ) ; 3 Dane return 0 ; } Kod Programowanie Proceduralne 16 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int a ) { a = a + 1; } Stos zwieksz(3) a 4 int main ( ) { int a = 3 ; main() a zwieksz ( a ) ; printf ( "%d\n" , a ) ; 3 Dane return 0 ; } Kod Programowanie Proceduralne 17 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int a ) { a = a + 1; } Stos int main ( ) { int a = 3 ; main() a zwieksz ( a ) ; printf ( "%d\n" , a ) ; 3 Dane return 0 ; } Kod Programowanie Proceduralne 18 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # include<s t d i o . h> Sterta void zwieksz ( int ∗a ) { ∗a = ∗a + 1 ; } Stos int main ( ) { int a = 3 ; zwieksz(&a ) ; printf ( "%d\n" , a ) ; Dane return 0 ; Kod } Programowanie Proceduralne 19 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int ∗a ) { ∗a = ∗a + 1 ; } Stos int main ( ) { int a = 3 ; main() a zwieksz(&a ) ; printf ( "%d\n" , a ) ; 0x7fff0be8 3 Dane return 0 ; Kod } Programowanie Proceduralne 20 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int ∗a ) { ∗a = ∗a + 1 ; } Stos zwieksz(0x7fff0be8) a int main ( ) { int a = 3 ; main() a zwieksz(&a ) ; printf ( "%d\n" , a ) ; 0x7fff0be8 4 Dane return 0 ; Kod } Programowanie Proceduralne 21 Wskaźniki jako argumenty funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 Sterta # include<s t d i o . h> void zwieksz ( int ∗a ) { ∗a = ∗a + 1 ; } Stos int main ( ) { int a = 3 ; main() a zwieksz(&a ) ; printf ( "%d\n" , a ) ; 0x7fff0be8 4 Dane return 0 ; Kod } Programowanie Proceduralne 22 Wskaźniki jako argumenty funkcji • poprzez wskaźnik (adres zmiennej) funkcja może zwrócić dodatkową wartość • scanf("%d", &x) ← funkcja modyfikuje zmienną x, stąd argumentem musi być adres http://xkcd.com Programowanie Proceduralne 23 Znajdowanie minimum i maksimum Problem: znajdź wartość minimalną i maksymalną w zbiorze liczb Algorytm 1 Wyznaczanie maksimum i minimum Dane wejściowe: ciąg n liczb {x1 , x2 , . . . , xn } Wynik: wartość xmin i xmax , odpowiednio najmniejszy i największy element podanego ciągu 1: xmin ← x1 2: xmax ← x1 3: dla każdego x ∈ {x2 , . . . , xn } wykonuj 4: jeżeli x < xmin wykonaj 5: xmin ← x 6: jeżeli x > xmax wykonaj 7: xmax ← x 8: zwróć xmin , xmax Programowanie Proceduralne 24 Element minimalny i maksymalny Przykład w C 1 2 3 4 5 6 7 8 9 10 11 12 13 void minmax ( float t [ ] , int n , float ∗ min , float ∗ max ) { int i = 1 ; ∗ min=t [ 0 ] ; ∗ max=t [ 0 ] ; while ( { if ( if ( i = } i < n) ∗ min > t [ i ] ) ∗ min = t [ i ] ; ∗ max < t [ i ] ) ∗ max = t [ i ] ; i + 1; } minmax1.c Programowanie Proceduralne 25 Element minimalny i maksymalny Przykład w C 1 2 3 4 5 6 7 8 9 10 11 int main ( ) { int n ; float t [ MAX ] , max , min ; n = wczytaj ( t , MAX ) ; minmax ( t , n , &min , &max ) ; printf ( " min =% f\ nmax =% f\n" , min , max ) ; return 0 ; } minmax1.c Programowanie Proceduralne 26 Złożoność przeszukiwania • Ilość porównań: 2(n − 1) • Czy istnieje szybszy sposób? • Dziel i zwyciężaj ! Programowanie Proceduralne 27 Algorytm 2 Wyznaczanie maksimum i minimum Dane wejściowe: ciąg n liczb {x1 , x2 , . . . , xn } Wynik: wartość xmin i xmax , odpowiednio najmniejszy i największy element podanego ciągu 1: A ← ∅, B ← ∅ 2: dla i = 1, 2, . . . , b n 2 c wykonuj 3: jeżeli x2i−1 < x2i wykonaj 4: A ← A ∪ {x2i−1 }, B ← B ∪ {x2i } 5: w przeciwnym wypadku 6: A ← A ∪ {x2i }, B ← B ∪ {x2i−1 } 7: jeżeli n jest nieparzyste wykonaj 8: A ← A ∪ {xn }, B ← B ∪ {xn } 9: xmin ← min(A) 10: xmax ← max(B) 11: zwróć xmin , xmax Programowanie Proceduralne 28 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 void minmax ( float t [ ] , int n , float ∗ min , float ∗ max ) { int i ; float tmin , tmax ; if ( n==1 ) { ∗ min=t [ 0 ] ; ∗ max=t [ 0 ] ; return ; } if ( t [0] < t [ 1 ] ) { ∗ min=t [ 0 ] ; ∗ max=t [ 1 ] ; } else { ∗ min=t [ 1 ] ; ∗ max=t [ 0 ] ; } Programowanie Proceduralne 29 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 for ( i =2; i < n −1; if ( t [ i]<t [ i +1] tmin=t [ i ] ; tmax=t [ i + 1 ] ; } else { tmin=t [ i + 1 ] ; tmax=t [ i ] ; } if ( ∗ max<tmax ) if ( ∗ min>tmin ) } if ( i == n−1 ) { if ( ∗ max<t [ i ] ) if ( ∗ min>t [ i ] ) } i=i+2 ) { ) { ∗ max=tmax ; ∗ min=tmin ; ∗ max=t [ i ] ; ∗ min=t [ i ] ; } minmax2.c Programowanie Proceduralne 30 Wskaźniki do elementów tablic p = &t [ 0 ] ; p p p 9 8 7 6 5 4 3 + p+ + p + p+ p+ 2 1 p p+ p+ p+ t[0] t[1] t[2] t[3] t[4] t[5] t[6] t[7] t[8] t[9] Programowanie Proceduralne 31 Arytmetyka na wskaźnikach 1 2 3 4 5 6 7 8 9 int a; char b ; int ∗ pa = &a ; char ∗ pb = &b ; printf ( " pa printf ( " pa +1 printf ( " pb printf ( " pb +1 = = = = %p %p %p %p % lu \n" , % lu \n" , % lu \n" , % lu \n" , pa pa+1 pb pb+1 , , , , pa ) ; pa+1) ; pb ) ; pb+1) ; pointer.c Przykładowy wynik: pa pa+1 pb pb+1 = = = = 0x7fff44cb239c 0x7fff44cb23a0 0x7fff44cb239b 0x7fff44cb239c 140734347551644 140734347551648 140734347551643 140734347551644 Programowanie Proceduralne 32 Tablice a wskaźniki Tablica jest wskaźnikiem ! i-ty element adres i-tego elementu Przykład 1 2 3 4 5 6 7 8 int t [ 5 ] ; int ∗ wsk ; t[i] ⇔ *(t+i) &(t[i]) ⇔ t+i t 1 t+ 2 t+ 3 t+ 4 t+ t[0] t[1] t[2] t[3] t[4] wsk wsk=t ; ∗t = 5 ; ∗ ( t+2) = 6 ; wsk = wsk + 1 ; t = t + 1; A d r e s t a b li c y j e s t s t aly Źle ! Programowanie Proceduralne 33 Ponownie środek masy 2 punktów float ∗ srodek ( float p1 [ ] , float p2 [ ] ) { float sm [ 4 ] ; z m ie nna int i =0; lok sm [ 3 ] = p1 [ 3 ] + p2 [ 3 ] ; al n a while ( i<3) { sm [ i ]=( p1 [ 3 ] ∗ p1 [ i]+ p2 [ 3 ] ∗ p2 [ i ] ) / sm [ 3 ] ; i = i + 1; } return sm ; zwra } c any adres z m ie n n ej lo k aln ej Programowanie Proceduralne ! e Źl 34 Ponownie środek masy 2 punktów • Deklaracja const zmiennej wskaźnikowej - kompilator wykryje próbę modyfikacji. • Zmienna sm zawiera adres zmiennej, która zostanie zmodyfikowana przez funkcję srodek(). void srodek ( const float p1 [ ] , const float p2 [ ] , float sm [ ] ) { int i =0; sm [ 3 ] = p1 [ 3 ] + p2 [ 3 ] ; while ( i<3) { sm [ i ]=( p1 [ 3 ] ∗ p1 [ i]+ p2 [ 3 ] ∗ p2 [ i ] ) / sm [ 3 ] ; i = i + 1; } } Wskaźn ik Programowanie Proceduralne 35 Wskaźnik jako wartość zwracana z funkcji 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 float ∗ wczytaj ( float ∗t , int n ) { float ∗p=t ; printf ( " Wprowadz %d liczb \n" , n ) ; while ( n >0 ) { scanf ( "%f" , t ) ; t = t + 1; n = n − 1; } return p ; } int main ( ) { float t [ 1 0 0 ] ; float min , max ; minmax ( wczytaj ( t , 1 0 ) , &min , &max ) ; } Programowanie Proceduralne 36 Rozszyfruj deklaracje Przykładowe deklaracje funkcji /∗ M i e j s c a z e r o w e p a r a b o l i ∗/ int pierwiastki ( float a , float b , float c , float ∗x1 , float ∗ x2 ) ; /∗ W y s z u k i w a n i e b i n a r n e ∗/ int szukaj ( const int ∗t , int n , int x ) ; /∗ S r o d e k masy u k l a d u punktow ∗/ struct punkt srodek ( const struct ogromna_chmura_punktow ∗u ) ; /∗ Dekompozycja l i c z b y z m i e n n o p o z y c y j n e j ( math . h ) ∗/ float modf ( float num , float ∗i ) ; /∗ A l o k a c j a p a m i e c i ( s t d l i b . h ) ∗/ void ∗ malloc ( int size ) ; /∗ K o p i o w a n i e t a b l i c ( s t d l i b . h ) ∗/ void ∗ memcpy ( void ∗ dest , const void ∗ src , int count ) ; /∗ O t w i e r a n i e p l i k u ( s t d i o . h ) ∗/ FILE ∗ fopen ( const char ∗ filename , const char ∗ mode ) ; Programowanie Proceduralne 37 Przykład: Wyszukiwanie dominanty Problem: znajdź wartość występującą najwięcej razy w zbiorze 8 1 -6 3 5 7 4 9 2 10 -4 88 6 3 1 3 332 2 Programowanie Proceduralne 38 Algorytm 3 Wyznaczanie dominanty (mody) - algorytm naiwny Dane wejściowe: ciąg n elementów {x1 , x2 , . . . , xn } Wynik: wartość dominanty xmoda oraz ilość wystąpień lmoda . Jeżeli istnieje więcej niż jedna wartość dominującą to zwracana jest pierwsza znaleziona. 1: lmoda ← 0 2: dla każdego x ∈ {x1 , . . . , xn } wykonuj 3: k←0 4: dla każdego y ∈ {x1 , . . . , xn } wykonuj 5: jeżeli x = y wykonaj 6: k ←k+1 7: jeżeli k > lmoda wykonaj 8: lmoda ← k 9: xmoda ← x 10: zwróć xmoda , lmoda Programowanie Proceduralne 39 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 int dominanta ( const int ∗t , int n , int ∗c ) { int i , j , k , x ; ∗c = 0 ; i =0; while ( i<n ) { k =0; j =0; while ( j<n ) { if ( t [ i]==t [ j ] ) k = k + 1 ; j = j + 1; } if ( k > ∗c ) { ∗c = k ; x = t[i]; } i = i + 1; } return x ; } dominanta1.c Programowanie Proceduralne 40 Złożoność algorytmu • ilość operacji rzędu n2 • jeżeli pewien element został aktualnie zaznaczony jako dominujący to nie musimy powtarzać dla niego obliczeń • zliczanie można rozpocząć od i + 1 miejsca, jeżeli wartość dominująca pojawiła się wcześniej to już została policzona • jeśli aktualna wartość dominująca ma k wystąpień, to szukanie możemy przerwać na pozycji n − k w zbiorze Programowanie Proceduralne 41 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 int dominanta2 ( const int ∗t , int n , int ∗c ) { int i , j , k , x ; ∗c =0; x=t [ 0 ] − 1 ; i =0; while ( i<n−∗c ) { if ( t [ i ] ! = x ) { k =1; j=i +1; while ( j<n ) { if ( t [ i]==t [ j ] ) k = k + 1 ; j = j + 1; } if ( k > ∗c ) { ∗c = k ; x = t[i]; } } i = i + 1; } return x ; } dominanta2.c Programowanie Proceduralne 42 Dynamiczny przydział pamięci Alokacja pamięci void *malloc(int rozmiar); Funkcja malloc zwraca adres przydzielonego bloku pamięci lub wartość 0 (NULL) w przypadku niepowodzenia. Zwolnienie przydzielonej pamięci void free(void *wskaznik); Argumentem funkcji free jest adres uzyskany wcześniej z funkcji malloc. Funkcja malloc i free zadeklarowane są w pliku stdlib.h. Programowanie Proceduralne 43 Przykład # include< s t d l i b . h> # include<s t d i o . h> Sterta int main ( ) { float ∗t ; 3 t = malloc ( 4 ∗ sizeof ( int ) ) ; if ( t == NULL ) { printf ( " Blad alokacji pamieci .\ n" ) ; exit ( 1 ) ; } ∗t = 3 ; Stos main() t Dane Kod free ( t ) ; adresy pamięci 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 return 0 ; } Programowanie Proceduralne 44 Podsumowanie • Operator referencji (adresowania) & &a to adres zmiennej a • Operator dereferencji (wyłuskania) * *b daje dostęp do wartości wskazywanej przez zmienną b • Deklaracja zmiennej wskaźnikowej również zawiera znak * int *wsk; float* wsk2; • Tablice to wskaźniki t[i] ⇔ *(t+i) • Przekazanie wskaźnika do funkcji pozwala na modyfikację zmiennej wskazywanej • Uważaj na co wskazujesz! Programowanie Proceduralne 45 Literatura dodatkowa Maciej M. Sysło, „Algorytmy”, WSiP, Warszawa, 2002. David Griffiths, Dawn Griffiths „Rusz głową! C.”, Helion, Gliwice, 2013. „Kurs programowania w C”, WikiBooks, http://pl.wikibooks.org/wiki/C Programowanie Proceduralne 46