Mam program, który czyta „surową” listę jednostek w grze i zamierzam stworzyć tablicę zawierającą numer indeksu (int) nieokreślonej liczby jednostek, do przetwarzania różnych rzeczy. Chciałbym uniknąć używania zbyt dużej ilości pamięci lub procesora do przechowywania takich indeksów ...
Szybkim i brudnym rozwiązaniem, którego dotychczas używam, jest zadeklarowanie w głównej funkcji przetwarzania (lokalny fokus) tablicy z rozmiarem maksymalnych jednostek gry i kolejną liczbą całkowitą, aby śledzić, ile zostało dodanych do listy. To nie jest satysfakcjonujące, ponieważ każda lista zawiera ponad 3000 tablic, co nie jest tak dużo, ale wydaje się marnotrawstwem, ponieważ będę mógł użyć rozwiązania dla 6-7 list dla różnych funkcji.
Nie znalazłem żadnych rozwiązań specyficznych dla C (nie C ++ lub C #), aby to osiągnąć. Potrafię używać wskaźników, ale trochę boję się ich używać (chyba że jest to jedyny możliwy sposób).
Tablice nie opuszczają zakresu funkcji lokalnej (mają być przekazane do funkcji, a następnie odrzucone), na wypadek, gdyby coś zmieniło.
Jeśli wskaźniki są jedynym rozwiązaniem, jak mogę je śledzić, aby uniknąć wycieków?
źródło
Odpowiedzi:
Jeśli potrzebujesz tablicy dynamicznej, nie możesz zmienić znaczenia wskaźników. Dlaczego się boisz? Nie gryzą (o ile jesteś ostrożny). W C nie ma wbudowanej tablicy dynamicznej, po prostu musisz ją napisać samodzielnie. W C ++ możesz użyć wbudowanej
std::vector
klasy. C # i prawie każdy inny język wysokiego poziomu również ma podobną klasę, która zarządza tablicami dynamicznymi.Jeśli planujesz napisać własną, oto od czego zacząć: większość implementacji tablic dynamicznych działa od początku od tablicy o pewnym (małym) domyślnym rozmiarze, a gdy zabraknie miejsca podczas dodawania nowego elementu, podwajaj rozmiar tablicy. Jak widać na poniższym przykładzie, nie jest to wcale trudne: (pominąłem kontrole bezpieczeństwa pod kątem zwięzłości)
Korzystanie z niego jest równie proste:
źródło
removeArray
Metoda, która pozbywa się ostatniego elementu będzie również schludny. Jeśli na to pozwolisz, dodam to do twojego przykładowego kodu.Przychodzi mi do głowy kilka opcji.
array[100]
bez konieczności wcześniejszego przejścia1-99
. I może nie być dla ciebie tak przydatne.Trudno powiedzieć, która opcja byłaby najlepsza w Twojej sytuacji. Samo utworzenie dużej tablicy jest oczywiście jednym z najłatwiejszych rozwiązań i nie powinno sprawiać wielu problemów, chyba że jest naprawdę duża.
źródło
realloc
z # 3 - przydziel tablicę o normalnym rozmiarze, a następnie powiększaj ją, gdy się skończy.realloc
w razie potrzeby zajmie się kopiowaniem Twoich danych. Jeśli chodzi o pytanie OP dotyczące zarządzania pamięcią, wystarczymalloc
raz na początku,free
raz na końcu i zarealloc
każdym razem, gdy zabraknie miejsca. Nie jest aż tak źle.7 * 3264 * 32 bit
brzmi jak91.39 kilobytes
. Nie tak bardzo w dzisiejszych czasach;)realloc
powróciNULL
:a->array = (int *)realloc(a->array, a->size * sizeof(int));
... Może najlepiej byłoby napisać jako:int *temp = realloc(a->array, a->size * sizeof *a->array); a->array = temp;
... W ten sposób byłoby oczywiste, że cokolwiek się wydarzy, musi się wydarzyć wcześniejNULL
wartość jest przypisany doa->array
(jeśli to w ogóle).Podobnie jak w przypadku wszystkiego, co na początku wydaje się straszniejsze niż później, najlepszym sposobem na przezwyciężenie początkowego strachu jest zanurzenie się w niewygodzie nieznanego ! W końcu jest to momentami, których uczymy się najwięcej.
Niestety istnieją ograniczenia. Podczas gdy nadal uczysz się korzystać z funkcji, nie powinieneś na przykład przyjmować roli nauczyciela. Często czytam odpowiedzi od tych, którzy pozornie nie wiedzą, jak używać
realloc
(tj . Aktualnie akceptowana odpowiedź! ), Mówiąc innym, jak używać jej nieprawidłowo, czasami pod pozorem, że pominęli obsługę błędów , mimo że jest to częsta pułapka co wymaga wzmianki. Oto odpowiedź wyjaśniająca, jakrealloc
prawidłowo używać . Zwróć uwagę, że odpowiedzią jest przechowywanie zwracanej wartości w innej zmiennej w celu sprawdzenia błędów.Za każdym razem, gdy wywołujesz funkcję i za każdym razem, gdy używasz tablicy, używasz wskaźnika. Konwersje zachodzą niejawnie, co powinno być jeszcze bardziej przerażające, ponieważ to rzeczy, których nie widzimy, często powodują najwięcej problemów. Na przykład wycieki pamięci ...
Operatory tablicowe są operatorami wskaźników.
array[x]
jest naprawdę skrótem do*(array + x)
, który można podzielić na:*
i(array + x)
. Najprawdopodobniej*
to właśnie cię dezorientuje. Możemy dodatkowo wyeliminować dodatek od problemu zakładającx
się0
, a tym samymarray[0]
staje się*array
ponieważ dodanie0
nie zmieni wartości ...... i dlatego widzimy, że
*array
jest to równoważnearray[0]
. Możesz użyć jednego, gdzie chcesz użyć drugiego i odwrotnie. Operatory tablicowe są operatorami wskaźników.malloc
,realloc
a przyjaciele nie wymyślają pojęcia wskaźnika, którego używałeś przez cały czas; używają tego jedynie do zaimplementowania innej funkcji, która jest inną formą czasu przechowywania, najbardziej odpowiednią, gdy chcesz drastycznych, dynamicznych zmian rozmiaru .Szkoda, że obecnie akceptowana odpowiedź jest również sprzeczna z innymi, bardzo dobrze ugruntowanymi radami na temat StackOverflow , a jednocześnie traci okazję do wprowadzenia mało znanej funkcji, która świeci dokładnie w tym przypadku: elastyczna tablica członków! To właściwie dość zepsuta odpowiedź ... :(
Kiedy definiujesz swoją
struct
, zadeklaruj swoją tablicę na końcu struktury, bez górnej granicy. Na przykład:Pozwoli ci to połączyć swoją tablicę
int
w tę samą alokację co twojacount
, a powiązanie ich w ten sposób może być bardzo przydatne !sizeof (struct int_list)
będzie zachowywał się tak, jakbyvalue
miał rozmiar 0, więc poinformuje Cię o rozmiarze struktury z pustą listą . Nadal musisz dodać rozmiar przekazany do,realloc
aby określić rozmiar listy.Inną przydatną wskazówką jest zapamiętanie, że
realloc(NULL, x)
jest to odpowiednikmalloc(x)
i możemy użyć tego do uproszczenia naszego kodu. Na przykład:Powód, dla którego zdecydowałem się użyć
struct int_list **
jako pierwszego argumentu, może nie wydawać się od razu oczywisty, ale jeśli pomyślisz o drugim argumencie, wszelkie zmiany wprowadzonevalue
od wewnątrzpush_back
nie będą widoczne dla funkcji, z której wywołujemy, prawda? To samo dotyczy pierwszego argumentu i musimy być w stanie zmodyfikować naszearray
, nie tylko tutaj, ale być może także w każdej innej funkcji, do której przekazujemy ...array
zaczyna wskazywać na nic; to jest pusta lista. Inicjowanie jest tym samym, co dodawanie do niego. Na przykład:PS Pamiętaj,
free(array);
kiedy skończysz!źródło
array[x]
to naprawdę skrót do*(array + x)
, [...]" Czy na pewno ???? Zobacz opis ich różnych zachowań: eli.thegreenplace.net/2009/10/21/… .array[index]
jest rzeczywiścieptr[index]
w przebraniu ... „definicja operatora indeksem[]
jest to, żeE1[E2]
jest identyczny(*((E1)+(E2)))
” Nie można zaprzeczyć stdint main(void) { unsigned char lower[] = "abcdefghijklmnopqrstuvwxyz"; for (size_t x = 0; x < sizeof lower - 1; x++) { putchar(x[lower]); } }
... Prawdopodobnie będziesz musiał#include <stdio.h>
i<stddef.h>
... Czy widzisz, jak napisałemx[lower]
(x
będąc typem całkowitym), a nielower[x]
? Kompilator C nie przejmuje się tym, ponieważ*(lower + x)
ma taką samą wartość jak*(x + lower)
ilower[x]
jest tym pierwszym gdzie-jakx[lower]
jest drugim. Wszystkie te wyrażenia są równoważne. Wypróbuj je ... przekonaj się sam, jeśli nie możesz uwierzyć mi na słowo ...gcc
lubclang
dla całej kompilacji C, ponieważ przekonasz się, że jest tak wiele pakietów, które przyjęły funkcje C99 ...Opierając się na projekcie Matteo Furlansa , kiedy powiedział, że „ większość implementacji tablic dynamicznych działa od początku z tablicą o pewnym (małym) domyślnym rozmiarze, a gdy zabraknie miejsca podczas dodawania nowego elementu, podwaj rozmiar tablicy ”. Różnica w poniższym „ pracy w toku ” polega na tym, że rozmiar nie podwaja się, a jego celem jest wykorzystanie tylko tego, co jest wymagane. Pominąłem również kontrole bezpieczeństwa ze względu na prostotę ... Opierając się również na idei brimborów , próbowałem dodać do kodu funkcję usuwania ...
Plik storage.h wygląda następująco ...
Plik storage.c wygląda następująco ...
Plik main.c wygląda tak ...
Czekamy na konstruktywną krytykę do naśladowania ...
źródło
malloc()
przed próbą użycia alokacji. W tym samym duchu błędem jest bezpośrednie przypisywanie wynikurealloc()
do wskaźnika do ponownie przydzielanej pierwotnej pamięci; jeśli sięrealloc()
nie powiedzie,NULL
jest zwracany, a kod pozostaje z przeciekiem pamięci. O wiele bardziej wydajne jest podwojenie pamięci podczas zmiany rozmiaru niż dodawanie 1 spacji na raz: mniej wywołań funkcjirealloc()
.int
itd.) Na raz. Podwojenie jest typowym rozwiązaniem, ale nie sądzę, aby istniało optymalne rozwiązanie pasujące do wszystkich okoliczności. Oto dlaczego podwojenie jest dobrym pomysłem (inny czynnik, np. 1,5 też byłby w porządku): jeśli zaczniesz od rozsądnej alokacji, może nie być konieczne ponowne przydzielanie. Gdy potrzeba więcej pamięci, rozsądna alokacja jest podwajana i tak dalej. W ten sposób prawdopodobnie potrzebujesz tylko jednego lub dwóch połączeńrealloc()
.Kiedy mówisz
w zasadzie mówisz, że używasz „wskaźników”, ale takiego, który jest wskaźnikiem lokalnym obejmującym całą tablicę zamiast wskaźnikiem obejmującym całą pamięć. Ponieważ koncepcyjnie już używasz "wskaźników" (tj. Numerów identyfikacyjnych, które odnoszą się do elementu w tablicy), dlaczego nie użyjesz zwykłych wskaźników (tj. Numerów identyfikacyjnych, które odnoszą się do elementu w największej tablicy: całej pamięci ).
Zamiast przechowywać w obiektach numery identyfikatorów zasobów, możesz zmusić je do przechowywania wskaźnika. Zasadniczo to samo, ale znacznie bardziej wydajne, ponieważ unikamy zamiany „tablicy + indeks” na „wskaźnik”.
Wskaźniki nie są przerażające, jeśli myślisz o nich jako o indeksie tablicy dla całej pamięci (a tak naprawdę są)
źródło
Aby utworzyć tablicę nieograniczonej liczby elementów dowolnego typu:
i jak z niego korzystać:
Ten wektor / tablica może pomieścić dowolny typ elementu i ma całkowicie dynamiczny rozmiar.
źródło
Cóż, myślę, że jeśli chcesz usunąć element, utworzysz kopię tablicy gardzącej elementem, który ma być wykluczony.
Załóżmy, że
getElement2BRemove()
,copy2TempVector( void* ...)
ifillFromTempVector(...)
sposoby pomocnicze do obsługi wektor temp.źródło