Informatika2-2012/Eloadas04

A MathWikiből
(Változatok közti eltérés)
1. sor: 1. sor:
 
== Mutatók (pointer-ek) ==
 
== Mutatók (pointer-ek) ==
  
 +
==== Mi is egy mutató? ====
  
==== Mi is egy mutató? ====
+
A mutató (angolul pointer) típusú adat egy másik adat címét tárolja, vagyis azt, hogy hol van tárolva az adat a számítógép memóriájában. (Ez tulajdonképpen csak egy szám, ami azonosítja a memória egyik "rekeszét".)
A mutató (angolul pointer) típusú adat egy másik adat címét tárolja, vagyis azt, hogy hol van tárolva az adat a számítógép memóriájában.
+
 
(Első kép [http://oreilly.com/catalog/pcp3/chapter/ch13.html innen].)
 
(Első kép [http://oreilly.com/catalog/pcp3/chapter/ch13.html innen].)
  
Minden mutatót ugyanolyan belső ábrázolással tárolunk (hiszen mindegyik egy cím egy memóriaterületre, egész pontosan a terület "kezdőpontjára"), mégis van "típusa", ami hivatkozott objektum típusát határozza meg. Ebből tudja majd a program futás közben, a mutató által mutatott objektum használatakor, hogy "mekkora" az objeltum maga, vagyis mekkora az az adatterület a memóriában, ami a mutatott változóhoz tartozik.
+
Minden mutatót ugyanolyan belső ábrázolással tárolunk (hiszen mindegyik egy cím egy memóriaterületre, egész pontosan a terület "kezdőpontjára"), mégis van "típusa", ami hivatkozott objektum típusát határozza meg. Ebből tudja majd a program futás közben, a mutató által mutatott objektum használatakor, hogy "mekkora" az objektum maga, vagyis mekkora az az adatterület a memóriában, ami a mutatott változóhoz tartozik.
 
+
  
 
==== Mire jók a mutatók? ====
 
==== Mire jók a mutatók? ====
14. sor: 13. sor:
  
 
A C egy alacsonyabb szintű nyelv, itt "gépközelibb" eszközökkel kell dolgoznunk. Ez egyrészt kényelmetlen lehet, másrészt viszont lehetőséget ad olyan optimalizálásokra, olyan hatékony programok írására, ami egy magasabb szintű nyelven nehézkes vagy lehetetlen lenne. (Pl: bitenkénti műveletek, bool vektor tárolása)
 
A C egy alacsonyabb szintű nyelv, itt "gépközelibb" eszközökkel kell dolgoznunk. Ez egyrészt kényelmetlen lehet, másrészt viszont lehetőséget ad olyan optimalizálásokra, olyan hatékony programok írására, ami egy magasabb szintű nyelven nehézkes vagy lehetetlen lenne. (Pl: bitenkénti műveletek, bool vektor tárolása)
 
  
 
==== Konkrétabban mire jók a mutatók? ====
 
==== Konkrétabban mire jók a mutatók? ====
22. sor: 20. sor:
 
* függvénynek paraméterként átadható: csak a cím másolódik, ez egyrészt hatékonyabb egy nagy adatstruktúra esetén, másrészt így a mutatott objektumot megváltoztathatja a függvény
 
* függvénynek paraméterként átadható: csak a cím másolódik, ez egyrészt hatékonyabb egy nagy adatstruktúra esetén, másrészt így a mutatott objektumot megváltoztathatja a függvény
 
* akár több mutatónk is lehet ugyanarra a memóriacímre: adatok sorbarendezése többféle sorrendben, adat-többszörözés nélkül
 
* akár több mutatónk is lehet ugyanarra a memóriacímre: adatok sorbarendezése többféle sorrendben, adat-többszörözés nélkül
 
  
 
==== Hogy néz ki egy mutató C-ben? ====
 
==== Hogy néz ki egy mutató C-ben? ====
  
Két operátort kell ismernünk a mutatók használatához (mindkettőt a változó neve ''elé'' kell írni amire vonatkoztanni akarjuk):
+
Két operátort kell ismernünk a mutatók használatához (mindkettőt a változó neve ''elé'' kell írni amire vonatkoztanni akarjuk, és lehet köztük szóköz is):
  
 
* Egy létező változó címét a "&" operátorral kérhetjük el.
 
* Egy létező változó címét a "&" operátorral kérhetjük el.
77. sor: 74. sor:
 
</c>
 
</c>
  
 +
==== Tömbök címzése, "pointer aritmetika" ====
  
===== Források és további olvasnivalók: =====
+
Mint tudjuk, a C-beli tömbök csak azonos típusú elemeket tartalmazhatnak, amik értelemszerűen fix méretű helyet foglalnak el a memóriában. Azt is érdemes tudni, hogy a tömbök elemei mindig sorban és közvetlenül egymás után lesznek a elérhetőek a memóriában. (Ezért ha nagyon nagy tömböt akarunk lefoglalni az problémás lehet, hiszan akkora helyet "egyben" kell találni a gép memóriájában.)
 +
 
 +
Egy tömb elemei tehát sorban vannak. Ez lehetővé teszi hogy az egyes elemek címeit (pointereit) összehasonlítsuk (aminek nagyobb a címe az hátrébb van a tömbben), vagy akár műveleteket végezzünk velük (összeadás, szorzás).
 +
 
 +
Így hozhatunk létre két mutatót amik egy tömb 4. és 8. elemére mutatnak:
 +
<c>
 +
double *d_ptr1, *d_ptr2;
 +
double a[10];
 +
d_ptr1 = &a[3];
 +
d_ptr2 = &a[7];
 +
</c>
 +
 
 +
===== Mutatók összehasonlítása =====
 +
 
 +
Csak olyan mutatókat hasonlítsunk össze, amik ugyanannak a tömbnek az elemeire mutatnak!
 +
A használható operátorok:
 +
<c>
 +
==  !=  >  <  >=  <=
 +
</c>
 +
 
 +
Például a fenti kódot kiegészíthetjük így, hogy a későbbi elemet írja ki:
 +
 
 +
<c>
 +
double *d_ptr1, *d_ptr2;
 +
double a[10];
 +
d_ptr1 = &a[3];
 +
d_ptr2 = &a[7];
 +
if (d_ptr2 > d_ptr1) {
 +
printf("%lf\n", *d_ptr2);  /* a mutatott értéket írjuk ki ! */
 +
} else {
 +
printf("%lf\n", *d_ptr1);
 +
}
 +
 
 +
</c>
 +
 
 +
===== Mutatók összeadása, kivonása =====
 +
 
 +
([http://c-faq.com/~scs/cclass/notes/sx10b.html Képek itt.])
 +
 
 +
A pointer aritmetika arra ad lehetőséget, hogy egyszerűen léptessük a mutatóinkat egy tömbön belül.
 +
Ha egyet hozzáadunk a mutatóhoz, az a következő tömbbeli elemre fog mutatni.
 +
Továbbírjuk az eredeti kódot, d_ptr2 az ötödik elemre fog mutatni a tömbben:
 +
<c>
 +
double *d_ptr1, *d_ptr2;
 +
double a[10];
 +
d_ptr1 = &a[3];
 +
d_ptr2 = &a[7];
 +
double *d_ptr3 = d_ptr1 + 1;
 +
/* és ezzel akár értéket is adhatunk a[4]-nek vagyis az 5. elemnek: */
 +
*d_ptr3 = 11;
 +
/* a rákövetkező (hatodik) elemet is beállítjuk, a d_ptr1-et és összeadást használva: */
 +
*(d_ptr1+2) = 45;
 +
</c>
 +
Az utolsó sorban muszáj a zárójeleket kitenni, mert a * operátor "erősebben köt" a változónévhez mint az összeadás operátora (az operátorok erősségi sorrendjéről, vagyis a precedenciákról később tanulunk részletesebben).
 +
 
 +
Ha van két mutatónk amik ugyanannak a tömbnek az elemeire mutatnak, akkor a különbségük megmondja hogy hány elem van köztük a tömbben. Például:
 +
 
 +
<c>
 +
double *d_ptr1, *d_ptr2;
 +
double a[10];
 +
d_ptr1 = &a[1];
 +
d_ptr2 = &a[9];
 +
printf("%d \n", (d_ptr2 - d_ptr1));
 +
/* kiírja hogy 8 */
 +
</c>
 +
 
 +
==== Többdimenziós tömbök ====
 +
 
 +
<code>
 +
                          +-----+-----+-----+
 +
            mat[0]  ---> | a00 | a01 | a02 |
 +
                          +-----+-----+-----+
 +
                          +-----+-----+-----+
 +
            mat[1]  ---> | a10 | a11 | a12 |
 +
                          +-----+-----+-----+
 +
                          +-----+-----+-----+
 +
            mat[2]  ---> | a20 | a21 | a22 |
 +
                          +-----+-----+-----+
 +
                          +-----+-----+-----+
 +
            mat[3]  ---> | a30 | a31 | a32 |
 +
                          +-----+-----+-----+
 +
 
 +
</code>
 +
 
 +
 
 +
=== Források és további olvasnivalók ===
  
 
* http://oreilly.com/catalog/pcp3/chapter/ch13.html
 
* http://oreilly.com/catalog/pcp3/chapter/ch13.html
 
* http://www.classle.net/book/simple-c-programming-pointers
 
* http://www.classle.net/book/simple-c-programming-pointers
 
* http://www.hit.bme.hu/~vitez/Progalap1/2011osz/Ea/ea06.pdf
 
* http://www.hit.bme.hu/~vitez/Progalap1/2011osz/Ea/ea06.pdf
 +
 +
=== Ellenőrző kérdések ===

A lap 2012. február 29., 11:53-kori változata

Tartalomjegyzék

Mutatók (pointer-ek)

Mi is egy mutató?

A mutató (angolul pointer) típusú adat egy másik adat címét tárolja, vagyis azt, hogy hol van tárolva az adat a számítógép memóriájában. (Ez tulajdonképpen csak egy szám, ami azonosítja a memória egyik "rekeszét".) (Első kép innen.)

Minden mutatót ugyanolyan belső ábrázolással tárolunk (hiszen mindegyik egy cím egy memóriaterületre, egész pontosan a terület "kezdőpontjára"), mégis van "típusa", ami hivatkozott objektum típusát határozza meg. Ebből tudja majd a program futás közben, a mutató által mutatott objektum használatakor, hogy "mekkora" az objektum maga, vagyis mekkora az az adatterület a memóriában, ami a mutatott változóhoz tartozik.

Mire jók a mutatók?

Első ránézésre a mutatók csak bonyolítják a dolgokat. Programozóként miért kell tudnunk egy változó címét? A Python jól el tudta rejteni előlünk azt, hogy a változók hogyan és hol is léteznek fizikailag a memóriában (sőt még a változók típusát is elrejti nagyjából), és ez eléggé kényelmes volt.

A C egy alacsonyabb szintű nyelv, itt "gépközelibb" eszközökkel kell dolgoznunk. Ez egyrészt kényelmetlen lehet, másrészt viszont lehetőséget ad olyan optimalizálásokra, olyan hatékony programok írására, ami egy magasabb szintű nyelven nehézkes vagy lehetetlen lenne. (Pl: bitenkénti műveletek, bool vektor tárolása)

Konkrétabban mire jók a mutatók?

  • komplex adatstruktúrák kialakítására (láncolt listák, fák)
  • tömbök címzésére
  • függvénynek paraméterként átadható: csak a cím másolódik, ez egyrészt hatékonyabb egy nagy adatstruktúra esetén, másrészt így a mutatott objektumot megváltoztathatja a függvény
  • akár több mutatónk is lehet ugyanarra a memóriacímre: adatok sorbarendezése többféle sorrendben, adat-többszörözés nélkül

Hogy néz ki egy mutató C-ben?

Két operátort kell ismernünk a mutatók használatához (mindkettőt a változó neve elé kell írni amire vonatkoztanni akarjuk, és lehet köztük szóköz is):

  • Egy létező változó címét a "&" operátorral kérhetjük el.
  • A "*" operátorral kérhetjük el a dolgot/objektumot amire egy mutató mutat

Egy egyszerű példa: a "szam" egész típusú változó értékét a mutatóján keresztül állítjuk be. (Emlékeztető: az értékadás operátora az "=", és ez mindig a bal oldali dolognak ad új értéket, a bal oldali változó új értéke a jobb oldali kifejezés kiértékelésének eredménye lesz.)

/* egy szám deklarálása */ 
int szam;
/* létrehozzuk a mutatót, a típusa "olyan mutató ami int típusra mutat"  */
int *szam_ptr;
 
/* itt még nincsenek "összekötve" a fenti változók, 
nincs is értékük (ill. a lokális int-nem 0 lesz az értéke), 
a lényeg hogy a memóriában lefoglalódott nekik a hely*/ 
 
/* most értéket adunk a mutatónknak, azt mondjuk mutasson 
a "szam"-ra vagyis a "szam" címével tesszük egyenlővé */
szam_ptr = &szam;
 
/* végül a "szam" értékét beállítjuk 3-ra a mutatót használva */
*szam_ptr = 3;

Nem kötelező, de ajánlott, hogy a mutató típusú változóknak a neve is utaljon erre, vagyis végződjön a neve "_ptr"-re, vagy kezdődjön "p_"-vel (mindegy melyiket választod, de utána - legalább egy programon belül - mindig csak azt a jelölést használd amit választottál!). Ez segít a kód megértésében, csökkenti a hibák valószínűségét.

Figyelem! Így NE!

int szam;
int *szam_ptr;
 
/* ha kihagyjuk ezt a lépést:*/
/* szam_ptr = &szam; */
 
/* és így írunk arra a memóriaterületre amire a szam_ptr mutat... */
*szam_ptr = 3;

... akkor nem tudjuk hogy mi is fog történni, a szam_ptr értéke véletlenszerűan akármi lehet, és mi erre a random helyre írunk! Véletlenül felülírhatunk más változókat, vagy akár magát a futó programkódot! Nehéz megtalálni egy ilyen hibát, ugyanis nem lesz determinisztikus a programunk működése, lehet hogy legtöbbször teljesen jól le fog futni, néha azonban kiszámíthatatlan hibák fognak történni.

Ennek elkerülésére egyrészt ha lehet a mutatókat rögtön a létrehozáskor definiáljuk is, pl:

int szam;
int * szam_ptr = & szam;

vagy definiáljuk a mutatót a NULL értékkel, ami azt jelenti hogy ez a mutató még nem mutat sehova:

int *szam_ptr = NULL;

Tömbök címzése, "pointer aritmetika"

Mint tudjuk, a C-beli tömbök csak azonos típusú elemeket tartalmazhatnak, amik értelemszerűen fix méretű helyet foglalnak el a memóriában. Azt is érdemes tudni, hogy a tömbök elemei mindig sorban és közvetlenül egymás után lesznek a elérhetőek a memóriában. (Ezért ha nagyon nagy tömböt akarunk lefoglalni az problémás lehet, hiszan akkora helyet "egyben" kell találni a gép memóriájában.)

Egy tömb elemei tehát sorban vannak. Ez lehetővé teszi hogy az egyes elemek címeit (pointereit) összehasonlítsuk (aminek nagyobb a címe az hátrébb van a tömbben), vagy akár műveleteket végezzünk velük (összeadás, szorzás).

Így hozhatunk létre két mutatót amik egy tömb 4. és 8. elemére mutatnak:

	double *d_ptr1, *d_ptr2;
	double a[10];
	d_ptr1 = &a[3];
	d_ptr2 = &a[7];
Mutatók összehasonlítása

Csak olyan mutatókat hasonlítsunk össze, amik ugyanannak a tömbnek az elemeire mutatnak! A használható operátorok:

==  !=  >  <  >=  <=

Például a fenti kódot kiegészíthetjük így, hogy a későbbi elemet írja ki:

	double *d_ptr1, *d_ptr2;
	double a[10];
	d_ptr1 = &a[3];
	d_ptr2 = &a[7];
	if (d_ptr2 > d_ptr1) {
		printf("%lf\n", *d_ptr2);  /* a mutatott értéket írjuk ki ! */
	} else {
		printf("%lf\n", *d_ptr1);
	}
Mutatók összeadása, kivonása

(Képek itt.)

A pointer aritmetika arra ad lehetőséget, hogy egyszerűen léptessük a mutatóinkat egy tömbön belül. Ha egyet hozzáadunk a mutatóhoz, az a következő tömbbeli elemre fog mutatni. Továbbírjuk az eredeti kódot, d_ptr2 az ötödik elemre fog mutatni a tömbben:

	double *d_ptr1, *d_ptr2;
	double a[10];
	d_ptr1 = &a[3];
	d_ptr2 = &a[7];
	double *d_ptr3 = d_ptr1 + 1;
	/* és ezzel akár értéket is adhatunk a[4]-nek vagyis az 5. elemnek: */
	*d_ptr3 = 11;
	/* a rákövetkező (hatodik) elemet is beállítjuk, a d_ptr1-et és összeadást használva: */
	*(d_ptr1+2) = 45;

Az utolsó sorban muszáj a zárójeleket kitenni, mert a * operátor "erősebben köt" a változónévhez mint az összeadás operátora (az operátorok erősségi sorrendjéről, vagyis a precedenciákról később tanulunk részletesebben).

Ha van két mutatónk amik ugyanannak a tömbnek az elemeire mutatnak, akkor a különbségük megmondja hogy hány elem van köztük a tömbben. Például:

	double *d_ptr1, *d_ptr2;
	double a[10];
	d_ptr1 = &a[1];
	d_ptr2 = &a[9];
	printf("%d \n", (d_ptr2 - d_ptr1)); 
	/* kiírja hogy 8 */

Többdimenziós tömbök

                          +-----+-----+-----+
            mat[0]   ---> | a00 | a01 | a02 |
                          +-----+-----+-----+
                          +-----+-----+-----+
            mat[1]   ---> | a10 | a11 | a12 |
                          +-----+-----+-----+
                          +-----+-----+-----+
            mat[2]   ---> | a20 | a21 | a22 |
                          +-----+-----+-----+
                          +-----+-----+-----+
            mat[3]   ---> | a30 | a31 | a32 |
                          +-----+-----+-----+


Források és további olvasnivalók

Ellenőrző kérdések

Személyes eszközök