InfoC adventi naptár
Adatrejtés C-ben
C-ben alapvetően egy struktúra teljesen átlátszó. Minden tagját ismerni lehet, mert látszik a definíciója. Ha nem látszik a definíció, akkor nem lehet létrehozni példányt belőle.
Vagy mégis. A C megengedi azt is, hogy egy deklarált, de definiálatlan típusú
változóra mutató
pointert hozzunk létre. Pontosan ez történik egy egészen egyszerű láncolt listánál
is. A struktúrán belül a pointer megadásánál a struct Lista
típus még ismeretlen: definiálatlan, hiszen éppen definiálás alatt van.
Rá mutató pointert létrehozni viszont lehet (a következő láncszem pointere):
struct Lista { double szam; struct Lista *kovetkezo; };
Ez a tény lehetővé teszi azt, hogy egy struktúra adattagjait elrejtsük.
1 Átlátszatlan struktúrák
Legyen példa egy dinamikus tömb. A tömböt lehessen lefoglalni a megadott mérettel, lehessen felszabadítani; lehessen átméretezni. Lehessen lekérdezni az egyik elemét, és beállítani azt valamilyen értékre.
Ha dinamikusan foglalunk egy memóriaterületet, akkor egy pointert kaptunk. A pointer mellé minden esetben meg kell jegyeznünk a darabszámot is, hogy a tömb mennyi elemet tartalmaz. A lefoglalt memóriára mutató pointer és a darabszám két összetartozó adat. Külön nincs értelmük, illetve ha az egyik változik, változnia kell a másiknak is. Tegyük be ezért őket egy struktúrába. Tegyünk így azért is, hogy egy függvénynek könnyen át lehessen adni egy ilyen tömböt, egyetlen paraméterrel:
#include <stdio.h> #include <stdlib.h> typedef struct DinTomb { double *szamok; int meret; } DinTomb; DinTomb *dintomb_foglal(int meret); void dintomb_free(DinTomb *dt); void dintomb_kiir(DinTomb const *dt); int main() { DinTomb *d; d=dintomb_foglal(50); d->szamok[0]=5; dintomb_kiir(d); dintomb_free(d); return 0; }
Ez így szerepelt előadáson is, és kiválóan működik. Probléma csak egy van: semmi akadálya nincs annak, hogy ezt a sort leírjuk:
d->meret=3217;
Ilyenkor a dinamikus tömböt kezelő függvények megzavarodnak: az átméretező függvény helytelenül fogja átmásolni az új, átméretezett tömbbe az adatokat, mert rosszul fogja tudni a tömb előző méretét.
2 A megoldás
Hogy lehet ezt megoldani? Rejtsük el a struktúra belsejét a main()
függvény
elől. Deklaráljuk a struktúrát, de ne definiáljuk azt:
struct DinTomb;
Ha így teszünk, és a main()
függvény ennyit lát csak, akkor
nem fog tudni hivatkozni az adattagokra. Egyetlen dolgot tud majd tenni: a struktúrára
mutató pointert létrehozni. (Ha a benne lévő mezőkre hivatkozna, akkor
dereferencing pointer of incomplete type hibaüzenetet fog adni a fordító.)
Szóval az adattagokat nem látja, de a dintomb_foglal()
függvénytől át tudja venni a lefoglalt struktúrára mutató pointert. Bármelyik
másik függvénynek át is tudja adni azt. Ez egyébként nem ismeretlen dolog,
ugyanígy működik a FILE
típus. Ez általában egy struktúra.
Nem tudjuk, mi van benne, de
ennek ellenére el tudjuk érni a fájlokat a fájlkezelő függvényeken keresztül.
A tömbös példánkban is minden művelethez, amelyet a
tömbön végzünk, egy függvényt kell írnunk. A függvények egy másik forrás
fájlban lesznek, amely forrás fájl tetején nem csak deklaráljuk, hanem
definiáljuk is a DinTomb
struktúrát.
3 Mire jó ez az egész?
Nagyon egyszerű: elválaszthatjuk a program részeit egymástól. Ha rosszul működik a dinamikus tömbünk, akkor az azt kezelő függvényekben kell a hiba okát keresnünk. Mert más nem nyúlhatott a struktúrában lévő mezőkhöz. (Legalábbis ha helyesen kezeljük mindenhol a memóriát.)
#include <stdio.h> #include <stdlib.h> #include <assert.h> /* ============ DINTOMB.H ============ */ typedef struct DinTomb DinTomb; /* csak deklaracio! */ DinTomb *dintomb_foglal(int meret); /* uj tombot foglal. */ DinTomb *dintomb_masolat(DinTomb const *eredeti); /* uj tomb – mely masolat. */ void dintomb_free(DinTomb *dt); /* felszabaditja a tombot */ void dintomb_kiir(DinTomb const *dt); /* kiir minden szamot */ void dintomb_atmeretez(DinTomb *dt, int ujmeret); /* atmeretezi; ha csokken, a hatsok elvesznek, ha no, az ujak memoriaszemet */ void dintomb_set(DinTomb *dt, int index, double adat); /* adott indexut beallit */ double dintomb_get(DinTomb const *dt, int index); /* adott indexut lekerdez */ /* ============ MAIN.C ============ */ int main() { DinTomb *d, *dm; d=dintomb_foglal(50); dintomb_set(d, 15, 3.14); printf("d[15]=%g\n", dintomb_get(d, 15)); printf("d=["); dintomb_kiir(d); printf("]\n"); dm=dintomb_masolat(d); dintomb_free(d); printf("dm=["); dintomb_kiir(dm); printf("]\n"); dintomb_free(dm); return 0; } /* ============ DINTOMB.C ============ */ /* #include "dintomb.h" termeszetesen lenne az elejen */ struct DinTomb { /* ez mar definicio is! */ double *szamok; int meret; }; DinTomb *dintomb_foglal(int meret) { DinTomb *uj; uj=(DinTomb *) malloc(sizeof(DinTomb)); uj->meret=meret; uj->szamok=(double *) malloc(meret*sizeof(double)); return uj; } void dintomb_free(DinTomb *dt) { free(dt->szamok); free(dt); } void dintomb_kiir(DinTomb const *dt) { int i; for (i=0; i<dt->meret; ++i) printf("%g ", dt->szamok[i]); } DinTomb *dintomb_masolat(DinTomb const *eredeti) { DinTomb *uj=dintomb_foglal(eredeti->meret); int i; uj->meret=eredeti->meret; for (i=0; i<eredeti->meret; i++) uj->szamok[i]=eredeti->szamok[i]; return uj; } void dintomb_atmeretez(DinTomb *dt, int ujmeret) { double *ujmemoria; int kisebb, i; if (dt->meret==ujmeret) return; /* uj double tombot foglalunk, es atmasoljuk bele */ kisebb=ujmeret<dt->meret?ujmeret:dt->meret; ujmemoria=(double *) malloc(ujmeret*sizeof(double)); for (i=0; i<kisebb; ++i) ujmemoria[i]=dt->szamok[i]; free(dt->szamok); /* uj adatok */ dt->szamok=ujmemoria; dt->meret=ujmeret; } void dintomb_set(DinTomb *dt, int index, double adat) { /* indexhatar ellenorzes - megvan az info hozza */ assert(index>=0 && index<dt->meret); dt->szamok[index]=adat; } double dintomb_get(DinTomb const *dt, int index) { assert(index>=0 && index<dt->meret); /* indexhatar */ return dt->szamok[index]; }