640 likes | 897 Views
Procedur álne programovanie: 5 . prednáška. Gabriela Kosková. Obsah. opakovanie (2 p ríklady) ukazovatele príklady (3). #include<stdio.h> #define PI 3.14 #define obvod_m(r) (2 * PI * (r)) #define obsah_m(r) (PI * (r) * (r)) double obvod_f(double r) { return 2 * PI * r; }
E N D
Procedurálne programovanie:5. prednáška Gabriela Kosková
Obsah • opakovanie (2 príklady) • ukazovatele • príklady (3)
#include<stdio.h> #define PI 3.14 #define obvod_m(r) (2 * PI * (r)) #define obsah_m(r) (PI * (r) * (r)) double obvod_f(double r) { return 2 * PI * r; } double obsah_f(double r) { return PI * r * r; } Príklad 1 Program vypočíta obvod a obsah kruhu pomocou makier a funkcií int main() { double polomer; printf("Zadajte polomer kruhu: "); scanf("%lf", &polomer); printf("\nMakro: obvod: %.2f, obsah: %.2f\n", obvod_m(polomer), obsah_m(polomer)); printf("Funkcie: obvod: %.2f, obsah: %.2f\n", obvod_f(polomer), obsah_f(polomer)); return 0; }
Príklad 2 Program vypíše obsah súboru naopak.
void vypis(FILE *f) { int c; if ((c=getc(f)) != EOF) { vypis(f); putchar(c); } } #include<stdio.h> int main() { int c; FILE *f; if ((f = fopen("subor.txt", "r")) == NULL) { printf("Nepodarilo sa otvorit subor.\n"); return 1; } vypis(f); if (fclose(f) == EOF) { printf("Nepodarilo sa zatvorit subor.\n"); return 1; } return 0; }
zásobník Použitá rekurzia void vypis(FILE *f) { int c; if ((c=getc(f)) != EOF) { vypis(f); putchar(c); } } volanie vypis c: 'j' volanie vypis c: 'o' volanie vypis c: 'h' volanie vypis c: 'a' volanie vypis c: EOF a h výpis c: 'a' joha obsah súboru: výpis c: 'h' o výpis c: 'o' j výpis c: 'j' h výstup programu: a o j
Čo sú to ukazovatele = pointery, smerníky • ukazovateľ • je premenná • jeho hodnota je adresa v pamäti • analógia: v texte článku nie je informácia priamo uvedená, ale je tam odkaz na nejaký iný článok, kde sa informácia nachádza
Príklad ukazovateľa pamäť • ukazovateľ p: • zapísaný na adrese 73 • jeho hodnota je 30 a vyjadruje adresu, kde je uložená skutočná hodnota • na adrese 30 v pamäti je hodnota 25, ktorá sa použije napr. pri výpočtoch • p: ukazovateľ • *p: hodnota, kam ukazuje *p: 30 25 p: 73 30
Ako poznáme ukazovateľ • ukazovateľ je definovaný pomocou * • int i- „klasická“ celočíselná premenná • int *p_i - ukazovateľ na celočíselnú premennú • definícia ukazovateľa: int i; int *p_i; int i, *p_i; je ekvivalentné
int i, p_i; ... *p_i = i; 7 7 Čo urobí *p_i = i; pamäť • ukazovateľ p_i • p_i == 30 • *p_i== 25 7 i: 28 *p_i: 30 25 • obsah pamäte, na ktorú ukazuje p_i sa prepíše hodnotou premennej i • p_i ukazuje na to isté miesto v pamäti treba inicializovať p_i, napr. alokovať pamäť pre p_i p_i: 73 30
int i, *p_i = &i; je ekvivalentné Ako získame adresu premennej • pomocou referenčného operátora & • int i- „klasická“ celočíselná premenná • &i – adresa premennej • definícia ukazovateľa: definícia ukazovateľaa súčasne inicializácia int i, *p_i; p_i = &i;
Čo urobí p_i = &i; pamäť • ukazovateľ p_i • p_i == 30 • *p_i == 25 int i, *p_i; p_i = &i; 7 i: 28 *p_i: 30 25 • hodnota p_i (adresa, kam p_i ukazuje) sa prepíše adresou premennej i • hodnota *p_i je tá istá ako hodnota i p_i: 73 30
Čo urobí p_i = &i; pamäť • ukazovateľ p_i • p_i == 30 • *p_i == 25 int i, *p_i; p_i = &i; 28 7 i: 28 7 *p_i: 30 25 • hodnota p_i (adresa, kam p_i ukazuje) sa prepíše adresou premennej i • hodnota *p_i je tá istá ako hodnota i p_i: 73 28 30
Príklady - správne p_i = &i; - chyba: (i + 3) nie je premenná p_i = &(i + 3); - chyba: konštanta nemá adresu p_i = &15; - chyba: priraďovanie absolútnej adresy p_i = 15; - chyba: priraďovanie adresy i = p_i; - chyba: priraďovanie adresy i = & p_i; -správne, ak p_i bol inicializovaný *p_i = 4;
Výpis adresy (pri ladení) • špecifikácia formátu (v printf()): %p int i, *p_i; p_i = &i; printf(„Adresa i: %p, hodnota p_i: %p\n”, &i, p_i);
Keď ukazovateľ neukazuje nikam • Nulový ukazovateľ: NULL • NULL - symbolická konštanta definovaná v stdio.h: • #define NULL 0 • #define NULL ((void *) 0) • Je možné priradiť ho ukazovateľom na ľubovoľný typ if (p_i == NULL) ...
Konverzia ukazovateľov • Vyhnúť sa jej! • Ak sa nedá vyhnúť – explicitne pretypovávať int *p_i; char *p_c; p_c = p_i; p_c = (char *)p_i; nevhodné vhodnejšie
Funkcie: volanie odkazom • V C nie je volanie odkazom, funkcie sa volajú len hodnotou • Vo funkcii vzniká kópia argumentu funkcie (lokálna premenná), ktorá zaniká s ukončením funkcie • Preto sa funkcia nevolá s premennou, ktorú chceme meniť, ale s jej adresou
spustenie programu, volanie main() vytvorí sa kópia volanie A() spustenie A(3) koniec A() návrat do main() koniec programu, main() Parametre funkcií - volanie hodnotou: int A(int i) dátová oblasť 3 3 4 zásobník
spustenie programu, volanie main() adresa: 15 volanie A() spustenie A(15) koniec A() návrat do main() koniec programu, main() Parametre funkcií - volanie odkazom : int A(int *i) dátová oblasť 4 3 adresa premennej 15 zásobník
p_x p_y pom: Príklad funkcie: výmena premenných • volanie funkcie:vymen(&i, &j) void vymen(int *p_x, int *p_y) { int pom; pom = *p_x; *p_x = *p_y; *p_y = pom; } i: 5 j: 7 7 5 5
Príklad funkcie: výmena premenných • volanie funkcie:vymen(&i, &j) chyba: vymieňa obsah adries, daných obsahom i, j: vymieňa hodnoty na adresách 5 a 7 vymen(i,j); chyba: vymieňa adresy adries z obsahu i, j: z adries 5 a 7 sa zoberú hodnoty a tie sa použijú ako adresy vymen(*i, *j);
Ukazovateľ na typ void • void: prázdny typ (napr. funkcia, ktorá nevracia typ) • void *p_void; • neukazuje na žiaden kokrétny typ • generický pointer: ukazovateľ na ľubovoľný typ (nezabudnúť na pretypovanie!)
Príklad ukazovateľa na typ void • pri priraďovaní je potrebné uviesť typ int i; float f; void *p_void = &i; *(int *) p_void = 2; p_void = &f; *(float *) p_void = 3.5; p_void ukazuje na i nastavenie i na 2 p_void ukazuje na f nastavenie f na 3.5
Ukazovateľ na typ void: parameter funkcie pre int * pre char * char c = 'a', *p_c = &c, d = 'b', *p_d = &d; vymen((void **) &p_c, (void **) &p_d); int i = 1, *p_i = &i, j = 2, *p_j = &j; vymen((void **) &p_i, (void **) &p_j); funkcia na výmenu dvoch ukazovateľov void vymen(void**p_x, void**p_y) { void *p; p = *p_x; *p_x = *p_y; *p_y = p; }
funkcia vracajúca ukazovateľ ukazovateľ na funkciu double *p_fd(); double (*p_fd)(); double scitaj(double x, double y) p_fd = scitaj; p_fd má adresu funkcie scitaj() Ukazovatele na funkcie • Funkcia môže vrátiť ukazovateľ na typ: • FILE *fopen(...) vracia smerník na typ FILE • Definovanie premennej ako ukazovateľ na funkciu: napr. double (*p_fd)(); to isté ako double *p_fd; double (*p_fd);
Príklad ukazovateľa na funkciu funkcia na výpočet hodnôt polynómov (napr. x2+ 3, x + 8) pre zadanú hornú, dolnú hranicu a krok - najprv pomocné funkcie pre polynómy double pol1(double x) { return (x * x +3); } double pol2(double x) { return (x + 8); }
void vypis(double d, double h, double k, double (*p_f)()) { double x; for(x=d; x<=h; x+=k) printf("%lf, %lf\n", x, (*p_f)(x)); } volanie vo funkcii main(): vypis(-1.0, 1.0, 0.1, pol1); vypis(-2.0, 2.0, 0.05, pol2); Príklad ukazovateľa na funkciu funkcia vypis() na vypísanie tabuľky
Príklady definícií -i je typu int int i; -y je ukazovateľ na typ float float *y; -z je funkcia vracajúca ukazovateľ na double double *z(); - ukazovateľ na funkciu vracajúcu int int (*v)(); - ukazovateľ na funkciu vracajúcu ukazovateľ na int int *(*v)();
Ako čítať zložitejšie definície • Nájdeme identifikátor, od neho čítame doprava • pokým nenarazíme na samotnú pravú zátvorku ")". Vraciame sa k zodpovedajúcej ľavej zátvorke. Potom pokračujeme doprava (preskakujeme prečítané) • Ak narazíme na ";" , vraciame sa na najľavejšie spracované miesto v čítame doľava
v v) ) () 1. Nájdeme identifikátor: v, čítame doprava • Nájdeme ), k nej zodpovedajúcu (, od nej čítame • doprava: * 3. Doprava, preskakujeme prečítané, po ), k nej ( 4. Doprava, preskakujeme prečítané, po ;, doľava Príklad: čítanie definície int *(*v)(); pointer na funkciu vracajúcu - v je (*v) int * * int *(*v)(); pointer na int
Definícia s využitím typedef • Operátor typedef • vytvára nový typ • najmä na definovanie zložitejších typov typedef float *P_FLOAT; P_FLOAT je ukazovateľ na typ float
int *p_i, **p_p_i; typedef int *P_INT; typedef P_INT *P_P_INT; P_INT p_i; P_P_INT p_p_i; typedef double (*P_FD)(); Príklady použitia typedef p_i– ukazvateľ na int p_p_i – ukazvateľ na ukazovateľ na int je ekvivalentné ukazovateľ na funkciu vracajúcu double
Ukazovateľová aritmetika • S ukazovateľmi sa dajú robiť niektoré aritmetické operácie: • Súčet ukazovateľa a celého čísla • Rozdiel ukazovateľa a celého čísla • Porovnávanie ukazovateľov rovnakého typu • Rozdiel dvoch ukazovateľov rovnakého typu • Majú zmysel len v rámci bloku dynamicky vytvorenej pamäte (POLIA) • Ostatné operácie nedávajú zmysel
int i, *p_i; i = sizeof(p_i); int i, *p_i; i = sizeof(*p_i); Operátor sizeof • Na vysvetlenie aritmetických operácií s ukazovateľmi potrebujeme operátor sizeof(): • zistí veľkosť dátového typu v Bytoch • vyhodnotí sa v čase prekladu (nezdržuje beh) počet Bytov potrebných na uloženie ukazovateľa na int – nevyužíva sa počet Bytov potrebných na uloženie typu int – využíva sa často
(n=3) p1: 30 1 2 32 3 34 p2 = (int*) p1 + sizeof(*p1)*n; 4 36 p2 = 30 + 2 * 3 = 36 Súčet ukazovateľa a celého čísla intn, *p1, *p2; ... p2 = p1+ n; sizeof(*p1)==2 p2:
p_c + 1 == p_i + 1 == p_f + 1 == Súčet ukazovateľa a celého čísla - príklady char *p_c = 10; int *p_i = 20; float *p_f = 30; Vieme: sizeof(char) == 1 sizeof(int) == 2 sizeof(float) == 4 11 Potom platí: 22 34
(n=3) 30 1 2 32 3 34 p1 = (int*) p2 - sizeof(*p2)*n; 4 p2: 36 p2 = 36 - 2 * 3 = 30 Rozdiel ukazovateľa a celého čísla intn, *p1, *p2; ... p1 = p2- n; sizeof(*p2)==2 p1:
Porovnávanie ukazovateľov • operátory: < <= > >= == != • porovnávanie má zmysel len keď ukazovatele: • sú rovnakého typu • ukazujú na ten istý úsek pamäte • výsledok porovnania: • ak je podmienka splnená: 1 • inak: 0
Porovnávanie ukazovateľov: príklad - výpis reťazca ... char *p1, *p2 , str[N]; strcpy(str, "ahoj"); p1 = str; p2=p1; while(p2 < p1+ N && *p2 != '\0') printf("%c", *p2++); str: pole s N znakmi, p1, p2: ukazovatele • vyisuje znaky pokiaľ: • nepresiahne dĺžku pridelenej pamäte premennej str a • pokým nedosiahnekoniec zapísaného slova
int n, *p; ... if(n >= 0) p = alokuj(n); else p = NULL; ... if (p != NULL) ... Porovnávanie ukazovateľov s konštantou NULL • bez explicitného pretypovávania • p = NULL: • neukazuje na žiadne zmysluplné miesto v pamäti
n = ((int *) p1 - (int *) p2) / sizeof(*p1); príklad: char *p1, *p2 , str[N]; ... for (p2=p1; p2<N && *p2 != '?'; p2++) ; printf("%d", (p2 < p1+N) ? (p2-p1+1) :-1); Rozdiel dvoch ukazovateľov rovnakého typu intn, *p1, *p2; ... n = p1 - p2; ak je v bloku pamäte '?', vypíše pozíciu, inak -1
1 p1: 10 2 3 7 p2: 50 Ukazovateľová aritmetika • aritmetické operácie: • Súčet ukazovateľa a celého čísla • Rozdiel ukazovateľa a celého čísla • Porovnávanie ukazovateľov rovnakého typu • Rozdiel dvoch ukazovateľov rovnakého typu • majú zmysel len vtedy, keď: • sú ukazovatele na rovnaký typ • ukazujú na ten istý úsek pamäte (OS nezaručí, že neskôr alokovaný blok bude na vyššej adrese)
Ukazovateľová aritmetika • aritmetické operácie: • Súčet ukazovateľa a celého čísla • Rozdiel ukazovateľa a celého čísla • Porovnávanie ukazovateľov rovnakého typu • Rozdiel dvoch ukazovateľov rovnakého typu • majú zmysel len vtedy, keď: • sú ukazovatele na rovnaký typ • ukazujú na ten istý úsek pamäte (OS nezaručí, že neskôr alokovaný blok bude na vyššej adrese) 7 p2: 15 p1: 20 1 2 3
Dynamické prideľovanie a uvoľňovanie pamäte • prideľovanie pamäte za chodu programu • v zásobníku (stack) - riadi operačný systém • v hromade (heap) - riadi programátor budeme sa zaoberať týmto prideľovaním • pomocou run-time funkcií • životnosť dynamických dát: • od alokovania po uvoľnenie pamäte
void *malloc(unsigned int) Prideľovanie pamäte • pomocou funkcie definovanej v stdlib.h (niekedy v alloc.h): počet Bytov Adresa prvého prideleného prvku - je vhodné pretypovať.Ak nie je v pamäti dosť miesta, vráti NULL.
Testovanie pridelenia pamäte • kontrola, či malloc() pridelil pamäť: int * p_i; if((p_i = (int *) malloc(1000)) == NULL) { printf("Nepodarilo sa pridelit pamat\n"); exit; }
void free(void *) príklad: char *p_c; p_c = (char *) malloc(1000 * sizeof(char)); ... free(p_c); p_c = NULL; Uvoľňovanie pamäte • nepotrebnú pamäť je vhodné ihneď vrátiť operačnému systému • pomocou funkcie: