Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych Autor artykułu: mgr Jerzy Wałaszek |
©2015 mgr
Jerzy Wałaszek
|
Tablice dynamiczne, podejście funkcyjne
Rozwiążmy następujący problem: w pewnym programie chcemy posiadać tablicę
dynamiczną, która będzie się dopasowywała do przechowywanych danych. Jak to
zrealizować? Rozwiązanie może być następujące:
Tworzymy tablicę dynamiczną T o pewnym
początkowym rozmiarze n. Jednocześnie w innej zmiennej
zapamiętujemy liczbę danych przechowywanych w tej tablicy. Niech zmienna
ta nosi nazwę c
(ang. counter = licznik). Początkowo c
zawiera wartość 0.
Dane dodajemy do tablicy za pomocą wybranej funkcji. Dodanie danych polega zawsze na zwiększeniu o 1 wartości c. Dlatego przed dodaniem nowego elementu sprawdzamy, czy c jest mniejsze od n. Jeśli tak, to w tablicy jest miejsce na dodatkowy element. Wstawiamy go i zwiększamy c o 1. Jeśli natomiast c jest równe n, to tablica została już w całości zapełniona. W takim przypadku należy utworzyć nową tablicę, np. o rozmiarze n + DT (gdzie DT oznacza pewien zapas komórek, np. 5, 10, 100 – zależy to od przewidywanej intensywności dodawania elementów do tablicy), przepisać do niej zawartość starej tablicy, starą tablicę usunąć z pamięci i adres nowej tablicy zapamiętać w T, zwiększając równocześnie n o DT. Po tych operacjach T wskazuje nową, większą tablicę, która zawiera informację przechowywaną przez poprzednią tablicę dynamiczną. Możemy zatem dodać dane do tablicy. Dane możemy również usuwać z tablicy. W tym przypadku maleje jej rozmiar c. Możemy się umówić, że jeśli po wykonaniem operacji zmniejszenia c = n - 2DT, to, podobnie jak przy zwiększaniu, tworzymy nową tablicę o rozmiarze n - DT, przepisujemy do niej starą, starą usuwamy, a za n przyjmujemy n - DT. W ten sposób tablica będzie przechowywała zawsze co najwyżej DT elementów więcej niż wynosi liczba wprowadzonych do niej danych.
W podejściu funkcyjnym problem ten rozwiązujemy przy pomocy kilku funkcji. Funkcje te muszą posiadać dostęp do T, n i c. Najlepiej zatem będzie umieścić te dane w strukturze, a do funkcji przekazywać referencję do tej struktury. Umówmy się, że referencja ta będzie przekazywana w pierwszym parametrze każdej funkcji. Oto elementy, które będą nam potrzebne (nazwy zostały dobrane w specjalny sposób, co zrozumiesz później – nie zmieniaj ich):
Program jest następujący:
Zwróć uwagę na to, że funkcje obsługujące strukturę Vector otrzymują referencję do niej w pierwszym parametrze. Dzięki temu mają pełny dostęp do pól tej struktury. Zamiast referencji można również przekazywać wskaźnik do struktury. Wtedy dostęp do jej pól odbywa się poprzez operator strzałki ->.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
Tablice dynamiczne, podejście obiektoweTeraz to samo zaprogramujemy, wykorzystując programowanie obiektowe.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||
Różnice pomiędzy podejściem funkcyjnym a obiektowymPrzyglądnijmy się różnicom w obu programach.
W strukturze Vector mamy jedynie pola danych. W klasie Vector, oprócz pól danych, mamy również definicje funkcji, które z tą klasą współpracują. Funkcje takie nazywamy metodami (ang. methods) lub funkcjami składowymi (ang. member functions). Dalej widzimy w klasie Vector dwa słowa kluczowe:
Jeśli metoda klasy jest krótka, to jej część wykonawczą możemy umieścić bezpośrednio w definicji klasy. U nas zrobiliśmy tak z metodami:
Tak zdefiniowane metody tworzą tzw. funkcje inline. Różnią się one tym od innych funkcji, że kompilator przy każdym odwołaniu do nich wstawia do programu ich kod. Dzięki temu wykonują się szybciej, ale program zajmuje oczywiście więcej miejsca. Z tego powodu funkcje inline powinny być krótkie. Klasa posiada dwie metody specjalne, które nazywamy konstruktorem i destruktorem. Cechą charakterystyczną tych metod jest to, iż mają one taką samą nazwę jak nazwa klasy. Zadaniem konstruktora jest budowa klasy. Jest on zawsze wywoływany przez program wtedy, gdy klasa jest tworzona w pamięci, gdy rozpoczyna swoje istnienie. W przypadku naszej klasy konstruktor inicjuje jej pola n i c oraz przydziela wstępnie DT komórek tablicy dynamicznej T:
W wersji funkcyjnej musieliśmy osobiście wywołać funkcję Destruktor ma z kolei za zadanie czysto usunąć klasę z pamięci, gdy przestaje ona istnieć. Ponieważ nasza klasa rezerwuje obszar dla tablicy dynamicznej T, to destruktor zwraca ten obszar z powrotem do systemu. Gdyby tego nie robił, to mógłby powstać wyciek pamięci (ang. memory leak) (jeśli program się kończy, jak w tym przypadku, to cała zarezerwowana przez niego pamięć i tak trafia do systemu, jednakże przy tworzeniu klas lokalnych w funkcjach zawsze należy zwracać zarezerwowane zasoby, a destruktory dbają o to automatycznie).
~Vector() { delete [] T;}
Kolejna różnica dotyczy samych funkcji i metod. Weźmy dla przykładu funkcję wstawiającą element na początek tablicy:
Nazwa metody jest poprzedzona nazwą klasy Vector oraz operatorem :: (operator czterokropek, operator zakresu). Informuje to kompilator, że definicja dotyczy metody należącej do klasy Vector. Kolejna różnica, to brak na liście parametrów metody referencji do obiektu zmiennej. W wersji funkcyjnej parametr ten musi zostać jawnie podany, aby funkcja miała dostęp do pól struktury, na których ma przeprowadzić jakieś działania. W przypadku klasy wszystkie jej metody dostają automatycznie tę referencję w ukrytym pierwszym parametrze. Parametr ten nazywa się this i jest wskaźnikiem aktualnej zmiennej klasy, na której operuje metoda. Zwykle nie musimy z niego jawnie korzystać. W wersji funkcyjnej dostęp do pól struktury odbywa się zawsze poprzez parametr, który funkcja otrzymała przy wywołaniu. U nas parametr ten nazywa się v. W wersji obiektowej zmienna klasy jest domyślna i pól oraz funkcji składowych nie musimy jawnie niczym poprzedzać. Oczywiście, na upartego moglibyśmy skorzystać z tego ukrytego parametru this w sposób następujący:
Ale i tak robi to automatycznie kompilator, więc tutaj nie ma to specjalnie sensu.
|
I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe