PROE wykład 7 kontenery tablicowe, listy dr inż. Jacek Naruniec
Prosty kontener oparty na tablicach Funkcja dodawanie pojedynczego słonia do kontenera: 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach W kodzie: Testujemy:
Prosty kontener oparty na tablicach Usunięcie ostatniego elementu analogicznie: 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy - 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy - 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy - 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy - 1. 3. Kopiujemy elementy starej tablicy do nowej. 4. Usuwamy starą, niepotrzebną tablicę. 41 42 43 44 45 46 47
Prosty kontener oparty na tablicach W kodzie:
Prosty kontener oparty na tablicach Operator indeksowania zwraca referencję na konkretnego słonia: W celu wykrycia błędów można wykorzystać wyjątki:
Prosty kontener oparty na tablicach Naturalnie w tym momencie musimy zadbać o ich odpowiednie wyłapanie:
Prosty kontener oparty na tablicach Wstawienie elementu w dowolne miejsce tablicy (operacja bardzo nieefektywna w wektorach!): 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 41 42 43 44 45 46 47 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej z pominięciem żądanego indeksu. 4. Kopiujemy nowy element w żądaną pozycję. 5. Usuwamy starą, niepotrzebną tablicę.
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 41 42 43 44 45 46 47 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej z pominięciem żądanego indeksu. 4. Kopiujemy nowy element w żądaną pozycję. 5. Usuwamy starą, niepotrzebną tablicę.
Prosty kontener oparty na tablicach Tutaj przykładowo wstawiamy w pozycji 2 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 41 42 43 44 45 46 47 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej z pominięciem żądanego indeksu. 4. Kopiujemy nowy element w żądaną pozycję. 5. Usuwamy starą, niepotrzebną tablicę.
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 41 42 43 44 45 46 47 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej z pominięciem żądanego indeksu. 4. Kopiujemy nowy element w żądaną pozycję. 5. Usuwamy starą, niepotrzebną tablicę.
Prosty kontener oparty na tablicach 1 2 3 4 5 6 7 11 12 13 14 15 16 17 21 22 23 24 25 26 27 31 32 33 34 35 36 37 41 42 43 44 45 46 47 1. Mamy jedynie starą tablicę. 2. Tworzymy nową tablicę o wielkości aktualnej tablicy + 1. 3. Kopiujemy elementy starej tablicy do nowej z pominięciem żądanego indeksu. 4. Kopiujemy nowy element w żądaną pozycję. 5. Usuwamy starą, niepotrzebną tablicę.
Prosty kontener oparty na tablicach W kodzie:
Prosty kontener oparty na tablicach Zamieniamy w szablon! W opcjach pliku (prawy/properties) Excluded from build = Yes Tu skompiluje się plik kontener.cpp
Prosty kontener oparty na tablicach W funkcjach zamieniamy Slon na T i odpowiednio definiujemy funkcje (Kontener.cpp):
Prosty kontener oparty na tablicach
Prosty kontener oparty na tablicach Możliwe ulepszenia zależnie od potrzeb: Przy dodawaniu kolejnego elementu do tablicy od razu rezerwowanie większej ilości pamięci na kolejne elementy (tak jak zachowuje się klasa vector). Usuwanie elementu nie musi oznaczać realokacji pamięci. Można usunąć wyjątki (szybsze działanie). Można usunąć szablony aby łatwiej debugować (dlatego dobrze na początku wykonać program BEZ szablonów). Usuwanie elementów ze środka, operatory, iteratory,
Prosty kontener oparty na tablicach Zalety: Prosta struktura Szybkie jeśli od razu rezerwujemy większą ilość pamięci Wady: Częste wykonywanie new/delete Wstawianie/usuwanie elementów ze środka tablicy zawsze wymaga kopiowania całej serii elementów.
Inne podejście kontenera Stwórzmy strukturę, która składa się z obiektu (będziemy trzymać słonie) i wskaźnika na inną strukturę tego samego typu: adres (np. 30)
Inne podejście do kontenera Stwórzmy obiekty Wezel. Każdy zawiera słonia Każdy zawiera wskaźnik na inny Wezel w1 trzyma adres na w2 w2 trzyma adres na w3 w3 trzyma adres na w1
Inne podejście do kontenera w1 w2 1 2 3 4 5 6 7 11 12 a15 13 14 15 16 a32 17 21 22 23 24 25 26 27 31 32 33 a11 34 35 36 37 41 42 43 44 45 46 47 w3
Inne podejście do kontenera Stwórzmy funkcję, która przyjmuje tylko wskaźnik na jeden węzeł: Na ekran zostanie wypisane imie i adres do w2 (bo taki wpisaliśmy)
Inne podejście do kontenera Jeśli mamy adres na drugi węzeł oznacza to, że możemy wypisać także imię przypisane do drugiego węzła: Jak widać w obiekcie typu Wezel wskazywanym przez *kolejny mamy wskaźnik do ostatniego już naszego węzła
Inne podejście do kontenera W takim razie wypiszmy dane wszystkich słoni: A co jeśli po raz kolejny weźmiemy kolejny węzeł?
Inne podejście do kontenera 1 2 3 4 5 6 7 11 12 a15 13 14 15 16 a32 17 21 22 23 24 25 26 27 31 32 33 a11 34 35 36 37 41 42 43 44 45 46 47 Według naszego obrazka przeszliśmy kolejno po adresach: a15, a32, a11, a15
Inne podejście do kontenera Można sobie to wyobrazić jako dłuuugą kolejkę na basen, w której chcemy znaleźć konkretną osobę. Adam Marysia - osoba ostatnia w kolejce (Adam) zna np. drugą osobę w kolejce (Marysia) i chce, żeby Marysia wykupiła karnet na dłużej, - Adam nie chce wychodzić z kolejki, żeby nie stracić miejsca. - każdy widzi tyko osobę przed sobą. Co robi? Przekaż dalej mówi kolejnej osobie, żeby przekazała kolejnej osobie itd. aż natrafi na Marysię, aby ta kupiła karnet na dłużej (nie ma innej Marysi w kolejce). [zdjęcie kolejki z www.mathworks.com]
Inne podejście do kontenera Inaczej: wskaźnik wskaźnik wskaźnik wskaźnik
Inne podejście do kontenera A może w sposób bardziej automatyczny?
Lista To jest właśnie lista! Spróbujmy jeszcze wykonać to samo, tylko stwórzmy węzły w sposób dynamiczny (przez new, wypiszimionasloni działa identycznie jak poprzednio): Program działa identycznie (ale oczywiście adresy się zmieniają za każdym wywołaniem programu, bo nie wiemy jaki tym razem adres przydzieli system)
Lista Oczywiście obiekty należałoby skasować, bo stworzyliśmy 3 obiekty w pamięci:
Lista Ale pamiętajmy, że trzymając pierwszy obiekt możemy dostać się do każdego kolejnego, więc napiszmy funkcję, która będzie kasować wszystkie elementy mając dostęp tylko do pierwszego węzła: btw dlaczego tak jest niepoprawnie?
Lista Analogicznie mając pierwszy element możemy stworzyć nowy węzeł i zawsze wstawić go na koniec naszej listy: tworzymy nowy węzeł w pamięci przechodzimy do ostatniego węzła zmieniamy jego wskaźnik na nasz nowy węzeł ustawiamy ostatni wskaźnik na pierwszy węzeł
Lista W pamięci teraz: 1 2 3 4 5 6 7 11 12 a15 13 14 15 16 a32 17 21 22 23 24 25 26 27 31 32 33 a11 34 35 36 37 41 42 43 44 45 46 47 1 2 3 4 5 6 7 11 12 a15 13 14 15 16 a32 17 21 22 23 24 25 26 27 31 32 33 a45 34 35 36 37 41 42 43 44 45 46 a11 47
Lista Dostęp do dowolnego elementu:
Lista Wszystkie operacje możemy wykonywać mając dostęp jedynie do pierwszego węzła! Zmodyfikujmy nasz program tak, aby widzieć tylko pierwszy węzeł (bo do reszty i tak umiemy się dostać):
Lista Innymi słowy mając dostęp do jednego elementu listy możemy: stworzyć kolejne elementy listy usunąć dowolne elementy listy (albo wszystkie) uzyskać dostęp do dowolnego elementu listy Elementy listy rezydują w pamięci (bo przecież każdy element stworzyliśmy przez new), ale mamy do nich dostęp tylko pośrednio. Możemy cały czas dbać o czystość pamięci (mimo że po new nie ma od razu delete)
Lista Przedstawiona lista jest listą cykliczną jednokierunkową (ostatni element wskazuje na pierwszy): wskaźnik wskaźnik wskaźnik wskaźnik
Lista Mogą być jeszcze listy (np.): acykliczne wskaźnik wskaźnik wskaźnik wskaźnik
Lista Dwukierunkowe: wskaźnik 1 wskaźnik 2 wskaźnik 1 wskaźnik 2 wskaźnik 1 wskaźnik 2
Lista Wystarczy, że klasa kontenera będzie zawierała tylko wskaźnik na pierwszy węzeł, bo przecież i tak do pozostałych węzłów potrafimy pośrednio dotrzeć.
Lista Utworzenie pustej listy (lista acykliczna) pierwszy NULL
Lista Przejście do konkretnego węzła. W przypadku chęci dostępu do nieistniejącego elementu zwracany jest NULL. p p P = NULL
Lista Dodanie kolejnego elementu na koniec listy stworzenie nowego węzła w pamięci (przez new) przejście na koniec listy zmiana wskaźnika ostatniego węzła na adres nowo stworzonego elementu ustawienie wskaźnika ostatniego węzła na NULL
Lista Usunięcie ostatniego elementu listy Dla przykładu: Przechodzimy do węzła o indeksie 3 pierwszy Usuwamy węzeł 4 Ustawiamy p3 na NULL p0 Indeks=1 Indeks=4 Indeks=3 Indeks=2 p1 p3 p4 = NULL p2
Lista Usunięcie ostatniego elementu listy:
Lista Usunięcie elementu listy Dla przykładowego węzła o indeksie 3: Przechodzimy do węzła o indeksie 2 Zapisujemy p3 pod tmp(wskaźnik z kasowanego węzła) Usuwamy węzeł 3 pierwszy Ustawiamy p2 na tmp p0 Indeks=1 Indeks=4 Indeks=3 Indeks=2 p1 p3 p4 = NULL p2
Lista Usunięcie elementu listy
Lista Operator indeksowania i wielkość kontenera: Operator [] zwraca referencję na słonia a nie na węzeł! Powinniśmy wprowadzić metodę size(), bo liczba_elementow jest prywatna.
Lista Oczywiście porządna lista powinna mieć destruktor kasujący wszystkie elementy po kolei:
I na końcu - szablon Wszystkie obiekty Slon zamienione na TYP.
I na końcu - szablon W pliku cpp:
Lista Działanie:
Lista Dodanie dwóch list, rozdzielenie ich jest banalnie proste! pierwszy pierwszy p0 Indeks=1 p0 Indeks=1 Indeks=2 p1 Indeks=2 p1 p2 p2