Az az igazság, hogy ezzel a résszel átugrottunk egy igen lényeges dolgot, nevezetesen a mutatókat. Azért döntöttem így, mert a Codex egy korábbi számában (a 4. számban) megjelent egy cikk a mutatókról (Nyisztor Károly: Mutatókról „kicsit másképp”), és az a cikk éppen a C++ mutatóit ismerteti. A C és a C++ mutatói pedig azonosan működnek.
A programozó által definiált adattípusok
A mindennapi programozás során sokszor felmerülhet olyan probléma, hogy szükségünk lenne egy olyan speciális adattípusra, amivel a C nem rendelkezik. Például: Egy biztosítási cég számítógépes rendszerében egy ügyfélhez többféle adat is tartozik. Név, cím, biztosítás fajtája, telefonszám és hasonlók. Ezek az adatok tehát logikailag összetartoznak, ezért jó lenne őket úgy is kezelni. Erre a C lehetőséget is ad: ezek a strukturált adattípusok.
Az előző két részben a tömbökkel foglalkoztunk, amelyek lehetővé tették, hogy azonos típusú adatokat logikailag egy egységben, a tömbben tároljunk. Mint a bevezetőben felhozott példa is mutatja, eltérő típusú adatok egységben való tárolására is szükség lehet. Ennek megoldására a C két, formailag szinte megegyező megoldást is kínál: A struktúrákat (struct) és az uniókat (union). Különbség csak a tárolás módjában jelentkezik, a gyakorlat mégis azt mutatja - ki tudja miért -, hogy a struct van általánosan használatban, még az union-t szinte egyáltalán nem alkalmazzuk.
Jó szokásunkhoz híven előbb nézzük a struct általános alakját:
struct valami
{
adattag1;
adattag2;
adattagn;
}; //Ide muszáj a pontosvessző!
Felhívnám a figyelmet arra, hogy más programozási nyelvekkel ellentétben a struktúrában szereplő adatok általános neve adattag (member) és nem mező (field), ugyanis a „mező” elnevezést a C-ben máshol használjuk.
Akkor talán nézzük a biztosítós példánkat:
struct ugyfel
{
char nev[31];
char lakcim[51];
int biztositasi_szam;
int telefonszam;
};
A typedef
Egy másik probléma lehet, hogy egy egyszerű adattípusra van szükségünk, amit típusmódosítóval hozunk létre. Például: unsigned volatile long int
Ha ezt a típus a program több pontján is szeretnénk alkalmazni, akkor igen-igen fáradságos többször leírogatni ezt (másrészt jön az aranyszabály: ~~~”A jó programozó lusta!”). Erre találták ki a typedef-et. Nevében is benne van, hogy típust definiál. Használata egyszerű:
typedef unsigned volatile long int entipusom;
Ezután ugyanúgy tudjuk használni az entipusom szót, mint egy valódi típust.
De a typedef ennél többet is tud. Például sok helyen szeretnénk 16 elemű vektort használni, akkor megtehetjük , hogy egyenként definiálgatjuk, de használhatjuk a typedef-et is:
typedef int[16] vektor16;
Ez tulajdonképpen az olvashatóságot, az áttekinthetőséget növeli, ami nem elhanyagolható szempont.
A következő felmerülő probléma pedig az újrafelhasználhatóság lehet, hiszen nem valószínű, hogy egy biztosítási cégnek csak egy ügyfele lenne- Ekkor jön képbe ismét a typedef, aminek segítségével struktúratípust deklarálunk:
typedef struct ugyfel
{
char nev[31];
char lakcim[51];
int biztositasi_szam;
int telefonszam;
} UGYFEL;
Ezután már tetszőleges számú ügyfelet deklarálhatunk:
UGYFEL ugyfel1, ugyfel2;
A struktúrákat egymásba is ágyazhatjuk. Például szükség lehet a körzetszám és a telefonszám elkülönítésére:
typedef struct ugyfel
{
char nev[31];
char lakcim[51];
int biztositasi_szam;
struct
{
int korzetszam;
int szam;
} telefonszam;
} UGYFEL;
Így most már el tudjuk érni a telefonszám körzetszámát és magát a számot is külön-külön, a következő alcím alatt taglaltak szerint.
A struktúra adatmezőinek kezelése
Tehát létrehoztunk egy ugyfel1 nevű struktúrát, négy adattaggal. A struktúra adattagjaira a következőképpen hivatkozhatunk (Ez az azonosítójuk):
ugyfel1.nev
Az egymásba ágyazott struktúra adattagjaira való hivatkozás hasonlóan egyszerű:
ugyfel1.telefonszam.korzetszam
ugyfel1.telefonszam.szam
Mutatóval is lehet hivatkozni a struktúrára és adattagjaira, erre két út kínálkozik. Az első módszer, hogy egyszerűen létrehozunk egy UGYFEL típusú mutatót és megadjuk neki az ugyfel1 címét:
UGYFEL* U_mutato;
U_mutato = &ugyfel1;
A másik lehetőség elegánsabb és bonyolultabb, és inkább csak az érdekesség kedvéért írom ide (a következő részben foglalkozunk ezzel): dinamikus memóriafoglalást végzünk.
U_mutato = (struct ugyfel*) malloc( sizeof(struct ugyfel) );
if ( U_mutato = NULL ) exit (-1) //Ha nem sikerülne, akkor kilépünk.
... //Ide kerülnek az utasítások
free(U_mutato); //Végül felszabadítjuk a lefoglalt memóriát.
Végül is nem olyan bonyolult, de az egyszerűség (és a gépeléssel eltöltött idő rövidebb volta miatt) én az első módszert javaslom. Ezután így tudunk hivatkozni az adattagokra:
(*U_mutato).nev
Az U_mutato-t a precedencia (elsőbbségi) szabály miatt kell zárójelbe tenni, mert ha nem tennénk a fordító azt hihetné, hogy ez egy U_mutato.nev változóra mutató pointer.
Mivel a C régi változatainak idejében elég gyakran volt szükség struktúra-mutatók használatára és kényelmetlen, zavaró volt minden egyes alkalommal zárójelezni, ezért kitaláltak egy új operátort, ez a -> (nyíl; egy mínusz jelből és egy ’nagyobb’ relációs jelből tevődik össze). Ennek használatával már nem kell zárójeleznünk:
U_mutato->nev
A figyelmes Olvasó felteheti magában a kérdést, hogy miért jó ez a hercehurca a mutatókkal, hiszen végül is ugyanazt meg tudjuk tenni mutatók nélkül is? Erre már tettem egy utalást, de szándékosan nem lőttem le a poént. A C régi változatiban azért volt szükség erre, mert gyakran adtak át függvényeknek paraméterül struktúrát, ami csak mutató formájában történhet.
Egy másik fontos dolog az értékadás.
Akik ismerik a Pascal nyelvet, azok bizonyára találkoztak a Pascal record-jaival. Ezek is strukturált adattípusok. A Pascalban nagyon remekül megoldották a record adattagjainak értékadását a With kulcsszó használatával. Sajnos ilyen lehetőség a C-ben nincs. Két út kínálkozik a feladat megoldására:
Az egyik az, hogy egyenként értéket adunk az adattagoknak:
Ugyfel1.nev = „Nagy Gábor”;
Ugyfel1.lakcim = „Kis út 23.”;
Ugyfel1.telefonszam.korzetszam = 56;
Ugyfel1.telefonszam.szam = 345223;
Ugyfel1.biztositasi_szam = 322576;
A másik módszer az, hogy amikor létrehozzunk a struktúrát, akkor adunk értéket neki, a következőképpen:
UGYFEL ugyfel1 = { „Nagy Gábor”, „Kis út 23.”, { 56, 345223}, 322576};
Én körülményessége ellenére is inkább az első módszert javaslom, mert sokkal áttekinthetőbb.
Szó esett még az uniókról is. Nem kell valami szörnyűséges dologra gondolni, sem pedig az EU-ra: gyakorlati szempontból a struct és union közötti különbség a nevében jelentkezik. Ezek szerint, ahova struct-ot írunk, oda bátran írhatunk union-t is. Hogy mégis mi a különbség? A tárolás módjában adódik, de ebbe nem szeretnék belemenni.