Koło olimpijskie

 

Tablice dynamiczne

Zdarza się, iż w trakcie pisania programu nie wiemy, ile dokładnie elementów będzie zawierała używana w tym programie tablica. W takim przypadku problem tworzenia tablicy możemy rozwiązać na dwa sposoby:
  1. Utworzyć tablicę o maksymalnej, przewidywanej liczbie elementów. Rozwiązanie nieefektywne ze względu na wykorzystanie pamięci. Jeśli w typowych przypadkach wykorzystujemy małą liczbę elementów tablicy, to i tak musimy rezerwować założoną ilość komórek dla przypadku pesymistycznego, który pojawia się bardzo rzadko, ale jest prawdopodobny.
  2. Utworzyć tablicę dynamicznie o tylu komórkach, ile w danej chwili jest nam potrzebne. Po wykorzystaniu, tablicę dynamiczną usuwamy, zwalniając w ten sposób zajmowany przez nią obszar pamięci, który teraz można wykorzystać do innych celów – np. dla nowej tablicy dynamicznej.

Code::Blocks

W celu utworzenia w języku C++ tablicy dynamicznej, tworzymy zmienną wskaźnikową na typ danych, które mają być przechowywane w tablicy:

 

typ_elementów * nazwa_tablicy_dynamicznej;

 

Zmienna wskaźnikowa (ang. pointer variable) nie przechowuje danych tylko adres obszaru pamięci komputera, w którym te dane się znajdują. Deklarację zmiennej wskaźnikowej zawsze poprzedzamy znakiem gwiazdki. W poniższym przykładzie tworzymy trzy wskaźniki a, b i c do danych typu double (czyli do obszaru pamięci, w którym będą przechowywane liczby zmiennoprzecinkowe o podwójnej precyzji):

 

...
double * a, * b, * c;
...
 

Pamięć rezerwujemy operatorem new i adres zarezerwowanego obszaru umieszczamy w zmiennej wskaźnikowej:

 

nazwa_tablicy_dynamicznej  = new typ_elementów[liczba_elementów];

 

Poniższy przykład tworzy trzy tablice dynamiczne, w których będzie można przechowywać odpowiednio 10, 100 i 1000 elementów typu double:

 

...
a = new double[10];    // elementy od a[0] do a[9]
b = new double[100];   // elementy od b[0] do b[99]
c = new double[1000];  // elementy od c[0] do c[999]
...
 

Po tej operacji do elementów tablic a, b  i c  odwołujemy się w zwykły sposób za pomocą indeksów. Istnieje również alternatywna metoda, wykorzystująca fakt, iż zmienne a, b  i c  są wskaźnikami. W języku C++ dodanie do wskaźnika liczby całkowitej powoduje obliczenie adresu elementu o indeksie równym dodawanej liczbie. Zatem wynik takiej operacji jest również wskaźnikiem:

 
Tablica Wskaźnik
a[2] = 10.54;
cout << a[2] << endl;
* (a + 2) = 10.54;
cout << * (a + 2) << endl;

 

W rzeczywistości zapis a[i] kompilator i tak przekształca sobie na zapis * (a  + i). Forma tablicowa jest tylko uproszczeniem zapisu wskaźnikowego.

Tablice dynamiczne nie są automatycznie usuwane z pamięci, jeśli utworzono je w funkcji. Dlatego po zakończeniu korzystania z tablicy program powinien zwolnić zajmowaną przez tablicę pamięć. Dokonujemy tego poleceniem delete w sposób następujący:

 

delete [] nazwa_tablicy_dynamicznej;

 

W poniższym przykładzie zwalniamy pamięć zarezerwowaną wcześniej na elementy tablic b i c.

 

...
delete [] b;  // usuwamy obszar wskazywany przez b
delete [] c;  // usuwamy obszar wskazywany przez c
...
 

Należy również wspomnieć, iż Code::Blocks dopuszcza konstrukcję:

 

typ_elementów  nazwa_tablicy[zmienna];

 

