10. gyakorlat Pointerek Tárolási osztályok
Pointer A pointer egy mutató egy memóriacellára, egyfajta "parancsikon", csak nem fájlokra, hanem változókra. Létrehozás: tipus * név;, ahol a típus a hivatkozott változó típusa. A pointerek használatával kapcsolatos függvények az stdlib.h-ban találhatók.
Pointerek malloc(meret): egy megadott méretű adatterületet egy pointerhez hozzárendel a méret a lefoglalandó tárterület bájtokban sizeof operátor segítségével állapíthatjuk meg A lefoglalt területet használat után fel KELL szabadítani a free(pointer) utasítással. A pointerhez tartozó értéket a * operátorral érjük el, pl. *pointerem. Ezt pointer-dereferenciának hívjuk.
Példa #include <stdlib.h> int main() { int a = 5; int *p; p = malloc( sizeof(int) ); //memóriafoglalás *p = a; //*p = 5 (*p)++; //*p = 6 a = *p; //a= 6 free(p); //memória felszabadítás return 0;
P: Nézzük meg, mi a különbség p, q, illetve *p és *q értéke között. #include <stdio.h> #include <stdlib.h> int main() { int *p, *q; p = malloc(sizeof(int)); q = malloc(sizeof(int)); *p = 3; *q = 3; ez azt vizsgálja, hogy p és q ugyanarra a címre mutat-e printf("p es q %s\n", p == q? "megegyezik" : "nem egyezik meg"); printf("*p == %d, *q == %d\n", *p, *q); *p = 4;
printf("*p == %d, *q == %d\n", *p, *q); free(p); p = q; printf("p es q %s\n", p == q? "megegyezik" : "nem egyezik meg"); printf("*p == %d, *q == %d\n", *p, *q); *p = 4; printf("*p == %d, *q == %d\n", *p, *q); free(p); return 0; ez azt vizsgálja, hogy p és q ugyanolyan értékre mutat-e
P: Futtassuk le a következő programot, és értelmezzük! Melyik érték melyik értékkel egyenlő, és miért? #include <stdio.h> int main() Egy változóhoz tartozó memóriacímet az & { operátorral érhetünk el, pl. &valtozo. Ezt hozzárendelhetjük egy pointerhez: p = &a;. int a = 10; int *pa; pa = &a; printf("%d %#x\n", a, (int)pa); printf("%#x %#x\n", (int)&a, (int)&pa); printf("%d\n", *pa); return 0;
F: Írj egy csere() függvényt, ami megcseréli két int típusú változó értékét. #include <stdio.h> void csere(int x, int y){ int tmp; tmp = x; x = y; y = tmp; int main(){ int x = 3, y = 4; printf("a fuggveny elott: x = %d, y = %d\n", x, y); csere(x,y); printf("a fuggveny utan: x = %d, y = %d\n", x, y); return 0;
F: Mi a hiba? Javítsuk a programot! #include <stdio.h> void csere(int * x, int * y){ int tmp; tmp = *x; *x = *y; *y = tmp; int main(){ int x = 3, y = 4; printf("a fuggveny elott: x = %d, y = %d\n", x, y); csere(&x,&y); printf("a fuggveny utan: x = %d, y = %d\n", x, y); return 0;
Pointerek vs tömbök A C nyelvben a tömbök is egyfajta pointerek: létrehozáskor a [] között megadott darabszámú egymás után elhelyezkedő memóriacella lefoglalódik valahol a memóriában ÉS létrejön (valahol máshol) egy megfelelő típusú pointer, ami az első (nulladik) cellára mutat. Ez a pointer a tömb elérésére használt változó. Mikor a tömb egy adott elemét használjuk (tomb[index]), akkor a "tomb" pointerben tárolt memóriacímhez hozzáadódik "index" értéke és az így kapott címen kezdődő megfelelő méretű cellát érjük el. (tomb[index] *(tomb+index) ) Ezért is kezdődik 0-ról a tömbök indexelése C-ben.
F: Deklarálj egy 20 elemű int tömböt, majd töltsd fel értékekkel az inputról. Deklarálj egy pointert, és a beolvasást azon keresztül valósítsd meg. #include <stdio.h> #define N 20 int main() { int t[n], *p, i; for(i=0; i<n; i++) { p=&(t[i]); scanf("%d", p); for(i=0; i<n; i++) { printf("%d\n", t[i]); return 0;
F: Először olvasd be a tömb méretét, és foglalj neki dinamikusan helyet! #include <stdio.h> #include <stdlib.h> int main() { int *t, *p, i, N; scanf("%d", &N); t=(int*)malloc(n*sizeof(int)); for(i=0; i<n; i++) { p=&(t[i]); scanf("%d", p); for(i=0; i<n; i++) { printf("%d\n", t[i]); free(t); return 0;
F: Olvass be 5 darab maximum 99 karakter hosszú szót úgy, hogy mindegyiknek pontosan annyi helyet foglalsz, amennyi kell! A sztringeket írasd ki, majd szabadítsd fel a lefoglalt területet! #include <stdio.h> #include <stdlib.h> #include <string.h> int main() { char buff[100]; char *ptr_tomb[5]; int i;
for(i=0; i<5; i++) { scanf("%s", buff); ptr_tomb[i] = (char*)malloc(strlen(buff)+1); strcpy(ptr_tomb[i], buff); for(i=0; i<5; i++) { puts(ptr_tomb[i]); for(i=0; i<5; i++) { free(ptr_tomb[i]); return 0;
F: Olvasd be egy tömb méretét, foglalj neki dinamikusan helyet, majd olvasd be az elemeit! #include <stdio.h> #include <stdlib.h> int main(){ int *t, *p, i, N; scanf("%d", &N); t=(int*)malloc(n*sizeof(int)); for(i=0, p=t; i<n; i++, p++) { scanf("%d", p); for(i=0, p=t; i<n; i++) { printf("%d\n", *(p++)); free(t); return 0;
Több dimenziós tömbök vs pointer Többdimenziós tömbök folytonosan tárolódnak a memóriában: tipus tomb[2][2]; esetén tomb[0][0] a 0., tomb[0][1] az 1., tomb[1][0] a 2. és tomb[1][1] a 3. egymás utáni memóriacellában tárolódik el. Ezenkívül, a pointerekben tárolt memóriacímet additív aritmetikai műveletekkel (összeadás, kivonás) módosíthatjuk is.
F: Adott egy kétdimenziós tömb. Pointer segítségével járjuk be az összes elemét. #include <stdio.h> #define SIZE 3 int main() { int tomb[size][size] = {{0, 1, 2, {3, 4, 5, {6, 7, 8 ; int i,j; int *pa = NULL; // így a pointer nem mutat sehova sem, azaz "nullpointer" pa = tomb; /* pa = &tomb[0][0] */ for(i = 0; i< SIZE*SIZE; i++) printf("%2d ", pa[i]); printf("\n");
for(i = 0; i< SIZE*SIZE; i++) printf("%2d ", *(pa+i)); printf("\n"); for(i = 0; i< SIZE*SIZE; i++, pa++) printf("%2d ", *pa); printf("\n"); return 0; vigyázat! mivel pa-t növeltük a for ciklusban, ezért a ciklus után már nem a tömb legelső elemére fog mutatni!
2D tömbök 2D-sként 2D-s dinamikus tömböt tömbök tömbjeként lehet létrehozni, ahol a "külső tömb" pointereket tárol. Pl. tomb[n][m] létrehozása dinamikusan úgy történik, hogy létrehozunk egy N-méretű pointertömböt, majd minden eleméhez lefoglalunk egy M méretű tömböt. Ekkor a tomb változó egy pointerre mutató pointer lesz. Itt már természetesen N és M lehet változó is, bekérhetjük felhasználótól, stb. Ennek felszabadításához először a "belső" tömböket kell egyenként felszabadítani, majd a külső pointertömböt.
2D tömb 2D-sként int **tomb; // a tömböt jelentő pointer, intre mutató pointerre mutat, ezért //két * kell tomb = malloc(n * sizeof(int*)); // a külső tömb lefoglalása intre mutató pointereknek for(i = 0; i < N; i++) { // a belső tömbök lefoglalása - inteknek tomb[i] = malloc(m * sizeof(int)); // használat - mintha egy "sima" tömb lenne // felszabadítás for(i = 0; i < N; i++) { // először a belső tömböket free(tomb[i]); free(tomb); // majd a külsőt
2D tömb 1D-sként Egy másik lehetőség, hogy egydimenziós tömböt hozunk létre megfelelő méretben, és abban sorfolytonosan tároljuk a 2D-s tömb elemeit. Ezt egyszerűbb létrehozni és megszüntetni, viszont bonyolultabb használni.
2D tömb 1D-sként int *tomb; // a tömböt jelentő pointer, így csak egy * kell tomb = malloc(n * M * sizeof(int)); // a tömb lefoglalása - N*M elemű kell // használat - ki kell számolni az indexet for(i=0; i < N; i++) { for(j=0; j < M; j++) { tomb[i*m+j] = valami; // eddig i sort léptünk át, egy sor M hosszú // felszabadítás - így egy lépésben lehet, mert csak 1D-s //tömbünk van free(tomb);
F: Dinamikus kétdimenziós tömb létrehozása #include <stdio.h> #include <stdlib.h> int main(){ int *p, **t; int N=3, M=4; int i, j, v=0; /* V1: egydimenziós tömb */ p=malloc(n*m*sizeof(int)); for(i=0; i<n; i++) { for(j=0; j<m; j++) { p[i*m+j]=v++; free(p); //i db sort léptünk át+j. helyen vagyunk
/* V2: sorokat külön-külön */ t=malloc(n*sizeof(int*)); for(i=0; i<n; i++) { t[i]=malloc(m*sizeof(int)); for(i=0; i<n; i++) { for(j=0; j<m; j++) { t[i][j]=v++; for(i=0; i<n; i++) { free(t[i]); free(t); return 0;
Tárolási osztályok A C nyelvben lehetőségünk van a változók tárolási tulajdonságait befolyásolni úgy, hogy különböző "tárolási osztályokba" szervezzük őket. Egy változó tárolási osztályát hasonlóan tudjuk megadni, mint ahogy a méretét/előjelességét is, pl. const int a;. Ezek a típus elé írható módosítók bármilyen sorrendben szerepelhetnek, const unsigned short int a;, unsigned const short int a;, short unsigned const int a;, stb. mindegyike helyes. A használható tárolási osztályok: auto alapértelmezett, nem kötelező kiírni; akkor van értelme, ha hangsúlyozni akarjuk, hogy ez a változó nem különleges, pl. ha több static változó között deklaráljuk. register a fordító MEGPRÓBÁLJA a változót mindig a processzor egy regiszterében tartani, de akár figyelmen kívül is hagyhatja. Így a változó használata sokkal gyorsabb, ezért gyakran változó értékekhez hasznos. Viszont, mivel nincs memóriacíme, nem lehet pointert mutatni rá.
volatile a változó értékét "külső erők" is módosíthatják, például ha egy porthoz van rendelve. Elsősorban különböző driverek és egyéb hardverközeli programok írásakor hasznos. A program minden esetben a memóriából használja a változót, akkor is, ha már a cacheben, vagy egy regiszterben van. Emiatt kicsit lassabb lehet a kezelése. extern a változó deklarációja valahol máshol van, itt csak jelezzük, hogy létezik ilyen. Több modulra bontott programok globális változóihoz használatos, a header-fájlokban. const a változó értéke nem módosítható. Pointeres trükkökkel megkerülhető, csak annyit okoz, hogy a fordító hibát dob, ha egy értékadó művelet bal oldalán szerepeltetjük. static a változó a program futása során végig létezik és elérhető (míg egy hagyományos változó csak akkor, ha a program futása épp a változó blokkjában van). Például függvények lokális változóit szokás staticként deklarálni, így a következő függvényhíváskor a korábbi értéket érhetjük el.
F: Készíts egy pointert, ami egy konstans értékre mutat. F: Készíts egy konstans pointert, ami egy nem konstans értékre mutat. F: Készíts egy pointert, ami egy tömbre mutat. #include <stdio.h> int main() { const int *p=null; int * const c=null; int (*t)[20]; p=malloc(sizeof(int)); *p=2007; /* HIBÁS */ free(p); c=malloc(sizeof(int)); /* HIBÁS */ *c=2007; free(c); return 0;
Feladatok Adottak a const int a=100; int *p; deklarációk. Duplázd meg a értékét! Adottak az int *p, *q változók. A p=malloc(sizeof(int)); után mikor lesz érvényes a free(q); utasítás? Olvasd be egy számsorozat hosszát, majd az elemeit, és írasd ki őket úgy, hogy előbb a negatív, majd a nemnegatív elemeket sorolod fel. Az elemek sorrendje a negatív illetve nemnegatív blokkon belül az eredetihez képest ne változzon. A beolvasást egy void típusú függvény végezze! Hozz létre egy NxM-es mátrixot, ahol N és M értékét a felhasználó adja meg, majd olvasd be az elemeit, szorozd meg egy konstanssal, végül írd ki! Próbáld ki mindkét módszerrel (1D-s tömbben sorfolytonosan és valódi 2D-s tömbben)!
Feladatok Add meg két N-dimenziós vektor összegét. A program először bekéri a vektorok méretét, lefoglal egy megfelelő méretű dinamikus tömböt az eredmény tárolására, majd beolvassa az első vektort, végül hozzáadja a másodikat. (Ehhez nem kell külön tömböt foglalni, a második vektor elemeit egyesével hozzá tudod adni a tömb megfelelő elemeihez.) Ne felejtsd el a végén a memóriát felszabadítani! Hasonlíts össze két mátrixot! Először kérd be a méretüket, foglalj le két megfelelő méretű 2D-s tömböt a tárolásukra, majd kérd be az elemeiket. Végül számold meg, hány pozíción különböznek. Ha egyen se, írd ki azt is, hogy a mátrixok egyenlőek! Az egyik tömböt foglald le valódi 2D-sként, a másikat pedig 1D-s tömbben tárold sorfolytonosan! Ne felejtsd el a végén a memóriát felszabadítani!
Puzzle #include <stdio.h> Miért 50 a megoldás? int f(int *p){ *p = *p+8; return *p*4; int main(){ int a, b; a = 2; b = f(&a); printf("%d", a + b); printf("\n"); return 0;