Gyakorlat, 8. hét: dinamikus tömbök I.
Czirkos Zoltán · 2019.07.15.
Dinamikus memóriakezelés. Dinamikusan foglalt tömbök.
Felkészülés a gyakorlatra:
- Az dinamikus memóriakezelésről szóló előadás anyagának megértése.
- A pointerekről és cím szerinti paraméterátadásról szóló gyakorlat átismétlése.
A feladat segít felmérni, hogy állsz a tanulással. A szövege csak a többi megoldással együtt jelenik meg. Óra után pedig rögzítsd a pontszámod – a dolgozat nálad marad.
Feladat
Írj függvényt, amely paraméterként vesz át egy egészekből álló tömböt! A függvény adja vissza címükkel átvett változókban a tömb legkisebb és legnagyobb elemének indexét! Ha több egyforma érték a legkisebb/legnagyobb, akkor ezek közül bármelyik indexét visszaadhatja.
Mutass példát a függvény hívására! Definiálj egy ötelemű tömböt, hívd meg a függvényt, majd a kapott indexeket felhasználva írd ki a legkisebb és legnagyobb elem értékét!
A feladat megoldására 10 perc van.
Mintamegoldás és pontozási útmutató
void minmax(int *tomb, int n, int *pmin, int *pmax) {
int min = 0, max = 0;
for (int i = 1; i < n; i++) {
if (tomb[i] < tomb[min]) min = i;
if (tomb[i] > tomb[max]) max = i;
}
*pmin = min;
*pmax = max;
}
int tomb[5] = { 89, 76, 34, 19, 47 };
int min, max;
minmax(tomb, 5, &min, &max);
printf("Legkisebb: %d, legnagyobb: %d", tomb[min], tomb[max]);
Mindegyik tétel 1 pontos:
main():
a) tömb létrehozása (legyen méret megadva vagy inicializálva)
b) eredmény változók, min és max; int típusúak, nem int*!
c) tömb (& nélkül, mérettel) és min/max (& kell) átadása
d) eredmény kiírásánál a kapott indexek helyes felhasználása
minmax():
e) paraméterek: int* és int a tömbhöz, int* és int* az eredményekhez
f) lokális min, max változók VAGY mindenhol *pmin, *pmax használata
g) szélsőérték keresése: változók kezdetben nullára állítása;
ez a függvény feladata, nem a hívóé!
h) szélsőérték keresése: ciklus vizsgálja a többi elemet
i) min,max eredmények végleges beírása a kért helyre (ha mindenhol
*pmin, *pmax volt, akkor automatikusan teljesül)
Összkép, olvashatóság:
j) helyes indentálás, nincs *(tomb+i) alakú kifejezés,
szerepre utaló változónevek
Ha hely (index) helyett a tömbbeli értéket adja a függvény
a hívójának, akkor a g-h-i pontok nem járnak.
Az alábbi kódrészletek mindegyike legalább egy, de lehet hogy több helyen is, hibás. Miért? Ne csak a hibára mutassunk rá, hanem magyarázzuk meg a hibát! Miért nem lehetséges, hogy működjenek a programkódok? Mi az elvi akadály? Vagy működnek, csak más baj van? Hogyan lenne javítható?

int *fv(void) {
int i = 2;
return &i;
}
Megoldás
A változó meg fog szűnni a függvényből visszatérés után. Hiába adjuk vissza a címét, a hívó már
nem használhatja azt semmire – dangling pointer. Ha csak a változó értékére vagyunk kíváncsiak,
adjuk vissza azt: int, és return i.

