A program elkészítése során gyakran találkozhatunk olyan esettel, hogy az adatok mennyiségét nem ismerjük a program írása közben, csak futásidőben.
Ilyen eset lehet pl. n darab szám átlagolása, ahol egy n elemű tömbben szeretnénk eltárolni a számokat - de hogy mennyi az n értéke azt a felhasználó fogja megadni futás közben. Az ilyen tömböt dinamikus tömbnek fogjuk nevezni a továbbiakban, míg az előző fejezetekből megismert tömböt statikus tömbnek.
Ez a fejezet azon adatszerkezetekkel foglalkozik, amelyeknek a mérete futásidőben állítható be, illetve igény szerint változtatható - de a kódbeli bonyolultság nem haladja meg túlzottan az eddigieket.
1, A dinamikus tömb, mint mutató
1a, malloc
1b, realloc
1c, free
1d, memset
1e, A dinamikus
tömb előállítása
2, A direkt struktúra
alapú dinamikus tömb
3, A dinamikus mátrix
3a, Miért kell két
csillag?
3b,
A
téglalapos dinamikus mátrix lefoglalása/felszabadítása
3c,
A
szabadalakos dinamikus mátrix lefoglalása/felszabadítása
3d,
Az indirekt struktúra alapú dinamikus tömb
4, A dinamikus tér
5, A dinamikus n-dimenziós tömb
6, A C++ egyszerűsítései
6a, A new/delete
utasítás
6b, std::vector
1, A dinamikus tömb, mint mutató
Mint ez már a fent megjelölt korábbi fejezetben említésre került, a tömb alapvetően egy mutató - ennek megfelelően azzal egyenértékűen helyettesíthető.
Továbbá az is említésre került a legelején, hogy a számítógép, mikor felveszünk egy változót, lefoglalja a neki szükséges memóriaterületet a változódeklaráció során. Ez a terület a sizeof(<típus>) operátorral kérhető le, byteokban.
Arról is esett szó hogy a tömb egy memóriafolytonos típus, vagyis az elemek egymás után következnek, így egy szorzással előállítható az n-edik elem címe.
Ezeket összevetve végül is arra lehet jutni, hogy a dinamikus tömb előállítható, ha megfordítjuk a változódeklaráció menetét:
- lefoglalunk kézzel egy n * sizeof(<típus>) méretű területet a memóriában
- ennek a területnek a címét eltároljuk egy mutatóban
- és ezt a mutatót tömbként fogjuk használni
Lényegében ennyiről van szó, de mielőtt ezt kivitelezhetnénk, meg kell ismerkedni néhány utasítással - ezek a malloc, a realloc, a free, és a memset.
1a, malloc
void* malloc(unsigned int size);
A malloc lesz az a függvény ami a kézi lefoglalást végzi, és ha ez sikeres akkor a terület memóriacímét adja vissza.
size: azt adja meg, hogy mekkora legyen a lefoglalt terület mérete byteokban
A függvény visszatérési értéke a terület memóriacíme ha sikeres volt a foglalás. Egyéb esetben NULL.
Használata:
int
elemszam =
25; int* tomb = (int*)malloc(elemszam * sizeof(int)); |
Megjegyzés:
A malloc egy típusfüggetlen mutatót ad vissza (void* típus), amit ahhoz
egy eltárolhassunk egy típusos mutatóban (pl. int* típus) át kell alakítani.
Ehhez típuskonverzióra van szükség, ami a memóriacímen magán nem
változtat semmit, csak azon hogy a rendszer hogy kezelje mutatóként.
Erre az átalakításra főként C++ alatt van szükség, sima C esetében ez
elhanyagolható (lásd alább). Ez abból adódik hogy a C++ egy erősen típusos
nyelv, és ilyen árnyalatnyi különbségeken is fennakad, míg a sima C nem.
int* tomb = malloc(elemszam * sizeof(int)); //csak sima C alatt! |
Fontos:
A malloc NULL értéket is visszaadhat. Mielőtt
használnánk az általa elkészített változót, mindig ellenőrizzük le hogy a
mutatónk értéke nem egyezik-e meg a NULL értékkel - különben a programunk
összeomolhat.
Megjegyzés:
Egyes helyeken a malloc helyett a calloc használatát
javasolják. Lényegében mindegy hogy melyiket választjuk, a callocnál két
paraméter van, az első az elemszám, a második egy elem mérete - annyiban
különbözik hogy nem mi szorozzuk össze ezt a két számot.
Én személyesen azért preferálom a malloc használatát, mert a callocnál
szükséges az #include <stdlib.h> sor, is azaz egy plusz függvénytárat bele kell
fordítani a programba. Ha más ok miatt nincs szükség erre a függvénytárra, akkor
inkább használom a szorzásjelet a mallocnál - ezzel is kicsit optimalizálva a
programot.
1b, realloc
void* realloc(void* ptr, unsigned int new_size);
A realloc egy már meglévő, kézzel lefoglalt memóriaterületet méretez át, egy megadott új méretre, miközben az ott tárolt adatokat megtartja.
ptr: a kézzel lefoglalt
memóriaterület memóriacíme
new_size: az új méret byteokban
Mivel bizonyos esetekben ez nem vitelezhető ki ugyanazon a címen, ezért a realloc visszatérési értéke az átméretezett terület címe, ha sikeres a művelet. Egyéb esetben NULL.
Használata:
int
elemszam =
25; int* tomb = (int*)malloc(elemszam * sizeof(int)); ... elemszam = 30; tomb = (int*)realloc(tomb, elemszam * sizeof(int)); |
Megjegyzés:
A fentebb leírt egyszerűsítésekkel, sima C alatt itt is lehet élni.
Fontos:
A realloc NULL értéket is visszaadhat. Mielőtt
használnánk az általa elkészített változót, mindig ellenőrizzük le hogy a
mutatónk értéke nem egyezik-e meg a NULL értékkel - különben a programunk
összeomolhat.
1c, free
void free(void* ptr);
Egy kézzel lefoglalt memóriaterületet szabadít fel, ha már nincs rá szükség.
ptr: a kézzel lefoglalt memóriaterület memóriacíme
Az eljárásnak nincs visszatérési értéke.
Használata:
int
elemszam =
25; int* tomb = (int*)malloc(elemszam * sizeof(int)); ... free(tomb); |
Fontos:
A free után a mutató érvénytelen lesz - és ezt már
nem lehet leellenőrizni. A további hibák elkerülése végett, a free használata
után, érdemes a mutatót átírni NULL értékre, így aztán egy sima feltételes
utasítás ellenőrizheti az érvényességét.
Mindig szabadítsuk fel az összes kézzel lefoglalt
változót a programból való kilépés előtt - a C/C++ ezeket nem számolja, így ha
mi nem tesszük meg, akkor automatikusan sem fog megtörténni.
1d, memset
void* memset(void* dest, char ch, unsigned int count);
A memset egy tetszőleges memóriaterületet tölt fel mintázattal. Ez a mintázat úgy áll elő hogy a ch változó értékét, count alkalommal bemásolja az adott területre.
dest: a memóriaterület címe (nem csak kézzel
lefoglalt lehet, lásd alább a példát)
ch: az a byte amiből a mintázat fog felépülni - lehet karakter,
szám, stb.
count: az hogy hányszor másolja be a ch változót - mivel a ch 1
byte, úgy is értelmezhető hogy hány byte kerüljön átírásra
A visszatérési értéke a dest másolata, ha sikeres volt a művelet. Egyéb esetben NULL.
Használata:
char szoveg[] =
"Ez egy teszt szöveg."; memset(szoveg, 'a', 6); //szoveg == "aaaaaa teszt szöveg." int elemszam = 25; int* tomb = (int*)malloc(elemszam * sizeof(int)); memset(tomb, 0, elemszam * sizeof(int)) |
Megjegyzés:
A malloc szándékosan nem ad kezdőértéket a területnek, emiatt az ott
található - más programok által otthagyott adatszemét lesz sokszor a kezdőérték.
Ha pl. azt szeretnénk hogy egy új tömb minden eleme nulla legyen, akkor a
for ciklussal való feltöltés helyett a memset lényegesebben gyorsabban feltölti
az területet nullákkal.
Fontos:
Ha a count nagyobb mint a terület, akkor a program
össze fog omlani (túlfutás). Szintén ez a helyzet hogyha NULL értéket
adunk meg a dest helyére.
1e, A dinamikus tömb előállítása
Most hogy láthattuk hogyan működnek ezek az függvények/eljárások, itt az ideje megnézni a gyakorlatban is hogy működnek.
Ez a legegyszerűbb példa, itt lefoglalunk egy 25 elemű tömböt (az n értéke lehetne más is - amit nem előre adunk meg), szórakozunk a tömb elemeivel, majd az egészet felszabadítjuk.
int
n =
25; int* tomb = (int*)malloc(n * sizeof(int)); tomb[0] = 12; ... tomb[n - 1] = -15; free(tomb); |
A példából látható hogy az indexelés továbbra is [0..(n-1)] határok között mozog.
Habár ez javallott volt, a malloc sikerességének ellenőrzése elmaradt. Ez megtörténhet akkor, ha egészen kis elemszámú tömbökkel dolgozunk - egy mai gépben jó eséllyel van 25 * 4 = 100 byte ~ 0,1 kByte szabad memória.
Egy olyan esetben, ahol az elemszám nagyobb már mindenféleképpen kell védelem:
int
n =
2500; int* tomb = (int*)malloc(n * sizeof(int)); if (tomb != NULL) { tomb[0] = 12; ... tomb[2499] = -15; free(tomb); } |
A következő kérdés az lehet, hogy mi van ha az n értéke megváltozik a foglalás után?
A válasz hogy semmi. A tömböt akkor foglaltuk le, mikor az n értéke 2500 volt, vagyis attól még hogy átírjuk 3000-re, a tömb mérete nem fog megváltozni automatikusan - de megváltoztatható:
int
n =
2500; int* tomb = (int*)malloc(n * sizeof(int)); if (tomb != NULL) { tomb[0] = 12; ... tomb[2499] = -15; n = 3000; tomb = (int*)realloc(tomb, n * sizeof(int)); if (tomb != NULL) { tomb[2999] = 0; free(tomb); } } |
Természetesen itt is ellenőrizni kell az új mutató helyességét, hiszen 3000 elemnél már előfordulhat hogy nem fog sikerülni az újrafoglalás.
Végül pedig, előfordulhat hogy egy olyan programot szeretnénk készíteni, ami tömbbe summáz bizonyoz értékeket.
Ekkor fontos lenne hogy kezdőértéke legyen, de ha minden elemre begépeljük hogy tomb[x] = 0 az nem hatékony - ezért ismerkedtünk meg a memsettel.
int
n =
10,
i, j; int* tomb = (int*)malloc(n * sizeof(int)); memset(tomb, 0, n * sizeof(int)); for (i = 0; i < n; i++) for (j = i; j < n; j++) tomb[i] += (j + 1); free(tomb); |
A felszabadítás előtt ezen kód hatására a tömb állapota:
tomb[0] = 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
tomb[1] = 0 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
tomb[2] = 0 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
tomb[3] = 0 + 4 + 5 + 6 + 7 + 8 + 9 + 10;
tomb[4] = 0 + 5 + 6 + 7 + 8 + 9 + 10;
tomb[5] = 0 + 6 + 7 + 8 + 9 + 10;
tomb[6] = 0 + 7 + 8 + 9 + 10;
tomb[7] = 0 + 8 + 9 + 10;
tomb[8] = 0 + 9 + 10;
tomb[9] = 0 + 10;
A memset hatása itt jelentős, hiányában a tömb értékei mind véletlenszerűek lennének - hisz nem a nulla kezdőértékhez adnánk a számokat, hanem valami zagyvasághoz.
2, A direkt struktúra alapú dinamikus tömb
Ez a témakör nem egy bonyolult dolog - lényegében ezúttal számok helyett struktúrákat pakolunk egymás után.
Ezeknek később a fájlkezelés során lehet nagy jelentősége, főként a direkt verziónak.
typedef
struct { unsigned int eletkor; char nev[50]; char lakcim[200]; float kedvenc_szam; int szeme_szine, haja_szine, csaladi_allapot; } TSzemely; |
A séma ugyanaz mint tömbnél, de most int helyett TSzemely lesz.
int
n =
35; TSzemely* 3b_osztaly = (TSzemely*)malloc(n * sizeof(TSzemely)); if (3b_osztaly != NULL) { 3b_osztaly[0].eletkor = 6; ... 3b_osztaly[34].nev = "Karcsi"; free(tomb); } |
Ezt nevezzük direkt struktúra alapú tömbnek, mivel a struktúráknak annyi hely kell folytonosan a memóriában, amekkora a struktúrák mérete. Egy struktúra mérete jelen esetben:
sizeof(unsigned int) + sizeof(char) * 50 + sizeof(char) * 200
+ sizeof(float) + 3 * sizeof(int) =
4 + 50 + 200 + 4 + 12 =
262 byte
Ezáltal a teljes 35 elemű tömb mérete 35 * 262 = 9 170 byte. Ez nem sok egy mai gépen, de ha mondjuk egy egész iskola tanulóit vennénk számba, és 5 000 elemet vennénk fel = 1 310 000 byte ~ 1,3 MB.
Ez már lehet akkora méret hogy a malloc elhasal - hiszen ez folytonosan, megszakítás nélkül kéne. Ilyenkor fog bejönni az indirekt módszer (később).
Természetesen kis esetekben, mint ez a 35 simán jó ez a megoldás is, majd ezt később látni fogjuk hogy az indirekt módszer sokkal bonyolultabb.
3, A dinamikus mátrix
Mint az a statikus tömböknél ki volt fejtve, nagyjából úgy gondolnánk hogy egy n * k * sizeof(int) méretű tömb túlindexelése megoldás lehet. A valóságban a megoldás ennél egy kicsit komplexebb.
A két dimenziós mátrix lehet téglalap alakú, de nem kötelező hogy az legyen. A készítésekor az ún. "tömbben a tömb" eljárással fogunk dolgozni, ahol készítünk egy "tömbökből álló tömböt" - ami egy oszlopvektor, és ezen belül lesznek a sorvektorokat jelképező tömbök, amiknek a hossza nem kell hogy megegyezzen.
Íme egy standard, téglalapos mátrix dinamikus kivitele [7x6] (pl. int[7][6]):
Illetve egy változó sorhosszú (szabadalakos) dinamikus mátrix kivitele [7x(3..10)]:
A statikus mátrixokkal ellentétben, itt valóban megvalósul a memóriabeli rendezettség, emiatt a túlindexelésre nincs lehetőség.
Amint az a képről is látható mindig az oszlopvektorból indul ki minden, vagyis először ennek a lefoglalását kell megkezdeni. Miután az oszlopvektor készen van nekiállhatunk a sorok előállításának - ez történhet fix mérettel (téglalapos mátrix), vagy algoritmus által (változó sorhosszú mátrix).
Természetesen a létrehozásnak nem ez az egyetlen módja, de ez követi a statikus változatnál megszokott elrendezést.
3a, Miért kell két csillag?
Azért van szükség két csillagra, mert tömb lesz a tömbben.
Ha megnézzük hogyan készül egy mátrix statikusan azt kapjuk hogy az
int matrix[6][7];
deklaráció egy 6x7-es mátrixot ad.
Ezt a deklarációt módosítsuk úgy, hogy a sorok dinamikus tömbök legyenek - ekkor lesz 6db tömbünk - vagyis az
int* matrix[6];
deklarációra jutunk.
Most pedig mondjuk azt hogy a sorok száma sem fix - így jön ki az
int** matrix;
deklaráció.
3b, A téglalapos dinamikus mátrix lefoglalása/felszabadítása
Vizsgáljuk meg az egyszerűbb esetet, mikor téglalap alakú a mátrix.
Elsőrendben tehát kell egy oszlopvektort készíteni, amibe a sorokat lefoglaljuk - ezt sorozatos malloc utasításokkal lehet előállítani:
int
size_x
= 10,
size_y = 10,
x, y; int** matrix = (int**)malloc(size_y * sizeof(int*)); for (y = 0; y < size_y; y++) matrix[y] = (int*)malloc(size_x * sizeof(int)); matrix[0][0] = 3; matrix[9][9] = 1; |
És felszabadítani:
for
(y = 0; y < size_y;
y++) free(matrix[y]); free(matrix); |
Lényegében a felszbadítás során a sorokat szabadítjuk fel, majd végül magát az oszlopvektort miután végeztünk. Fordítva nem működne a dolog, hiszen az oszlopvektor tartalmazza a sorokat - anélkül a sorokat már nem tudnánk elérni.
A fenti példa egy egyszerűbb kivitel, ami megint csak korlátozott méretig működik. 10 * 10 = 100 elem lefoglalása történt meg ebben a példában, de mivel 10-es csoportokban, külön memóriaterületen vannak, ezért jó eséllyel elhanyagolható a védelem.
Nézzük meg egy 2 000 * 2 000 = 4 000 000 elemű mátrix dinamikus lefoglalását védelemmel:
int
size_x
= 10,
size_y = 10,
x, y, e; int** matrix = (int**)malloc(size_y * sizeof(int*)); if (matrix != NULL) { for (y = 0; y < size_y; y++) { matrix[y] = (int*)malloc(size_x * sizeof(int)); if (matrix[y] == NULL) { for (e = 0; e < y; e++) free(matrix[y]); free(matrix); matrix = NULL; break; } } } |
Ha hibába ütközik, minden addig lefoglalt sort visszafele intelligens módon felszabadít, majd végül az oszlopvektort, majd végül befejezi a ciklust, hogy ne történjen hiba.
Ez már egy komplex megoldás, erre csak nagy mátrixok esetén van szükség. A felszabadítás ugyanakkor ugyanúgy megy végbe mint a fenti példában.
Végül egy nullmátrix előállítása az alábbi módon történhet meg:
int
size_x
= 10,
size_y = 10,
x, y; int** matrix = (int**)malloc(size_y * sizeof(int*)); for (y = 0; y < size_y; y++) { matrix[y] = (int*)malloc(size_x * sizeof(int)); memset(matrix[y], 0, size_x * sizeof(int)); } matrix[0][0] += 3; matrix[9][9] += 1; |
A memset segítségével a sorokat fogjuk kinullázni - az oszlopvektor itt érdektelen, hiszen az a sorok memóriacímeit tartalmazza, kinullázásakor nem érnénk el többet a sorokat.
3c, A szabadalakos dinamikus mátrix előállítása/felszabadítása
Itt különösen fontos, hogy mindig tudjuk hogy az indexelés határai hol vannak.
Ha egy olyan sorban pl. ahol csak két elem van a harmadikra hivatkozunk, hibát kapunk, még akkor is ha van olyan másik sor ahol van harmadik elem.
int
size_y =
10,
x, y; int** matrix = (int**)malloc(size_y * sizeof(int*)); for (y = 0; y < size_y; y++) matrix[y] = (int*)malloc((y + 1) * sizeof(int)); |
A lényeg annyi hogy a size_x helyett valamilyen kifejezéssel számítunk sorhosszt, most jelen esetben size_x = y + 1.
Az így kapott mátrix képe:
[0]
[0, 1]
[0, 1, 2]
[0, 1, 2, 3]
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4, 5]
[0, 1, 2, 3, 4, 5, 6]
[0, 1, 2, 3, 4, 5, 6, 7]
[0, 1, 2, 3, 4, 5, 6, 7, 8]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Az elemek mennyisége pedig:
Ilyen esetben pl. a matrix[0][6] hibát okoz, míg a matrix[9][6] helyes lesz.
A felszabadítás ugyanaz mint a fentebbi példában, hiszen a free utasítást nem érdekli a sorok hossza, és a sorok száma pedig állandó.
for
(y = 0; y < size_y;
y++) free(matrix[y]); free(matrix); |
Egy másik szabadalakos mátrix (ehhez már kell a math.h):
int
size_y =
10,
x, y; int** matrix = (int**)malloc(size_y * sizeof(int*)); for (y = 0; y < size_y; y++) matrix[y] = (int*)malloc((sin(y * M_PI / 2) + 2) * sizeof(int)); |
Íme a képe:
[0, 1] |
![]() |
És a számok mennyisége:
Az oldalsó szélét megnézve láthatunk egy szinusz hullámot - természetesen egy ilyen mátrixnak ritkán van értelme, de ki tudja...
Fontos:
Bármilyen képlettel is állítjuk elő a sorok hosszát,
mindig győződjünk meg arról hogy a kapott érték mindig legyen nagyobb mint
nulla! A malloc nem fog sem nulla, sem negatív területet lefoglalni, és később
hibába futhatunk ezáltal.
3d, Az indirekt struktúra alapú dinamikus tömb
Képzeljük el hogy az oszlopvektorunk ezúttal nem sorvektorokra mutat, hanem struktúrákra - meg is kaptuk az indirekt struktúra alapú dinamikus tömböt.
Természetesen mivel több köze van a mátrixokhoz mint a tömbökhöz, lényegesen összetettebb lesz kivitelezni is mint a direkt verziót.
Előnye abban nyilvánul meg a struktúrák külön memóriaterületeken helyezkednek majd el, ezáltal nem muszáj nagy folytonos területeknek lennie, 5000 elem esetén sem fog elhasalni a malloc.
Az ilyen tömböknél ezen felül ki fogjuk használni a nyíl operátort is - ezért lesz indirekt ez a verzió, hiszen a struktúrához csak mutatónk van, nem érjük el közvetlenül.
int
meret =
5000,
n; TSzemely** egesz_iskola = (TSzemely**)malloc(meret * sizeof(TSzemely*)); for (n = 0; n < meret; n++) egesz_iskola[n] = (TSzemely*)malloc(meret * sizeof(TSzemely)); egesz_iskola[0]->eletkor = 12; egesz_iskola[4999]->nev = "Zsoltika"; for (n = 0; n < meret; n++) free(egesz_iskola[n]); free(egesz_iskola); |
4, A dinamikus tér
A tereknél is előállítható kocka alakú, illetve szabadalakos tér. Igazából a mátrixokra bevezetett tippeket kell továbbvinni csak, vagyis - vegyünk fel egy Z irányú lapvektort, aminek az elemei egy-egy (téglalapos/szabadalakos) mátrixra hivatkozhatnak.
A trükk most annyi, hogy az Y és X irányú összetevők is változhatnak (szabadalakos), és a Z lesz állandó.
Íme egy téglatest alakú elrendezés [3x7x6] (pl. int[3][7][6]):
Ezen megoldás elemszáma egyszerűen számítható - 3*7*6 = 126.
Továbbá egy szabadalakos elrendezés [3x(5..7)x(3..10)]:
Itt már az elemszám nem ilyen egyszerűen számítható, az egyes lapmátrixokat summázva kell összeadni az értékeket.
Mivel egy egy komplex dolog, mégis hasonlít a mátrixokra, egyetlen példa van itt - egy védelem nélküli, téglatest alakú tér lefoglalása, és felszabadítása:
int
size_x
= 10,
size_y = 10,
size_z = 10,
x, y, z; int*** ter = (int***)malloc(size_y * sizeof(int**)); for (z = 0; z < size_z; z++) { ter[z] = (int**)malloc(size_y * sizeof(int*)); for (y = 0; y < size_y; y++) ter[z][y] = (int*)malloc(size_x * sizeof(int)); } ter[0][0][0] += 3; ter[9][9][9] += 1; for (z = 0; z < size_z; z++) { for (y = 0; y < size_y; y++) free(ter[z][y]); free(ter[z]); } free(ter); |
A felszabadításnál egy kis plusz nehézség, hogy az y irányú méretet is tudni kell - szabadalaknál ez gondot okozhat, hiszen az sem feltétlenül állandó.
5, A dinamikus n-dimenziós tömb
Mint azt az előzőekben láthattuk az alakzatok egymásra épülnek, vagyis egy 4D-s megoldásnál a terekből álló tömb lenne a megoldás, illetve 5D-nél a 4D-ből álló tömb stb...
Íme egy n-dimenziós szögletes tömböt foglaló rekurziv függvény:
void*
nd_tomb(unsigned int*
sizes,
const unsigned int
dimensions, const
unsigned int element_size) { int n, size_n = sizes[dimensions - 1]; void** tomb; if (dimensions = 1) return malloc(size_n * element_size); else { tomb = (void**)malloc(size_n * sizeof(void*)); for (n = 0; n < size_n; n++) tomb[n] = nd_tomb(sizes, dimensions - 1, element_size); return (void*)tomb; } } |
Ez a függvény rekurzió által tetszőleges n-dimenziós tömböt fog lefoglalni (védelem nélkül, tehát csak kis méretű tömbökhöz alkamas).
sizes: egy tömb ami tartalmazza az adott
dimenzióhoz tartozó méretet, pl. 3D esetén [0 = X, 1 = Y, 2 = Z]
dimensions: a méreteket tartalmazó tömb hossza, pl. 3D esetén 3;
(amúgy meg > 0)
element_size: egyetlen elem mérete: pl. int esetén sizeof(int)
A visszatérési érték az n-dimenziós tömb, ha elég kevés iránnyal, és kis méretekkel dolgoztunk. Egyéb esetben a program összeomlik, a NULL értékű malloc kezeletlensége miatt. (a védett verzió jelentősen több kóddal járna, már egy mátrix esetében is kb. 2x-es volt a nagysága).
És végül pedig egy eljárás ami felszabadítja az n-dimenziós tömböt:
void
nd_free(void*
ptr,
unsigned int* sizes,
const unsigned int
dimensions) { int n, size_n = sizes[dimensions - 1]; void** tomb = (void**)ptr; if (dimensions > 1) for (n = 0; n < size_n; n++) nd_free(tomb[n], sizes, dimensions - 1); free(ptr); return; } |
ptr: a sikeresen lefoglalt n-dimenziós tömb
memóriacíme
sizes: azok a méretek amivel a tömb le lett foglalva
dimensions: a méreteket tartalmazó tömb hossza (> 0)
Az eljárásnak nincs visszatérési értéke.
A függvények használatára is van egy példa, íme:
int
5d_meret[] = {5,
4,
3,
5,
2};
//600 elem int***** 5d_tomb = (int*****)nd_tomb(5d_meret, 5, sizeof(int)); 5d_tomb[0][0][0][0][0] = 2; //első elem 5d_tomb[1][4][2][3][4] = 5; //utolsó elem nd_free(5d_tomb, 5d_meret, 5); |
Természetesen ez az utasításpár alkalmas tömb, mátrix, illetve tér lefoglalására is.
Ugyanakkor egy tömb lefoglalása ezek által elég értelmetlennek tűnhet - hiszen tömbben kell megadni azt az egy méretet amit le kell foglalni, de képes rá.
Arra kell odafigyelni a használatakor, amint az a példából látható, hogy a legnagyobb elem koordinátái megfordulnak (a hosszokat tartalmazó tömböt pl.{X, Y, Z} alakban kell megadni, de ekkor indexelni már [z][y][x] alakban kell).
Ezen felül, a beadandó feladatokhoz is remekül használhatóak, sőt jól terjeszthető utasítások ezek ha pl. a tanult módon .H + .C fájlt készítünk belőlük. Sok beadandó mátrixfoglalásokat kér, viszont ez a két utasítás rövidre zárja a témát - minimális kóddal lesz megvalósító a feladat.
6, A C++ egyszerűsítései
A C++ nevében azért van két plusz jel, mert többet tud mint idősebb társa. Ezen leírás példái csakis C++ nyelv alatt fognak működni. Ahhoz hogy ezekkel az egyszerűsítésekkel tudjunk dolgozni, szükséges a fentebbi ismeretek elsajátítása.
6a, A new/delete utasítás
Mindez szép és jó amivel eddig foglalkoztunk, de a C++ nyújt ezekre a problémákra gyorsabb megoldást is:
A new utasítás a malloc szerepét tudja kiváltani lényegében, lefoglal egy n elemű tömböt. Íme:
int*
tomb = new int[20]; tomb[24] = 50; |
A delete utasítás pedig a free helyére lép:
delete [] tomb; |
Használatukhoz kell a
#include <new> |
sor is. Ezek segítségével a korábbi példák is újraértelmezhetőek.
A jó hír hogy egyfajta védelmet is kapunk, ugyanis a program hiba esetén kivételt dob fel (try-catch blokk később), amit okosan lehet kezelni.
A hátránya az ilyen tömböknek hogy átméretezni nem lehet őket, mint a fenti megoldásnál a realloc segítségével tettünk.
6b, std::vector
Az std::vector egy objektum (elöljáróban annyi hogy egy olyan struktúra aminek utasításai is vannak). A lényege hogy a tömböket helyettesíti ki, nagyon okos módon.
Egyrészt mindent amit a tömb tudott, azt tudja ez is, - az átméretezést is beleértve.
Használatához kell a
#include <vector> |
sor is.
Nézzük meg egy egyszerű tömböt hogyan helyettesít ki:
std::vector<int>
tomb(25); tomb[24] = 50; |
Felszabadítására nincs szükség, a rendszer elvégzi magától.
Ha több/kevesebb elemre van szükségünk:
tomb.resize(60); tomb[59] = 50; |
A legszebb hogy minden védve történik itt is, kivételek által.
Az std::vector ennél sokkal többet is tud, pl. elemek beszúrása, egy adott indexre. Ehhez tömböknél kellett volna egy realloc, illetve át kellett volna helyezni az elemeket, eggyel odébb.
tomb.insert(10, -5); |
Ennek a sornak a hatására, a tömb most már 61 elemű, és a tomb[10] == -5
Szintén extra tudás az elem törlése is, ez is komoly fejfájást okozhat egy egyszerű tömbnél:
tomb.erase(10); |
Akár egész szakaszok is törölhetőek (10..14-ig töröl mindent):
tomb.erase(10, 15); |
Ha pedig csak hozzá akarunk adni egy elemet a végére:
tomb.push_back(-7); |
Ha valami oknál fogva úgy lenne rá szükségünk mint egy sima tömbre:
int* sima_tomb = &tomb[0]; |
Az std::vector alkalmas mátrixok létrehozására is, manipulátor műveleteivel pedig a szabadalak is könnyen kezelhető.
std::vector<std::vector<int>>
matrix;
//nem kötelező a méret matrix.resize(7); //7x10 int y; for (y = 0; y < 7; y++) matrix[y].resize(10); matrix[0][0] = 7; //legkisebb matrix[6][9] = 4; //legnagyobb matrix[6].erase(9); //az utolsó sor utolsó elemének törlése matrix.erase(6); //az egész utolsó sor törlése |