co pozwala na tworzenie statycznych tablic o liczbie elementów podanej w zmiennej. Na przykład poniższa konstrukcja programowa tworzy statyczną tablicę a  o liczbie elementów odczytanej ze strumienia wejściowego konsoli znakowej:

 

...
int n;
cin >> n;
double a[n];
...
 

Jednakże nie jest to zbyt standardowe rozwiązanie i może nie być przenośne na inne kompilatory C++, dlatego odradzam używania go – lepiej zastosować tablicę dynamiczną.

 

Tablice dynamiczne o dynamicznym rozmiarze

Gdy operujemy na dynamicznych strukturach danych, to często okazuje się, że taką strukturę należy zwiększyć lub zmniejszyć w czasie działania programu. Np. utworzyliśmy na początku algorytmu dynamiczną tablicę 100 elementową, lecz dalej okazało się, że w tablicy tej należy dodatkowo umieścić jeszcze 50 kolejnych elementów. Co możemy zrobić? Odpowiedź jest prosta – dynamicznie zmienić rozmiar naszej struktury. Zasada jest następująca:

 

Oprócz wskazania do tablicy tworzymy dodatkową zmienną, która przechowuje jej maksymalny rozmiar. Oznaczmy tę zmienną przez nn. Każdą tablicę tworzymy z pewnym zapasem komórek, oznaczmy go przez z. Zmienna n przechowuje informację o ilości zajętych komórek.

Gdy dodajemy element do tablicy, to najpierw zwiększamy n  o 1 i sprawdzamy, czy warunek n  >= nn  jest spełniony. Jeśli tak, to tablica zawiera za mało komórek, i należy zwiększyć jej rozmiar. Operację tę przeprowadzamy następująco:

  1. Rezerwujemy nowy obszar pamięci o rozmiarze nn + z  i zapamiętujemy jego adres w tymczasowej zmiennej.
  2. Przepisujemy zawartość starego obszaru tablicy do nowego.
  3. Usuwamy stary obszar – w ten sposób system odzyska pamięć.
  4. Do wskaźnika tablicy przepisujemy adres ze zmiennej tymczasowej.
  5. Zmienną nn zwiększamy o z

Zwróć uwagę, że cała tablica zmieniła swoje położenie w pamięci. Jeśli zatem posiadałeś jakieś zmienne, które przechowywały adresy jej elementów (wskaźniki), to teraz przestaną one być aktualne, ponieważ wskazują stary obszar, który już do tej tablicy nie należy (chociaż przez pewien czas może zachowywać swoją zawartość aż system zapisze go innymi danymi). Należy to wziąć pod uwagę, gdy planujesz wykorzystywanie dynamicznych tablic o dynamicznym rozmiarze.

 

Code::Blocks
...
if(++n >= nn)
{
  typ_danych * T = new typ_danych[nn+z];
  for(int i = 0; i < nn; i++) T[i] = A[i];
  delete [] A;
  A = T;
  nn += z;
}
...

 

Przy usuwaniu elementów z tablicy postępujemy podobnie. Gdy usuniemy element, to rozmiar tablicy n zostanie zmniejszony o 1. Sprawdzamy, czy jest spełniony warunek  n  ≤ nn  - 2z. Jeśli tak, to tablica zajmuje zbyt duży obszar i powinniśmy zmniejszyć jej rozmiar. Dlaczego w nierówności użyliśmy 2z  a nie po prostu z? Chodzi o to, aby po zmniejszeniu rozmiaru tablica wciąż posiadała zapas z  komórek, co zmniejszy ryzyko częstych przeładowań pamięci, które są przecież kosztowne czasowo. Zmianę rozmiaru tablicy wykonujemy podobnie jak przy zwiększaniu liczby elementów:

 

Code::Blocks
...
if(--n <= nn-z-z)
{
  typ_danych * T = new typ_danych[nn-z];
  for(int i = 0; i < n; i++) T[i] = A[i];
  delete [] A;
  A = T;
  nn -= z;
}
...

 

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

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