User Tools

Site Tools


dydaktyka:cprog:2016:pointers

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
dydaktyka:cprog:2016:pointers [2017/01/03 09:02]
pkleczek [Zadanie PAL]
— (current)
Line 1: Line 1:
-====== Wskaźniki ====== 
  
-===== Wskaźnik – czym to się je?  ===== 
- 
-Wskaźnik (//​pointer//​) to zmienna, której wartość jest adresem w pamięci (podobnie jak np. wartością zmiennej typu int jest liczba całkowita). 
- 
-==== Deklaracja ==== 
- 
-Deklaracja wskaźnika: 
-<​code>​ 
-<​typ_wskazywanego_obiektu_danych>​* nazwa_wskaznika;​ 
-</​code>​ 
- 
-Znak ''​*''​ w tym przypadku informuje kompilator, że chodzi o zmienną wskaźnikową,​ a nie zwykłą zmienną. 
- 
-Przykład: 
-<code c> 
-char* pc; 
-</​code>​ 
- 
-Mówimy, że ''​pc''​ to "​wskaźnik do zmiennej typu char" (bądź w skrócie: "​wskaźnik do char"​). 
- 
-==== Inicjalizacja ==== 
- 
-Adres, pod którym w pamięci znajduje się zmienna, można uzyskać używając jednoargumentowego operatora ''&'',​ zatem wskaźnikowi przypisujemy adres w następujący sposób: 
-<code c> 
-int n = 11; 
-int* ptr = &n; 
-</​code>​ 
-Mówimy, że wskaźnik ''​ptr''​ "​wskazuje na" zmienną ''​n''​. 
- 
-Oto schemat pokazujący,​ jak wygląda pamięć komputera po takiej operacji: \\ 
-{{:​dydaktyka:​cprog:​2015:​pointer.png?​nolink|}} 
- 
- 
-:!: Nazwa tablicy jest równocześnie adresem jej pierwszego elementu -- stąd jeśli ''​tab''​ jest tablicą, prawdziwe jest następujące stwierdzenie:​ ''​tab == &​tab[0]''​. 
- 
-==== Wyświetlanie ==== 
- 
-Do wypisania wartości wskaźnika -- a więc adresu w pamięci -- można użyć specyfikatora konwersji ''​%p'',​ który wyświetla wartość w kodzie szesnastkowym (taka zwykło się podawać adresy): 
-<code c> 
-int n = 5; 
-int* ptr = &n; 
- 
-printf("​n:​ wartosc = %d , adres = %p\n", n, &n); 
-printf("​ptr:​ wartosc = %d badz rownowaznie %p\n", ptr, ptr); 
-</​code>​ 
-Oczywiście,​ ponieważ adres to po prostu liczba całkowita, można równie dobrze użyć specyfikatora ''​%d''​. 
-==== Uzyskiwanie i zmiana wartości wskazywanej ==== 
- 
-Aby uzyskać wartość przechowywaną pod danym adresem, korzystamy z jednoargumentowego operatora dereferencji ''​*''​ (ang. //​dereference//​),​ zwanego w żargonie //​operatorem wyłuskiwania//:​ 
-<code c> 
-int n = 3; 
-int* p_int = &n; 
-printf("​adres = %p , wartosc = %d\n", p_int, *p_int); 
-</​code>​ 
- 
-:!: Nie pomyl jednoargumentowego operatora dereferencji z dwuargumentowym operatorem arytmetycznym mnożenia -- oba oznaczane są tym samym symbolem ''​*''​. 
- 
-Operator dereferencji ''​*''​ jest //​operatorem odwrotnym// dla operatora adresu ''&'',​ zatem ''​p = *&​q;''​ jest równoważne ''​p = q;''​ 
- 
-Wartość wskazywaną można zmienić w następujący sposób: 
-<code c> 
-int n = 1; 
-int* ptr = &n; 
-*ptr = 3; 
-// wartosc `n` wynosi teraz 3 
-</​code>​ 
- 
-...i jeszcze jeden przykład na zmianę przeprowadzoną w identyczny sposób: 
-<code c> 
-int i = 1; 
-int j = 2; 
-int *p, *q; 
-  
-p = &i; 
-q = p; // obie zmienne `p` i `q` wskazuja na `i` 
- 
-q = &j; // teraz `p` i `q` wskazuja na rozne zmienne 
- 
-*q = *p; // przypisz wartosc wskazywana przez `p` w miejsce wskazywane 
-         // ​  przez `q` - wartosc `j` wynosi teraz 1 
-</​code>​ 
- 
-:!: Przypisanie ''​q = p''​ nie jest tym samym, co ''​*q = *p''​! 
- 
-==== Wskaźnik pusty ==== 
- 
-Wskaźnik nazywamy pustym (//null pointer//), gdy jego wartość wynosi 0. Należy pamiętać, że wskaźnik pusty (zerowy) nie może nigdy wskazywać danych uznawanych za poprawne. Aby zainicjalizować wskaźnik pusty wystarczy przypisać mu 0. (To będzie nam później przydatne...) 
- 
-==== Zadania ==== 
- 
-=== Zadanie CHG === 
- 
-Uzupełnij poniższy szkielet programu: 
-  * zdefiniuj funkcję ''​zamien()'',​ która umożliwi zamianę ze sobą wartości dwóch zmiennych typu całkowitego,​ oraz 
-  * uzupełnij wywołanie funkcji ''​zamien()''​ wewnątrz funkcji ''​main()''​ (podaj odpowiednie argumenty) 
- 
-<code c> 
-#include <​stdio.h>​ 
-#include <​stdlib.h>​ 
- 
-/* miejsce na definicje funkcji "​zamien()"​ */ 
- 
-int main() { 
-    int a = 4; 
-    int b = 5; 
-    ​ 
-    zamien(/* tu wstaw odpowiednie argumenty */); 
-    ​ 
-    if (a == 5 && b == 4) { 
-        printf("​OK\n"​);​ 
-    } else { 
-        printf("​Cos poszlo nie tak...\n"​);​ 
-    } 
-    ​ 
-    return 0; 
-} 
-</​code>​ 
-Jak się zapewne domyślasz, konieczne będzie użycie wskaźników... 
- 
-:!: Dla powtórzenia przeczytaj uważnie trzeci punkt z **[[dydaktyka:​cprog:​2016:​functions#​jak_to_wszystko_dziala|tej listy]]**. 
-===== Arytmetyka wskaźników ===== 
- 
-Komputery PC są adresowalne bajtowo (tzn. wszystkie bajty pamięci są ponumerowane w rosnącej kolejności,​ a zatem każda lokalizacja w pamięci ma swój niepowtarzalny adres). Adresem obiektu zajmującego kilka bajtów (np. typu ''​int''​) jest adres (numer) pierwszego bajtu. 
- 
-Dodanie liczby całkowitej do wskaźnika zwiększa jego wartość o odpowiednią wielokrotność rozmiaru (**w bajtach**) wskazywanego obiektu (analogicznie:​ odejmowanie zmniejsza wartość): 
-<code c> 
-#include <​stdio.h>​ 
-#include <​stdlib.h>​ 
- 
-#define ROZMIAR 4 
- 
-int main () 
-{ 
-    short tab_short[ROZMIAR];​ 
-    double tab_double[ROZMIAR];​ 
- 
-    short* ptr_short = tab_short; 
-    double* ptr_double = tab_double; 
- 
-    int index; 
- 
-    printf("​%25s %10s\n",​ "​short",​ "​double"​);​ 
-    for (index = 0; index < ROZMIAR; index = index + 1) { 
-        printf("​wskazniki + %d: %10p %10p\n",​ 
-               ​index,​ ptr_short + index, ptr_double + index); 
-    } 
- 
-    return 0; 
-} 
-</​code>​ 
-Jak widzisz, dodanie ''​1''​ do wskaźnika na typ ''​short''​ zwiększa wartość wskaźnika (czyli adres) o mniej niż w przypadku wskaźnika na typ ''​double''​ -- dzieje się tak, ponieważ typ ''​short''​ zajmuje mniej bajtów pamięci niż typ ''​double''​. 
- 
-Poprzez odjęcie jednego wskaźnika od drugiego otrzymuje się przesunięcie między wskaźnikami,​ wyrażone w jednostce o rozmiarze typu (np. dla wskaźników typu ''​int'',​ jeśli ''​p2 – p1 == 2'',​ to między ''​p1''​ i ''​p2''​ znajdują się dwie wartości typu ''​int''​ -- a nie dwa bajty!). Przesunięcie może mieć wartość ujemną. 
-<code c> 
-#include <​stdio.h>​ 
-#include <​stdlib.h>​ 
- 
-int main () 
-{ 
-    int tab[] = {1, 2, 3}; 
-    int* p1 = tab; 
-    int* p2 = tab + 2; 
- 
-    printf("​p1 = %p\n" 
-           "​p2 = %p\n" 
-           "​p1 - p2 = %d\n" 
-           "​p2 - p1 = %d\n", 
-           p1, p2, p1 - p2, p2 - p1); 
- 
-    return 0; 
-} 
-</​code>​ 
- 
-Operacja jest przydatna do obliczania wielkości tablicy (długości łańcucha znaków), jeżeli mamy wskaźnik na jej pierwszy i ostatni element. 
- 
-:!: Komputer nie sprawdza do jakiej zmiennej należy pamięć, po której aktualnie piszemy (dopóki znajdujemy się w pamięci naszego programu)! 
- 
-===== Wskaźniki a tablice ===== 
- 
-Dla powtórzenia -- nazwa tablicy jest równocześnie adresem jej pierwszego elementu, stąd: 
-  * ''​tab + 2 == &​tab[2]''​ -- ten sam adres 
-  * ''​*(tab + 2) == tab[2]''​ -- ta sama wartość 
- 
-:!: Ze względu na kolejność operatorów ''​*(tab + 2)''​ różni się od ''​*tab + 2''​. Odpowiednio,​ w pierwszym przypadku otrzymujemy wartość trzeciego elementu, a w drugim -- dodajemy 2 do wartości pierwszego elementu. 
- 
-Z punktu widzenia C notacja tablicowa i wskaźnikowa są równoważne,​ ale użycie notacji tablicowej pokazuje intencje programisty. 
- 
-:!: Komputer nie sprawdza, czy po wykonaniu operacji arytmetycznej na wskaźniku wciąż znajdujemy się w obszarze pamięci tablicy! Programista musi o to zadbać sam. 
- 
-:!: Nie można (tzn. standard języka C tego nie definiuje) skonstruować wskaźnika wskazującego gdzieś poza zadeklarowaną tablicę. Wyjątkiem jest wskaźnik na obiekt tuż za ostatnim elementem tablicy (//one past last//). 
- 
-===== Wskaźniki a funkcje ===== 
- 
-Przekazywanie wskaźników do zmiennych jako parametrów funkcji umożliwia zmienianie wartości tych zmiennych podczas wykonywania funkcji. 
- 
-Do funkcji przekazujemy wartości zmiennej, chyba że występuje konieczność zmiany jej wartości wewnątrz funkcji (wtedy przekazujemy wskaźnik). \\ 
-W przypadku tablic __musimy__ przekazać wskaźnik -- to wymóg języka związany z wydajnością (w przeciwnym razie trzeba by zarezerwować tyle miejsca w pamięci, by zmieścić kopię całej tablicy). 
- 
----- 
- 
-Oto przykład funkcji rozkładającej liczbę rzeczywistą na część całkowitą i ułamkową: 
-<code c> 
-void rozloz(double x, int* czesc_calkowita,​ double* czesc_ulamkowa) 
-{ 
-    *czesc_calkowita = (int) x; 
-    *czesc_ulamkowa = x - *czesc_calkowita;​ 
-} 
-</​code>​ 
- 
-Funkcja korzysta z **operatora rzutowania**. Operator rzutowania ''​(typ)''​ umożliwia wyraźne określenie zamiany jednego typu na inny (tj. rzutowania; ang. //​cast//​). ​ 
-W tym przypadku ''​(int)''​ mówi "​potraktuj wartość stojącą po prawej stronie jak wartość typu całkowitego"​ (co powoduje pominięcie części ułamkowej). 
- 
-Jej wywołanie wygląda następująco:​ 
-<code c> 
-double x = 3.14; 
-int i; 
-double f; 
- 
-rozloz(x, &i, &f); 
- 
-printf("​%.2f = %d + %.2f\n",​ x, i, f);  // 3.14 = 3 + 0.14 
-</​code>​ 
- 
----- 
- 
-Teraz pewnie rozumiesz, dlaczego przekazując zmienną do funkcji ''​scanf()''​ poprzedzaliśmy ją operatorem ''&''​ -- chcieliśmy przekazać jej adres w pamięci, a nie przechowywaną wartość. W ten sposób funkcja ''​scanf()''​ mogła zmienić wartość zmiennej zgodnie z wczytanymi wartościami:​ 
-<code c> 
-int n; 
-scanf("​%d",​ &n); 
-</​code>​ 
-===== Zadania podsumowujące ===== 
- 
-W poniższych zadaniach **nie korzystaj** z notacji tablicowej ''​[]''​! 
-==== Zadanie LEN ==== 
- 
-([size=10]poziom trudności:​[/​size] {{stars>​1/​4}}) 
- 
-Napisz funkcję ''​dlugosc()'',​ która obliczy długość [[dydaktyka:​cprog:​2015:​arrays#​lancuchy_znakow|łańcucha znaków]]. Nie korzystaj z funkcji ''​strlen()'':​ \\ 
-<code c> 
-int dlugosc(char* str); 
-</​code>​ 
- 
-Przetestuj kod następującym programem: 
-<code c> 
-#include <​stdio.h>​ 
-#include <​stdlib.h>​ 
- 
-int dlugosc(char* str) 
-{ 
-    // TODO: uzupelnij 
-} 
- 
-int main () 
-{ 
-    char str[] = "Jaka mam dlugosc?"; ​ // lancuch dlugosci 17 znakow 
-    printf("​Dlugosc lancucha \"​%s\"​ wynosi %d.\n",​ str, dlugosc(str));​ 
- 
-    return 0; 
-} 
-</​code>​ 
- 
-//​Wskazówka:​ Pamiętaj, że łańcuch znaków kończy się znakiem zerowym -- znalezienie położenia tego znaku wystarczy do określenia długości całego łańcucha.//​ 
- 
-==== Zadanie DIS ==== 
- 
-([size=10]poziom trudności:​[/​size] {{stars>​2/​4}}) 
- 
-Napisz funkcję, która przy pomocy wskaźników wyświetli dane znajdujące się w tablicy liczb całkowitych. \\ 
-Użyj [[dydaktyka:​cprog:​2015:​arrays#​tablice_bezwymiarowe|tablicy bezwymiarowej]],​ a rozmiar określ za pomocą operatora [[dydaktyka:​cprog:​2015:​arrays#​operator_sizeof|sizeof()]]. 
- 
-Deklaracja funkcji powinna wyglądać następująco:​ 
-<code c> 
-void wyswietl(int* poczatek, int* koniec); 
-</​code>​ 
-gdzie ''​poczatek''​ to wskaźnik na pierwszy element tablicy, a ''​koniec''​ -- wskaźnik na obiekt tuż za ostatnim elementem tablicy. 
- 
-==== Fotoszop ==== 
- 
-([size=10]poziom trudności:​[/​size] {{stars>​2/​4}}) 
- 
-Wykonaj zadania w ramach polecenia [[dydaktyka:​cprog:​2015:​photoshop:​exercises:​data_un_packing|Umieszczanie bajtów obrazu w tablicy]]. 
- 
- 
- 
-==== Zadanie OFF ==== 
- 
-([size=10]poziom trudności:​[/​size] {{stars>​2/​4}}) 
- 
-Wyświetl ilość komórek pamięci między zmiennymi ''​v1''​ i ''​v2''​. Nie odejmuj wskaźników (gdyż w tym przypadku nie jest to dozwolone). 
- 
-<code c> 
-#include <​stdio.h>​ 
-#include <​stdlib.h>​ 
- 
-int main () 
-{ 
-    int v1; 
-    double x; 
-    int v2; 
- 
-    // TODO: uzupelnij 
- 
-    return 0; 
-} 
-</​code>​ 
- 
-// Wskazówka: Pamiętaj, że adres to po prostu liczba całkowita.//​ 
- 
-==== Zadanie PAL ==== 
- 
-([size=10]poziom trudności:​[/​size] {{stars>​4/​4}}) 
- 
-Napisz funkcję ''​palindrom()'',​ która **rekurencyjnie** (bez użycia pętli!) sprawdzi, czy dany łańcuch znaków jest [[https://​pl.wikipedia.org/​wiki/​Palindrom|palindromem]]:​ 
-<code c> 
-int palindrom(char* str); 
-</​code>​ 
-Funkcja powinna zwrócić wartość ''​1'',​ jeśli łańcuch znaków jest palindromem,​ a w przeciwnym przypadku ''​0''​. 
- 
-//​Wskazówka 1: Zapisz __słownie__ rekurencyjną definicję palindromu (analogicznie jak to było np. z silnią czy potęgowaniem). Nie myśl w kategoriach programu w języku C.// \\ 
-//​Wskazówka 2: Napisz pomocniczą funkcję o nagłówku zbliżonym do funkcji ''​wyswietl()''​ z [[dydaktyka:​cprog:​2015:​pointers#​zadanie_dis|zadania DIS]] -- operowanie na zakresie wskaźników będzie pomocne.// 
dydaktyka/cprog/2016/pointers.1483430535.txt.gz · Last modified: 2020/03/25 11:46 (external edit)