Adventi naptár
Czirkos Zoltán · 2021.08.24.
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 már deklarált, de még
definiálatlan típusú struktúrá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 lehetővé teszi azt, hogy egy struktúra adattagjait elrejtsü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(void) {
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.
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ásfájlban lesznek, amely forrásfájl tetején nem csak deklaráljuk, hanem
definiáljuk is a DinTomb
struktúrát.
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(void) {
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) {
for (int i = 0; i < dt->meret; ++i)
printf("%g ", dt->szamok[i]);
}
DinTomb *dintomb_masolat(DinTomb const *eredeti) {
DinTomb *uj = dintomb_foglal(eredeti->meret);
uj->meret = eredeti->meret;
for (int i = 0; i < eredeti->meret; i++)
uj->szamok[i] = eredeti->szamok[i];
return uj;
}
void dintomb_atmeretez(DinTomb *dt, int ujmeret) {
if (dt->meret == ujmeret)
return;
/* uj double tombot foglalunk, es atmasoljuk bele */
int kisebb = ujmeret<dt->meret?ujmeret:dt->meret;
double *ujmemoria = (double *) malloc(ujmeret*sizeof(double));
for (int 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];
}