int *fv(void) {
int tomb[100];
tomb[0] = 2;
return tomb;
}
Megoldás
Ugyanaz a hiba, mint az előbb. Nem a tömböt adjuk vissza, csak a tömb kezdőcímét – a visszatérés pillanatában azonban a tömb kitörlődik a memóriából, a helye felszabadul. Ezért a hívó használhatatlan pointer kapna. Javítani kétféleképpen lehet. A hívó is lefoglalhatja a tömböt:
void fv(int *tomb) {
tomb[0] = 2;
}
int tomb[100];
fv(tomb);
Vagy dinamikus memóriakezeléssel, ilyenkor a meghívott függvény foglalja a tömböt:
int *fv(void) {
int *tomb;
tomb = (int*) malloc(sizeof(int) * 100);
tomb[0] = 2;
return tomb;
}
A feladat ismert lehet egy régebbi gyakorlatról: adott egy sztring, amelyből ki kell szűrni a szóközöket. Az előállított sztringben „mindenegybeleszírva”. Oldjuk ezt meg úgy, hogy a függvény visszatérési értéke az új sztring! (Tehát se a meglévő karaktertömb módosításáról, se egy meglévő karaktertömbbe írásról most nem lehet szó.)
Honnan fogja tudni a hívó, hogy milyen hosszú lett a kapott karaktertömb?
Mutassunk példát a függvény használatára!
Megoldás
A dinamikus memóriakezelésnek itt két előnye is jól jön számunkra. Egyik, hogy a függvényből vissza tudunk adni olyan tömböt, amelyet annak belsejében foglaltunk le. Ennek dinamikusnak kell lennie, különben megszűnne visszatéréskor. A másik, hogy a tetszőlegesen nagy tömb méretét futási időben megadhatjuk.
Kétszer megyünk végig a tömbön: egyszer azért, hogy megszámoljuk, hány nem szóköz karakter van, másodszor pedig azért, hogy az addigra lefoglalt tömbbe a karaktereket átmásoljuk.
A szóköztelenítő függvény NULL értéke jelezheti a hibát, a malloc()-hoz hasonlóan.
#include <stdio.h>
#include <stdlib.h>
char *szokoztelenit(char const *mit) {
int nemszokoz = 0;
for (int i = 0; mit[i] != '\0'; ++i)
if (mit[i] != ' ')
nemszokoz += 1;
char *str = (char*) malloc(sizeof(char) * (nemszokoz+1));
if (str == NULL)
return NULL; /* :( */
int j = 0;
for (int i = 0; mit[i] != '\0'; ++i)
if (mit[i] != ' ')
str[j++] = mit[i];
str[j] = '\0';
return str;
}
int main(void) {
char *egybeirva = szokoztelenit("hello vilag alma korte");
if (egybeirva == NULL) {
printf("hiba történt, nincs szóköztelenített sztring");
} else {
printf("%s", egybeirva);
free(egybeirva);
}
return 0;
}
Írjunk függvényt, amelyik egy paraméterben kapott double tömbből kiválogatja az átlagnál kisebb elemeket, és egy
újonnan lefoglalt dinamikus tömbbe rakja azokat!
Honnan fogja tudni a hívó, hogy milyen hosszú lett a kapott tömb? Oldjuk meg ezt a következőképp: az új tömb címével és méretével térjen vissza a függvény cím szerint átadott paraméterben! A visszatérési értéke legyen IGAZ, ha sikerült a művelet, és HAMIS, ha nem.
Mutassunk példát a függvény használatára! Készítsünk ábrát a főprogram és a függvény lokális változóiról, illetve a dinamikusan foglalt memóriaterületekről abban a pillanatban, amikor épp visszatérni készül a függvény!
Megoldás
A gondolatmenet: double tömböt kell foglalnunk, és abba az elsőből válogatnunk. De mekkorát? Azt a foglalás előtt
még meg kell számolni, mint az előző feladatban! Vagyis:
- átlag számítása,
- keresett elemek számlálása (mert most már tudjuk az átlagot),
- foglalás (mert most már tudjuk a méretet),
- keresett elemek másolása (mert most már van hova másolni).
Koncentráljunk a nyelvi elemekre! A double **ujtomb ijesztőnek tűnhet. De nincs itt semmi újdonság: a tömböt
pointerével és méretével vesszük át, az új tömböt pointerével és méretével adjuk vissza. Mivel a függvény által módosított
double* változót cím szerint kell átvenni, az ujtomb paraméter double** típusú.
Lásd a hívás helyét a főprogramban.
A memóriaképen sárgával az alsó sorban a main() függvény lokális változói láthatóak. A középső sorban kékkel a
valogat() függvény lényeges lokális változói sorakoznak. Legfelül zöld szín a dinamikusan foglalt területet
jelöli.
A main() függvény uj nevű változója egy pointer. Erre mutat rá a valogat() függvény
ujtomb nevű változója. Ezért is lett annak double ** a típusa, ahogy arról fentebb már szó esett.
Mindkettő uj nevű változó a dinamikus tömbre mutat; a main() függvény uj és db
nevű változójának is a valogat() ad értéket.
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
bool valogat(double *eredeti, int meret, double **ujtomb, int *ujmeret) {
/* átlag meghatározása */
double sum = 0;
for (int i = 0; i < meret; ++i)
sum += eredeti[i];
double atlag = sum/meret;
/* darabszám (tömbméret) meghatározása */
int db = 0;
for (int i = 0; i < meret; ++i)
if (eredeti[i] < atlag)
++db;
double *uj = (double *) malloc(sizeof(double) * db);
if (uj == NULL)
return false;
int ide = 0;
for (int i = 0; i < meret; ++i)
if (eredeti[i] < atlag) {
uj[ide] = eredeti[i];
++ide;
}
*ujtomb = uj;
*ujmeret = db;
return true;
}
int main(void) {
double szamok[5] = {3.4, 8.5, 4.6, 9.1, 1.2};
double *uj;
int db;
valogat(szamok, 5, &uj, &db);
/* itt ellenőrizni kellene, sikerült-e a foglalás... */
for (int i = 0; i < db; ++i)
printf("%g ", uj[i]);
free(uj);
return 0;
}