Zmienne i struktury dynamiczne Zmienne dynamiczne są to zmienne, które tworzymy w trakcie działania programu za pomocą operatora new. Usuwa się je operatorem delete. Czas ich występowania w programie jest uzależniony od potrzeb programu, zajmują więc miejsce w pamięci tylko wówczas, gdy są wykorzystywane (zmienne statyczne istnieją od początku do końca trwania programu). Zmienna dynamiczna nie ma własnej nazwy, dlatego dostęp do niej jest możliwy wyłącznie przez adres obszaru pamięci, który ona zajmuje. Adres ten jest przechowywany we wskaźniku do tej zmiennej. Przy deklaracji wskaźnika, dobrym zwyczajem jest nadanie mu wartości początkowej NULL (adres zerowy). Tablica dynamiczna jednowymiarowa Def. Tablica dynamiczna to taka tablica, która zostaje utworzona w trakcie działania programu i może być usunięta przed jego zakończeniem. Tablice dynamiczne mogą przechowywać elementy jednego typu (podobnie statyczne). Aby móc zadeklarować tablicę, musimy najpierw zadeklarować wskaźnik zdolny wskazywać element tablicy, a następnie za jego pomocą dynamicznie alokować pamięć przeznaczoną na przechowywanie tego elementu. Na żądanie napisanego przez nas programu system operacyjny przyznaje nam obszar pamięci, w którym będą przechowywane dane, w takiej ilości, w jakiej jest nam aktualnie potrzebne. Uzyskane w ten sposób dynamicznie alokowane obszary pamięci należy zwolnić przed zakończeniem działania programu. Deklaracja dynamicznej tablicy przykład: float *tab = NULL; //na razie wskaźnik na nic nie wskazuje tab = new float[k]; //k rozmiar tablicy (może być wpisany z klawiatury) Powyższe instrukcje można połączyć: float *tab = new float[k]; Program pyta użytkownika, ile liczb chce wprowadzić do tablicy, następnie wypełnia tablicę podanymi liczbami i wyświetla je. #include <iomanip> float *tab = NULL; cout << "Ile liczb wpiszesz do tablicy? "; int ile; float liczba; cin >> ile; // dołączamy bibliotekę w której zdefiniowany // jest sposób postępowania w razie braku // miejsca na utworzenie tablicy // deklaracja wskaźnika i ustawienie jego // wartosci na adres zerowy czyli NULL // za pomocą operatora new tab = new float [ile]; // próbujemy powołać do życia nową tablicę // jesli nie udało sie utworzyć // tablicy z powodu braku wolnej pamieci cout << "Brak miejsca na utworzenie tablicy"; for (int i=0; i<ile; i++) // jeśli utworzenie powiodło się
cout << "Podaj liczbe: "; cin >> liczba; *(tab+i) = liczba; // wypełniamy komórkę tablicy cout << endl << "Wypisuje zawartosc tablicy:" << endl; for (int i=0; i<ile; i++) cout << setw(6) << *(tab+i); delete [] tab; // usuwamy tablicę z pamięci // za pomocą operatora delete Jeśli w programie, korzystając z zapasu pamięci, zamierzamy utworzyć bardzo dużą tablicę, wówczas możemy nie znaleźć wystarczająco dużego, spójnego obszaru na jej przechowanie. W takim wypadku wskaźnik tab zamiast wskazywać na początek tablicy, wskazywał będzie na adres NULL. W bloku umieszczamy funkcje, które chcemy śledzić pod kątem wystąpienia błędów. W naszym programie jest to błąd związany z nieudaną alokacją pamięci za pomocą operatora new. Instrukcja catch przechwytuje przypadki, kiedy funkcja wewnątrz bloku się nie powiodła. W programie nie przesuwamy wskaźnika tab, a jedynie wpisujemy (lub wypisujemy) liczby do komórek odpowiednio przesuniętych względem komórki, na którą wskazuje wskaźnik tab. Jeśli przypadkową instrukcją przypisania zmienisz wartość wskaźnika wskazującego na tablicę dynamiczną, możesz stracić możliwość korzystania z tej tablicy. Po instrukcji zwolnienia pamięci zajętej przez tablicę wskaźnik tab nadal istnieje, ponieważ jest on zmienną statyczną, którą nie da się usunąć z programu. Tablica dynamiczna dwuwymiarowa Dwuwymiarowa tablica dynamiczna będzie jednowymiarową tablicą dynamiczną, przechowującą wskaźniki do tablic dynamicznych jednowymiarowych, w których umieszczone zostaną dane. Deklaracja dwuwymiarowej tablicy dynamicznej [w][k] przykład: int **tab; //tab jest wskaźnikiem do wskaźnika na zmienne typu int tab = new int *[w]; //w liczba wierszy; tablica jednowymiarowa wskaźników //do zmiennych typu int for (i=0; i<w; i++) //dla każdego wskaźnika z poprzedniej tablicy tab[i] = new int [k]; //tworzymy tablicę liczb całkowitych; k liczba kolumn Napiszemy program, który utworzy dwuwymiarową tablicę dynamiczną o żądanych wymiarach (podanych z klawiatury) i wypełni ją losowymi liczbami z przedziału <0, 100>. #include <iomanip> #include <cstdlib> int wie, kol, i, j; cout << "Podaj liczbe wierszy i kolumn tablicy:\n"; cin >> wie >> kol; srand(time(null)); // inicjacja generatora liczb int **tab; // deklaracja wskaźnika do wskaźnika na zmienne typu int // próba tworzenia tablicy wskaźników tab = new int *[wie];
cout << "Brak miejsca na utworzenie tablicy. Koncze program"; for (i=0; i<wie; i++) // próba tworzenia dynamicznych tablic liczb tab[i] = new int [kol]; cout << "Brak miejsca na utworzenie tablicy. Koncze program"; for (i=0; i<wie; i++) for (j=0; j<kol; j++) tab[i][j] = rand()%101; // wypełnianie tablic liczbami cout << setw(4) << tab[i][j]; // wyświetlanie elementów cout << endl; // teraz usuniemy tablicę z obszaru zajmowanej pamieci for (i=0; i<wie; i++) delete [] tab[i]; // usuwamy kolejne tablice z liczbami delete [] tab; // usuwamy tablicę wskaźników Poszczególne tablice z danymi nie muszą mieć takiego samego rozmiaru. Jak to jest w tym przykładzie? Napiszemy program, który utworzy tabelę odległości pomiędzy miastami. Nazwy miast oraz odległości pomiędzy nimi podaje użytkownik. #include <iomanip> int liczba, i, j; // liczba zmienna przechowująca liczbę miast cout << "Podaj liczbe miast: "; cin >> liczba; char **miasta; // deklarujemy tablicę dwuwymiarową o nazwie miasta miasta = new char *[liczba]; // w której poszczególne wiersze zawierają // nazwy miast (będące tablicami znaków) miasta[i] = new char[20]; cout << "Podaj nazwe miasta: "; cin >> miasta[i]; int **tab; // deklarujemy tablicę odległości między miastami tab = new int *[liczba];
// próba utworzenia tablicy tab[i] = new int[i+1]; // jeśli brak miejsca na tablicę // to zakończ działanie programu for (j=0; j<i+1; j++) if (i==j) tab[i][j]=0; else cout << "Podaj odleglosc z miasta " << miasta[i]; cout << " do miasta " << miasta[j] << ": "; cin >> tab[i][j]; cout << " "; cout << setw(10) << miasta[i]; cout << endl; for (j=0; j<i+1; j++) if (j==0) cout << setw(10) << miasta[i]; cout << setw(10) << tab[i][j]; cout << endl; delete [] tab[i]; delete [] tab; delete [] miasta;
Lista jednokierunkowa tworzenie listy i wprowadzanie do niej elementów Dynamiczna struktura danych jest ciągiem powiązanych ze sobą zmiennych dynamicznych, których liczba nie jest znana w czasie kompilacji, a nawet podczas uruchamiania programu. Lista jest złożoną dynamiczną strukturą danych, która przechowuje dane wraz z zapamiętaniem ich kolejności. W przeciwieństwie do tablicy elementy listy nie muszą być położone w sąsiednich komórkach pamięci. Lista jest dobrym rozwiązaniem, gdy chcemy np. zapamiętać dużą liczbę elementów, a nie dysonujemy wystarczająco dużym spójnym obszarem na utworzenie tablicy dynamicznej. Jednak główną i najważniejszą cechą listy jest możliwość dołączania elementów bez zdefiniowania uprzednio ich liczby. Nawet w przypadku tablicy dynamicznej po podaniu rozmiaru nie możemy już jej powiększyć. Tworzenie listy zaczynamy od zdefiniowania jej pierwszego elementu, czyli głowy. Głowa jest jednocześnie identyfikatorem listy. Do głowy dołączamy następne elementy. Dla każdego kolejnego elementu listy będzie znajdowany wolny obszar pamięci, do którego zostaną wpisane dane z nim związane. W wypadku listy jednokierunkowej każdy element zna adres elementu następnego, natomiast nie zna adresu elementu poprzedniego. Elementami listy są struktury. Każda ze struktur musi mieć, oprócz pól z danymi, dodatkowe pole, w którym jest przechowywany wskaźnik do następnego elementu listy. Ostatni element listy wskazuje na adres zerowy, niezwiązany z żadnym obiektem (NULL) informacja, że w liście nie ma już więcej elementów. Lista jest strukturą o dostępie sekwencyjnym, co oznacza, że aby dostać się np. do czwartego elementu, należy przejść wszystkie wcześniejsze elementy, począwszy od głowy. Natomiast w tablicy mamy do czynienia z dostępem swobodnym, czyli wystarczy podać indeks elementu, aby uzyskać do niego dostęp. W listach mamy dwa zasadnicze sposoby dołączania kolejnych elementów: dołączanie do początku listy i dołączanie do końca listy. /Który ze sposobów jest użyty w poniższym przykładzie?/ Napiszemy program, który zadaje pytanie, czy chcemy dodać element do listy. Dopóki otrzymuje pozytywną odpowiedź, pobiera wpisaną na klawiaturze liczbę całkowitą i dołącza ją do listy. Na koniec wyświetla na ekranie monitora całą zawartość listy. struct element_listy int liczba; element_listy *nastepny; ; element_listy *glowa = NULL; // tworzymy listę pustą element_listy *nowy; char odp; int d; cout << "Czy chcesz podac element listy? t/n: "; while (odp!='n') // dołaczamy kolejne elementy
cout << "Podaj dane : "; cin >> d; // próba dołaczenia nowego elementu nowy = new element_listy; // jeśli nie ma miejsca na nowy element cout << "Nie ma juz miejsca na nastepny element"; break; nowy->nastepny = glowa; // jest miejsce na nowy element nowy->liczba = d; glowa = nowy; cout << endl << "Chcesz podac kolejny element listy? t/n: "; element_listy *temp = glowa; // początek wypisywania elementów listy cout << endl << "Oto utworzona przez ciebie lista:" << endl; while (temp!=null) cout << temp->liczba << " "; temp = temp->nastepny; Pobieranie liczb jak wyżej i dołączanie kolejnych elementów do końca listy. #include <cstdlib> struct element_listy int liczba; element_listy *nastepny; ; element_listy *glowa = NULL; element_listy *ogon = NULL; element_listy *nowy; char odp; int d; cout << "Czy chcesz podac element listy? t/n: "; while (odp!='n')
cout << "Podaj dane: "; cin >> d; nowy = new element_listy; cout << endl << "Brak pamieci na ostatni element"; break; nowy->nastepny = NULL; nowy->liczba = d; if (glowa==null) glowa = nowy; ogon = glowa; else ogon->nastepny = nowy; ogon = nowy; cout << endl << "Czy chcesz podac kolejny element listy? t/n: "; element_listy *temp=glowa; // początek wypisywania elementów listy cout << endl << "Oto utworzona przez ciebie lista:" << endl; while (temp!=null) cout << temp->liczba << " "; temp = temp->nastepny;