Programowanie i struktury danych Wykład 4 Dr Piotr ybula <cybula@math.uni.lodz.pl>
Typ wska ź nikowy int* pointer; //wskaźnik do zmiennych typu int pozwala na dostęp do dowolnego miejsca pamięci (zmienne wskaźnikowe są stałej wielkości zależnej jedynie od architektury procesora, np. 32-bit, 64-bit) zmienna typu wskaźnikowego zawiera adres zmiennej, na którą wskazuje (domyślnie adres przypadkowy): inicjalizacja adresem pustym NULL (stała zdefiniowana w bibliotece cstdlib) lub nullptr (słowo kluczowe dla c++11) pointer = nullptr; inicjalizacja adresem istniejącej zmiennej automatycznej (niebezpieczne) int variable; pointer = &variable; //operator adresu (referencji) inicjalizacja adresem zmiennej tworzonej dynamicznie pointer = new int; //operator alokacji pamięci delete pointer; //operator dealokacji pamięci dostęp do wskazywanej zmiennej tylko dla niepustych wskaźników p v if(pointer!=nullptr) cout<< *pointer; //operator dereferencji 2/47
Typ wska ź nikowy operator przypisania zmiennych wskaźnikowych kopiuje tylko adresy (nie wartości zmiennych wskazywanych) int* p1 = new int; int* p2; p2 = p1; //p2 wskazuje na to samo co p1 p1 współdzielenie adresu jednej zmiennej doprowadzi do powstania tzw. wskaźnika wiszącego (ang. dangling pointer) po zwolnieniu pamięci zajmowanej przez tę zmienną delete p1; cout << *p2; //zachowanie nieokreślone p1 p2 przed zwolnieniem pamięci dla zmiennej wskazywanej przez wskaźnik należy zmienić wartości dla wszystkich wskaźników wskazujących na tę zmienną poza jednym (tzw. zliczanie referencji) p2 = nullptr; //p2 nie wskazuje już na wspólną zmienną //p1 jest jedynym wskaźnikiem do tej zmiennej delete p1; //zwolnienie pamięci jest bezpieczne? p2 3/47
Wska ź niki do struktur dostęp do wskazywanej zmiennej strukturalnej (tylko dla niepustych wskaźników) za pomocą: operatorów dereferencji i selekcji (* i.): (*pointer).member operatora dereferencji wskaźnikowej (->): pointer->member class Student { string name; int index; public: Student(string _name); int getname() const; //inne metody }; Student* p = new Student( Scott Tiger ); //alokacja struktury cout << (*p).getname(); //operatory dereferencji i selekcji cout << p->getname(); //operator dereferencji wskaźnikowej delete p; //dealokacja struktury 4/4747
Wska ź nik this wskaźnik do obiektu, dla którego została wywołana metoda (dostępny w każdej metodzie struktury/klasy) class Vector { double* v; int dim; public: Vector(int dim) { v = new double[this->dim = dim]; } Vector& operator=(const Vector&); }; Vector& Vector::operator=(const Vector &r) { if(this!= &r) { //sprawdzenie adresów obiektów delete[] v; v = new double[dim = r.dim]; for(int i = 0; i < dim; i++) v[i] = r.v[i]; } return *this; //dereferencja wskaźnika zwraca obiekt } 5/47
ezpiecze ń stwo wska ź ników dereferencja wskaźnika możliwa tylko gdy wskaźnik nie jest pusty (warunkowe użycie dereferencji, pustość wskaźnika oznacza najczęściej przypadek szczególny w algorytmie) nazwa wskaźnika powinna jednoznacznie określać jego zastosowanie (proste wnioskowanie w przypadku pustości wskaźnika) powoływanie dedykowanych wskaźników dla alokacji i dealokacji pamięci 4 kroki poprawnej dealokacji pamięci (zapobieganie powstawania wskaźników wiszących): (1) powołanie wskaźnika i przypisanie mu adresu obiektu do zwolnienia (2) opcjonalne zabezepieczenie ważnych danych z obiektu (3) odpięcie (modyfikacja) wszystkich wskaźników przechowujących adres obiektu (poza wskaźnikiem z kroku pierwszego) (4) zwolnienie obiektu za pomocą jedynego możliwego wskaźnika 6/47
Lista łą czona pozwala na nieciągły przydział pamięci przy implementacji DT złożona z węzłów połączonych wskaźnikami - każdy węzeł zawiera dane i adres następnego (lista jednokierunkowa) oraz poprzedniego (lista dwukierunkowa) zalety: wysoka skalowalność i rozszerzalność, optymalne zużycie pamięci, łatwe dodawanie i usuwanie danych bez konieczności zmiany położenia w pamięci pozostałych danych wady: implementacja (operacje na wskaźnikach), niska wydajność odczytu (brak dostępu bezpośredniego do dowolnego elementu), dodatkowa pamięć wymagana dla zapamiętania adresów węzłów 7/47
Implementacja listowa DT pozwala na nieograniczony rozmiar struktury wskaźnik na pierwszy (opcjonanie również ostatni) węzeł listy łączonej główne metody: konstruktor bezparametrowy (puste wskaźniki, brak węzłów) destruktor (zwolnienie pamięci wszystkich węzłów) konstruktor kopiujący (przydział pamięci dla węzłów) operator przypisania (zwolnienie starej i przydział nowej pamięci dla węzłów) dodawanie elementu (przydział pamięci dla nowego węzła) usuwanie elementu (zwolnienie pamięci węzła) 8/47
Wstawianie na pocz ą tek (1) tworzymy nowy węzeł (creator) z podaną wartością i adresem pierwszego węzła () jako adresem następnego () 9/47
Wstawianie na pocz ą tek (1) tworzymy nowy węzeł (creator) z podaną wartością i adresem pierwszego węzła () jako adresem następnego () creator V 10/47
Wstawianie na pocz ą tek (1) tworzymy nowy węzeł (creator) z podaną wartością i adresem pierwszego węzła () jako adresem następnego () (2) przestawiamy wskaźnik pierwszego węzła () na nowy węzeł (3) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na pusty adres, ustawiamy go na nowy węzeł creator V 11/47
Wstawianie na pocz ą tek (1) tworzymy nowy węzeł (creator) z podaną wartością i adresem pierwszego węzła () jako adresem następnego () (2) przestawiamy wskaźnik pierwszego węzła () na nowy węzeł (3) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na pusty adres, ustawiamy go na nowy węzeł creator V 12/47
Wstawianie na pocz ą tek (1) tworzymy nowy węzeł (creator) z podaną wartością i adresem pierwszego węzła () jako adresem następnego () (2) przestawiamy wskaźnik pierwszego węzła () na nowy węzeł (3) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na pusty adres, ustawiamy go na nowy węzeł V 13/47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł 14/47 47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł killer 15/47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł (3) przestawiamy wskaźnik pierwszego węzła () na węzeł drugi (używając adresu zapisanego we wskaźniku z pierwszego węzła) (4) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na usuwany, ustawiamy go na pusty adres killer 16/47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł (3) przestawiamy wskaźnik pierwszego węzła () na węzeł drugi (używając adresu zapisanego we wskaźniku z pierwszego węzła) (4) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na usuwany, ustawiamy go na pusty adres killer 17/47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł (3) przestawiamy wskaźnik pierwszego węzła () na węzeł drugi (używając adresu zapisanego we wskaźniku z pierwszego węzła) (4) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na usuwany, ustawiamy go na pusty adres (5) zwalniamy węzeł wskazywany przez wskaźnik dedykowany (killer) killer 18/47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł (3) przestawiamy wskaźnik pierwszego węzła () na węzeł drugi (używając adresu zapisanego we wskaźniku z pierwszego węzła) (4) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na usuwany, ustawiamy go na pusty adres (5) zwalniamy węzeł wskazywany przez wskaźnik dedykowany (killer) killer 19/47
Usuwanie na pocz ą tku (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) ustawiamy dedykowany wskaźnik (killer) na pierwszy węzeł (3) przestawiamy wskaźnik pierwszego węzła () na węzeł drugi (używając adresu zapisanego we wskaźniku z pierwszego węzła) (4) jeżeli jest wskaźnik na ostatni węzeł (tail) i wskazuje on na usuwany, ustawiamy go na pusty adres (5) zwalniamy węzeł wskazywany przez wskaźnik dedykowany (killer) 20/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła 21/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła tmp 22/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła tmp 23/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła (2) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () tmp 24/47 47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła (2) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () V tmp creator 25/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła (2) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (3) przestawiamy wskaźnik w węźle wskazywanym przez tmp (lub wskaźnik gdy tmp jest pusty) na nowy węzeł V tmp creator 26/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła (2) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (3) przestawiamy wskaźnik w węźle wskazywanym przez tmp (lub wskaźnik gdy tmp jest pusty) na nowy węzeł V tmp creator 27/47
Wstawianie na koniec (wer. 1) wersja ze wskaźnikiem na pierwszy węzeł (): (1) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do ostatniego węzła (2) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (3) przestawiamy wskaźnik w węźle wskazywanym przez tmp (lub wskaźnik gdy tmp jest pusty) na nowy węzeł V 28/47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () tail 29/47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () V tail creator 30/47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (2) przestawiamy wskaźnik w ostatnim węźle wskazywanym przez tail (lub wskaźnik gdy tail jest pusty) na nowy węzeł V tail creator 31/47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (2) przestawiamy wskaźnik w ostatnim węźle wskazywanym przez tail (lub wskaźnik gdy tail jest pusty) na nowy węzeł tail V creator 32/47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (2) przestawiamy wskaźnik w ostatnim węźle wskazywanym przez tail (lub wskaźnik gdy tail jest pusty) na nowy węzeł (3) przestawiamy wskaźnik tail na nowy węzeł tail V creator 33/47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (2) przestawiamy wskaźnik w ostatnim węźle wskazywanym przez tail (lub wskaźnik gdy tail jest pusty) na nowy węzeł (3) przestawiamy wskaźnik tail na nowy węzeł tail V creator 34/47 47
Wstawianie na koniec (wer. 2) wersja ze wskaźnikami na pierwszy i ostatni węzeł ( i tail): (1) tworzymy nowy węzeł (creator) z podaną wartością i wartością pustą jako adresem następnego () (2) przestawiamy wskaźnik w ostatnim węźle wskazywanym przez tail (lub wskaźnik gdy tail jest pusty) na nowy węzeł (3) przestawiamy wskaźnik tail na nowy węzeł tail V 35/47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację tail 36/47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) tmp tail 37/47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) tmp tail 38/47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) tmp tail 39/47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) tmp tail killer 40/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres tmp tail killer 41/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres tmp tail killer 42/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres (5) jeżeli jest wskaźnik na ostatni węzeł (tail), ustawiamy go na tmp tmp tail killer 43/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres (5) jeżeli jest wskaźnik na ostatni węzeł (tail), ustawiamy go na tmp tmp tail killer 44/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres (5) jeżeli jest wskaźnik na ostatni węzeł (tail), ustawiamy go na tmp (6) zwalniamy węzeł wskazywany przez wskaźnik dedykowany (killer) tmp killer tail 45/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres (5) jeżeli jest wskaźnik na ostatni węzeł (tail), ustawiamy go na tmp (6) zwalniamy węzeł wskazywany przez wskaźnik dedykowany (killer) tmp killer tail 46/47 47
Usuwanie na ko ń cu (1) jeżeli lista jest pusta zgłaszamy wyjątek i przerywamy operację (2) przechodzimy wskaźnikiem pomocniczym (tmp) od początku listy do przedostatniego węzła (ostatniego węzła, którego wskaźnik jest niepusty) (3) ustawiamy dedykowany wskaźnik (killer) na ostatni węzeł używając adresu zapisanego we wskaźniku węzła wskazywanego przez tmp (lub wskaźnika jeżeli tmp jest pusty, lub wskaźnika tail jeżeli istnieje) (4) ustawiamy wskaźnik węzła wskazywanego przez tmp (lub wskaźnik gdy tmp jest pusty) na pusty adres (5) jeżeli jest wskaźnik na ostatni węzeł (tail), ustawiamy go na tmp (6) zwalniamy węzeł wskazywany przez wskaźnik dedykowany (killer) tail 47/47