Język C – zajęcia nr 9
Transkrypt
Język C – zajęcia nr 9
Język C – zajęcia nr 9 I. Operatory języka C – ciąg dalszy Operator indeksowania Dwuargumentowy operator indeksowania [ ] jest operatorem sumowania: wylicza sumę swoich argumentów i dostarcza wartości obszaru pomięci, na który wskazuje uzyskana suma. Jednym z argumentów musi być wyrażenie mające wartość wskaźnika, a drugim wyrażenie o wartości całkowitej. Wyrażenie postaci: a[b] ma wartość: *( a+b ) Operator indeksowania umożliwia między innymi dostęp do elementów tablicy. Ponieważ nazwa tablicy jest równocześnie stałym wskaźnikiem na jej początek, wartością wyrażenia: tab [ k ] jest wartość elementu o numerze k tablicy tab. Uwaga: Z racji przemienności operacji indeksowania wyrażenie tab[k] jest w pełni równoważne wyrażeniu k[tab], czyli x[5] ma tę samą wartość, co 5[x]. Operator sizeof Użycie operatora sizeof może mieć formę: sizeof zmienna gdzie zmienna jest nazwą zmiennej dowolnego typu lub formę: sizeof ( typ ) gdzie typ jest nazwą typu. Wartością wyrażeń z operatorem sizeof jest liczba bajtów przeznaczonych w pamięci dla podanego jako argument obiektu lub typu. Wprowadź i uruchom program, zinterpretuj wyniki: #include <stdio.h> void main() { char a; int b; long int c; float d; double e; long double f; int g[12]; printf("\n%d %d %d %d %d %d %d",sizeof a, sizeof b, sizeof c, sizeof d, sizeof e, sizeof f, sizeof g); printf("\n%d %d",sizeof(int), sizeof(double)); } 1 2 4 4 8 10 24 2 8 Operator adresu Jednoargumentowy operator & zastosowany do l-wartości lub nazwy funkcji dostarcza adresu swojego argumentu. Wyrażenie postaci: & obiekt ma wartość adresu argumentu obiekt. Operator wyłuskania Jednoargumentowy operator wyłuskania * dostarcza wartości (zawartości) obszaru pamięci na który wskazuje argument operatora. Jeżeli wartością zmiennej p jest wskaźnik na obiekt q, to wyrażenie *p ma wartość obiektu q. Operator rzutowania Jawne wymuszenie konwersji typów dokonywane jest poprzez operator rzutowania mający postać: ( typ ) gdzie typ jest nazwą typu. Wyrażenie: ( typ ) arg ma wartość, jaka powstaje z wartości argumentu arg przekształconej do typu typ. Wprowadź i uruchom program, zinterpretuj wyniki: #include <stdio.h> #include <math.h> void main() { float x; scanf("%f",&x); printf("\n%f",sqrt(x)); printf("\n%d",(int)sqrt(x)); } 18.4 4.289522 4 KLAWIATURA 18.4 Priorytety i wiązania operatorów (wykaz kompletny): Priorytet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Operator ( ) [ ] . -> + ~ ! ++ -* & sizeof ( ) * / % + << >> < <= > >= == != & ^ | && || ?: = += -= *= /= %= &= |= <<= >>= ^= , Wiązanie Lewostronne Lewostronne Lewostronne Lewostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Lewostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Prawostronne Lewostronne Nazwa operatora Wywołanie funkcji Indeksowanie Wybór składowej Wybór wskaźnikowy składowej Jednoargumentowy plus Jednoargumentowy minus Bitowa negacja Logiczna negacja Inkrementacja Dekrementacja Wyłuskanie Pobranie adresu Rozmiar Rzutowanie Mnożenie Dzielenie Modulo Dodawanie Odejmowanie Bitowe przesunięcie w lewo Bitowe przesunięcie w prawo Relacja mniejszy Relacja mniejszy lub równy Relacja większy Relacja większy lub równy Równy Różny Bitowa koniunkcja Bitowa różnica symetryczna Bitowa alternatywa Logiczna koniunkcja Logiczna alternatywa Warunek Przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Złożony przypisania Połączenie Uwaga: Wiązanie prawostronne mają jedynie operatory: jednoargumentowe, warunku i przypisania. Pozostałe operatory mają wiązanie lewostronne. Uwaga: Pozostałe operatory: bitowe, operator składowej, wywołania funkcji oraz połączenia – zostaną omówione w dalszej części kursu. II. Dynamiczna alokacja pamięci Język C umożliwia tworzenie tablic o dowolnym rozmiarze, określanym w trakcie wykonywania programu. Jest to możliwe dzięki dynamicznej alokacji pamięci z zastosowaniem wskaźników. Zmienne lokalne zdefiniowane w programie wewnątrz tzw. bloku (blok funkcyjny zawiera ujęte w nawiasy klamrowe { } instrukcje wykonywane przy wywołaniu funkcji) przechowywane są na tzw. stosie (ang. stack). Zmienne takie powstają, gdy program wchodzi do bloku, w którym są one zadeklarowane; natomiast w momencie, gdy program opuszcza ten blok, zmienne te są likwidowane a przeznaczona na nie pamięć jest zwalniana. Rozmiar tablic musi być znany w momencie kompilacji - aby kompilator zarezerwował odpowiednią ilość pamięci. Dostępny jest jednak inny rodzaj rezerwacji (czyli alokacji) pamięci – tzw. dynamiczna alokacja 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 samemu rezerwować dla nich miejsce i to miejsce zwalniać, ale dzięki temu można to zrobić w dowolnym momencie działania programu. Operacje na stercie zajmują więcej czasu niż analogiczne działania na stosie; ponadto zmienna zajmuje na stercie więcej miejsca niż na stosie. Należy zatem używać dynamicznej alokacji tam, gdzie jest ona rzeczywiście potrzebna - dla obiektów, których rozmiaru nie jesteśmy w stanie przewidzieć na etapie kompilacji lub ich żywotność ma być niezwiązana z wyłącznie z pojedynczym blokiem. Podstawową funkcją do alokacji pamięci jest funkcja malloc. Zwraca ona wskaźnik do zaalokowanego obszaru. malloc(rozmiar_pamieci_w_bajtach) Definicje funkcji związanych z dynamiczną alokacją pamięci znajdują się w pliku stdlib.h (należy go dołączyć za pomocą odpowiedniej dyrektywy preprocesora). Przykład: należy stworzyć n-elementową tablicę liczb typu double: int n; double *tab; scanf(“%d”,&n); tab = (double*) malloc(n * sizeof(*tab)); *(tab+n-1)=2.; ………… Należy także sprawdzić, czy funkcja malloc nie zwróciła wartości NULL - tak się dzieje, gdy zabrakło pamięci (lub jeżeli jako argument funkcji podano zero). Jeśli zaalokowany obszar pamięci nie będzie już więcej potrzebny, należy go zwolnić, aby był dostępny dla innych procesów. Do zwolnienia obszaru należy zastosować funkcję free(), która posiada jeden argument - wskaźnik, który wygenerowała funkcja malloc(). free (addr); Należy pamiętać o zwalnianiu niepotrzebnej pamięci, w przeciwnym wypadku może jej zabraknąć. Należy też uważać, by nie zwalniać dwa razy tego samego miejsca. Błędem jest też zwalnianie miejsca, które nie zostało wcześniej zaalokowane! Po wywołaniu free wskaźnik nie zmienia wartości, aby przypadkiem go wtedy nie użyć, można mu przypisać wartość NULL. scanf(“%ld”,&n); tab = (double*) malloc(n * sizeof(*tab))); if (tab==NULL) {printf(“\nBLAD!!”); exit(1);}; ………… free(tab); tab=NULL; Jeżeli istnieje potrzeba zmiany rozmiaru już przydzielonego bloku pamięci, można zastosować funkcję realloc: tab = realloc(tab, 2*n*sizeof *tab); Funkcja ta zwraca wskaźnik do bloku pamięci o pożądanej wielkości (lub NULL gdy zabrakło pamięci). Uwaga - może to być inny wskaźnik. Jeśli zażądamy zwiększenia rozmiaru a za zaalokowanym aktualnie obszarem nie będzie wystarczająco dużo wolnego miejsca, funkcja znajdzie nowe miejsce i przekopiuje tam starą zawartość. Do dynamicznej alokacji pamięci może też posłużyć funkcja calloc(). Ma ona dwa argumenty: liczbę elementów tablicy oraz wielkość pojedynczego elementu. Podstawową różnicą pomiędzy funkcjami malloc() i calloc() jest to, że calloc() zeruje wartość przydzielonej pamięci (do wszystkich bajtów wpisuje 0). tab = (int *) calloc(n, sizeof(int)); III. Zadania 1. Wczytaj z klawiatury ciąg n liczb rzeczywistych i wypisz te liczby w kolumnie w kolejności odwrotnej. Zastosuj dynamiczną alokację pamięci. 2. Wczytaj z klawiatury ciąg n znaków i oblicz, ile razy wystąpił w tym ciągu ostatni (n-ty) znak.