VII-1
Pointery Pointery (ukazatele, smerníky) sú srdcom a dušou jazyka C. Pokiaľ ich nebudete používať, ušetríte si veľa problémov (každý algoritmus sa dá napísať bez nich, aj keď možno neefektívne). Pointer je premenná ako každá iná, ibaže definuje adresu v pamäti, a na tejto adrese sa ukrýva skutočná hodnota. hodnota 25 18 symbolická adresa poi *poi absolútna adresa 87 25 premenná poi je pointer hodnota poi je 25 (číslo uložené na symbolickej adrese poi je 25) číslo 25 sa nevyužije priamo k výpočtu, ale predstavuje absolútnu adresu v pamäti na absolútnej adrese 25 v pamäti je číslo 18 (určené na použitie k výpočtu) poi ukazuje na hodnotu 18, ale sám má hodnotu 25 VII-2
Načo sú pointery? Urýchlenie programov. "Profesionalita." Zmena hodnôt premenných dodávaných funkcii bez toho, že by museli byť definované ako globálne - volanie odkazom - pointery použijeme, keď chceme zmeniť vo funkcii hodnotu parametra natrvalo, namiesto hodnoty dáme funkcii adresu premennej. Narábanie s polom. operátor * povie prekladači, že hodnota premennej je iba adresa a k výpočtu sa má použiť hodnota na dotyčnej adrese pomocou operátoru * môžeme získať obsah na adrese, na ktorú ukazuje pointer i=*poi; ale aj opačne *poi=5; Operátor & má opačný význam ako *, teda dostane sa ním adresa pamäti, na ktorej je premenná uložená. VII-3
Starý program #include <stdio.h> #define MAX 10 void main(void) { int a[max], b[max], i; for(i=0; i<max; i++) a[i]=i; for(i=0; i<max; i++) b[i]=a[i]; for(i=0; i<max; i++) printf("%d",b[i]); Program s pointerom #include <stdio.h> #define MAX 10 void main(void) { int a[max], b[max], i, *p, *q; for(i=0; i<max; i++) a[i]=i; for(i=0,p=a,q=b; i<max; i++) *q++ = *p++; for(i=0; i<max; i++) printf("%d",b[i]); VII-4
Starý program #include <stdio.h> int x=0; void zmena_premennej(void) { x++; main() { zmena_premennej(); printf("x = %d",x); Program s pointerom #include <stdio.h> void zmena_premennej(int *premenna) { (*premenna)++; main() { int x; zmena_premennej(&x); printf("x = %d",x); VII-5
Prehodenie prvkov #include <stdio.h> void vymen(int *p_x, int *p_y) { int pom; pom=*p_x; *p_x=*p_y; *p_y=pom; main() { int i=5, j=3; vymen(&i,&j); printf("i=%d, j=%d"); VII-6
výmena adresy a dynamické priraďovanie adries sú typickým použitím pointerov Napr. namiesto výmeny dvoch veľkých "štruktúr", napr. polí, sa vymenia iba adresy ich počiatočného prvku veľmi častá chyba je vymen(i,j); bude zapisovať na absolútne adresy 3 a 5 program zmrzne vymen(*i,*j); bude zapisovať na adresy adries - program zmrzne VII-7
Pred volaním vymen(&i,&j); 5 3 i j Po zavolaní vymen(&i,&j); 5 3 i p_x pom p_y j Pred koncom vymen(&i,&j); 5 3 i p_x pom p_y j VII-8
Pozor, pamäť, na ktorú ukazuje pointer, musí byť pred zápisom na tú adresu pridelená, v prípade zápisu do nepridelenej pamäti nás prekladač neupozorní!!! int *poi; // pointer poi *poi=5; // zle!!!! Keďže pointeru nebola priradená adresa, ukazuje na náhodné miesto v pamäti, teda hodnota 5 môže prepísať inú premennú alebo príkaz v programu Pointer je vždy zviazaný s nejakým dátovým typom (napr int), ale pozor, u príkazu int *poi, poi2; iba poi je definícia pointera, poi2 je už normálne celé číslo, správne má byť int *poi, *poi2; int i, *p_i=&i; Toto je definícia p_i a jeho súčasná inicializácia adresou premennej i VII-9
Priradenie hodnoty pointerom *p_i=1; // OK *(p_i+3)=1; // podozrive, pokial // p_i nie je pole p_i=&i; // OK p_i=&(i+3); // chyba p_i=&15; // chyba p_i=3; // takmer isto chyba //p_i ukazuje na absol. adresu 3 i=p_i; // takmer isto chyba // do i je dana adresa i=&p_i; // takmer isto chyba // do i je dana adresa adresy p_i=&i; // adresa i do p_i i=3; // aj i, aj *p_i je 3 *p_i=4; // aj i, aj *p_i je 4 j=5; *p_i=j; // aj i, aj *p_i je 5 VII-10
Operátor * má vyššiu prioritu ako +, teda i=*p_i+1; je vlastne i=(*p_i)+1; #include <stdio.h> main() { int i, j, *p_i; scanf("%d %d",&i,&j); if(i>j) p_i=&i; else p_i=&j; printf("vetsi je %d \n",*p_i); VII-11
void spocti_medzery(int *p_medzery)) { int c; *p_medzery=0; while((c=getchar())!= '\n') if(c==' ') (*p_medzery)++; main() { int medzery; spocti_medzery(&medzery); printf("na riadku bolo %d medzier.", medzery); VII-12
Pointer na typ void void *p_void; takýto pointer neukazuje na žiaden konkrétny typ, preto sa dá použiť na ľubovoľný typ int i; float f; void *p_void=&i; // p_void ukazuje na i main() { *(int *) p_void=2; // i bude 2 p_void=&f; // ukazuje na f *(float *) p_void=1.2; // f je 1.1 VII-13
void ako formálny parameter funkcie #include <stdio.h> void vymen_pointery(void **p_x, void **p_y) { void *p_pom; p_pom=*p_x; *p_x=p_y; *p_y=p_pom; main() { int i=5, j=6, p_i=&i, p_j=&j; vymen_pointery((void*)&p_i, (void*)&p_j); printf("%d %d",*p_i,*p_j); Namiesto niekoľko funkcií, pre každý typ dvojice pointerov zvlášť, sa dá použiť jedna funkcia. VII-14
Pointery na funkcie double *p_fd(); znamená funkciu, ktorá vracia pointer na double double (*p_fd)(); znamená pointer na funkciu, ktorá vracia double VII-15
Tabelácia dvoch polynómov x 2 +8, x 3-3 od 0 po 1 po 0.1 double pol1(double x) { return(x*x+8); double pol2(double x) { return(x*x*x-3); main() { double x; double (*p_fd)(double x); for(int i=0;i<2;i++) { if(i==0) p_fd=pol1; else p_fd=pol2; for(x=0;x<=1;x+=0.1) printf("%lf %lf\n", x,(*p_fd)(x)); VII-16
Ako čítať komplikované definície int x; // x je typu int float *y; // y je pointer na typ float double *z(); // z je funkcia vracajúca // na typ double int *(*v)(); // v je pointer na funkciu // vracajúcu pointer na typ int nájdeme identifikátor, teda "v" a povieme v je čítame doprava, dokiaľ nenarazíme na ")", ktorá nás vracia doľava na "(", odkiaľ čítame zasa doprava, teda "*"... pointer na... preskakujeme už prečítané doprava, pokiaľ nenarazíme na ")" alebo bodkočiarku, u nás to bude (), teda...funkciu vracajúcu... bodkočiarka nás vráti doľava, pretože vpravo už sme všetko prečítali, čítame doľava "*"...pointer na... doľava "int"...int teda v je pointer na funkciu vracajúcu pointer na int VII-17
Pointerová aritmetika pre i je typu int pointer +i pointer i pointer1 <= pointer2 (aj všetky ostatné druhy porovnávania) pointer1 - pointer2 Operáror sizeof zistí veľkosť skúmaného dátového typu v Bytoch. Je vyhodnotený pri prekladu, nezdržiava teda program. Využitie pri prideľovaní dynamickej pamäti int i, *p_i; i=sizeof(*p_i); // v i bude počet Bytov // nutných pre uloženie objektu, na ktorý ukazuje // p_i, teda int (pozor, pri poliach sa musí násobiť // veľkosťou pola) VII-18
Súčet pointeru a celého čísla Výraz p+n znamená, že sa odkazujeme na n-tý prvok za prvkom, na ktorý práve ukazuje pointer p. Keď p ukazuje na char, hodnota adresy tohto prvku je (char *) p + sizeof(*p) * n teda k pointeru pričítame nie číslo n, ale násobok tohto čísla a veľkosti typu, na ktorý pointer ukazuje. char *p_c=10; // sizeof(char)==1 int *p_i=10; // sizeof(int)==2 float *p_f=10; // sizeof(float)==4 Po pričítaní jednotky platí p_c+1==11 p_i+1==12 p_f+1==14 Výraz typu p_i=p_i+5; bude ukazovať na 5-tý prvok za pôvodným prvkom časté u polí VII-19
Odčítanie celého čísla od pointeru p-i funguje podobne ako pričítanie, teda odkazuje na n-tý prvok pred prvkom, na ktorý odkazuje pointer p Porovnávanie pointerov < <= > >= ==!= má zmysel vtedy, keď obidva pointery ukazujú napr. na jedno pole Kopírovanie pamäti Keď p_c a p_d sú pointery na typ char, pričom p_c ukazuje na začiatok pole veľkosti MAX, skopírujeme toto pole nasledovne char *p_t; for(p_t=p_c; p_t<p_c+max; *p_d++=*p_c++) ; Po ukončení kopírovaní ukazuje p_d za prvý Byte za novo skopírovaným blokom, teda je dobre dať príkaz p_d -=MAX; VII-20
Odpočítavanie pointerov p_d-p_c má zmysel, keď pointery ukazujú na rovnaké pole dát k zistení počtu prvkov medzi pointerami. Sčítanie pointerov je nezmysel. Predpokladajme predchádzajúci blok dát. Nasledujúca časť programu nájde v tomto bloku znak "?" a vytlačí jeho pozíciu, alebo keď "?" nie je, vytlačí 1. for(p_d=p_c; *p_d!='?' && p_d<p_c+max; p_d++) ; printf("%d\n", (p_d<p_c+max)? p_d-p_c+1 : -1); VII-21
Práca s pamäťou Štatická alokácia keď vieme prekladači dopredu presne povedať, aké budeme mať v programu pamäťové nároky. V priebehu behu nášho programu sa nerobí žiadna manipulácia s prideľovaním pamäti. Kedy to nestačí? Napr. pri rekurzívnom volaní funkcie každé volanie vyžaduje nový blok pamäti pre svoje premenné a prekladač nevie, koľkokrát bude funkcia volaná. Alebo pri načítaní celého súboru do pamäti nevieme obecne dopredu povedať jeho dĺžku. Štatiská alokácia vymedzuje miesto v dátovej oblasti napr. všetky globálne premenné. VII-22
Dynamická alokácia vymedzuje pamäť na "hromade" (heap). Pamäť možno prideľovať v priebehu výpočtu. Nemá symbolické meno a pristupuje sa do nej pomocou pointerov. Existencia lokálnych premenných vo funkciách ("podprogramoch") začína pri vstupu do funkcie a končí pri výstupu z funkcie (potom môže byť využitá na iné účely) premennú, ktorú potrebujeme iba vo funkcii, ale musí si ponechávať hodnotu medzi volaniami tejto funkcie treba definovať špeciálne definíciou tejto premennej. VII-23
Pamäťové triedy extern pre premenné v inom moduli static pre lokálne premenné, ktoré si ponechávajú svoju hodnotu aj medzi jednotlivými volaniami funkcie #include <stdio.h> void f(void) { int x=2; static int i; printf("f bola volana %d-krat,x=%d\n",i,x); i++; x++; main() { for(j=0;j<3;j++) f(); f bola volana 0-krat, x=2 f bola volana 1-krat, x=2 f bola volana 2-krat, x=2 VII-24
Typový modifikátor const keď sa raz premenná zadefinuje, nesmie byť menená const float pi=3.14159; ale const int max=100; int pole[max]; // sa nesmie pouzit VII-25
Dynamické prideľovanie a návrat pamäti Štandardné funkcie, napr. malloc() pridelia blok pamäti potrebnej veľkosti a vráti jeho adresu. Veľkosť pridelenej dynamickej pamäti musí byť závislá na veľkosti objektu, na ktorý pointer ukazuje. Dáta existujú až do konca programu alebo do uvolnení pamäti príkazom free(). Knižnice: #include <stdlib.h> #include <alloc.h> Funkcie malloc() má jediný parameter typu unsigned int určujúci počet bytov, ktoré chceme alokovať. Vracia pointer na typ void, ktorý treba pretypovať. Keď nie je dostatok miesta, vracia hodnotu NULL je dobre testovať, či sa podarilo prideliť pamäť. VII-26
#include <stdio.h> void main(void) { int *p_i; if((p_i=(int *) malloc(sizeof(int)*1000) == NULL) { printf("malo pamati\n"); exit(1); VII-27
Uvoľňovanie pamäti Je dobré nepotrebnú pamäť ihneď vrátiť. Parametrom funkcie free() je pointer na typ void. free((void *) p_i); Keby sme chceli prideliť ďaľšiu pamäť na rovnaký pointer p_i=malloc(20); bez predchádzajúceho uvolnenia pamäti, tých 1000 predchádzajúcich integerov nám ostane "visieť" v pamäti a až do konca behu programu sa k nim nedostaneme, lebo nevieme ich pointer. Funkcia calloc() na pole calloc(n,size) odpovedá malloc(n*size) VII-28
Pole a pointery int pole[10]; // staticke pole pole[5] je totožné s *(pole+5) &pole[5] je totožné s pole+5*sizeof(int) int *pole2; // dynamicke pole pole2=(int*)malloc(10*sizeof(int)); pole aj pole2 sú pointery na int, ale pole je konštanta, jej hodnota sa už nedá meniť, zatiaľ čo pole2 je premenná, ktorej sa dá priradiť iná pamäť Je jedno, či bolo pole alokované štaticky alebo dynamicky, môže sa adresovať aj pole2[5] aj *(pole2+5) platí pole2[0]==*pole2 VII-29
Pole s rôznou dĺžkou riadkov napríklad pre polovicu matice pod diagonálou int *xx[3];//pole troch pointerov for(i=0;i<3;i++) xx[i]= (int *)malloc((i+1)*sizeof(int)); xx xx[0] xx[0][0] xx[1] xx[1][0] xx[1][1] xx[2] xx[2][0] xx[2][1] xx[2][2] POZOR, pole xx[0][1] by ukazovalo na neznámu oblasť pamäti!!! VII-30
Polia ako parametre funkcií Pole môže byť parametrom funkcie tak, že sa prenáša adresa začiatku poľa pomocou pointeru. Položky poľa potom môžu byť vo funkcii menené a túto zmenu si ponechajú aj po opustení funkcie. Nasledujúca funkcia nájde najväčší prvok z poľa o ROZSAH prvkoch. double maxim(double pole[],int rozsah) { double *p_max=pole, *p_pom; for(p_pom=pole+1; p_pom<pole+rozsah; p_pom++) if(*p_pom>*p_max) p_max=p_pom; return(*p_max); double pole[] môže byť nahradené pomocou double *pole VII-31
funkcia by bola volaná napr. max=maxim(pole, velkost_pole); Pokiaľ vo funkcii pracujeme s poľom a je nutné poznať jeho veľkosť, potom táto veľkosť musí byť daná ako ďalší formálny parameter. Predchádzajúca funkcia sa dá použiť aj na nájdenie maxima z výseku polia, napr. od tretieho do siedmeho prvku max=maxim(pole+2, 5); alebo max=maxim(&pole[2], 5); VII-32
Pointer ako skutočný parameter funkcie Program prečíta z klávesnice 10 double čísel, uloží ich do pamäti a vypočíta ich súčin #include <stdio.h> #include <stdlib.h> #define SIZE 10 double *init(void) { return ((double *) malloc(size * sizeof(double))); void citanie(double *p_f) { for(int i=0;i<size;i++) { printf("%d. cislo: ",i+1); scanf("%lf",p_f+i); // bez &! void nasob(double *p_f, int size, double *sucin) { for(size -, *sucin=*(p_f+size); size>=0; size--) *sucin *= *(p_f+size); VII-33
main() { double *p_dbl; suc; if((p_dbl=init())==null) return; // nedostatok pamati-koniec citanie(p_dbl); nasob(p_dbl,size,&suc); printf("sucin: %12.3lf \n",suc); Pri funkcii nasob() sa prvý parameter p_dbl uvádza bez &, pretože je to pointer, zatiaľ čo tretí parameter suc sa uvádza s &, pretože je to premenná. VII-34
VII-35