KURZUS: Programozás alapjai
MODUL: IV. modul
11. lecke: Struktúrák és uniók
Ebben a fejezetben megismerkedünk a struktúrákkal, melyekkel egy tetszőleges entitás különböző tulajdonságait tudjuk egyetlen, összetett változó segítségével, egységként kezelni. A szintaktikailag nagyon hasonló uniókkal lehetővé válik, hogy valamilyen tulajdonság értékétől függően mindig más-más típust használjunk egy adat leírására, vagy ugyanannak az adatnak a különböző részeit (pl. egy 4 bájtos egész bájtjait) könnyebben érjük el. Röviden kitérünk még a bitmezőkre is, melyeknek főként hardver-közeli alkalmazásokban vehetjük hasznát. | |||||||||||||||||||||
A lecke végére a hallgatónak tisztában kell lennie | |||||||||||||||||||||
| |||||||||||||||||||||
A lecke kulcsfogalmai: struktúra, unió, bitmező, összetett típus, felhasználói típus, tag, címke, címkézetlen struktúra, szelekciós operátor/tagszelektor, tárillesztés, bájt/szó/dupla szóhatárra igazítás, MSB, balérték/jobbérték, módosítható balérték, névterület. | |||||||||||||||||||||
Struktúrák és uniók áttekintése | |||||||||||||||||||||
A struktúra és az unió aggregátum. Egy vagy több, esetleg különböző típusú változó (elnevezett tag) együttese, melynek önálló azonosítója van. A struktúrát más nyelvben rekordnak nevezik. Azt teszi lehetővé, hogy a valamilyen módon összetartozó változók egész csoportjára egyetlen névvel hivatkozhassunk, azaz hogy a változócsoport kezelése egyszerűbb legyen. | |||||||||||||||||||||
Tulajdonképpen minden struktúrával és unióval új, összetett, felhasználói adattípust hozunk létre. | |||||||||||||||||||||
Az ANSI szabvány megengedi, hogy a struktúrákat át lehessen másolni egymásba, hozzá lehessen rendelni, és átadhatók legyenek függvényeknek, ill. rutinok visszatérési értéke is lehessen struktúra. Képezhető természetesen a struktúra címe (&), mérete (sizeof), és benne lehet explicit típusmódosító szerkezetben is, de a struktúrák nem hasonlíthatók össze. | |||||||||||||||||||||
A struktúrában felsorolt változókat struktúratagoknak (member) nevezik. Struktúratag kis megszorításokkal - melyre később kitérünk - akármilyen típusú lehet. Lehet alap és származtatott típusú bármilyen sorrendben. | |||||||||||||||||||||
A deklarációbeli típusspecifikátor egyik alternatívája a | |||||||||||||||||||||
struktúra-vagy-unió-specifikátor: | |||||||||||||||||||||
struktúra-vagy-unió: | |||||||||||||||||||||
A struktúratag-deklarációlista struktúra, ill. uniótag deklarációk sorozata: | |||||||||||||||||||||
struktúratag-deklarációlista: | |||||||||||||||||||||
struktúratag-deklaráció: | |||||||||||||||||||||
típusspecifikátor-lista: | |||||||||||||||||||||
struktúra-deklarátorlista: | |||||||||||||||||||||
A struktúra-deklarátor többnyire a struktúra, ill. az unió egy tagjának deklarátora. A struktúratag azonban meghatározott számú bitből is állhat, azaz lehet ún. bitmező (bit field) is, mely a nyelvben struktúrákon kívül nem is használható másutt. A mező bitszélességét a kettőspontot követő, egész értékű konstans-kifejezés határozza meg. | |||||||||||||||||||||
struktúra-deklarátor: | |||||||||||||||||||||
Struktúradeklaráció | |||||||||||||||||||||
Alakja tehát a következő: | |||||||||||||||||||||
<tárolási-osztály-specifikátor> struct <struktúracímke> <{ | |||||||||||||||||||||
Például: | |||||||||||||||||||||
struct datum{ | |||||||||||||||||||||
ahol: | |||||||||||||||||||||
| |||||||||||||||||||||
Az azonosítólista nélküli struktúradeklarációt, ahol van struktúratag-deklarációlista, azaz megadottá válik a struktúra szerkezete, szokás struktúradefiníciónak is nevezni, ugyan nincs memóriafoglalása. | |||||||||||||||||||||
| |||||||||||||||||||||
Vigyázat! Struktúradefinícióban a struktúra típusa a struktúra-tagdeklarációlistában csak akkor válik teljessé, ha elérjük a specifikátor bezáró kapcsos zárójelét (}). | |||||||||||||||||||||
| |||||||||||||||||||||
Típusdefiníció | |||||||||||||||||||||
| |||||||||||||||||||||
Összesítve: A typedef címke nélküli struktúrák, uniók és enum-ok típusdefiníciójára is alkalmas. Struktúrák esetében használjunk azonban struktúracímkét, vagy typedef-es szerkezetet, de a kettőt együtt nem javasoljuk! | |||||||||||||||||||||
| |||||||||||||||||||||
Struktúratag deklarációk | |||||||||||||||||||||
A { }-ben álló struktúratag-deklarációlista a deklarátor szintaktikát követve meghatározza a struktúratagok neveit és típusait. | |||||||||||||||||||||
| |||||||||||||||||||||
Struktúrák inicializálása | |||||||||||||||||||||
A struktúrát konstans kifejezésekből álló inicializátorlistával láthatjuk el kezdő értékkel. Az inicializátorlista elemek értékét a struktúratagok a deklarációbeli elhelyezkedés sorrendjében veszik fel: | |||||||||||||||||||||
struct struki { | |||||||||||||||||||||
Pontosítsunk még néhány dolgot! | |||||||||||||||||||||
| |||||||||||||||||||||
Ha az inicializátorlistában nincs beágyazott inicializátorlista, akkor az ott felsorolt értékek az alaggregátumok, s őket a deklaráció sorrendjében veszik fel az aggregátum elemei. Kapcsos zárójelek ugyanakkor akár az egyes inicializátorok köré is tehetők, de ha a fordítót nem kívánjuk "becsapni", akkor célszerű őket az aggregátum szerkezetét pontosan követve használni! | |||||||||||||||||||||
typedef struct { int n1, n2, n3; } triplet; | |||||||||||||||||||||
A sizeof-ot struktúrákra alkalmazva mindig teljes méretet kapunk akár a típust adjuk meg operandusként, akár az ilyen típusú objektumot. Például: | |||||||||||||||||||||
#include <stdio.h> | |||||||||||||||||||||
Struktúratagok elérése | |||||||||||||||||||||
A struktúra és az uniótagok eléréséhez ugyanazokat a tagelérés operátorokat alkalmazza a nyelv. A tagelérés operátort szelekciós operátornak, tagszelektornak is szokás nevezni. Prioritásuk magasabb az egyoperandusos műveletekénél, s közvetlenül a () és a [] után következik. Kétfajta tagelérés operátor van: | |||||||||||||||||||||
| |||||||||||||||||||||
A közvetlen tagelérés operátor alakja: | |||||||||||||||||||||
utótag-kifejezés.azonosító | |||||||||||||||||||||
Az utótag-kifejezésnek struktúra típusúnak, s az azonosítónak e struktúra típus egy tagja nevének kell lennie. A konstrukció típusa az elért tag típusa, értéke az elért tag értéke, s balérték akkor és csak akkor, ha az utótag-kifejezés az, és az azonosító nem tömb. | |||||||||||||||||||||
A közvetett tagelérés operátor formája: | |||||||||||||||||||||
utótag-kifejezés->azonosító | |||||||||||||||||||||
Az utótag-kifejezésnek struktúra típusra mutató mutatónak, s az azonosítónak e struktúra típus egy tagja nevének kell lennie. A konstrukció típusa és értéke az elért tag típusa és értéke. Balérték, ha az elért tag nem tömb. | |||||||||||||||||||||
Feltéve, hogy s struct S típusú struktúra objektum, és sptr struct S típusú struktúrára mutató mutató, akkor ha t az struct S struktúrában deklarált, típus típusú tag, az | |||||||||||||||||||||
s.t | |||||||||||||||||||||
és az | |||||||||||||||||||||
sptr->t | |||||||||||||||||||||
kifejezések típusa típus, és mindkettő a struct S struktúra t tagját éri el. A következők pedig szinonimák, ill. azt is mondhatjuk, hogy a -> szelekció operátoros kifejezés a másik rövidítése: | |||||||||||||||||||||
sptr->t | |||||||||||||||||||||
Az s.t és az sptr->t balértékek, feltéve, hogy t nem tömb típusú. Például: | |||||||||||||||||||||
struct S{ | |||||||||||||||||||||
| |||||||||||||||||||||
Ha már mindig hozzárendelési példákat hoztunk, akkor itt kell megemlítenünk, hogy struktúrákat csak akkor lehet egymáshoz rendelni, ha a forrás és a cél struktúra azonos típusú. (Az azonos szerkezetű, de eltérő címkéjű, a fordító által különböző típusúként kezelt struktúrák közötti hozzárendelést esetleg helyettesíthetjük egy memmove hívással.) | |||||||||||||||||||||
struct A { | |||||||||||||||||||||
Vegyünk valamilyen példát a struktúratömbökre! | |||||||||||||||||||||
Keressük meg a megadott, síkbeli pontok közt a két, egymástól legtávolabbit! A pontok száma csak futás közben dől el (n), de nem lehetnek többen egy fordítási időben változtatható értéknél (N). Az x koordináta bevitelekor üres sort megadva, a bemenet előbb is befejezhető, de legalább két pont elvárandó! Az input ellenőrzendő, s minden hibás érték helyett azonnal újat kell kérni. Az eredmény közlésekor meg kell jelentetni a két pont indexeit, koordinátáit és persze a távolságot is. | |||||||||||||||||||||
/* PELDA28.C: A ket, egymastol legtavolabbi pont | |||||||||||||||||||||
A feladatot síkbeli pontot leíró struktúra segítségével fogjuk megoldani: | |||||||||||||||||||||
struct Pont { /* A Pont struktura. */ | |||||||||||||||||||||
A struktúratömb definíciója: | |||||||||||||||||||||
struct Pont p[N]; /* Strukturatomb. */ | |||||||||||||||||||||
Kitűnően látszik, hogyan kell elérni a struktúra tömbelem tagjait! | |||||||||||||||||||||
if((d=sqrt((p[j].x-p[i].x)*(p[j].x-p[i].x)+ | |||||||||||||||||||||
Struktúrák és függvények | |||||||||||||||||||||
Említettük már, hogy a struktúra másolható, hozzárendelhető, elérhetők a tagjai, képezhető a címe, ill. tömb is előállítható belőle, de függvény is visszaadhat struktúrát vagy erre mutató mutatót. Az fv1 visszaadott értéke struct struki struktúra. | |||||||||||||||||||||
struct struki fv1(void); | |||||||||||||||||||||
Az fv2 viszont struct struki struktúrára mutató mutatót szolgáltat. | |||||||||||||||||||||
struct struki *fv2(void); | |||||||||||||||||||||
A függvény paramétere is lehet struktúra e két módon. Az fv3 struct struki struktúrát fogad paraméterként. | |||||||||||||||||||||
void fv3(struct struki s); | |||||||||||||||||||||
Az fv4 viszont struct struki struktúrát címző mutatót fogad. | |||||||||||||||||||||
void fv4(struct struki *sp); | |||||||||||||||||||||
A következő példa a "helytelen" gyakorlatot szemlélteti. A függvény paraméterei és visszaadott értéke egyaránt struktúra. Struktúra persze akármekkora is elképzelhető. | |||||||||||||||||||||
typedef struct{ | |||||||||||||||||||||
Amíg a strurend fut, hat darab STUDENT struktúra létezik: a, b, c, aztán a és b másolata és a függvény visszaadott értéke a veremben. Célszerű tehát nem a struktúrát, hanem arra mutató mutatót átadni a függvénynek, ill. vissza is kapni tőle, ha lehet, azaz: | |||||||||||||||||||||
STUDENT *strurnd(STUDENT *a, STUDENT *b){ | |||||||||||||||||||||
Prototípus hatásköre ellenére a benne megadott struct hatásköre globális, azaz figyelmeztető üzenet nélkül nem hívhatjuk meg a következő függvényt: | |||||||||||||||||||||
void fv(struct S *); | |||||||||||||||||||||
A probléma elhárításához deklarálni vagy definiálni kell a struktúrát prototípus előírása előtt: | |||||||||||||||||||||
struct S; | |||||||||||||||||||||
Készítsünk struktúrát és kezelő függvénycsaládot dátumok manipulálására! | |||||||||||||||||||||
A datum struktúrában nyilvántartjuk a dátum évét (ev), hónapját (ho), napját (nap), a Krisztus születése óta a dátumig eltelt napok számát: az ún. dátumsorszámot (datumssz), a dátum karakterlánc alakját (datumlanc) és azt, hogy a dátum az év hányadik napja (evnap). Az egészet úgy képzeljük el, hogy | |||||||||||||||||||||
| |||||||||||||||||||||
Figyeljük meg, hogy mindegyik függvény struct datum objektumra mutató paraméterként kapja meg a manipulált dátum struktúrá(ka)t! A DatumMegEgy és a DatumMegint visszatérési értéke struct datum struktúra. | |||||||||||||||||||||
Fedezzük fel, hogy a globális MAXDATUM, a hónapi napszámokat tartalmazó honap tömb, és a Datume függvény static tárolási osztálya miatt lokális a datum.c modulra! Nincs is prototípus a Datume-re a datum.h fejfájlban. Az értéküket nem változtató, de csak a rutin blokkjából elérendő oszto változó, és a napnév karakterláncokra mutatókból álló hetnev mutatótömb statikus élettartamúak, de hatáskörük lokális. | |||||||||||||||||||||
Vegyük még észre a datum.h-ban, hogy a _DATUMH makró egyetlen struct datum definíciót tesz lehetővé akkor is, ha a fordítási egységben többször kapcsolnák be a fejfájlt. | |||||||||||||||||||||
/* DATUM.H: Datumok kezelese. */ | |||||||||||||||||||||
A dátumsorszámból 7-tel képzett modulus alapján állapítja meg a héten belüli napindexet a NapNev rutin. | |||||||||||||||||||||
static int Datume(struct datum *pd) { | |||||||||||||||||||||
A Datume ugyanúgy a dátumot ellenőrzi, s logikai választ ad a "formálisan jó-e a dátum?" kérdésre, mint a korábbi datume függvények. Nem karakterláncból dolgozik azonban, hanem a struktúra ev, ho, nap tagjaiból, melyeket a DatumEHN, ill. a DatumKAR készítettek oda. Ha hibás a dátum, akkor nullázza a rutin az evnap és a datumssz tagokat. | |||||||||||||||||||||
Ha jó a dátum, akkor a Datume képezi a datumlanc-ba a dátum karakterlánc alakját, s meghatározza az evnap és a datumssz értékét. Az evnap a dátum napszámáról indul, s a rutin hozzáadogatja a megelőző hónapok maximális napszámait. A dátumsorszám megállapításához a szökőév vizsgálatához használatos kifejezést alkalmazza a függvény. | |||||||||||||||||||||
Az stdio.h bekapcsolásával rendelkezésre álló sprintf ugyanúgy működik, mint printf társa, de nem a szabvány kimenetre, hanem az első paramétereként kapott karaktertömbbe dolgozik. | |||||||||||||||||||||
A formátumspecifikációkban a mezőszélesség előtt álló 0 hatására a jobbra igazított számok balról nem szóköz, hanem '0' feltöltést kapnak. Magyarán a 932.2.3. dátumból 0900.02.03 karakterlánc lesz. | |||||||||||||||||||||
Szóltunk már róla, hogy a Datume nem hívható más forrásmodulból. A datum.c- ben is csak a DatumEHN és a DatumKAR idézi meg utolsó lépéseként. | |||||||||||||||||||||
int DatumEHN(int e, int h, int n, struct datum *pd) { | |||||||||||||||||||||
A DatumEHN a kapott, int típusú év, hó, nap segítségével tölti fel az utolsó paraméterként elért dátum struktúrát. | |||||||||||||||||||||
Az sprintf hívás előtti vizsgálatra azért van szükség, hogy a rutin ne tudja túlírni a 11 elemű karaktertömb, datumlanc tagot a memóriában valamilyen egészen "zöldség" év, hó, nap paraméter miatt. Ilyenkor üres lesz a datumlanc. | |||||||||||||||||||||
A DatumKAR átmásolja a karakterlánc alakban kapott dátum első 10 karakterét a datumlanc-ba. Nullázza a honapot és napot. Látszik, hogy a függvény nem köti meg olyan szigorúan sem az év, sem a hónap és nap jegyszámát, mint a korábbi datume, ill. elválasztó karakterként csak valamilyen nem numerikust vár el. A karakterlánc egésszé konvertált elejét évnek, az első elválasztó karakter utáni részt hónapnak, s a második elválasztó karakter mögöttieket napnak tekinti a rutin, hacsak időközben vége nem lesz a karakterláncnak. | |||||||||||||||||||||
Végül mindkét függvény meghívja a Datume-t, s ennek visszatérési értékét szolgáltatja. | |||||||||||||||||||||
long DatumKul(struct datum *pd1, struct datum *pd2) { | |||||||||||||||||||||
A DatumKul képzi a két paraméter dátum struktúra dátumsorszám tagjainak különbsége abszolút értékét. | |||||||||||||||||||||
struct datum DatumMegEgy(struct datum *pd) { | |||||||||||||||||||||
A paramétere dátumot inkrementáló DatumMegEgy munkaváltozókba rakja az évet, a hónapot és a napot. Meghatározza az év szerinti február pontos napszámát. Növeli eggyel a napot. Ha ez túlmenne a hónap szerinti maximális napszámon, akkor 1 lesz, és a hónapszám növelése jön. Ha ez 13 lenne, akkor 1 lesz, és az évszám növelése következik. | |||||||||||||||||||||
A megállapított, új év, hó, nap alapján a DatumEHN feltölti a lokális, d dátum objektumot. Ha az új dátum érvénytelen volt, akkor a változatlanságot jelzendő a rutin hozzárendeli d-hez a paraméter címen levő, eredeti dátumot. | |||||||||||||||||||||
A hozzárendelés a paraméter címen levő (ezért kell elé az indirekció) struct datum minden tagját egy az egyben átmásolja a balérték, d, lokális dátum objektum tagjaiba rendre. | |||||||||||||||||||||
A visszatérés során a DatumMegEgy létrehoz a veremben egy ideiglenes dátum objektumot, melybe tagról-tagra bemásolja a d lokális dátum változót. Visszatérés után aztán a main hozzárendeli az ideiglenes dátum objektumot a main-ben lokális d-hez. | |||||||||||||||||||||
struct datum DatumMegint(struct datum *pd, int np) { | |||||||||||||||||||||
Csak a kezdetét és a végét tekintve a pozitív napszámot a dátumhoz adó DatumMegint a DatumMegEgy-gyel megegyezően dolgozik. | |||||||||||||||||||||
Látszik, hogy negatív, hozzáadandó napszámot, vagy a művelet végén érvénytelen dátumot kapva, az eredeti dátum objektumot szolgáltatja a rutin változatlanul. | |||||||||||||||||||||
A dátumsorszámot megnöveli a napszámmal és még 365-tel. Ha így meghaladná a 9999.12.31-et, akkor ezt adná vissza. Ha nem, akkor az új dátumsorszámot elosztja a tapasztalati alapon a [MINDATUM, MAXDATUM] tartományban érvényes évenkénti átlagos napszámmal, s ez lesz az új évszám. Visszaszámolja belőle az új év pontos napszámát, s a két érték különbségéből hónap és napszámot képez. | |||||||||||||||||||||
/* PELDA29.C: A datumok kezelesenek kiprobalasa. */ | |||||||||||||||||||||
Struktúra tárillesztése | |||||||||||||||||||||
A fordító a struktúratagokat deklarációjuk sorrendjében növekvő memória címeken helyezi el. Minden adatobjektum rendelkezik tárillesztési igénnyel is. A fordító olyan eltolással helyezi el az adatobjektumot, hogy az | |||||||||||||||||||||
eltolás % tárillesztési-igény == 0 | |||||||||||||||||||||
zérus legyen. Struktúrák esetén ez a szabály a tagok elhelyezésére vonatkozik. Ha példának vesszük a | |||||||||||||||||||||
struct struki { | |||||||||||||||||||||
struktúrát, akkor tudjuk, hogy az s objektumot növekvő memória címeken úgy helyezi el a fordító, hogy | |||||||||||||||||||||
| |||||||||||||||||||||
Bizonyos fordító opciók, vagy valamilyen #pragma direktíva segítségével vezérelhetjük a struktúra adatok memóriabeli illeszkedését. Ez azt jelenti, hogy az adatokat 1-gyel, 2-vel, 4-gyel stb. maradék nélkül osztható címeken: bájthatáron, szóhatáron, dupla szóhatáron stb. kell elhelyezni. Felkérjük az olvasót, hogy nézzen utána a dolognak a programfejlesztő rendszere segítségében! Bárhogyan is, eme beállítások hatására a fordító minden struktúratagot, az elsőt követően, olyan határon tárol, mely megfelel a tag tárillesztési igényének. | |||||||||||||||||||||
A bájthatárra igazítás azt jelenti, hogy | |||||||||||||||||||||
| |||||||||||||||||||||
A példa s objektum összesen 15 bájtot foglal el ilyenkor, és a memória térkép a következő: | |||||||||||||||||||||
A szóhatárra igazítás azt jelenti, hogy | |||||||||||||||||||||
| |||||||||||||||||||||
A példa s objektum így összesen 16 bájtot foglal el. Egy bájt elveszik, és a memória térkép a következő: | |||||||||||||||||||||
A dupla szóhatárra igazítás azt jelenti, hogy | |||||||||||||||||||||
| |||||||||||||||||||||
Dupla szóhatárra igazítva az s objektum megegyezik az előzővel. A határra igazítási "játék" folytatható értelemszerűen tovább. | |||||||||||||||||||||
Persze a struktúrát nem ilyen "bután" definiálva igazítástól függetlenül elérhetjük, hogy egyetlen bájt elvesztése se következzék be: | |||||||||||||||||||||
struct struki { | |||||||||||||||||||||
Uniók | |||||||||||||||||||||
Az unió típus a struktúrából származik, de a tagok között zérus a címeltolás. Az unió azt biztosítja, hogy ugyanazon a memória területen több, különféle típusú adatot tárolhassunk. Az | |||||||||||||||||||||
union unio{ | |||||||||||||||||||||
definícióban az u union unio típusú objektum, a pu ilyen típusú objektumra mutató mutató, és a tu egy 23 ilyen típusú elemből álló tömb azonosítója. Az u objektum - például - egyazon memória területen biztosítja az i nevű int, a d azonosítójú double és a t nevű karaktertömb típusú tagjainak elhelyezését, azaz: | |||||||||||||||||||||
&u | |||||||||||||||||||||
Ennek szellemében aztán igaz, hogy az unió objektumot címző mutató annak egyben minden tagjára is mutat. | |||||||||||||||||||||
(pu=&u) | |||||||||||||||||||||
Természetesen a dolog csak a mutatók értékére igaz, mert az &u (union unio *), az &u.i (int *) és az &u.d (double *), azaz típusban eltérnek. Ha azonban uniót megcímző mutatót explicit típusmódosítással tagjára irányuló mutatóvá alakítjuk, akkor az eredmény mutató magára a tagra mutat: | |||||||||||||||||||||
u.d=3.14; | |||||||||||||||||||||
Az unió helyfoglalása a tárban akkora, hogy benne a legnagyobb bájtigényű tagja is elfér, azaz: | |||||||||||||||||||||
sizeof(union unio) | |||||||||||||||||||||
Tehát 4 bájt felhasználatlan, ha int adatot tartunk benne, ill. 3 bájt elérhetetlen, ha karaktertömböt rakunk bele. Az unió egy időben csak egyetlen tagját tartalmazhatja. | |||||||||||||||||||||
Láttuk már, hogy az uniótagokat ugyanazokkal a tagszelektor operátorokkal érhetjük el, mint a struktúratagokat: | |||||||||||||||||||||
u.d = 3.15; | |||||||||||||||||||||
Valahonnan tehát célszerű tudni - például úgy, hogy nyilvántartjuk - milyen típusú adat is található pillanatnyilag az unió objektumban, és azt szabad csak elérni. | |||||||||||||||||||||
Ha egy unió többféle, de azonos kezdő szerkezetű struktúrával indul, és az unió tartalma e struktúrák egyike, akkor lehetőség van az unió közös kezdeti részére hivatkozni. Például: | |||||||||||||||||||||
union{ | |||||||||||||||||||||
Uniókkal pontosan ugyanazok a műveletek végezhetők, mint a struktúrákkal. Hozzárendelhetők, másolhatók, hozzáférhetünk a tagjaikhoz, képezhető a címük, átadhatók függvényeknek és rutinok visszatérési értékei is lehetnek. | |||||||||||||||||||||
Uniódeklarációk | |||||||||||||||||||||
Az általános deklarációs szabály azonos a struktúráéval. Az eltérések a következők: | |||||||||||||||||||||
| |||||||||||||||||||||
Bitmezők (bit fields) | |||||||||||||||||||||
Bitmezők csak struktúra vagy unió tagjaként definiálhatók, de a struktúrában és az unióban akár keverten is előfordulhatnak bitmező és nem bitmező tagok. A bitmező struktúratag deklarációs szintaktikája kicsit eltér a normál tagokétól: | |||||||||||||||||||||
típusspecifikátor <deklarátor> : konstans-kifejezés; | |||||||||||||||||||||
ahol a típusspecifikátor csak | |||||||||||||||||||||
| |||||||||||||||||||||
lehet az ANSI C szabvány szerint. Az int tulajdonképpen signed int. A deklarátor a bitmező azonosítója, mely el is maradhat. Ilyenkor a névtelen bitmező specifikálta bitekre nem tudunk hivatkozni, s a bitek futásidejű tartalma előre megjósolhatatlan. A konstans-kifejezés csak egészértékű lehet. Zérus és sizeof(int)*8 közöttinek kell lennie, s a bitmező szélességét határozza meg. | |||||||||||||||||||||
Bitmező csak struktúra vagy unió tagjaként deklarálható. Nem képezhető azonban bitmezők tömbje. Függvénynek sem lehet visszaadott értéke a bitmező. Nem megengedett a bitmezőre mutató mutató és tilos hivatkozni a bitmező tag címére, azaz nem alkalmazható rá a cím (&) operátor sem. | |||||||||||||||||||||
A bitmezők az int területen (dupla szóban, vagy szóban) deklarációjuk sorrendjében többnyire az alacsonyabb helyi értékű bitpozícióktól a magasabbak felé haladva foglalják el helyüket. | |||||||||||||||||||||
Az int pontos mérete, bitmezővel való feltöltésének szabályai és sorrendje a programfejlesztő rendszertől függ. Célszerű tehát a segítségben utánanézni a dolognak. Maradjunk meg azonban az előző bekezdésben említett szabálynál, és a könnyebb szemléltethetőség végett még azt is tételezzük fel, hogy az int 16 bites! Ilyenkor például a: | |||||||||||||||||||||
struct bitmezo{ | |||||||||||||||||||||
által elfoglalt szó bittérképe a következő: | |||||||||||||||||||||
Ha az m bitmező tag szélessége 4-nél nagyobb lett volna, akkor új szót kezdett volna a fordító, s az előző szó felső négy bitje kihasználatlan maradt volna. Általánosságban: a (dupla)szón túllógó bitmező új (dupla)szót kezd, s az előző (dupla)szóban a felső bitek kihasználatlanok maradnak. | |||||||||||||||||||||
Ha a deklarációban valamely (névtelen) bitmezőnél zérus szélességet adunk meg, akkor mesterségesen kényszerítjük ki ezt a következő (dupla) szóhatárra állást. | |||||||||||||||||||||
A bitmezőnek elég szélesnek kell lennie ahhoz, hogy a rögzített bitminta elférjen benne! Például a következő tagdeklarációk illegálisak: | |||||||||||||||||||||
int alfa : 37; | |||||||||||||||||||||
A bitmezők ugyanazokkal a tagszelektor operátorokkal (. és ->) érhetők el, mint a nem bitmező tagok: b.i vagy pb->k | |||||||||||||||||||||
A bitmezők kis signed vagy unsigned egész értékekként viselkednek (rögtön átesnek az egész-előléptetésen), azaz kifejezésekben ott fordulhatnak elő, ahol egyébként aritmetikai (egész) értékek lehetnek. signed esetben a legmagasabb helyi értékű bit (MSB - most significant bit) előjelbitként viselkedik, azaz az int i : 2 lehetséges értékei például: | |||||||||||||||||||||
00: 0, 01: +1, 10: -2, 11: -1 | |||||||||||||||||||||
Az unsigned m : 4 lehetséges értékei: | |||||||||||||||||||||
0000: 0, 0001: 1, . . ., 1111: 15 | |||||||||||||||||||||
Általánosságban: | |||||||||||||||||||||
unsigned x : szélesség; /* 0 <= x <= 2szélesség-1 */ | |||||||||||||||||||||
A nyelvben nincs sem egész alul, sem túlcsordulás. Ha így a bitmezőnek ábrázolási határain kívüli értéket adunk, akkor abból is lesz "valami". Méghozzá az érték annyi alsó bitje, mint amilyen széles a bitmező. Például: | |||||||||||||||||||||
b.i = 6; /* 110 -> 10, azaz -2 lesz az értéke! */ | |||||||||||||||||||||
A bitmezők ábrázolása gépfüggő, mint már mondottuk, azaz portábilis programokban kerüljük el használatukat! | |||||||||||||||||||||
Vegyük elő ismét a Bit szintű operátorok részben tárgyalt dátum és időtárolási problémát! Hogyan tudnánk ugyanazt a feladatot bitmezőkkel megoldani? | |||||||||||||||||||||
| |||||||||||||||||||||
A dátum és az idő adatot egy-egy szóban, azaz C nyelvi fogalmakkal egy-egy unsigned short int-ben tartjuk. A két szó bitfelosztása az ábrán látható! | |||||||||||||||||||||
A bitmezős megoldás például a következő is lehetne: | |||||||||||||||||||||
struct datum{ | |||||||||||||||||||||
Balérték - jobbérték | |||||||||||||||||||||
Most már tökéletesen pontosíthatjuk a balérték kifejezést a C-ben, mely: | |||||||||||||||||||||
| |||||||||||||||||||||
A const objektum nem módosítható balérték, hisz csak a deklarációban kaphat kezdőértéket. | |||||||||||||||||||||
A jobbérték (rvalue) olyan kiértékelhető kifejezés, melynek értékét balérték veheti fel. Például: | |||||||||||||||||||||
a = c + d; /* OK */ | |||||||||||||||||||||
A balérték (lvalue) olyan kifejezés, mely eléri az objektumot (a hozzá allokált memória területet). Triviális például egy változó azonosítója. Lehet azonban *P alakú is, ahol a P kifejezést nem NULL mutatóra értékeli ki a fordító. Onnét is származtatható a két fogalom, hogy a balérték állhat a hozzárendelés operátor bal oldalán, s a jobbérték pedig a jobb oldalán. | |||||||||||||||||||||
Beszélhetünk módosítható balértékről is! Módosítható balérték nem lehet tömb típusú (a tömbazonosító praktikusan cím konstans), nem teljes típusú, vagy const típusmódosítóval ellátott objektum. Módosítható balérték például a konstans objektumra mutató mutató maga, miközben a mutatott konstans objektum nem változtatható. Például | |||||||||||||||||||||
int tomb[20]; | |||||||||||||||||||||
esetén balértékek: | |||||||||||||||||||||
tomb[3] = 3; *(tomb+4) = 4; | |||||||||||||||||||||
A következő deklarációban viszont a kar nem balérték, hisz konstansságára való tekintettel értéket egyedül a definíciójában kaphat: | |||||||||||||||||||||
const char kar = 'k'; | |||||||||||||||||||||
Azonban ha van egy | |||||||||||||||||||||
char *pozicio(int index); | |||||||||||||||||||||
függvény, akkor balérték lehet a következő is: | |||||||||||||||||||||
*pozicio(5) = 'z'; | |||||||||||||||||||||
Névterületek | |||||||||||||||||||||
A névterület az a "hatáskör", melyen belül egy azonosítónak egyedinek kell lennie, azaz más-más névterületen konfliktus nélkül használható ugyanaz az azonosító, s a fordító meg tudja különböztetni őket. A névterületeknek a következő fajtái vannak: | |||||||||||||||||||||
| |||||||||||||||||||||
Nézzünk néhány példát! | |||||||||||||||||||||
struct s{ |
Feladatok | |||||||||||||||||||||
1. Fejlessze tovább a pelda28.c-t úgy, hogy a tárolt síkbeli pontokat rendezi az origótól való távolságuk csökkenő sorrendjében, majd meg is jeleníti a pontokat és távolságukat fejléccel ellátva, táblázatosan és lapozhatóan! | |||||||||||||||||||||
/* PELDA28X.C: A ket, egymastol legtavolabbi pont keresese, | |||||||||||||||||||||
2. Egyszeri programozó megpróbált olyan függvényt írni, amely egy dátum struktúra paraméterből dátum karakterláncot állít elő oly módon, hogy abban a hónap nem számmal, hanem szövegesen szerepel. A megoldás sajnos nem hibátlan. | |||||||||||||||||||||
/*01*/struct datum{ | |||||||||||||||||||||
Jelölje meg azokat a sorokat, amelyekkel a függvény működőképessé válik!
![]() | |||||||||||||||||||||
3. Kezdő programozó megpróbált készíteni egy olyan függvényt, ami a paraméterként kapott óó:pp:mm formátumú karakterláncból időt reprezentáló struktúrát készít, majd annak címét visszaadja. Hiba esetén NULL-t szolgáltat. A megoldás azonban nem tökéletes. | |||||||||||||||||||||
/*01*/typedef struct { | |||||||||||||||||||||
Jelölje meg azokat a sorokat, melyekkel működőképessé tehető!
![]() | |||||||||||||||||||||
4. Kezdő programozó megpróbálta számítógépének IPv4 címét kiírni egy darab 4 bájtos értékként, és 4 darab 1 bájtos adatként is. | |||||||||||||||||||||
/*01*/#include<stdio.h> | |||||||||||||||||||||
Jelölje meg azokat a sorokat, amelyek használatával hibás programja működni fog!
![]() |