Edytujmy plik tabwsk.c, który jest kolejnym przykładem dynamicznej alokacji pamięci. Program ten jest podobny do programu dynlist.c, ale w tym przypadku definiujemy tablice wskaźników, która często jest używana przez programistów przy budowie dużych baz danych.
*ptaszek[12] jest nową konstrukcją, więc poświęcimy jej trochę miejsca. Jest to definicja tablicy 12 wskaźników, zaczynając od ptaszek[0], a kończąc na ptaszek[11]. Właściwie nazwa tablicy jest sama w sobie wskaźnikiem zawierającym adres jej zerowego elementu, zatem ptaszek jest wskaźnikiem do wskaźnika. Możemy - razem ze wskaźnikiem pisklak - operować na 13 wskaźnikach typu zwierzak. W pętli for każdemu wskaźnikowi przydzielamy dynamicznie blok danych i wypełniamy odpowiednie pola struktury jakimiś przykładowymi informacjami. Kilka wartości podstawiono za pętlą bezpośrednio do wybranych pól. Dane są następnie drukowane na ekranie. Użyto tu wskaźnika pisklak, który - w zależności od wartości zmiennej index - wskazuje na ten sam obiekt, co ptaszek[index]. Przed końcem programu wszystkie 12 bloków danych jest uwalniane komendą free.
Kolejny programik o nazwie linklist.c prezentuje bardzo podobną strukturę jak poprzednia, z tym że pojawia się tu dodatkowe pole, które jest wskaźnikiem. Jest to nic innego, jak wskaźnik do obiektu tego samego typu tj. do struktury zwierzak. Będziemy go używać w celu odwołania się do kolejnego rekordu bazy danych. Identyfikuje on następnego zwierzaka w kolejności. Nie definiujemy żadnych zmiennych (oprócz lokalnego licznika pętli), lecz tylko trzy wskaźniki do powyższej struktury, z którymi będziemy pracować w dalszej części programu.
Używając funkcji malloc rezerwujemy sobie przestrzeń na stercie i wypełniamy ją danymi. Dodatkowe pole w tym przykładzie - wskaźnik nastepny - otrzymuje wartość NULL. Podkreślamy tym sposobem fakt, iż jest to ostatni element !listy dowiązań (linked list). Pozostawiamy w spokoju pierwszy wskaźnik start; będzie on zawsze wskazywał na pierwszy element listy. Inaczej traktujemy elementy z końca listy, a inaczej z środka. W następnym wierszu mamy przypisanie utworzonej struktury (pierwszego elementu ze wskaźnikiem start) do wskaźnika poprzedni (zostanie to dalej wyjaśnione). Mamy teraz pierwszy element listy wypełniony danymi.
Następna grupa działań wykonuje się w pętli for. W tej pętli - wykonuje się ona aż do stałej REKORDY zdefiniowanej przez nas dyrektywą #define - budujemy resztę naszej listy. Za każdym razem, gdy alokujemy pamięć, wypełniamy trzy pola struktury wymyślonymi danymi, a następnie ustalamy wskazania naszych wskaźników. Wskaźnik ostatniego rekordu otrzymuje adres nowoutworzonego, ponieważ wskaźnik poprzedni identyfikuje poprzedni rekord. Zatem poprzedni->nastepny otrzymuje wskazanie na rekord, który właśnie wypełniliśmy danymi. Niech teraz wskaźnik nastepny bieżącego rekordu będzie wskaźnikiem NULL, a wskaźnik poprzedni odwołuje się do nowoutworzonego rekordu; następnym razem gdy utworzymy rekord (w nowym obiegu pętli), ten aktualny rekord będzie wtedy rekordem poprzednim. Brzmi to troszkę zagmatwanie, ale zapewniam, że ma sens.
Kiedy pętla wykona się 6 razy, będziemy posiadać listę 7 struktur (włączając w to tę pierwszą wygenerowaną przed pętlą). Lista ta będzie posiadać następującą charakterystykę:
Ten diagram powinien pomóc w zrozumieniu listy dowiązań.
Nie jest w tej bazie możliwy skok do środka listy i zmiana kilku wartości. Jedynym sposobem dostania się przykładowo do trzeciego rekordu jest rozpoczęcie od początku listy i posuwanie się element po elemencie w jej głąb. Wydaje się to dość niewygodne, ale jest całkiem dobrą metodą na składowanie pewnego rodzaju danych.
Przykładem aplikacji używającej tego typu struktury jest prosty edytor tekstu, gdyż nie zachodzi tam potrzeba swobodnego dostępu do danych. Opiera się on na rekordach zawierających jedną linię tekstu każdy. Program może używać też podwójnej listy dowiązań (doubly linked list). Każdy rekord posiadałby wtedy dwa wskaźniki: jeden wskazując w dół na następny rekord, a drugi w górę na poprzedni. Dzięki tej metodzie możliwe jest wertowanie bazy w obu kierunkach.
Wracając do naszego programu następną rzeczą jest wyświetlenie informacji z listy. Wskaźniki są inicjalizowane i używane w celu przechodzenia z rekordu do rekordu, czytania zawartych tam danych i wyświetlania ich na ekranie. Prezentowanie danych jest kończone w momencie napotkania wskaźnika NULL w ostatnim rekordzie; program nie musi nawet wiedzieć ile jest aktualnie elementów listy.
W końcu cała utworzona przez nas lista jest niszczona; zwolnione miejsce mogłoby być przeznaczone do przechowywania innych danych programu. Należy zwrócić baczną uwagę, że ostatni element listy nie jest kasowany przed sprawdzeniem wskaźnika NULL. W przeciwnym razie usunęlibyśmy dane i nie wiedzielibyśmy czy to już koniec.
W jaki sposób dodać element do środka listy? Po pierwsze niezbędne jest utworzenie nowego rekordu, a następnie wypełnienie go danymi. Po drugie wskaźnik nastepny nowego rekordu musi wskazywać na kolejny element listy. Jeśli nowy rekord ma znajdować się pomiędzy trzecim i czwartym, wskaźnik nowego rekordu musi identyfikować rekord czwarty, a wskaźnik trzeciego musi wskazywać na nowy rekord. Dodawanie nowego rekordu na początek lub koniec listy jest zadaniem odmiennym, lecz również niezbyt skomplikowanym. Co należy w takich sytuacjach zrobić, jak również modyfikacja podwójnej listy dowiązań niech będzie zadaniem do samodzielnego wykonania przez Ciebie.