Az ArrayList funkcionalitást a C-ben a következőképpen valósítottam meg:
#include <stdlib.h> #include <assert.h> #include "ArrayList.h" struct _arraylist { size_t size; void ** data; }; struct _arraylist *arraylist_create() { /* Allocate Memory */ struct _arraylist *list = malloc(sizeof(struct _arraylist)); assert(list != NULL); list->size = 0; list->data = calloc(2, sizeof(void *)); assert(list->data != NULL); list->data[0] = NULL; return list; } void arraylist_setdata(struct _arraylist *list, void ** data, int max, int clear_data) { /* Sets the internal array of the arraylist */ clear_data ? arraylist_clear(list) : NULL; list->data = data; list->size = max; } void arraylist_add(struct _arraylist *list, void *elem) { /* Adds one element of generic pointer type to the internal array */ void ** new_data = realloc(list->data, arraylist_getsizeof(list)); assert(new_data != NULL); new_data[list->size] = elem; arraylist_setdata(list, new_data, list->size + 1, 0); } void *arraylist_get(struct _arraylist *list, int index) { /* Gets an member of the array at an index */ return list->data[index]; } size_t arraylist_getsizeof(struct _arraylist *list) { /* Returns the size of the internal array in memory */ return sizeof(*list->data); } size_t arraylist_getsize(struct _arraylist *list) { /* Returns the number of elements in the arraylist */ return list->size; } void arraylist_remove(struct _arraylist *list, int index, int freeit) { /* Removes one element at and index */ if (index > list->size - 1) return; if (list->size == 1) { arraylist_clear(list); return; } if (freeit) free(arraylist_get(list, index)); for ( int i = index; i < list->size; ++i ) { if (i == list->size - 1) list->data[i] = NULL; else list->data[i] = list->data[i + 1]; } void ** new_data = realloc(list->data, arraylist_getsizeof(list)); --list->size; assert(new_data != NULL); arraylist_setdata(list, new_data, list->size, 0); } void arraylist_clear(struct _arraylist *list) { /* Clears the internal array */ list->size = 0; free(list->data); list->data = NULL; } void arraylist_deallocate(struct _arraylist *list) { /* De-allocates the arraylist from memory No usage of the arraylist is allowed after this function call */ if (list->data != NULL) free(list->data); free(list); } int arraylist_getindex(struct _arraylist *list, void *elem) { /* Looks for elem in list and returns the index or -1 if not found */ for(int i = 0; i < list->size; ++i) if (elem == arraylist_get(list, i)) return i; return -1; }
I “Ezt így tesztelem:
#include <stdio.h> #include "ArrayList.h" int main(int argc, char const *argv[]) { ArrayList *list = arraylist_create(); int i; for(i = 0; i < 100; ++i) arraylist_add(list, &i); for(i = 0; i < 100; ++i) printf("i: %d\n", *(int *)arraylist_get(list, i)); for(i = 0; i < 100; ++i) arraylist_remove(list, i, 0); arraylist_deallocate(list); return 0; }
Remekül működik, bár realloc
néha összeomlik a program. javítson ezen?
Megjegyzések
Válasz
Először egy szó a elnevezésről:
A név, amelyet kiválasztott a te típusod, _arraylist
rossz név a könyvtár interfész típusának. A _
kezdetű nevek nem kellemesek a felhasználói kódban. Általában a könyvtár belsejében használják őket. A jobb nevek a következők lehetnek: ArrayList
vagy array_list
.
Valójában a használati példában a ArrayList
. Ez azt jelenti, hogy az itt nem szereplő fejlécben van valami ilyesmi?
typedef _arraylist ArrayList;
Ha átlátszatlan típust definiáltál a fejlécben, mint fent, ez jó gyakorlat lenne. De akkor ne használjon a _arraylist
hivatkozást a kódban. Az összetévesztés elkerülése érdekében mindig használja a typedef “d nevet.
A függvénynév előtagnak pontosan a típus nevét is követnie kell, ezért a ArrayList
esetén az összes függvényt meg kell adni. a ArrayList_
előtagot, pl .:
ArrayList * ArrayList_create();
Azt is javasoljuk, hogy kerülje az tightlypacked
nevek, például a arraylist_getsize()
címkéknél. Ha aláhúzást adunk a szavak elkülönítéséhez, azok sokkal olvashatóbbak. Például: ArrayList_get_size()
.
A memóriával kapcsolatos problémák :
A arraylist_create()
:
struct _arraylist *arraylist_create() { struct _arraylist *list = malloc(sizeof(struct _arraylist)); assert(list != NULL); list->size = 0; list->data = calloc(2, sizeof(void *)); assert(list->data != NULL); list->data[0] = NULL; return list; }
Első dolog, ami szokatlan, ezek az állítások. Az állítások nem alkalmasak a memóriaelosztási hiba kezelésére. , általában le vannak tiltva a kiadás buildjeinél, így kiadáskor, ha esetleg elfogyna a memória, a program csak csendesen összeomlik. Ebben az esetben valószínűleg vissza kell adnia egy NULL
-t (esetleg be kell jelentkeznie a stderr
címre is), és hagynia kell, hogy a hívó kezelje ezt a hibát, ahogy látja fit.
A második probléma itt a calloc()
. 2 érvénytelen mutatót oszt ki, azonban a size
értéke nulla. Nem igazán értem ennek a lényegét. Mivel a struktúrája inkább hasonlít és tömbök tömbje, majd egy lista, akkor azt kell tennie, hogy elosztja a mutató tömböt valamilyen előre definiált alapértelmezett méretűvel, majd szükség szerint kiosztja az egyes tömböket. igény szerint mutató tömb. Hogyan kell kinéznie arraylist_create()
:
ArrayList * ArrayList_create() { ArrayList *list = malloc(sizeof *list); if (list == NULL) { return NULL; } list->size = 0; list->data = calloc(INITIAL_BASE_ARRAY_SIZE, sizeof(void *)); if (list->data == NULL) { free(list); // Don"t leek memory here! return NULL; } return list; }
Egy másik nagy memóriaprobléma az állandó az újrafoglalásokat a arraylist_add()
és a arraylist_remove()
végezte.
Az eltávolítás nem csökkenti a sorrendet. Tartsa ezt a helyet körül, ha a tömb a jövőben újra növekszik. Felvehet egy kifejezett módszert arra, hogy a felhasználó szükség esetén csökkentse a tárhelyet (a la std::vector::shrink_to_fit()
).
Hozzáadás a A tömb amortizált-állandó időben futtatható, ha előre kiosztja a tárhelyet a kért méretnél. (Ismét az STL vector
ihlette).
sizeof
hiba :
Ez nem adja vissza azt, amire számított:
size_t arraylist_getsizeof(struct _arraylist *list) { /* Returns the size of the internal array in memory */ return sizeof(*list->data); }
A sizeof
operátor mindig visszaadja az alkalmazott típus méretét. Nem tud következtetni egy mutató által mutatott tömb nagyságára, mert ez fordítási idejű művelet. A arraylist_getsizeof()
mindig ugyanazt az értéket adja vissza, az érvénytelen mutató méretét, amely az architektúrától függően 4 vagy 8 lesz.
Állítások használatával ellenőrizze az invariánsokat :
assert
Minden funkció div id = “1979ffde07”>
paramétere érvényes. Ez minden függvény előfeltétele, érvényesArrayList
példány nélkül nem tudnak működni, ezért állítsd be, hogy amint a függvény belép.
Vegyes :
Nem kell ellenőriznie, hogy a mutató null, mielőtt felszabadítaná . A arraylist_deallocate()
mezőben a if (list->data != NULL)
ellenőrzés nem szükséges.
arraylist_deallocate
szimmetrikusabb lenne a arraylist_create
vel, ha arraylist_destroy
.
Megjegyzések
- Hogyan megfelelően ellenőrizhetem, hogy van-e érvényes
ArrayList
példányom?Eddig egy olyan makró áll rendelkezésemre, amely egystruct _arraylist
fájlhoz hozzáadott új mező konkrét értékét ellenőrzi. Mivel a struct deklaráció nem ‘ t érhető el a fejlécben, aArrayList
felület felhasználói nem férhetnek hozzá közvetlenül egyetlen mezőhöz sem (vagyis a következők egyikét kell használniuk: a burkoló funkciók). És kifejezetten nem adtam ‘ semmilyen nyomot erről a mezőről. - @AmrAyman, az érvényes definíciójától függ, de azt mondanám, hogy a minimális érvényesítés ellenőrizze, hogy a
ArrayList
mutató nem null-e, és hogy aArrayList::data
szintén nem null-e. Ellenőrizheti azt is, hogy adata
összes tömbje nem null-e:assert( list->data[i] != NULL );
Válasz
Beszéljünk a teljesítményről
Mi van, ha nagyon gyakran kell használni a listáját?
Nézzük meg közelebbről a arraylist_add
függvényt; ha szükségem van egy 1 millió bájtos listára, amely 1 MB, akkor átcsoportosítja a data
struct tag milliószor.
Ez a lista legalsó része!
Javaslatok
Rendeljen memóriát darabokra , pl., a C ++ std::vector
növekvő méretű csonkokat használ, a std::vector
jelenlegi méretétől függően.
Ez megnő néhányszor előadni új elemek hozzáadása céljából.
Beszéljünk a kódról a jelenlegi állapotban
Próbáljon meg valami elegáns, de egyszerű programfolyamatot megvalósítani.
Hozzon létre értéktípust (int) ArrayList, amely helyette darabokra osztja a memóriát csoportosítsa át a tömböt, és adjon hozzá néhány viselkedést a motorháztető alatt. Úgy értem, hogy a darabok listája, akkor is kezelnie kell.
Íme a megoldásom azzal a példával, hogy adatcsomókat használok minden csomóponthoz az átcsoportosítás helyett. csomópontok. Különböző chunck méretű lehet a legjobb az egyik célra: írás, hosszú tömbök olvasása; r \ w rövid tömbök; elemek eltávolítása; stb.
#include <stdio.h> #include <stdlib.h> typedef struct ArrayList ArrayList; typedef ArrayList* ArrayListPtr; struct ArrayList { size_t capacity; size_t size; int *data; ArrayListPtr parent; ArrayListPtr child; }; const size_t ARRAY_LIST_CHUNCK_SIZE = 64; ArrayListPtr array_list_create_with_parent_and_chunck_size(ArrayListPtr parent, size_t chunck_size) { ArrayListPtr result = (ArrayListPtr)calloc(sizeof(ArrayList), 1); result->parent = parent; result->capacity = chunck_size; result->data = (int*)malloc(sizeof(int) * chunck_size); return result; } ArrayListPtr array_list_create_with_parent(ArrayListPtr parent) { return array_list_create_with_parent_and_chunck_size( parent, ARRAY_LIST_CHUNCK_SIZE ); } ArrayListPtr array_list_create() { return array_list_create_with_parent_and_chunck_size( NULL, ARRAY_LIST_CHUNCK_SIZE ); } void array_list_push_back(ArrayListPtr list, int value) { if (list->size >= list->capacity) { if (!list->child) { list->child = array_list_create_with_parent(list); } array_list_push_back(list->child, value); } else { list->data[list->size++] = value; } } int* array_list_get_value_by_index(ArrayListPtr list, size_t index) { if (index >= list->capacity || index >= list->size) { if (list->child) { return array_list_get_value_by_index(list->child, index - list->size); } else { return NULL; } } return list->data + index; } int main(int argc, char *argv[]) { ArrayListPtr list = array_list_create(); for (int i = 0; i < 100*1000; ++i) { array_list_push_back(list, i); } size_t test[] = {0,1,2,3,4,5,6,7,8,9,10,11,12,31,32,33,63,64,65,999,1000}; for (int i = 0; i < sizeof(test) / sizeof(size_t); ++i) { int* result = array_list_get_value_by_index(list, test[i]); if (result) { printf("list[%ld] = %d\n", test[i], *result); } else { printf("Can"t get value by index %ld\n", test[i]); } } }
Megjegyzések
- Tisztelem az érdeklődését. Ez azonban C, nem C ++. Ha C ++ lenne, akkor csak vektorokat használnék erre.
- @AmrAyman, ellenőrizze
- Ez ‘ lenyűgöző! De szeretnék egy tömblistát, nem pedig linkelt listát. Bár a linkelt lista megvalósítása itt fejlettebb, mint a normál struktúrás megvalósítás, a glampert megoldotta a problémát.
- A teljesítménynövekedésről. Nincs igazán olyan sok ‘: A megvalósításom a kupacra támaszkodik, általában azért, mert tömbre támaszkodik; A tiéd nagyban függ a rekurziótól, és ez ‘ természetes, mert ‘ csomópontokra támaszkodik. Ezenkívül a lista felszabadítása sok lassú lenne viszonylag lassan, mert ‘ vagy rekurziót használ (ami tényleg alacsony a teljesítményen), vagy meglehetősen bonyolult míg a hurok ..
Válasz
A mások által nem említett probléma az, hogy a tesztje nem működik. Úgy tűnik, hogy működik, de a valóságban nem. Ha értékeket ad hozzá a listához, átadja a i
változó címét:
arraylist_add(list, &i);
És arraylist_add
csak elmenti az átadott értéket (ahogy kell):
void arraylist_add(struct arraylist *list, void *elem) { .... new_data[list->size] = elem;
Tehát ha egyszer áthúzódott az i = 0. .99 a listában csak az i címe van 100-szor. Amikor visszaolvassa az adatokat, újra a i
ciklusváltozót használja, és az értékét 0..99 értékre módosítja, és a kinyomtatott érték jól néz ki. De valójában csak azt látja, hogy a ciklusváltozó értékét a hurok módosítja.
Ha nem hiszed, nyomtass ki bármilyen rögzített tömb bejegyzést, például 50-et, mint a következőben:
printf("i: %d\n", *(int *)arraylist_get(list, 50));
kinyomtat 100 (vagy bármi is legyen az i értéke jelenleg).
Ehelyett a valós értéket kell tárolnia:
arraylist_add(list, (void*) i);
és a nyomtatáshoz ki kell adnod az értéket arra a típusra, amelyik akkor volt, amikor belépett:
printf("i: %d\n", (int)arraylist_get(list, t));
Sok más probléma van a kóddal, amint mások megjegyezték . A arraylist_setdata
eszköz minden módosítás elvégzésének alapja meghibásodott. Helytelen az átcsoportosítás minden változásnál – realloc
drága. És A lista alapötlete, amely úgy tárol dolgokat, hogy úgy tesz, mintha void*
lenne, számomra zavaró és rossz ötletnek tűnik.
Megjegyzések
- Nos, lehet, hogy nem veszi észre, de ez ‘ pontosan olyan, amit tesztelni szerettem volna. Hogy a mutatókat tárolják és lekérik helyesen a funkcióburkolatokon keresztül ..
- A dolgok
void *
tárolása nem ‘ valójában olyan rossz, mint amilyennek látszik.Gondoljon csak bele:void *
egyszerűen eltárol egy memóriacímet, amelyet nem érdekel a div tárolt érték típusa. ‘ Röviden: a tömb csak állítólag memóriacímeket tárol, és ez ‘ gyakorlatilag az egyetlen módja annak, hogy C egyetlen tömbben kezelje a különféle típusokat. - A
realloc
névről egyetértek veled, de nem tudtam ‘ megtalálni a dinamikus tömb. Mindenesetre követtem a glampert ‘ tanácsát, hogy csomagoljak egy speciális függvényt, ashrink_to_fit
függvényt. - Úgy képzeltem, hogy változó méretű skaláris adatokat próbál meg menteni egy
void*
fájlban való tárolással (ehhez különféle emberek küldtek kódot). Ha valóban mutatókat szeretett volna tárolni, akkor jobb teszt lenne, ha számos különböző mutatót tárolna ismert sorrendben, és ellenőrizné, hogy ugyanabban a sorrendben kapja-e vissza őket – ahelyett, hogy ugyanazokat mentené mutató 100-szor. A mutatók tárolásával az a probléma, hogy a mutatott objektumnak állandónak kell lennie a tömbben lévő címe fennállásának élete során. A void * ellenére természetesen nem keverhet össze típusokat egy tömbön belül. - Csak ugyanaz a dolog más módja, ahol a tömb közvetlenül a szerkezet vége után következik. Ennek a módszernek megvannak a maga problémái, ezért felejtsd el, hogy említettem.
vector
néven hivatkoznak C / C ++, nem pedigArrayList
néven a Java világból.