200 likes | 518 Views
WSKAŹNIKI: Definicja: Wskaźnik jest zmienna, która zawiera adres innej zmiennej Niech x będzie zmienną typu int, a wx wskaźnikiem (nie jest ważny w tej chwili sposób utworzenia tego skaźnika). Jendoargumentowy operator &
E N D
WSKAŹNIKI: Definicja: Wskaźnik jest zmienna, która zawiera adres innej zmiennej Niech x będzie zmienną typu int, a wx wskaźnikiem (nie jest ważny w tej chwili sposób utworzenia tego skaźnika). Jendoargumentowy operator & podaje adres obiektu, zatem &x jest adresem zmiennej x. Możemy więc napisac: px=&x PAMIETAJ: Komputer rozmieszcza wartości zmiennych w „komórkach” pamięci. „Komórki” są „numerowane” - adresowane. Wartość x zidentyfikować możemy a) odczytując zawartość odpowiedniej „komórki” b) podając numer „komórki” - wskaźnik napisanie: y=x oznacza przeniesienie wartości „komórki” x-sa do „komórki” y wx=&x oznacza nadanie zmiennej wx wartości równej adresowi „komórki z wartością x”. „Odczytanie” adresu - to wykonanie operacji &x
Operator „odwrotny” względem operatora & to * Tak więc jeśli wx jest wskażnikiem zmiennej x, to *wx „odczytuje” zawartość „komórki” o adresie wx - to znaczy, że wartość *wx jest równa x! Zatem jeśli mamy w programie fragment int x,y; int *wx; wx=&x; to oznacza, że y=x !!!! y=*wx; PAMIĘTAJ: jeśli wx jest wskażnikiem to zmiennej typu, powiedzmy AAA (o definiowaniu typów mówiliśmy poprzednio) to aby instrukcja y=*wx nie prowadziła do błędów (np. zaokrągleń) to *wx oraz y (y=*wx) powinny być typu AAA
Jeśli (jak poprzednio) wx jest wskaźnikiem do x, wx=&x, to wyrażenie y=*wx+1 oznacza y=x+1; wyrażenie printf(``%d\n”,*wx) oznacza to, co printf(``%d\n”,x). ALE WYRAŻENIE: y=*(wx+1) nie oznacza, że y=x+1!!! bowiem wówczas wx+1 jest adresem kolejnej „komórki” (następnej po adresie zmiennej x), a *(wx+1) jest „zawartością tej (następnej) komórki” ! Zawartość ta może nie być przez programistę kontrolowana; będzie ona kontrolowana wtedy, gdy wx wskazuje np. na adres pierwszego elementu tablicy. Napisanie *wx=0 oznacza wyzerowanie wartości zmiennej x, czyli x=0 Napisanie *wx+=1 to x+=1, zatem zwiększenie x o jeden Podobnie działa (*wx)++ . WAŻNA UWAGA: napisanie *wx++ to nie to samo co (*wx)++, bowiem operacje zawsze wykonywane są od prawej do lewej. Tak więc *wx++ to zwiększenie wskaźnika o jeden, a następnie pobranie wartości komórki odpowiadającej temu (zwiększonemu) wskaźnikowi. (*wx)++ to zwiększenie zmiennej x o jeden.
Jeśli wx=&x oraz wy=&y, to operacja wy=wx powoduje, że oba skaźniki: wx i wy będą wskazywały na x; zawarość komórki y nie zmieni się. Zmianę wartości y spowoduje y=*wx WSKAŹNIKI JAKO ARGUMENTY FUNKCJI chcemy napisać funkcję (procedurę; w C nie ma rozróżnienia), która zamienia zmienną x na y a y na x. Jeśli napiszemy swap(x,y) int x,y; { int temp; temp=x; x=y; y=temp; } TO BĘZIE ŹLE!. Pamiętamy, że argumenty funkcji przekazywane są przez wartość. W momencie wywołania funkcji tworzone są „lokalne” kopie argumentów a ich wartość w programie wywołującym NIE ZMIENIA SIĘ
Jednak program wywołujący może przekazywać WSKAŹNIKI do ZMIENIANYCH WARTOŚCI ( Jak to było w PASCALU?) piszemy: swap(wx,wy) int *wx,*wy; { int temp; temp=*wx; *wx=*wy; *wy=temp; } A WYWOŁUJEMY: swap(&a,&b) jeśli chcemy zamienić wartościami a z b (Pamiętamy funkcję scanf, wówczas nie podaliśmy uzasadnienia, dlaczego czytając zmienna x pisaliśmy &x; uzasadnienie takiego zapisu znajdujemy teraz)
Zadanie 13 Napisz program „kalkulator”, który oblicza sumę lub różnicę lub iloczyn lub iloraz dwu wczytanych liczb rzeczywistych w zależnosci od wczytanego operatora: + lub - lub * lub / . Obliczanie sumy, różnicy iloczynu i ilorazu MUSI BYĆ zapisane w oddzielnych funkcjach (procedurach). Wynik działania MUSI BYĆ parametrem funkcji, a NIE MOŻE być podstawiony pod nazwę funkcji. Decyzja wyboru działania MUSI być zapisana instrukcja switch
WSKAŹNIKI I TABLICE Jeśli mamy deklarację: int tab[100]; to deklaruje ona tablicę o elementach tab[0],...,tab[99] Niech ta będzie wskaźnikiem do obiektów całkowitych int *ta; to wówczas instrukcja ta=&tab[0]; „ustawia” wskaźnik ta, aby wskazywał na pierwszy (tj. zerowy) element tablicy. Jeśli x jest całkowite, to wtedy x=*ta „skopiuje” wartość tab[0] do x. JEŚLI WIĘC ta wskazuje na tab[0], to ta+i wskazuje na tab[i]. Zatem x=*(ta+i) jest równoważne x=tab[i]. Kompilator zmienia odwołanie do tablicy na wskaźnik do jej początku. Skutkiem tego NAZWA tablicy jest „sama z siebie” wyrażeniem wskaźni- kowym. Ponieważ nazwa tablicy REPREZENTUJE położenie jej ele- mentu zerowego, to instrukcje ta=&tab[0]; oraz ta=tab; SĄ RÓWNOWAŻNE.
Dalej: Jeśli ta=&a[0] to (jak już wiemy) x=*(ta+i) oznacza, że x=a[i], ALE RÓWNIEŻ x=*(tab+i) oznacza x=a[i]! oraz, że ta=&a[i] (wskazanie na i-ty element tablicy) jest równoważne ta+i, tzn. „przesunięciu” wskaźnika! PODSUMOWANIE: Nazwa tablicy (np. tab) to wskaźnik. Wyrażenie z tablicą i indeksem można zapisać za pomocą wskaźnika i przesunięcia i odwrotnie, nawet w tej samej instrukcji. Ale istnieje istotnaróżnica pomiedzy nazwą tablicy a wskaźnikiem. Wskaźnik jest zmienną, a więc operacja np.. ta=a, ta++, ta*=a, itp. ma sens, natomiast instrukcje tab=a, tab++, tab*=a SĄ NIEDOZWOLONE
Jeżeli nazwa tablicy to wskaźnik (do pierwszego elementu), a kolejne elementy tablicy wyznaczane są „przesuwaniem” wskaźnika, to deklarując tablice nie zawsze musimy podawać jej rozmiar. Dotyczy to funkcji (i procedur, o procedurach - później). Dla przykładu, funkcja obliczająca długość łańcucha s: dlugosc(s); char s[]; /*nie podajemy wymiaru, jest on automatycznie dobierany */ { /* przy wywoływaniu */ int k; /*deklarujemy zmienną do zliczania długości */ k=0; /*zerujemy zmienną */ while(s[k++]) /*a to w istocie znaczy s[k++]!=0, czyli „jeśli s[k++] jest */ /*prawdą, to pętla jest wykonywana, pamiętaj o różnicy k++ oraz ++k */ /* w pierwszym przypadku najpierw dla k, potem zwiększamy o 1! */ ; /*instrukcja pusta, pętla while jest PUSTA */ return(k-1); /*ale po zbadaniu warunku while wartość k zwiększyła się o 1, musimy to skorygować */ } Funkcje dlugosc wywolujemy np. i=dlugosc(lancuch), gdzie lancuch jest typem string
Zadanie 14 Funkcję obliczającą długość łańcucha można również zapisać w postaci: dlugosc(s); char *s { int n; for (n=0; *s!=‘\0’; s++) n++; return(n); } Zadanie polega na DOKŁADNYM opisie „co robią kolejne instrukcje” oraz napisaniu programu głównego, który będzie wywoływał funkcję dlugość. Należy też uzasadnić następujące stwierdzenie: definicje parametrów formalnych funkcji typu: char s[]; oraz char *s; są całkowicie równoważne.
Aby mocniej podkreślić związek: wskaźniki<->tablice rozważmy funkcje, która kopiuje pierwszych n znaków łańcucha string na łańcuch substr fun1(string,substr,n) char string[],substr[]; int n; { int i; for(i=0; i<n; i++) substr[i]=string[i]; sub[i]=0; } fun1(string,substr,n) char *string,*substr; int n; { while(n--) *subsrt++=*string++; /*CO TO JEST?? */ *sub=0; } Obie funkcje są IDENTYCZNE. W pierwszej (po lewej) operujemy na elemen- tach tablic. W drugiej - na wskaźnikach. Co oznacza while(n--)? Jeśli w programie głównym chcemy wywołać funkcję kopiującą n znaków i „zabezpieczyć” się przed przypadkiem, w którym łańcuch string jest krótszy niż n, to możemy np. zastosować instrukcję: if(!fun1(string,substr,n)) printf(“ nie mogę kopiowac\n”); Wytłumacz co te instrukcje znaczą.
Zadanie 15. Funkcja len(string) zwraca długość łańcucha. Posługując się podanymi przykładami napisz analogiczne dwie funkcje kopiujące „od końca” n elementów łańcucha string na łańcuch substr Oto funkcja len(s) len(s) char *s; { char *b; b=s; while(*s++) ; return(s-begin-1); }
WSKAŹNIKI ZNAKOWE A FUNKCJE Stała tekstowa “ala ma kota” jest tablicą znakową. Pamiętaj, że w repre- zentacji wewnętrznej kompilator “dopisuje” znak \0 . Długość tekstu jest więc o 1 większa niż sam tekst. Znak \0 jest znakiem końca tekstu. W języku C nie istnieją operatory obsługi tekstów jako całość; nie ma też typu “pascalowskiego” string. Jeżeli zmienna zdanie zadeklarowana jest jako char *zdanie; to instrukcja zdanie= “ala ma kota”; przypisuje jej WSKAŹNIK do podanego tekstu. NIE jest to „kopiowanie” tekstu - zaangażowane są jedynie wskaźniki. Ponieważ zagadnienie to jest bardzo ważne dla zrozumienia języka, rozważmy jeszcze przykład: napiszemy funkcje kopiowania tekstu t na tekst s na różne sposoby nie możemy napisac: char zdanie[13]; zdanie= “ala ma kota”; DLACZEGO?
copy(s,t) char *s,*t; { int i=0; while((s[i]=t[i])!=‘\0’) i++ } copy(s,t) char s[],t[]; { int i=0; while((s[i]=t[i])!=‘\0’) i++ } to wersja „klasyczna” to wersja „wskaźnikowa” a to wersja „uproszczona wskaźnikowa” copy(s,t) char *s,*t; { while((*s++=*t++)!=‘\ 0’) ; }
Jeden z najczęściej czynionych błędów to przypisywanie wskaźników obiektom całkowitym. Pamiętaj: WSKAŹNIKI NIE SĄ (OGÓLNIE) OBIEKTAMI CAŁKOWITYMI TABLICE WIELOWYMIAROWE np. int tab[2][10]; definicja tablicy o elementach 00,01,02,03,...09,10,11,...19 wywołanie: tab[i][j] gdzie 0<=i<=1;0<=i<=9. INACZEJ NIŻ W PASCALU, gdzie było: tab[i.j]!!! <- ŹLE W C!! Nadawanie wartości początkowej tablicy tab[2][10]: int tab[2][10]= { {1,12,14,21,1,3,5,5,8,91}, {2,15,21,28,3,5,2,1,1,100} };
Jeśli tablica dwu (lub więcej) wymiarowa ma być przekazywana funkcji to w deklaracji parametrów funkcji MUSIMY podać rozmiar kolumn, np. w main mamy float tab[3][100]; i mamy funkcje f(tabela) to w deklaracji musi być lub float f(tabela) float(tabela) float tabela[3][100]; float tabela[][100]; { { ale NIE MOŻE BYĆ float tabela [][]; !!! to po prawej stronie może być również: float (*tabela)[100]; ale NIE MOŻE BYĆ: float *tabela[100] !!!! Nawias [] ma WYŻSZY priorytet niż *, a więc ostatnia instrukcja deklaruje tabelę 100 wskaźników do float, a nie tabele [][100] !
przykład funkcji, która z podania roku. miesiąca i dnia (np. 1977 3 13) wyznacza który był to dzień w roku na począstku programu (zewnętrznie do funkcji) deklarujemy stat int dni_miesiac[2][13]= { {0,31,28,31,30,31,30,31,31,3031,30,31}, {0,31,29,31,30,31,30,31,31,3031,30,31} }; dzien_roku(rok,miesiac,dz) int rok,miesiac,dz {int i,j; j=rok%4==0&&rok%100!=0||rok%400==0; /*czyli j=1 jeśli prawa strona jest prawdą (rok przestepny) i 0 jeśli fałsz*/ for(i=1;i<month;i++) dz=dz+dni_miesiac[j][i]; return(dz); }
ZADANIE 16 do wyboru: a lub b a) napisz funkcje, która na podstawie roku i numeru dnia w roku wyznacza miesiąc i dzień miesiąca (tj. funkcje „odwrotną” do ostatniego przykładu. b) w szkole są klasy 1a, 1b,1c,2a,2b,3a,3b,3c,4a i 4b. Liczby uczniów w klasach wynoszą kolejno: 25,24,25,29,31,26,24,25,31,30 Zakładając, że spis uczniów w szkole sporządzony jest nastepująco najpiew wszyscy z 1a, numery od 1 do 25; nastepnie z 1b - numery od 26 do 49, ..... itd., aż do klasy 4b, napisz program, który określi pod którym numerem w szkole jest np.. uczeń 20 z klasy 3c, itp..
TABLICE WSKAŹNIKÓW pamiętamy o równoważności: tablica (jednowymiarowa) <-> wskaźnik zatem wskaźniki zebrane w tablicę jednowymiarową odpowiadają tablicy dwuwymiarowej. Jeśli mamy deklaracje int a[10][10]; int *b[10]; to oba odwołania do elementów: a[5][5] oraz b[5][5] są poprawne. Różnica pomiędzy nimi jest analogiczna jak w przypadku nieindeksowanych wskaźników i tablic jednowymiarowych. Tablica*b[10] wprowadza 10 wskażników; każdy z nich wskazuje na obiekt całkowity, na tablicę o elementach całkowitych. Jednak jedynie a[10][10] jest prawdziwą tablicą 1oo-elementową: wszystkie komórki są dostępne w „tradycyjny” („pascalowski”) sposób.
INICJOWANIE WSKAŹNIKÓW. przykład funkcji, która zwraca nazwę miesiąca na podstawie jego numeru (1 - styczeń, 2 - luty, itd.) char *nazwa_miesiaca(n) int n; { static char *name[]= { “zly miesiac”,”styczeń”,”luty”,”marzec”,kwiecień”,”maj”,”czerwiec”, “lipiec”,”wrzesien”,”pazdziernik”,”listopad”,”grudzień” }; return((n<1||n>12))?name[0]:name[n]); } ZADANIE 17 Program z zadania 16a zmodyfiukuj tak, aby zamiast numeru miesiaca podawał jego nazwe