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, jak realloc
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ąc x
się 0
, a tym samym array[0]
staje się *array
ponieważ dodanie 0
nie zmieni wartości ...
... i dlatego widzimy, że *array
jest to równoważne array[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:
struct int_list {
size_t size;
int value[];
};
Pozwoli ci to połączyć swoją tablicę int
w tę samą alokację co twoja count
, a powiązanie ich w ten sposób może być bardzo przydatne !
sizeof (struct int_list)
będzie zachowywał się tak, jakby value
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 odpowiednik malloc(x)
i możemy użyć tego do uproszczenia naszego kodu. Na przykład:
int push_back(struct int_list **fubar, int value) {
size_t x = *fubar ? fubar[0]->size : 0
, y = x + 1;
if ((x & y) == 0) {
void *temp = realloc(*fubar, sizeof **fubar
+ (x + y) * sizeof fubar[0]->value[0]);
if (!temp) { return 1; }
*fubar = temp; // or, if you like, `fubar[0] = temp;`
}
fubar[0]->value[x] = value;
fubar[0]->size = y;
return 0;
}
struct int_list *array = NULL;
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 wprowadzone value
od wewnątrz push_back
nie będą widoczne dla funkcji, z której wywołujemy, prawda? To samo dotyczy pierwszego argumentu i musimy być w stanie zmodyfikować nasze array
, 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:
struct int_list *array = NULL;
if (!push_back(&array, 42)) {
// success!
}
PS Pamiętaj,free(array);
kiedy skończysz!