kategória | ||||||||||
|
||||||||||
|
||
Mutatók a C programozási nyelvben: Többszörös indirektségű mutatók. Pointer operátorok, címaritmetika. A mutatók és a tömbök kapcsolata.
Általában mutatónak ( a továbbiakban pointer ) nevezzük az olyan változót amely valamely objektum címét tartalmazza. C - ben ez az objektum lehet egy egyszerű változó, tömb, struktúra és függvény is. A pointer definíciója a típusmegadással kezdôdik. Itt nem a pointer típusáról van szó hanem annak az objetumnak a típu-sáról, amelyre a pointer mutat. A pointer neve elôtti "*" jelzi, hogy nem egyszerű változóról van szó, hanem pointe 434d31e rrôl. Ezután következik a pointer neve. Maga a pointer általában 4 byte hosszú. Ebbôl 2 byte a szegmenscím, 2 byte pedig az offset. Ezalól egyetlen kivétel van : a near ( közeli ) pointer amely csak 2 byte hosszú !
Példa : int *px ; // px
nevű, integer típusú változóra mutató pointer.
Változó címének átadása az & operátor segítségével
Hogyan adhatjuk át egy változó címét egy pointer változónak ?
Példa : Tekintsük a következô definíciókat :
int x, *px;
px = &x
; // Az & a cím operátor !
Ezen utasítás hatására a px nevű pointer felveszi az x változó címét. Azaz, ha az x változó az 1000 memória címen kezdôdik, akkor a px értéke 1000 lesz. A változó típusának ill. a pointer típusának meg kell egyeznie, vagy típuskonverziót kell alkalmaznunk.
Indirekció.
Indirekciónak nevezzük azt az eljárást, melynek során egy változó értékét nem a változó nevének segítségével érjük el, hanem a változóra mutató pointer segítségével. Az indirekció operátora a "*" ( csillag ).
Példa : Tekintsük a következô programrészletet :
int x, *px;
x = 2;
px = &x ; // px
felveszi x címét. Ezek után a px
által mutatott cím tartalmát így iratjuk ki :
printf("\n PX által
mutatott cím tartalma = %d", *px) ;
// A képernyôn 2 jelenik meg, az = jel után !
Vigyázat : A "*" használata kettôs jelentésü. A definíciónál azt jelenti hogy a definiált változó pointer lesz. A program során valamely műveletnél, pedig tartalomra való hivatkozást jelent ( Indirekció ! ).
Összefoglaló példa az eddigiekre :
#include
<stdio.h>
void main()
Megjegyzés :Természetesen a gyakorlatban ritkán valósul meg az x=y érték-adás ebben a formában. Az indirekció szemléltetésére azonban kiválóan alkalmas !
Létezik egy speciális eset a pointer értékére vonatkozóan : amikor a pointer a 0 (nulla) értéket tartalmazza. Ekkor beszélünk NULL pointerrôl, aminek a konkrét jelentése az, hogy maga a pointer nem mutat érvényes adatra. A fordító gondoskodik arról, hogy a <stdio.h>-ban definiált NULL szimbólumhoz tartozó 0 (nulla) érték, ne a nullás memóriahelyet jelentse, nehogy a pointer erre a helyre mutasson. Ez a konstrukció kiválóan alkalmas hibakezelésre, mint azt a file kezelésnél, látni fogjuk !A másik speciális eset egy pointer típusra vonatkozik, aminek a neve void. A void a pointer deklarálásnál azt jelenti, hogy - egyelôre - nincs meghatározott típusa. A void-nak deklarált pointer típusát akkor kell megadni, amikor a pointerrel valami-lyen műveletet végzünk.
Példa :Tekintsük
a következô utasításokat : ( Hasonló a Borland C++ 2.0 helpjében szereplô
példához ! )
int i ;
float f ;
void *vp ; // A vp pointernek egyelôre nem
ismerjük a típusát ,
//
azaz nem tudjuk, hogy milyen típusú változóra mutat
vp = &i ; // vp felveszi az i
változó címét.
* ( int * ) vp
= 5 ; // vp tartalma 5 lesz, az ( int* ) típusmódosítás, az
//
egyértelmű méret meghatározás miatt szükséges !
vp = &f ; // vp az f változó
címét veszi fel.
* ( float * ) vp = 4.8; // itt is szükséges a ( float * ) típusmódosítás !
Egydimenziós tömbök és a pointerek kapcsolata
Ez a kapcsolat már csak azért is érdekes, mert felmerül a kérdés, hogy milyen módon adjunk át egy vektort egy függvénynek. Nyilván nem úgy, hogy egyenként átadjuk az összes elemét. Az is kérdéses, hogy egy függvény hogyan adjon vissza egy vektort. Mint láttuk a függvény return-ja után csak egy kifejezés szerepelhet. Ezen kérdésekre is választ ad a következô szabály :
Egy egydimenziós vektor neve megegyezik a vektor 0-dik elemének a címével, tehát a vektor kezdôcímével : vektor = &vektor[0] , ahol "vektor" egy n elemű egydimenziós tömb.
Példa :
int x[10], *px;
px = x; // Egyenértékü a px = &x[0] értékadással. px jelenleg
az //
x vektor kezdôcímét tartalmazza.
Amennyiben egy vektor kezdôcímét egy azonos típusú pointernek adjuk át, akkor mind a pointer mind a vektor neve a vektor kezdôcímét tartalmazza. A két kifejezés között van azonban egy alapvetô különbség ! A pointer egy változó, a tömb neve pedig egy konstans. Ebbôl következôen, az elôbbi példánál maradva :
px++;
// Megengedett művelet : A vektor következô
elemére mutat.
x++; //
Nem megengedett művelet, mert az x konstans !
Ha válaszolni akarunk a feltett kérdésekre, akkor azt mondhatjuk : egy vektort úgy adhatunk át egy függvénynek, hogy a nevét adjuk át aktuális paraméterként. A függvénybôl való visszatérés esetén pedig a vektor nevével tér vissza a függvény. Ennek az esetnek nyilván csak akkor van értelme, ha a függvényben static-nak deklaráljuk a vektort. Itt újabb kérdés merül fel : hogyan hivatkozhatunk a vektor kezdô címének ismeretében a vektor tetszôleges elemének címére ill. annak tartalmára. Ezt a kérdést a C nyelv címaritmetikára vonatkozó szabályainak ismeretében válaszolhatjuk meg !
Címaritmetika
Egy cím + egész kifejezésnek az az
alapvetô tulajdonsága, hogy mindig az illetô cím típusának megfelelôen a
következô elemre mutató címet állítja elô.
&x[2] = x+2; //
A vektor 3. elemének a címe = A vektor kezdôcíme + 2
//
Általában : &x[i] = x + i
x[2] = *(x+2); //
A vektor 3. elemének értéke = Az arra mutató cím
//
tartalmával
//
Általában : x[i] = *(x + i)
// Buborékrendezés
függvénnyel megvalósítva.
#include <stdio.h>
void main()
;
void bubble_sort(
int *, int );
int ii;
bubble_sort( a, 10
);
for(ii=0;
ii<10; ii++)
printf("\nA[%2d]
= %2d",ii,a[ii]);
}
void bubble_sort( int
*bs_v, int bs_n )
}
Konkrétan : a bs_v+1 ( vagy bs_v++ ) művelet a bs_v címet két byte-nyival lépteti, ha bs_v egy integer típusú pointer. Ha float vagy double típusú lenne, akkor a léptetés nagysága 4 ill. 8 byte lenne.
További szabályok :
Ha két pointer ugyanannak a tömbnek az elemeire mutat, akkor értelmezettek a <, >, ==, != stb. relációs műveletek.
Ha két pointer ugyanannak a tömbnek az elemeire mutat, ( pl.: a és b ), akkor a - b az a és b közötti elemek darabszámát adja.
Pointerhez hozzáadhatunk, ill. levonhatunk egész számot.
Tilos : Pointerek szorzása, osztása, shiftelése, maszkolása, ill. float vagy double mennyiségek pointerekhez való hozzáadása vagy kivonása.
Kétdimenziós tömbök és a pointerek kapcsolata
Amint megállapítottuk, kétdimenziós tömböt vektorok vektoraként adhatunk meg. A C a mátrixot sorfolytonosan tárolja !
Példa a definícióra : int y[10][5];
Amíg az egydimenziós tömbök esetén a tömb neve a tömb kezdô elemének a címével egyenlô, kétdimenziós tömbök esetén a tömb neve a tömb kezdô sorának címével egyenlô ! Fizikailag a kezdô elem, ill. a kezdô sor címe megegyezik, csak más a típusuk : amíg az elôbbi egy integerre mutató pointer, addig az utóbbi egy integer vektorra mutató pointer. ( Természetesen a típus mindkét esetben integer ! ) Ez azért lényeges, mert pld. ha az elôbbit eggyel növeljük, akkor egy integernyit lép arrébb a cím, az utóbbi esetében az eggyel való növelés egy integer vektornyi léptetést jelent. Ennek a vektornak a méretét nyilván a mátrix oszlopmérete határozza meg.
Példa : Amennyiben
a már definiált y tömb után, a következô definíciókat végezzük el :
int (*ppy)[5], *py ; akkor a következôket állapíthatjuk meg :
Az y nem az y tömb kezdô elemének a címe, hanem a kezdô (azaz 0.) sorának - egy öt elemű vektornak - a címe. A ppy egy öt elemű egész típusú vektorra mutató pointer , így a következô értékadások helyesek :
ppy = &y[0] ; vagy ppy = y; de a py = y ; rossz, mivel py egy
egyszerű változóra mutató pointer !
Az y+1 művelet a következô sor, azaz y[1]-nek a címét állítja elô, tehát ebben
az esetben az eggyel való növelés öt integernyi léptetést jelentett, mert az
oszlopméret öt.A mátrix kezdôelemének a címét a py = &y[0][0]; művelettel
helyezhetjük el py-ban. Ekkor a py+1 művelet a mátrix következô elemének a
címét állítja elô, tehát az y[0][1] elem címét ! Ha adott a mátrix kezdô
elemének a címe, akkor egy tetszôleges elemét a *( py + sorindex*oszlopméret + oszlopindex) m velettel
érhetjük el.
Találat: 2715