Informatika2-2015/Eloadas 10 C-3 Mutatok
(Új oldal, tartalma: „= Preprocesszor = A C programok fordítása két részből áll. Először lefut az úgynevezett "preprocesszor", és utána fut le a rendes fordító. A preprocesszor…”) |
|||
5. sor: | 5. sor: | ||
Persze általában nem kell tudnotok hogy egy fordító hogy működik belül, de mivel itt a preprocesszor eléggé mást és máshogy csinál mint a rendes fordítók, ezért külön tanuljuk azt ami hozzá tartozik. | Persze általában nem kell tudnotok hogy egy fordító hogy működik belül, de mivel itt a preprocesszor eléggé mást és máshogy csinál mint a rendes fordítók, ezért külön tanuljuk azt ami hozzá tartozik. | ||
− | A preprocesszornak a kettőskereszttel kezdődő sorok szólnak. Mivel ezek nem C parancsok egészen pontosan, más nevük van, ''preprocesszor direktívá''knak hívjuk őket. | + | A preprocesszornak a kettőskereszttel kezdődő sorok szólnak. Mivel ezek nem C parancsok egészen pontosan, más nevük van, ''preprocesszor direktívá''knak hívjuk őket. Emiatt van az is, hogy a C-re vonatkozó általános szabályokkal ellentétben ezeknél számít hogy egy parancs mindig egy sor, aminek az első karaktere kettőskereszt, tehát a preprocesszor az újsorokat figyelembe veszi ellenben a C fordító másik felével, ahol az is csak egyfajta whitespace. |
== include == | == include == | ||
27. sor: | 27. sor: | ||
== define == | == define == | ||
− | Vegyük ezt a | + | Vegyük ezt a példát az előző előadásról: |
<c> | <c> | ||
57. sor: | 57. sor: | ||
</c> | </c> | ||
− | Ezzel elértük hogy egy helyen elég módosítani a méretet. Azonban ezzel a #define-nal vigyázni kell. Ezt a preprocesszor csinálja, ami nem érti a C kódot, és annak különböző részeit, bárhol ahol azt a nevet látja amit megadtunk neki, oda behelyettesíti azt a másik betűsort amit megadtunk neki. Így pl. ha ezt írjuk: | + | Ezzel elértük hogy egy helyen elég módosítani a méretet. |
+ | |||
+ | Azonban ezzel a #define-nal vigyázni kell. Ezt a preprocesszor csinálja, ami nem érti a C kódot, és annak különböző részeit, bárhol ahol azt a nevet látja amit megadtunk neki, oda behelyettesíti azt a másik betűsort amit megadtunk neki. Így pl. ha ezt írjuk: | ||
<c> | <c> | ||
71. sor: | 73. sor: | ||
Tehát a lényeg az, hogy olyan nevet adjak a #define-al megadott konstansnak, ami nem fordulhat elő máshol a kódban. Az általánosan elfogadott szokás az, hogy a #define-al megadott konstansoknak a neve csupa nagybetűből áll, ezt ajánlom követni. | Tehát a lényeg az, hogy olyan nevet adjak a #define-al megadott konstansnak, ami nem fordulhat elő máshol a kódban. Az általánosan elfogadott szokás az, hogy a #define-al megadott konstansoknak a neve csupa nagybetűből áll, ezt ajánlom követni. | ||
+ | |||
+ | === igaz/hamis példa === | ||
+ | |||
+ | Mutatok egy példát ilyen preprocesszorral megadott konstansra, amit sok tényleges C programban is használnak. Ha zavar hogy az igaz/hamis értékek tárolására is egész számokat használunk, és a 0-val jelképezzük a hamist, és általában az 1-el az igazat, akkor ehelyett be lehet vezetni egy-egy konstanst ezekre a célokra, és ezzel lehet hogy átláthatóbbá tesszük a kódunkat: | ||
+ | |||
+ | <c> | ||
+ | #define TRUE 1 | ||
+ | #define FALSE 0 | ||
+ | |||
+ | int prime(int szam) { | ||
+ | int i; | ||
+ | for(i = 2; i < szam; ++i) { | ||
+ | if(szam % i == 0) { | ||
+ | return FALSE; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | return TRUE; | ||
+ | } | ||
+ | </c> | ||
+ | |||
+ | Így az olvasó számára látszik hogy az "1" az mikor az 1 mint szám, és mikor jelenti azt hogy "igaz". A C fordító teljesen ugyanazt a kódot látja, mint ha 1 és 0 lenne ott, tehát neki mindegy. | ||
= Mutatók = | = Mutatók = | ||
82. sor: | 106. sor: | ||
== Egyszerű változókra mutatók == | == Egyszerű változókra mutatók == | ||
− | A mutató típusú változók | + | === A * és & operátor === |
+ | |||
+ | A mutató típusú változók nem arra valók hogy adatokat tároljanak, hanem más változókra való hivatkozások, és a másik változó tárolja a tényleges adatokat. Így érhető el olyasmi dolog amit a python-ban a mutable változókkal, hogy két különböző néven keresztül is elérhető ugyanaz a változó, és ha az egyiken keresztül megváltoztatjuk az értékét, akkor mindkettőn nézve megváltozik. | ||
+ | |||
+ | Ahhoz hogy egy változóra a hivatkozást kiszámoljuk, ahhoz használható az '''&''' operátor, és ahhoz hogy a hivatkozáson keresztül elérjünk egy változót, ahhoz használható a '''*''' operátor. Egyszerű példa: | ||
+ | |||
+ | <c> | ||
+ | int a; | ||
+ | int *mutato; | ||
+ | // Itt a mutato-t egy a-ra valo hivatkozassa tesszuk: | ||
+ | mutato = &a; | ||
+ | // Ettol az a erteke valtozik 5-re | ||
+ | *mutato = 5; | ||
+ | </c> | ||
+ | |||
+ | Itt egy összefoglaló táblázat ennek a két operátornak a hatásáról. | ||
+ | |||
+ | {| class="wikitable" | ||
+ | |+ | ||
+ | ! ''a'' típusa !! ''&a'' típusa !! ''*a'' típusa | ||
+ | |- | ||
+ | | int || int* || nincs értelme | ||
+ | |- | ||
+ | | int* || int** || int | ||
+ | |- | ||
+ | | float || float* || nincs értelme | ||
+ | |- | ||
+ | | float* || float** || float | ||
+ | |} | ||
+ | |||
+ | === Mutató mint függvényparaméter === | ||
+ | |||
+ | Így tehát csak akkor módosítható több helyról egy változó, ha a kódban készítünk egy hivatkozást rá. Ennek a következménye az is, hogy egy függvényben definiált változó csak akkor változhat meg, ha a függvényen belül megváltoztatjuk, vagy ha explicit egy hivatkozást róla átadunk, nem tud meglepni minket, mint egy mutable változó. | ||
+ | |||
+ | <c> | ||
+ | void f() { | ||
+ | int x = 4; | ||
+ | // Ettol nem változhat meg x erteke, | ||
+ | // g csak egy masolatot kap rola: | ||
+ | g(x); | ||
+ | // h-nak egy hivatkozast adunk oda, | ||
+ | // igy az megvaltoztathatja x erteket: | ||
+ | h(&x); | ||
+ | } | ||
+ | </c> | ||
+ | |||
+ | Ez megmagyarázza azt is, hogy miért kell a ''printf()''-et és a ''scanf()''-et máshogy használni. A ''printf()'' csak használja az értéket amit odaadunk neki, a ''scanf()'' viszont meg akarja változtatni a változónk értékét: | ||
+ | |||
+ | <c> | ||
+ | void main() { | ||
+ | int n; | ||
+ | // A scanf()-nek az a celja hogy a | ||
+ | // valtozonk erteket megvaltoztassa: | ||
+ | scanf("%d", &n); | ||
+ | // Vegzunk egy kis szamolast: | ||
+ | n = n*2; | ||
+ | // A printf()-nek viszont csak az | ||
+ | // aktualis erteke kell: | ||
+ | printf("%d", n); | ||
+ | } | ||
+ | </c> | ||
+ | |||
+ | === Példa a mutató használatára === | ||
+ | |||
+ | Nézzünk egy példát, hogy már ennyi alapján is lehet hasznos a mutató. Tegyük fel, hogy van néhány változónk, és a legnagyobbat ki akarjuk írni, majd a felére csökkenteni. Általánosságban ezt a feladatot két részre bonthatjuk: | ||
+ | # Kiválasztjuk hogy melyik változót akarjuk módosítani (a legnagyobbat) | ||
+ | # Azon elvégzünk valamilyen műveletet (kiírjuk és elfelezzük) | ||
+ | Ennek az általános mintának sok feladat megfelel. Most mindkét lépés elég egyszerű, így akár így is megírhatnánk a kódot: | ||
+ | |||
+ | <c> | ||
+ | void main() | ||
+ | { | ||
+ | int a, b, c; | ||
+ | scanf("%d%d%d", &a, &b, &c); | ||
+ | // A legnagyobbat modositjuk: | ||
+ | if(a > b) { | ||
+ | if(a > c) { | ||
+ | printf("%d", a); | ||
+ | a = a / 2; | ||
+ | } else { | ||
+ | printf("%d", c); | ||
+ | c = c / 2; | ||
+ | } | ||
+ | } else { | ||
+ | if(b > c) { | ||
+ | printf("%d", b); | ||
+ | b = b / 2; | ||
+ | } else { | ||
+ | printf("%d", c); | ||
+ | c = c / 2; | ||
+ | } | ||
+ | } | ||
+ | // Tovabbiak... | ||
+ | }</c> | ||
+ | |||
+ | De azért láthatjuk hogy lényegében ugyanazt a két sort kellett 4-szer leírni. Általános szabály hogy az nem jó a programban ha ugyanaz többször le van írva, mert az általában azt jelenti hogy ha az egyiket módosítani kell, akkor a többit is, és abból könnyen születnek hibák. Ezért ehelyett jobb megoldás ez: | ||
+ | |||
+ | <c>void main() | ||
+ | { | ||
+ | int a, b, c; | ||
+ | scanf("%d%d%d", &a, &b, &c); | ||
+ | int *mutato; | ||
+ | // A legnagyobbat kivalasztjuk: | ||
+ | if(a > b) { | ||
+ | if(a > c) { | ||
+ | mutato = &a; | ||
+ | } else { | ||
+ | mutato = &c; | ||
+ | } | ||
+ | } else { | ||
+ | if(b > c) { | ||
+ | mutato = &b; | ||
+ | } else { | ||
+ | mutato = &c; | ||
+ | } | ||
+ | } | ||
+ | // Majd modositjuk: | ||
+ | printf("%d", *mutato); | ||
+ | *mutato = *mutato / 2; | ||
+ | // Tovabbiak... | ||
+ | }</c> | ||
+ | |||
+ | === Null mutató === | ||
+ | |||
+ | A mutatók egy speciális lehetséges értéke a '''NULL'''. Ez arra használható, hogy ha a mutató változó (még) nem mutat semmire, akkor legyen ''NULL'' az értéke, és így a program tudja hogy ne próbálja meg ezen keresztül elérni a hivatkozott másik változót, mert nem létezik olyan. Pl.: | ||
+ | |||
+ | <c> | ||
+ | void otte_tesz(int *p) { | ||
+ | if(p != NULL) { | ||
+ | *p = 5; | ||
+ | } | ||
+ | }</c> | ||
+ | |||
+ | Ezért (főleg persze bonyolultabb programoknál) érdemes lehet a mutató változókat alapból ''NULL''-ra inicializálni, hogy ha nem kap értéket, akkor ne próbáljuk használni: | ||
+ | |||
+ | <c> | ||
+ | void main() { | ||
+ | int *mutato = NULL; | ||
+ | int a; | ||
+ | scanf("%d", &a); | ||
+ | if(a > 5) { | ||
+ | mutato = &a; | ||
+ | } | ||
+ | otte_tesz(mutato); | ||
+ | printf("%d", a); | ||
+ | }</c> | ||
+ | |||
+ | == Mutatók és tömbök == | ||
+ | |||
+ | A mutatóra absztrakt módon úgy gondolhatunk, hogy a mutató értéke egy hivatkozás egy másik változóra. A gyakorlatban ez azt jelenti, hogy a mutató értéke az, hogy a másik változó hol található meg a memóriában. Egy tömb elemei mindig egymás mellett találhatók meg a memóriában. Így a mutatók használhatók arra, hogy ide-oda mozogjunk egy tömbben, a + és a - használatával: | ||
+ | |||
+ | <c> | ||
+ | int v[10] // Egy 10 elemu tomb | ||
+ | int *m = &v[3] // Mutato a tomb 3. elemere | ||
+ | *m = 5 // Modositjuk a tomb 3 elemet | ||
+ | *(m-1) = 8 // Modositjuk a tomb 2 elemet | ||
+ | *(m+3) = 30 // Modositjuk a tomb 6 elemet | ||
+ | </c> | ||
+ | |||
+ | Ezt nem igazán érdemes használni ilyen rendes tömböknél, mert ha van egy mutatóm a közepére, és onnan megyek jobbra-balra, nem tudom hogy hol van a tömb két vége, és a C nem akadályozza meg hogy tovább menjek valamelyik végénél, és akkor undefined behaviour-ral találkozhatok. | ||
+ | |||
+ | De a karakterláncoknál szokták használni, ott a végén úgyis van egy 0 karakter, ami jelzi a végét, így az meg van oldva. (Ennek megfelelően általában csak előrefelé szoktak menni a karakterláncokra mutató "char*" típusú mutatókkal, mert előrefelé menve tudják hol kell megállni.) Példa, ami kiírja a karakterlánc elemeinek ASCII kódjait: | ||
+ | |||
+ | <c> | ||
+ | char str[] = "Karakterlanc!"; | ||
+ | char *p = &str[0]; | ||
+ | while(*p) { // mivel nem 0 ertek igaznak szamit | ||
+ | printf("%d ", *p); | ||
+ | ++p; | ||
+ | }</c> | ||
+ | |||
+ | Sőt, igazából létrehozás után a C a tömböket és a mutatókat teljesen egyformán kezeli, a tömböt úgy értelmezve mintha a tömb első elemére mutató mutató lenne. Az egyetlen kivétel hogy a tömb (mint mutató) nem módosítható, csak az elemei: | ||
+ | |||
+ | <c> | ||
+ | int tomb[] = {10, 20, 30, 40}; | ||
+ | int *p = &tomb[1]; | ||
+ | // Ez a tomb elso elemet irja ki: | ||
+ | printf("%d", *tomb); | ||
+ | // Ez a tomb 3. elemet irja ki, | ||
+ | // mert p mar eleve a masodikra mutat: | ||
+ | printf("%d", p[1]); | ||
+ | // Sot ezt is lehet, hogy p is a tomb | ||
+ | // elejere mutasson: | ||
+ | p = tomb; | ||
+ | // Ennek megfelelően így p a tomb 3. | ||
+ | // elemere mutat: | ||
+ | p = tomb + 2; | ||
+ | // Ezt viszont nem tudom megtenni, a | ||
+ | // tomb valtozo maga nem valtoztathato: | ||
+ | tomb = p; | ||
+ | </c> | ||
+ | |||
+ | === Karakterláncok mutatózása === | ||
+ | |||
+ | Mint említettem, a karakterláncokat sokszor mutatókkal kezelik, tehát ''char*''-al. Ez igaz a beépített könyvtár függvényeire is. Mutatok egy példát. | ||
+ | |||
+ | Tegyük fel, hogy szét akarunk vágni egy mondatot szavakra, és minden szót külön sorba kiírni, ezt így lehet megoldani: | ||
+ | |||
+ | |||
+ | |||
+ | == Változók élettartama == | ||
+ | |||
+ | == Tömbök visszaadása függvényből == |
A lap 2015. április 22., 11:21-kori változata
Tartalomjegyzék |
Preprocesszor
A C programok fordítása két részből áll. Először lefut az úgynevezett "preprocesszor", és utána fut le a rendes fordító. A preprocesszor olyan utasításokat kap, amiket a programon mint szövegen hajt végre, úgy, hogy még nem próbálja értelmezni hogy C-ben mit jelent az. Mindjárt meglátjuk hogy ennek milyen következményei vannak.
Persze általában nem kell tudnotok hogy egy fordító hogy működik belül, de mivel itt a preprocesszor eléggé mást és máshogy csinál mint a rendes fordítók, ezért külön tanuljuk azt ami hozzá tartozik.
A preprocesszornak a kettőskereszttel kezdődő sorok szólnak. Mivel ezek nem C parancsok egészen pontosan, más nevük van, preprocesszor direktíváknak hívjuk őket. Emiatt van az is, hogy a C-re vonatkozó általános szabályokkal ellentétben ezeknél számít hogy egy parancs mindig egy sor, aminek az első karaktere kettőskereszt, tehát a preprocesszor az újsorokat figyelembe veszi ellenben a C fordító másik felével, ahol az is csak egyfajta whitespace.
include
Az egyetlen példa amit láttunk eddig erre, az az "#include" parancs. Az #include az azt csinálja, hogy egy másik fájl teljes tartalmát beilleszti annak a sornak a helyére. tehát amikor azt írjuk, hogy
#include <stdio.h>
Akkor a fordító fog egy "stdio.h" nevű fájlt (ami a fordítón belül van), és annak a teljes tartalmát beilleszti ennek a sornak a helyére. Ez a fájl az, ami a különböző beépített függvények deklarációit tartalmazza, ezért használhatjuk őket, miután volt ilyen #include sor.
Ezzel lehet saját másik fájlokat is együtt felhasználni, akkor nem kacsacsőröket, hanem dupla idézőjeleket kell használni a fájlnévnél, így:
#include "masik_fajl.h"
Az a különbség a kettő között, hogy az idézőjellel megadott fájlokat először az aktuális könyvtárban keresi, míg a kacsacsőrrel megadottakat először a fordító könyvtárában keresi.
define
Vegyük ezt a példát az előző előadásról:
// Dokumentaljuk is le, hogy max. 1000-el mukodik int fun(int n) { if(n > 1000) { return -1; // Ezzel a hibat jelezzuk } int tomb[1000]; // A tenyleges szamolasok... // Amik a tomb-nek csak az elso n elemet hasznaljak }
Itt az a probléma, hogy az "1000" mint limit, kétszer is le van írva. Ha meg akarjuk növelni a limitet 2000-re, könnyen elfelejthetjük, hogy két helyen is módosítani kell, és csak az egyiket írjuk át. Azonban, a szabvány szerint, nem használhatunk változót a tömb méretének megadására, ezért az sem működik, hogy eg változóba tároljuk el. Ehelyett a "#define" direktívát használhatjuk egy konstans meghatározására. Itt van ez jól megvalósítva:
#define TOMB_MAX_MERET 1000 // Dokumentaljuk is le, hogy max. 1000-el mukodik int fun(int n) { if(n > TOMB_MAX_MERET) { return -1; // Ezzel a hibat jelezzuk } int tomb[TOMB_MAX_MERET]; // A tenyleges szamolasok... // Amik a tomb-nek csak az elso n elemet hasznaljak }
Ezzel elértük hogy egy helyen elég módosítani a méretet.
Azonban ezzel a #define-nal vigyázni kell. Ezt a preprocesszor csinálja, ami nem érti a C kódot, és annak különböző részeit, bárhol ahol azt a nevet látja amit megadtunk neki, oda behelyettesíti azt a másik betűsort amit megadtunk neki. Így pl. ha ezt írjuk:
#define a xxx int alap(int u) { int az = 0; // stb... }
Akkor ebben a kódban a preprocesszor a kis "a" betű minden előfordulását kicseréli, tehát "xxxlxxxp" nevű függvényt definiáltunk, és azon belül egy "xxxz" nevű változónk van. Ez még túlélhető lenne, de ha pl. az "i" betűt #define-olom, akkor már azt se írhatom le sikeresen hogy "int", így egész változókat nem tudok megadni.
Tehát a lényeg az, hogy olyan nevet adjak a #define-al megadott konstansnak, ami nem fordulhat elő máshol a kódban. Az általánosan elfogadott szokás az, hogy a #define-al megadott konstansoknak a neve csupa nagybetűből áll, ezt ajánlom követni.
igaz/hamis példa
Mutatok egy példát ilyen preprocesszorral megadott konstansra, amit sok tényleges C programban is használnak. Ha zavar hogy az igaz/hamis értékek tárolására is egész számokat használunk, és a 0-val jelképezzük a hamist, és általában az 1-el az igazat, akkor ehelyett be lehet vezetni egy-egy konstanst ezekre a célokra, és ezzel lehet hogy átláthatóbbá tesszük a kódunkat:
#define TRUE 1 #define FALSE 0 int prime(int szam) { int i; for(i = 2; i < szam; ++i) { if(szam % i == 0) { return FALSE; } } return TRUE; }
Így az olvasó számára látszik hogy az "1" az mikor az 1 mint szám, és mikor jelenti azt hogy "igaz". A C fordító teljesen ugyanazt a kódot látja, mint ha 1 és 0 lenne ott, tehát neki mindegy.
Mutatók
A C-ben az ilyen mutable és immutable dolgok helyett van egy speciális külön típus, a mutató. Már az előző előadáson említettem a szintaxist, hogy hogy néz ki, a *-gal jelöljük. Tehát egy int típusú változóra mutató mutatót így definiálunk:
int *mutato;
Egyszerű változókra mutatók
A * és & operátor
A mutató típusú változók nem arra valók hogy adatokat tároljanak, hanem más változókra való hivatkozások, és a másik változó tárolja a tényleges adatokat. Így érhető el olyasmi dolog amit a python-ban a mutable változókkal, hogy két különböző néven keresztül is elérhető ugyanaz a változó, és ha az egyiken keresztül megváltoztatjuk az értékét, akkor mindkettőn nézve megváltozik.
Ahhoz hogy egy változóra a hivatkozást kiszámoljuk, ahhoz használható az & operátor, és ahhoz hogy a hivatkozáson keresztül elérjünk egy változót, ahhoz használható a * operátor. Egyszerű példa:
int a; int *mutato; // Itt a mutato-t egy a-ra valo hivatkozassa tesszuk: mutato = &a; // Ettol az a erteke valtozik 5-re *mutato = 5;
Itt egy összefoglaló táblázat ennek a két operátornak a hatásáról.
a típusa | &a típusa | *a típusa |
---|---|---|
int | int* | nincs értelme |
int* | int** | int |
float | float* | nincs értelme |
float* | float** | float |
Mutató mint függvényparaméter
Így tehát csak akkor módosítható több helyról egy változó, ha a kódban készítünk egy hivatkozást rá. Ennek a következménye az is, hogy egy függvényben definiált változó csak akkor változhat meg, ha a függvényen belül megváltoztatjuk, vagy ha explicit egy hivatkozást róla átadunk, nem tud meglepni minket, mint egy mutable változó.
void f() { int x = 4; // Ettol nem változhat meg x erteke, // g csak egy masolatot kap rola: g(x); // h-nak egy hivatkozast adunk oda, // igy az megvaltoztathatja x erteket: h(&x); }
Ez megmagyarázza azt is, hogy miért kell a printf()-et és a scanf()-et máshogy használni. A printf() csak használja az értéket amit odaadunk neki, a scanf() viszont meg akarja változtatni a változónk értékét:
void main() { int n; // A scanf()-nek az a celja hogy a // valtozonk erteket megvaltoztassa: scanf("%d", &n); // Vegzunk egy kis szamolast: n = n*2; // A printf()-nek viszont csak az // aktualis erteke kell: printf("%d", n); }
Példa a mutató használatára
Nézzünk egy példát, hogy már ennyi alapján is lehet hasznos a mutató. Tegyük fel, hogy van néhány változónk, és a legnagyobbat ki akarjuk írni, majd a felére csökkenteni. Általánosságban ezt a feladatot két részre bonthatjuk:
- Kiválasztjuk hogy melyik változót akarjuk módosítani (a legnagyobbat)
- Azon elvégzünk valamilyen műveletet (kiírjuk és elfelezzük)
Ennek az általános mintának sok feladat megfelel. Most mindkét lépés elég egyszerű, így akár így is megírhatnánk a kódot:
void main() { int a, b, c; scanf("%d%d%d", &a, &b, &c); // A legnagyobbat modositjuk: if(a > b) { if(a > c) { printf("%d", a); a = a / 2; } else { printf("%d", c); c = c / 2; } } else { if(b > c) { printf("%d", b); b = b / 2; } else { printf("%d", c); c = c / 2; } } // Tovabbiak... }
De azért láthatjuk hogy lényegében ugyanazt a két sort kellett 4-szer leírni. Általános szabály hogy az nem jó a programban ha ugyanaz többször le van írva, mert az általában azt jelenti hogy ha az egyiket módosítani kell, akkor a többit is, és abból könnyen születnek hibák. Ezért ehelyett jobb megoldás ez:
void main() { int a, b, c; scanf("%d%d%d", &a, &b, &c); int *mutato; // A legnagyobbat kivalasztjuk: if(a > b) { if(a > c) { mutato = &a; } else { mutato = &c; } } else { if(b > c) { mutato = &b; } else { mutato = &c; } } // Majd modositjuk: printf("%d", *mutato); *mutato = *mutato / 2; // Tovabbiak... }
Null mutató
A mutatók egy speciális lehetséges értéke a NULL. Ez arra használható, hogy ha a mutató változó (még) nem mutat semmire, akkor legyen NULL az értéke, és így a program tudja hogy ne próbálja meg ezen keresztül elérni a hivatkozott másik változót, mert nem létezik olyan. Pl.:
void otte_tesz(int *p) { if(p != NULL) { *p = 5; } }
Ezért (főleg persze bonyolultabb programoknál) érdemes lehet a mutató változókat alapból NULL-ra inicializálni, hogy ha nem kap értéket, akkor ne próbáljuk használni:
void main() { int *mutato = NULL; int a; scanf("%d", &a); if(a > 5) { mutato = &a; } otte_tesz(mutato); printf("%d", a); }
Mutatók és tömbök
A mutatóra absztrakt módon úgy gondolhatunk, hogy a mutató értéke egy hivatkozás egy másik változóra. A gyakorlatban ez azt jelenti, hogy a mutató értéke az, hogy a másik változó hol található meg a memóriában. Egy tömb elemei mindig egymás mellett találhatók meg a memóriában. Így a mutatók használhatók arra, hogy ide-oda mozogjunk egy tömbben, a + és a - használatával:
int v[10] // Egy 10 elemu tomb int *m = &v[3] // Mutato a tomb 3. elemere *m = 5 // Modositjuk a tomb 3 elemet *(m-1) = 8 // Modositjuk a tomb 2 elemet *(m+3) = 30 // Modositjuk a tomb 6 elemet
Ezt nem igazán érdemes használni ilyen rendes tömböknél, mert ha van egy mutatóm a közepére, és onnan megyek jobbra-balra, nem tudom hogy hol van a tömb két vége, és a C nem akadályozza meg hogy tovább menjek valamelyik végénél, és akkor undefined behaviour-ral találkozhatok.
De a karakterláncoknál szokták használni, ott a végén úgyis van egy 0 karakter, ami jelzi a végét, így az meg van oldva. (Ennek megfelelően általában csak előrefelé szoktak menni a karakterláncokra mutató "char*" típusú mutatókkal, mert előrefelé menve tudják hol kell megállni.) Példa, ami kiírja a karakterlánc elemeinek ASCII kódjait:
char str[] = "Karakterlanc!"; char *p = &str[0]; while(*p) { // mivel nem 0 ertek igaznak szamit printf("%d ", *p); ++p; }
Sőt, igazából létrehozás után a C a tömböket és a mutatókat teljesen egyformán kezeli, a tömböt úgy értelmezve mintha a tömb első elemére mutató mutató lenne. Az egyetlen kivétel hogy a tömb (mint mutató) nem módosítható, csak az elemei:
int tomb[] = {10, 20, 30, 40}; int *p = &tomb[1]; // Ez a tomb elso elemet irja ki: printf("%d", *tomb); // Ez a tomb 3. elemet irja ki, // mert p mar eleve a masodikra mutat: printf("%d", p[1]); // Sot ezt is lehet, hogy p is a tomb // elejere mutasson: p = tomb; // Ennek megfelelően így p a tomb 3. // elemere mutat: p = tomb + 2; // Ezt viszont nem tudom megtenni, a // tomb valtozo maga nem valtoztathato: tomb = p;
Karakterláncok mutatózása
Mint említettem, a karakterláncokat sokszor mutatókkal kezelik, tehát char*-al. Ez igaz a beépített könyvtár függvényeire is. Mutatok egy példát.
Tegyük fel, hogy szét akarunk vágni egy mondatot szavakra, és minden szót külön sorba kiírni, ezt így lehet megoldani: