Download - Procedur álne programovanie: 5 . prednáška
Procedurálne programovanie:5. prednáška
Gabriela Kosková
Obsah
1. opakovanie (2 príklady)
2. ukazovatele
3. príklady (3)
Príklad 1#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;}
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;}
Program vypočíta obvod a obsah kruhu pomocou makier a funkcií
Príklad 2
Program vypíše obsah súboru naopak.
#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;}
void vypis(FILE *f) { int c;
if ((c=getc(f)) != EOF) { vypis(f); putchar(c); }}
Použitá rekurzia void vypis(FILE *f) { int c;
if ((c=getc(f)) != EOF) { vypis(f); putchar(c); }}
johaobsah súboru:
j
zásobník
o
h
a
výstup programu:
volanie vypis c: 'j'
volanie vypis c: 'o'
volanie vypis c: 'h'
volanie vypis c: 'a'
volanie vypis c: EOF
výpis c: 'a'
výpis c: 'h'
výpis c: 'o'
výpis c: 'j'
ahoj
Procedurálne programovanie: Základy práce s ukazovateľmi
Č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
25
• 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
30
73 30
pamäť
*p:
p:
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é
Čo urobí *p_i = i;
2530
73 30
pamäť• ukazovateľ p_i
– p_i == 30 – *p_i == 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
28 7i:
*p_i:
p_i:
7
7
int i, p_i;...*p_i = i;
treba inicializovať p_i,napr. alokovať pamäť pre p_i
Ako získame adresu premennej
• pomocou referenčného operátora &• int i - „klasická“ celočíselná premenná• &i – adresa premennej
• definícia ukazovateľa:
int i, *p_i;p_i = &i;
int i, *p_i = &i;je ekvivalentné
definícia ukazovateľa a súčasne inicializácia
Čo urobí p_i = &i;
2530
73 30
pamäť• ukazovateľ p_i
– p_i == 30 – *p_i == 25
28 7i:
*p_i:
p_i:
• hodnota p_i (adresa, kam p_i ukazuje) sa prepíše adresou premennej i
• hodnota *p_i je tá istá ako hodnota i
int i, *p_i;
p_i = &i;
Čo urobí p_i = &i;
2530
73 30
pamäť• ukazovateľ p_i
– p_i == 30 – *p_i == 25
int i, *p_i;
p_i = &i;28 7i:
*p_i:
p_i:
7
28
28
• hodnota p_i (adresa, kam p_i ukazuje) sa prepíše adresou premennej i
• hodnota *p_i je tá istá ako hodnota i
Príklady
p_i = &i; - správne
p_i = &(i + 3); - chyba: (i + 3) nie je premenná
p_i = &15; - chyba: konštanta nemá adresu
p_i = 15; - chyba: priraďovanie absolútnej adresy
i = p_i; - chyba: priraďovanie adresy
i = & p_i; - chyba: priraďovanie adresy*p_i = 4; - správne, ak p_i bol inicializovaný
Výpis adresy (pri ladení)
int i, *p_i;
p_i = &i;printf(„Adresa i: %p, hodnota p_i: %p\n”, &i, p_i);
• špecifikácia formátu (v printf()): %p
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
Parametre funkcií - volanie hodnotou: int A(int i)
3
zásobník
spustenie programu, volanie main()
volanie A() spustenie A(3)
koniec programu, main()
návrat do main() koniec A()4
3
dátová oblasť
vytvorí sa kópia
Parametre funkcií - volanie odkazom : int A(int *i)
15
zásobník
spustenie programu, volanie main()
volanie A() spustenie A(15)
koniec programu, main()
návrat do main() koniec A()
dátová oblasť
adresa: 15
adresapremennej
34
• volanie funkcie: vymen(&i, &j)
Príklad funkcie: výmena premenných
void vymen(int *p_x, int *p_y){ int pom;
pom = *p_x; *p_x = *p_y; *p_y = pom;}
i: 5 j: 7
p_x p_y
pom:
7 5
5
Príklad funkcie: výmena premenných
• volanie funkcie: vymen(&i, &j)
vymen(i, j);
vymen(*i, *j);
chyba: vymieňa obsah adries, daných obsahom i, j: vymieňa hodnoty na adresách 5 a 7
chyba: vymieňa adresy adries z obsahu i, j: z adries 5 a 7 sa zoberú hodnoty a tie sa použijú ako adresy
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!)
• 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;
Príklad ukazovateľa na typ void
nastavenie i na 2
p_void ukazuje na i
p_void ukazuje na f
nastavenie f na 3.5
void vymen(void **p_x, void **p_y){ void *p;
p = *p_x; *p_x = *p_y; *p_y = p;}
pre int *
int i = 1, *p_i = &i, j = 2, *p_j = &j;
vymen((void **) &p_i, (void **) &p_j);
Ukazovateľ na typ void: parameter funkcie
char c = 'a', *p_c = &c, d = 'b', *p_d = &d;
vymen((void **) &p_c, (void **) &p_d);
pre char *
funkcia na výmenu dvoch ukazovateľov
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)();
double (*p_fd); to isté ako double *p_fd;double *p_fd();
funkcia vracajúca ukazovateľdouble (*p_fd)();
ukazovateľ na funkciu
double scitaj(double x, double y)p_fd = scitaj;
p_fd má adresu funkcie scitaj()
double pol1(double x){ return (x * x + 3);}
Príklad ukazovateľa na funkciu
double pol2(double x){ return (x + 8);}
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
vypis(-1.0, 1.0, 0.1, pol1);vypis(-2.0, 2.0, 0.05, pol2);
volanie vo funkcii main():
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));}
Príklad ukazovateľa na funkciu
funkcia vypis() na vypísanie tabuľky
Príklady definícií
int i; - i je typu int
float *y; - y je ukazovateľ na typ float
double *z(); - z je funkcia vracajúca ukazovateľ na double
int (*v)(); - ukazovateľ na funkciu vracajúcu int
int *(*v)(); - ukazovateľ na funkciu vracajúcu ukazovateľ na int
Ako čítať zložitejšie definície
1. Nájdeme identifikátor, od neho čítame doprava
2. pokým nenarazíme na samotnú pravú zátvorku ")". Vraciame sa k zodpovedajúcej ľavej zátvorke. Potom pokračujeme doprava (preskakujeme prečítané)
3. Ak narazíme na ";" , vraciame sa na najľavejšie spracované miesto v čítame doľava
int *(*v)();*int *
Príklad: čítanie definície int *(*v)();
- v je pointer na funkciu vracajúcu
pointer na int
)
3. Doprava, preskakujeme prečítané, po ), k nej (
v
1. Nájdeme identifikátor: v, čítame doprava
v)
2. Nájdeme ), k nej zodpovedajúcu (, od nej čítame doprava: *
(*v)
4. Doprava, preskakujeme prečítané, po ;, doľava
()
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
Príklady použitia typedef
je ekvivalentné
typedef int *P_INT;typedef P_INT *P_P_INT;
P_INT p_i;P_P_INT p_p_i;
typedef double (*P_FD)();
int *p_i, **p_p_i; p_i – ukazvateľ na intp_p_i – ukazvateľ na ukazovateľ na int
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
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)
int i, *p_i;i = sizeof(p_i);
int i, *p_i;i = sizeof(*p_i);
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)
Súčet ukazovateľa a celého čísla
int n, *p1, *p2;...
p2 = p1 + n;
p2 = (int *) p1 + sizeof(*p1)*n;
p2:
p2 = 30 + 2 * 3 = 36
130p1:
2
3
436
32
34
sizeof(*p1)==2
Súčet ukazovateľa a celého čísla - príklady
char *p_c = 10;
int *p_i = 20;
float *p_f = 30;
p_c + 1 ==
p_i + 1 ==
p_f + 1 ==
11
2234
Potom platí:
Vieme:
sizeof(char) == 1
sizeof(int) == 2
sizeof(float) == 4
Rozdiel ukazovateľa a celého čísla
int n, *p1, *p2;...
p1 = p2 - n;
p1 = (int *) p2 - sizeof(*p2)*n;
p2 = 36 - 2 * 3 = 30
p1:
p2:
130
2
3
436
32
34
(n=3)
sizeof(*p2)==2
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 nedosiahne koniec zapísaného slova
Porovnávanie ukazovateľov s konštantou NULL
• bez explicitného pretypovávania • p = NULL:
– neukazuje na žiadne zmysluplné miesto v pamäti
int n, *p;...if (n >= 0) p = alokuj(n);else p = NULL; ...if (p != NULL) ...
Rozdiel dvoch ukazovateľov rovnakého typu
int n, *p1, *p2;...n = p1 - p2;
n = ((int *) p1 - (int *) p2) / sizeof(*p1);
char *p1, *p2 , str[N];...for (p2=p1; p2<N && *p2 != '?'; p2++) ;printf("%d", (p2 < p1+N) ? (p2-p1+1) :-1);
príklad:ak je v bloku pamäte '?', vypíše pozíciu, inak -1
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)
p1: 10 1
2
3
7p2: 50
p1: 20 1
2
3
7p2: 15
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)
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
• pomocou run-time funkcií• životnosť dynamických dát:
– od alokovania po uvoľnenie pamäte
budeme sa zaoberať týmto prideľovaním
Marienka, vy ste moje najlepšie pamäťové médium!
Prideľovanie pamäte
• pomocou funkcie definovanej v stdlib.h (niekedy v alloc.h):
void *malloc(unsigned int)
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;}
Uvoľňovanie pamäte
• nepotrebnú pamäť je vhodné ihneď vrátiť operačnému systému
• pomocou funkcie: void free(void *)
char *p_c;
p_c = (char *) malloc(1000 * sizeof(char));...free(p_c);p_c = NULL;
príklad:
char *p_c;*p_c = 'a';p_c = malloc(1);if ((p_c = (char *) malloc(1)) == NULL) ... /* chybová správa a ukončenie */
Príklad prideľovania pamäte: pre jeden char
...free(p_c);
p_c nemá pridelenú pamäť, p_c môže ukazovať kamkoľvek do pamäte - program spadne
správne, ale neošetrujeme pamäť
int *p_i;
if ((p_i = (int *) malloc(1)) == NULL) { printf("Nie je dostatok pamate\n"); exit(1);}
Príklad prideľovania pamäte: pre jeden int
if ((p_i = (int *) malloc(1 * sizeof(int))) == NULL) { printf("Nie je dostatok pamate\n"); exit(1);}
free(p_i);
sizeof(p_i) == 2, preto nebude dostatok pamäte pre int.
Funkcia calloc()
• rovnkako ako malloc(), len automaticky inicializuje Byty na 0:
void *calloc(unsigned int)
Príklad: načítanie 5 čísel a vypočítanie ich súčinu
• 3 pomocné funkcie + funkcia main():– alokovanie pamäte n čísel
– načítanie n čísel z klávesnice
– vypočítanie súčinu n čísel
Príklad: funkcia na alokovanie pamäte n čísel
int *alokuj(int n){ return ((int *) malloc(n * sizeof(int)));}
Príklad: funkcia na načítanie n čísel z klávesnice
void nacitaj(int *p_i, int n){ int i;
for (i = 0; i < n; i++) { printf("Zadajte %d-te cislo: ", i+1); /* doplnte nacitanie i-teho cisla */}
scanf("%d", p_i + i);
Príklad: funkcia na vypočítanie súčinu n čísel
void sucin(int *p, int n, int sucin){ int i;
sucin = 1; for (i = 0; i < n; i++) sucin *= *(p + i);}
čo je tu chybné? pozrite si kratšie napísanú funkciu v (Herout)
*
*
*
Príklad: funkcia main
int main(){ int *cisla, suc; cisla = alokuj(N); nacitaj(cisla, N); sucin(cisla, suc); printf("Sucin je: %d\n", suc); return 0;}
&
na začiatok programu nezabudnúť: #include <stdio.h>
#include <stdlib.h>
#define N 5
doplňte, aby to bolo správne
Procedurálne programovanie:Príklady
Príklad 1
program vypíše načítané číslo pričom použije ukazovateľ na int a na void ako
ukazovatele na celé číslo
#include <stdio.h>
int main() { int i, *p_int = &i; void *p_void = &i;
printf("Zadajte cele cislo: "); scanf("%d", &i);
printf("i: %d, p_int: %d, p_void: %d\n", i, *p_int, (*(int *)p_void)); return 0;}
Príklad 2
Funkcia vypočíta obvod a obsah kruhu vo funkcii kruh(). Volanie odkazom.
#include <stdio.h>
#define PI 3.14#define na_druhu(i) ((i) * (i))
void kruh(int r, float *o, float *s){ *o = 2 * PI * r; *s = PI * na_druhu(r);}
int main() {
int polomer; float obvod, obsah;
printf("Zadaj polomer kruhu: "); scanf("%d", &polomer);
kruh(polomer, &obvod, &obsah); printf("obvod: %.2f, obsah: %.2f\n", obvod, obsah); return 0;}
Príklad 3
Program bude načítavať písmená. Po stlačení 'A' vypíše Ahoj, po stlačení 'C' vypíše Cau, po stlačení 'K' skončí. Použijeme ukazovateľ na
funkcie.
#include <stdio.h>#include <stdlib.h>
void ahoj() {printf("Ahoj\n");
}
void cau() {printf("Cau\n");
}
int main() { int c; void (* p_fnc)(); /* definicia ukazovatela na funkciu */
printf("Ahoj / Cau / Koniec\n"); while((c = toupper(getchar())) != 'K') { if (c == 'A') p_fnc = ahoj; else if (c == 'C') p_fnc = cau;
else continue;
(*p_fnc)(); } return 0;}