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];
}
