pointerat - · pdf fileprogramimi 2 1 pointerat të dhënat në një sistem...

11
Programimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë e përbërë, kanë lokacionin e tyre memorik, nëpërmjet të cilit atyre mund te ju qasemi. Në C++, nëpërmjet krijimit të ndryshoreve të tipit të caktuar, është e mundur puna me të dhëna pa kontrollim të lokacionit ku ato janë të vendosura: pra, në të gjitha programet që i kemi shkruar deri më tani, aspak nuk jemi brengosur për lokacionin e ruajtjes së të dhënave. Në këtë ligjëratë do të shpjegojmë për pointerat (shënjuesat, treguesit), që janë tip special i të dhënave që mundëson punë me lokacionet e memories. Në C++, pointerat krijohen ashtu që ndërmjet tipit të dhënave deri te i cili tregohet (shënjohet) dhe emrit të ndryshores e cila shërben si printer vendoset shenja ‘*’ (yllëz apo asterisk). Lokacioni memorik i një ndryshoreje fitohet me anë të shenës ‘&’ përpara emrit të ndryshores, adresën e së cilës dëshirojmë ta dimë. Të shohim disa shembuj: #include <iostream> using namespace std; int main() { int a = 5; //ndryshore 'a' me vlere 5 int *b; //pointer deri te e dhena me tip int b = &a; //b e permban adresen e a ("b tregon te a") /* double *k; k = &a; //GABIM: tip i gabuar i pointerit (double) */ cout << a << endl; //shtype '5' (vlera e a) cout << b << endl; //shtype '0x27ff44' (adresa e a) return 0; } Së pari, është me rëndësi të thuhet se, gjatë krijimit të pointerave (int *x), pozita e vendosjes së shenjës ‘*’ nuk ka kurrfarë ndikimi në ekzekutimin e programit. Në fakt, shenja ‘*’ mundet të ndodhet menjëherë pas tipit të dhënës (int* x), ndërmjet tipit të dhënës dhe emrit të pointerit (int * x) ose pranë emrit të pointerit (int *x): të gjitha urdhëresat e kanë efektin e njëjtë. Kur krijohen më shumë pointera të tipit të njëjtë, duhet bërë kujdes që të vendoset shenja ‘*’ për secilin pointer. int a, b; //a eshte e tipit 'int', b eshte e tipit 'int' int *a, *b; //a dhe b jane pointera deri te e dhena me tip 'int' int *a, b; //a eshte pointer, b eshte ndryshore e thjeshte e tipit 'int' int a, *b; //a eshte e tipit 'int', b eshte pointer

Upload: ngodieu

Post on 09-Feb-2018

224 views

Category:

Documents


2 download

TRANSCRIPT

Page 1: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

1

Pointerat

Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë e përbërë, kanë lokacionin e tyre memorik, nëpërmjet të cilit atyre mund te ju qasemi. Në C++, nëpërmjet krijimit të ndryshoreve të tipit të caktuar, është e mundur puna me të dhëna pa kontrollim të lokacionit ku ato janë të vendosura: pra, në të gjitha programet që i kemi shkruar deri më tani, aspak nuk jemi brengosur për lokacionin e ruajtjes së të dhënave.

Në këtë ligjëratë do të shpjegojmë për pointerat (shënjuesat, treguesit), që janë tip special i të dhënave që mundëson punë me lokacionet e memories. Në C++, pointerat krijohen ashtu që ndërmjet tipit të dhënave deri te i cili tregohet (shënjohet) dhe emrit të ndryshores e cila shërben si printer vendoset shenja ‘*’ (yllëz apo asterisk). Lokacioni memorik i një ndryshoreje fitohet me anë të shenës ‘&’ përpara emrit të ndryshores, adresën e së cilës dëshirojmë ta dimë. Të shohim disa shembuj:

#include <iostream> using namespace std; int main() { int a = 5; //ndryshore 'a' me vlere 5 int *b; //pointer deri te e dhena me tip int b = &a; //b e permban adresen e a ("b tregon te a") /* double *k; k = &a; //GABIM: tip i gabuar i pointerit (double) */ cout << a << endl; //shtype '5' (vlera e a) cout << b << endl; //shtype '0x27ff44' (adresa e a) return 0; }

