Zmienne i struktury dynamiczne
Transkrypt
Zmienne i struktury dynamiczne
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; tab = new float[k]; //na razie wskaźnik na nic nie wskazuje //k – rozmiar tablicy (może być wpisany z klawiatury) Powyższe instrukcje można połączyć: float *tab = new float[k]; Przykład: Program pyta użytkownika, ile liczb chce wprowadzić do tablicy, następnie wypełnia tablicę podanymi liczbami i wyświetla je. #include <iostream> #include <iomanip> #include <cstdio> #include <new> using namespace std; int main() { float *tab = NULL; // 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 cout << "Ile liczb wpiszesz do tablicy? "; int ile; float liczba; cin >> ile; try // próbujemy powołać do życia nową tablicę { // za pomocą operatora new tab = new float [ile]; } catch(bad_alloc) // jesli nie udało sie utworzyć { // tablicy z powodu braku wolnej pamieci cout << "Brak miejsca na utworzenie tablicy"; cin.ignore(); getchar(); return -1; } for (int i=0; i<ile; i++) // jeśli utworzenie powiodło się { cout << "Podaj liczbe: "; cin >> liczba; *(tab+i) = liczba; // } cout << endl << "Wypisuje zawartosc for (int i=0; i<ile; i++) cout << setw(6) << *(tab+i); delete [] tab; // cin.ignore(); // getchar(); return 0; wypełniamy komórkę tablicy tablicy:" << endl; 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 try 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 try 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 = new int *[w]; //tab jest wskaźnikiem do wskaźnika na zmienne typu int //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 Przykład: 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 <iostream> #include <iomanip> #include <cstdio> #include <cstdlib> #include <new> using namespace std; int main() { 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 try // próba tworzenia tablicy wskaźników { tab = new int *[wie]; } catch(bad_alloc) { cout << "Brak miejsca na utworzenie tablicy. Koncze program"; return -1; } for (i=0; i<wie; i++) try // próba tworzenia dynamicznych tablic liczb { tab[i] = new int [kol]; } catch(bad_alloc) { cout << "Brak miejsca na utworzenie tablicy. Koncze program"; return -1; } 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 cin.ignore(); getchar(); return 0; } Poszczególne tablice z danymi nie muszą mieć takiego samego rozmiaru. Jak to jest w tym przykładzie? Przykład: 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 <iostream> #include <iomanip> #include <cstdio> #include <new> using namespace std; int main() { 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ą for (i=0; i<liczba; i++) // nazwy miast (będące tablicami znaków) miasta[i] = new char[20]; for (i=0; i<liczba; i++) { cout << "Podaj nazwe miasta: "; cin >> miasta[i]; } int **tab; // deklarujemy tablicę odległości między miastami tab = new int *[liczba]; for (i=0; i<liczba; i++) try // próba utworzenia tablicy { tab[i] = new int[i+1]; } catch(bad_alloc) // jeśli brak miejsca na tablicę { return -1; // to zakończ działanie programu } for (i=0; i<liczba; i++) 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 << " "; for (i=0; i<liczba; i++) cout << setw(10) << miasta[i]; cout << endl; for (i=0; i<liczba; i++) { for (j=0; j<i+1; j++) { if (j==0) cout << setw(10) << miasta[i]; cout << setw(10) << tab[i][j]; } cout << endl; } for (i=0; i<liczba; i++) delete [] tab[i]; delete [] tab; delete [] miasta; cin.ignore(); getchar(); return 0; } 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?/ Przykład: 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. #include <iostream> #include <cstdio> #include <new> using namespace std; struct element_listy { int liczba; element_listy *nastepny; }; int main() { element_listy *glowa = NULL; // tworzymy listę pustą element_listy *nowy; char odp; int d; cout << "Czy chcesz podac element listy? t/n: "; cin >> odp; while (odp!='n') // dołaczamy kolejne elementy { cout << "Podaj dane : "; cin >> d; try // próba dołaczenia nowego elementu { nowy = new element_listy; } catch(bad_alloc) // 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: "; cin >> odp; } 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; } cin.ignore(); getchar(); return 0; } Przykład: Pobieranie liczb jak wyżej i dołączanie kolejnych elementów do końca listy. #include <iostream> #include <cstdlib> #include <new> using namespace std; struct element_listy { int liczba; element_listy *nastepny; }; int main() { element_listy *glowa = NULL; element_listy *ogon = NULL; element_listy *nowy; char odp; int d; cout << "Czy chcesz podac element listy? t/n: "; cin >> odp; while (odp!='n') { cout << "Podaj dane: "; cin >> d; try { nowy = new element_listy; } catch(bad_alloc) { 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: "; cin >> odp; } 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; } cin.ignore(); getchar(); return 0; }