C alapok - pointerek
vitafórum(2)
szerző: Crystaldátum: 2008-03-31
Kategóriák:
C/C++/C alapok
Az olvasó már valószínűleg találkozott más nyelvekben (pl. pascal) a mutató típussal (a pointer és mutató kifejezést egyaránt használni fogom). Ebben a tutorial-sorozatban ezt a témát azért hoztam ennyire előre, mert a c-ben ez alapvető fontosságú programozási eszköz, melyet készségszinten kell ismerni, mivel olyan alapszintű módszerek épülnek rá, mint a sztringek és tömbök kezelése.
Mi is az a mutató?
A mutató egy olyan speciális változó, melynek értéke egy memóriacím. A mutató értékét ill. az értéke által jelölt memóriacímen levő értéket (továbbiakban: a mutató által mutatott értéket) egyaránt tudjuk módosítani a mutató segítségével. A mutató deklarációjakor meg kell adnuk, hogy milyen típusú érték van az általa mutatott helyen. Nézzünk egy példát:
Tehát egy olyan pointert deklaráltunk, ami egy int típusú értékre mutat. A * jelzi, hogy pointert deklaráltunk (tehát pointerdeklkarációkor mindig meg kell adnunk a változónév előtt a csillagot.int *p;
Mikor kezdőértéket adunk egy mutatónak, akkor általában nem konstans számot adunk meg, hanem egy másik (már korábban deklarált) változó memóriacímét. C-ben bármely változó memóriacímét megkapjuk, ha a változó neve elé írunk egy & jelet. Nézzünk erre is egy példát:
Tehát itt a programunk lefoglalja a memóriát az i változónak, majd a p mutatónak, majd p értéke az i kezdőcíme lesz.int i, *p=&i;
Pointerek használata alapszinten
Mint azt fentebb írtam, a mutató segítségével egyaránt tudjuk írni/olvasni a mutató értékét és a mutató által mutatott értéket. Előbbit értelemszerűen a mutató nevével (tehát az előző pédában p), utóbbit pedig egy * jellel és utána a mutató nevével (pl. *p) érhetjük el. Nézzünk egy egyszerű példát:
int i=5;
int *p;
p=&i;
(*p) = (*p)+1;
Tehát itt a 3. sorban p értékének beállítottuk az i memóriacímét. Innentől kezdve a *p kifejezés ugyanazt jelenti, mint az i. Utána a 4. sorban megnöveltük 1-el a p által mutatott változó értékét. Értelemszerűen ekkor i értéke 6 lett.
Többszörösen indirekt pointerek
Természetesen a mutató is egy változó, aminek szintén van memóriacíme. Ebből következően használhatunk mutatóra mutató mutatókat is, meg persze mutatóra mutató mutatóra mutató mutatóra mutató mutatókat is és így tovább. Mikor c-ben programozunk, akkor elsősorban a kétszeres indirektésű mutatók a fontosak, tehát a mutatóra mutató mutatók. Nézzünk erre is egy példát:
int i, *p, **pp;
p = &i;
pp = &p;
Tehát a p lesz az int-re mutató pointer, pp pedig az int-re mutató pointerre mutató pointer (ez szerintem a jelölések alapján egyértelmű). Ebből következően innentől kezdve a i, *p és a **pp kifejezések ugyanazt a memóriacímet fogják jelölni.
Dinamikus memóriakezelés
Programozás során gyakran nem tudhatjuk előre, hogy mekkora memóriaterületre lesz szükségünk az adataink eltárolásához - vegyük azt a triviális példát, mikor tetszőlegesen sok számot kell beolvasnunk. Olyan is van, hogy az egész programunk által használt összes adat nem fér be egyszerre a memóriába, ezért azt szeretnénk, ha mindig csak azok az adatok lennénenek a tárban, melyekkel éppen dolgozunk, és valóban szükségünk van rá. C-ben ezt is mutatókkal oldjuk meg. Vannak olyan függvények, melyek futásidőben foglalnak memóriát, és fel is tudják szabadítani az így lefoglalt helyet, hogy átadják azt más változóknak. Nézzünk erre is egy példát:
#include<stdio.h>
#include <stdlib.h>
int main() {
int *p;
p = malloc(sizeof(int));
*p=55;
printf("%d\n", *p);
free(p);
}
Az első két sorban meghívtuk az stdio.h-t, mely a printf() függvényt tartalmazza, és az stdlib.h-t, mely a malloc() függvényt tartalmazza. A program elején deklaráltuk a p mutatót, majd értékül adtuk neki a malloc(sizeof(int)) függvényhívás eredményét. Ez egy nagyon fontos függvény c-ben, lefoglal egy akkora memóriaterületet, amekkora méretet megadtunk neki paraméterként, majd visszatér annak kezdőcímével. A sizeof() függvénynek pedig bármilyen kifejezést vagy típust megadhatunk, annak tárolásához szükséges memóriaterület méretével tér vissza (pl a sizeof(char) értéke 1 lesz, a sizeof(int) értéke pedig - 32 bites processzor esetén - 4). Magyarul a malloc(sizeof(int)) lefoglal egy 4 bájtos területet és visszatér annak memóriacímével. Fontos tudni, hogy az új területen levő érték a malloc()-nál határozatlan, ugyanúgy mint változódeklarációkor. Ezután az újonnan lefoglalt memóriaterületre berakjuk az 55-öt, majd ezt kiíratjuk (a printf() függvény működéséről majd később írok). Végül a free(p) utasítással felszabadítjuk a p által lefoglalt memóriaterületet, tehát ide mostmár bármilyen más változó kerülhet. Persze ez most kicsit erőltetett példa, mert a program futásának végén úgyis felszabadul az összes memória, de azért remélem, hogy érthető mire jó a free() (egyébként ez is az stdlib.h-ban van).
Amit nem szabad csinálni:
int i, *p;
p = malloc(sizeof(int));
p = &i;
Ekkor ugyebár a p-nek dinamikusan lefoglalunk egy memóriaterületet, amire a p mutatni fog. Ezután p-t ráállítjuk i-re. Magyarul most egyik változó és semmilyen kifejezés nem mutat a malloc(sizeof(int)) által lefoglalt területre, tehát semmire nem tudjuk használni, viszont attól még teljesen feleslegesen lefoglalva marad.
Ez a tutorial talán kicsit túl elméletire sikerült, és talán nem igazán érti az Olvasó hogy mire jó ez az egész, de a következő tutorialokban majd rámutatunk, hogy a pointerek nagyon hasznosak és fontosak, legalapvetőbb C problémák megoldásakor is.