Së pari, është me rëndësi të thuhet se, gjatë krijimit të pointerave (int *x), pozita e vendosjes së shenjës ‘*’ nuk ka kurrfarë ndikimi në ekzekutimin e programit. Në fakt, shenja ‘*’ mundet të ndodhet menjëherë pas tipit të dhënës (int* x), ndërmjet tipit të dhënës dhe emrit të pointerit (int * x) ose pranë emrit të pointerit (int *x): të gjitha urdhëresat e kanë efektin e njëjtë.

Kur krijohen më shumë pointera të tipit të njëjtë, duhet bërë kujdes që të vendoset shenja ‘*’ për secilin pointer.

int a, b; //a eshte e tipit 'int', b eshte e tipit 'int' int *a, *b; //a dhe b jane pointera deri te e dhena me tip 'int' int *a, b; //a eshte pointer, b eshte ndryshore e thjeshte e tipit 'int' int a, *b; //a eshte e tipit 'int', b eshte pointer

Page 2: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

2

Shpeshherë, kur shfrytëzojmë pointera dëshirojmë të dimë edhe vlerën e cila ruhet në një lokacion të caktuar memorik – gjegjësisht, cila është vlera e ndryshores deri te cila tregon pointeri jonë. Në C++, atë e bëjmë në mënyrë që para emrit të pointerit e vendosim shenjën ‘*’ (shenjën e njëjtë e përdorëm edhe për krijimin e tij. Këtu është me rëndësi të dihet se me shenjën apo simbolin ‘*’ ne i qasemi të dhënës në një lokacion të caktuar memorik, e jo vetëm që mund ta shohim vlerën e saj, por edhe mund ta ndryshojmë!

#include <iostream> using namespace std; int main() { int a = 5; //ndryshore 'a' me vlere 5 int *p; //pointer deri te e dhena me tip int p = &a; //p e permban adresen e a ("p tregon te a") cout << a << endl; //shtype '5' cout << *p << endl; //shtype '5' *p = 3; //ndryshimi i vleres cout << p << endl; //shtype '0x27ff44' (adresa) cout << *p << endl; //shtype '3' (vlera) cout << a << endl; //shtype '3' return 0; }

Dallimin ndërmjet operatorëve ‘&’ dhe ‘*’ mund ta definojmë me këto shprehjeË

‘&’ lexohet “adresa e” dhe e kthen adresën e një të dhëne të caktuar. Për shembull ‘&x’ e kthen adresën e x.

‘*’ lexohet “vlera e treguar nga” dhe e kthen vlerën e cila ruhet në një lokacion të caktuar. Për shembull, “*p” shërben për qasje deri te vlera e treguar(shënjuar) nga pointeri p.

Përpara se të vazhdojmë më tutje, është me rëndësi të theksojmë se një pointer mund të përdoret disa herë (në një program të njëjtë) edhe atë (në momente të ndryshme) të tregoj në ndryshore dhe lokacione të ndryshme memorike. Nëse duam që në mënyrë eksplicite ta shprehim se një pointer i caktuar nuk tregon askund, të njëjtit mund ti japim vlerë 0 (që quhet edhe si pointeri NULL). Standardet garantojnë se nuk ekziston e dhënë në memorien kompjuterike me adresë 0.

E japim edhe një program të thjeshtë ku ilustrohen të gjitha konceptet e lartpërmendura.

Page 3: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

3

#include <iostream> using namespace std; int main() { int a = 5, b = 2; int *pa = &a, *pb; //inicijalizimi pb = &b; //pb tregon te b *pa = 3; cout << a << ", " << *pa << ", " << *pb << endl; //shtyp '3 3 2' *pb = -1; cout << b << ", " << *pb << endl; //shtyp '-1 -1' *pa = *pb; cout << a << ", " << b << endl; //shtyp '-1 -1' pa = 0; double c = 8.0123, d = 0.0000; double *px; px = &c; (*px) += 2.0; cout << (*px) << endl; //shtyp '10.0123' px = &d; cout << (*px) << endl; //shtyp '0' return 0; }

Madhësia e një pointeri varet nga arkitektura e sistemit kompjuterik te i cili ekzekutohet programi si dhe nga sistemi operativ: sistemi kompjuterik prej 32 bitëve do të shfrytëzojë adresa memorike prej 32 bitëve, dhe pointerat do të zënë hapësirë adresuese prej 4 bajtëve, kurse te sistemet kompjuterike me 64 bite, pointeri do të zë hapësirë adresuese prej 8 bajtëve. Më poshtë e ilustrojmë këtë me një shembull.

#include <iostream> using namespace std; struct typeX { int t1, t2, t3, t4; double t5, t6, t7, t8; }; int main() {

Page 4: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

4

int a = 5, b = 2; double c = 3.8; char d = 'Z'; typeX e; int *pa = &a, *pb = &b; double *pc = &c; char *pd = &d; typeX *pe = &e; cout << sizeof(pa) << endl; //shtyp '4' cout << sizeof(pb) << endl; //shtyp '4' cout << sizeof(pc) << endl; //shtyp '4' cout << sizeof(pd) << endl; //shtyp '4' cout << sizeof(pe) << endl; //shtyp '4' return 0; }

Pointerat dhe vektorët

Në C++ vektorët janë ngushtë të lidhur me pointerat. Në fakt, kur krijojmë vektor me madhësi të caktuar, emri i ndryshores të cilën e shfrytëzojmë për qasje deri te elementi i atij vektori nuk paraqet asgjë tjetër përveç se pointer deri te elementi i parë i vektorit.

#include <iostream> using namespace std; int main() { int vektor[] = {1, 2, 3, 4, 5}; cout << *vektor << endl; //shtyp '1' *vektor = 0; //tani, vektor[] = {0, 2, 3, 4, 5} int *parr; parr = vektor; cout << *parr << endl; //shtyp '0' return 0; }

Në programin e sipërm, pasi që parr do të tregojë në lokacionin e njëjtë si edhe vektor, nuk ka dallim ndërmjet mundësive që i ofron vektor dhe atyre që i ofron parr – të dy pointerat mund të shfrytëzohen për qasje deri te elementi i vektorit. Dallimi i vetëm është se parr, në pjesën e mbetur të programit, mund të ndryshohet dhe të tregoj në diçka tjetër (në ndonjë lokacion tjetër), kurse vektor është pointer konstant dhe nuk mund të ndryshohet – ai gjithmonë do të tregoj në vektorin me të cilin është krijuar.

Page 5: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

5

Aritmetika e pointerave

Në C++ është e mundur përdorimi i të ashtuquajturës aritmetika e pointerave. Në fakt, secili pointer në C++ është i definuar me tip përkatës të dhënave te i cili tregon, dhe i njëjti mund të ndryshohet në mënyrë që në të ardhmen do të tregojë në ndonjë të dhënë paraardhëse ose pasardhëse në memorie. Kjo realizohet me përdorimin e thjeshtë të operatorëve ‘+’ dhe ‘-‘.

Për shembull, nëse vektor tregon në elementin e parë të një vektori të caktuar, atëherë vektori+1 do të tregon në elementin e dytë të vektorit, kurse vektor+2 tregon në elementin e tretë, etj.

Programi vijues do të na shtyp disa elemente të vektorit vektor.

#include <iostream> using namespace std; int main() { int vektor[] = {1, 2, 3, 4, 5, 6, 7}; int *parr = vektor; // cikli do te shtyp '2 3 4 5 6 ' for (int x=0; x<5; x++) { parr = parr+1; //parr do tregon te elementi pasardhes cout << (*parr) << " "; } cout<<endl; parr--; //parr do tregon te elementi paraardhes cout << (*parr) << endl; //shtypet '5' parr = parr - 2; cout << (*parr) << endl; //shtypet '3' return 0; }

Është me rëndësi të theksohet se shprehja *(vektor+5) ka plotësisht kuptimin e njëjtë m shprehjen vektor[5]. Në fakt, vektor[5] është një formë alternative e të shënuarit të operacionit *(vektor+5). Në sfond, kompilatori sigurisht që ekzekuton operacione me pointera.

Supozojme se kemi 3 pointera:

Page 6: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

6

char *mychar; short *myshort; long *mylong;

dhe dime se ata shenjojne ne adresen 1000, 2000 and 3000 perkatesisht.

Nese shkruajme:

mychar++; myshort++; mylong++;

mychar, duhet te shenjoj te 1001. Ndersa, myshort te adresa 2002, dhe mylong do te kete vleren 3004. Kjo per arsye se, kur shtojme pointerin me 1 po bejme te mundur qe shenjuesi te shenjoj 1 element me tej dhe numri i byteve qe do te spostohet percaktohet nga numri i byteve per cdo tip..

Duhet te cilesojme se si operatori increase (++) dhe decrease (--) kane prioritet me te madh se sa operatori i references (*), Keshtu qe:

*p++; *p++ = *q++;

E para eshte ekuivalent me *(p++) rrit vleren e pointerit p (dhe jo vleren qe pemban). ne te dyten, per faktin se operatoret (++) jane mbas shprehjes qe po vleresohet. Atehere fillimisht vlera e *q i kalohet te *p dhe pastaj te dy pointerat inkrementohen. Eshte ekuivalent me:

*p = *q; p++; q++;

Page 7: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

7

Pointerat mbi pointera

C++ lejon perdorimin e shenjuesve mbi shenjues. Per shembull:

char a; char * b; char ** c; a = 'z'; b = &a; c = &b;

Supozojme se adresat jane 7230, 8092 and 10502, atehere:

(Brenda kuadrateve jane vlerat; poshte jane adresat)

c eshte variabel i tipit (char **) me vlere 8092 *c eshte variabel i tipit (char*) me vlere 7230 **c eshte variabel i tipit (char) me vlere 'z'

Pointerat dhe funksionet

C++ lejon operacione me shenjuesat te funksionet. Perdorimi me i madh i tyre eshte per te kaluar nje funksion si nje parameter te nje funksion tjeter, sepse kjo nuk realizohet dot ndryshe. Me qellim qe te deklarojme nje shenjues mbi je funksion duhet ta deklarojme si prototip te funksionit pervec emrit qe mbyllet ne kllapa () dhe nje pointer asterisk (*) shtohet para emrit.

Fakti që ndryshorja përmes së cilës i qasemi elementeve të një vektori të caktuar është pointer ka një efekt të rëndësishëm në shfrytëzimin e vektorëve si argumente të një funksioni: pra, kur dërgojmë vektor si argument në funksion, sistemi nuk kryen kopjimin e elementeve, por funksionit i dërgohet pointer deri te elementi i parë i vektorit. Për këtë arsye, të gjitha ndryshimet e elementeve të cilat do të ekzekutohen në funksion do të jenë të dukshme edhe pas ekzekutimit të tij.

Page 8: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

8

#include <iostream> using namespace std; void func(int vektor[]) { vektor[0] = 10; } int main() { int vektor[] = {1, 2, 3, 4, 5, 6, 7}; func(vektor); cout << vektor[0] << endl; //shtyp '10' return 0; }

Pointerat dhe strukturat

Pointerat shpesh herë përdoren në kombinacion me strukturat. Për shembull, një nga mënyrat ku mund të përfshihet struktura si argument i një funksioni të caktuar është përmes dërgimit të pointerit deri te ajo strukturë. Megjithatë, është me rëndësi të theksohet se ekzistojnë dy mënyra të qasjes deri te anëtarët e strukturës:

me ndihmën e operatorit ‘.’ – (*pointerTeStruktura).emriElementit me ndihmën e operatorit ‘->‘ – pointerTeStruktura-> emriElementit

Duhet të theksohet se operatori ‘->‘ është operator special i cili ka kuptim vetëm në pointerat deri te strukturat.

#include <iostream> using namespace std; struct TipiRi { int x, y; }; int funksion(TipiRi *p) { int vlera = (*p).x + (*p).y; //int vlera = p->x + p->y; return vlera; } int main() { TipiRi p; p.x = 3;

Page 9: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

9

p.y = 7; //argumenti i funksionit 'funksion' eshte pointer, pra //nevojitet ta dergojme adresen e (p) int rez = funksion(&p); cout << rez << endl; //shtyp '10' return 0; }

Alokimi i memories (Memoria dinamike)

Deri tani ne programet tona, kemi perftuar ate memorje qe kemi kerkuar nga deklarimet e variablave. Po nese duam te huazojme nje memorje madhesine e te ciles e percaktojme vetem gjate ekzekutimit?

Pergjigjja eshte memorja dinamike, per te cilen C++ perdor operatoret new dhe delete.

Operatori new dhe new[ ] Me qellim qe te huazojme nje memorja dinamike, perdoret operatori new. I cili shoqerohet nga nje tip te dhenash dhe ne menyre opsionale numrin e elementeve brenda []. Kthen nje shenjues mbi bllokun e huazuar

pointer = new type

pointer = new type [elements]

E para tregon huazim te memorjes per te mbajtur nje element type. E dyta perdoret per te mbajtur nje bllok elementesh te tipit type. Per shembull:

int * b; b = new int [5];

ne kete rast huazohet hapsira per 5 elemente int dhe kthen nje pointer mbi bllokun e memorjes. Huazimi i memorjes dinamike lejon huazim memorje gjate ekzekutimit te programit. Memorja dinamike qe gjenerohet zakonisht menaxhohet nga sistemi operativ. Ka raste kur sistemi operativ nuk e gjen ate sasi memorje qe ti kerkon te huazosh me new, ne kete rat kthehet nje pointer null. Per kete arsye eshte e rekomandueshme te kontrollohet nese huazimi i memorjes u krye. Per shembull.

int * b; b = new int [5]; if (b == NULL) { // error. };

Page 10: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

10

Operatori delete Per te mundesuar huazim sa me te mire te memorjes dinamike kur ajo nevojitet, duhet te clirohet ne momentin qe nuk perdoret me. Operatori delete perdoret per kete qellim:

delete pointer;

delete [] pointer;

E para fshin memorjen e huazuar per nje element te caktuar, dhe e dyta memorjen e huazuar per vektore elementesh (arrays).

Më poshtë e japim programet te të cilat i sqarojmë konceptet e memories dinamike.

#include <iostream> using namespace std; int main() { int *parr; parr = new int; *parr = 2; cout << *parr << endl; //shtyp '2' delete parr; //SHPRAZIM MEMORIEN!!! int N = 100; parr = new int[N]; //vektor me 100 elemnte parr[5] = 3; cout << parr[5] << endl; //shtyp '3' delete [] parr; //SHPRAZIM MEMORIEN!!! return 0; }

#include <iostream> #include <stdlib.h> using namespace std; int main () { char input [100]; int i,n; long * l; cout << "Sa numra deshiron te fusesh? "; cin.getline (input,100); i=atoi (input); l= new long[i]; if (l == NULL) exit (1); for (n=0; n<i; n++) { cout << "jepe numrin: "; cin.getline (input,100); l[n]=atol (input); }

Page 11: Pointerat - · PDF fileProgramimi 2 1 Pointerat Të dhënat në një sistem kompjuterik, pa dallim nëse bëhet fjalë për numër të plotë, simbol, vektor tekstual ose strukturë

Programimi 2

11

cout << "Ju i dhate numrat: "; for (n=0; n<i; n++) cout << l[n] << ", "; delete[] l; return 0; }

Nga fakti se secili sistem kompjuterik ka sasi të kufizuar të memories, ndonjëherë është e mundur që të mos kemi sukses me alokimin dinamik të memories (new int[N]). Në këto raste, nëse sistemi nuk mund të rezervojë sasi të mjaftueshme të memories, programi ynë do të ndërpritet së ekzekutuari dhe do të na paraqitet gabim (runtime error).