Typy wskaźnikowe, dynamiczne struktury danych
Sterta przeznaczenie stosu (przypomnienie) sterta
Typ wskaźnikowy type typ_wskaznikowy = ^typ_wskazywany; np.: tpr = ^real; { zmienne typu pr będą} { wskaźnikami na zmienne real } var pr : tpr; { można było też od razu napisać pr: ^real }
Wskaźnik wskaźnik jak każda zmienna jest określony przez: typ rozmiar – niezależny od typu zmiennej wskazywanej adres zbiór operacji – silnie ograniczony zmienna wskazywana to również zmienna własności wynikają z jej typu
Wskaźnik Rozmiar zmiennej wskaźnikowej: 4 bajty kompilator 32-bitowy: 4B adres kompilator 16-bitowy: 2B segment + 2B offset kompilator 16-bitowy, „mały” model pamięci: 2B offset, segment domyślny Adres bezwzględny w pamięci fizycznej a przestrzeń adresowalna wskaźnikiem
Najważniejsze, oczywiste Wskaźniki należy inicjalizować przed odwołaniem do zmiennych wskazywanych! alokacja nowej zmiennej (nie zapomnij o dealokacji) przypisanie adresu istniejącej zmiennej
Inicjalizacja wskaźnika Procedury new i dispose var pr : ^real; begin new(pr); { rezerwacja obszaru pamięci (6 bajtów - tyle ma real) } { Teraz można używać zarezerwowanego obszaru } pr^ := 2.718; { tak się odwołujemy do zarezerwowanego obszaru } pr^ := 2*pr^; { identycznie jak wyżej, pr^ jest typu real } writeln(pr^); { identycznie jak wyżej, pr^ jest typu real } { Po zakończeniu używania zarezerwowanego obszaru pamięci} { należy go koniecznie zwolnić!!! } dispose(pr); { Od tej chwili nie należy używać pr^ } end.
Zwolnienie wskaźnika Nie należy zwalniać niezarezerwowanego obszaru pamięci ani zwalniać ponownie tego samego (już zwolnionego) obszaru pamięci! Jeśli programista nie zwolni zarezerwowanego przez siebie obszaru pamięci, to nie będzie on dostępny dla następnych wywołań procedury new. Jednak po zakończeniu wykonywania programu wszystkie zarezerwowane obszary zostaną automatycznie zwolnione. Podobny efekt, ale przed zakończeniem programu, można uzyskać wykorzystując parę procedur mark i release.
nil wyróżnina, specjalna wartość zmiennej wskaźnikowej każdego typu wskaźnikowego nil w innych językach
Operator @ (TP) type tpr = ^real; var pr : tpr; x : double; t : array [0..5] of byte; begin pr = @x; pr^ := 2.718; { Nie wykonujemy dispose!!! } end. Operator @ zwraca typ wskaźnikowy nie związany z konkretnym typem bazowym (pointer). Dlatego możliwe jest „mieszanie typów” i odwoływanie się do tego samego obszaru pamięci traktując go jak zmienne różnych typów.
Wskaźniki do funkcji i procedur Po co nam wskaźniki do funkcji i procedur?
To się przydaje: wskaźniki do struktur zawierających wskaźniki czy na każdą zmienną alokowaną dynamicznie musi przypadać jeden wskaźnik? type tr2 = ^telem; telem = record { taki typ będzie czytelniejszy } { po narysowaniu } dana: real; { ew. inne pola } nast: tr2 end;
Lista dynamiczna type tr2 = ^telem; telem = record dana: real; { ew. inne pola } nast: tr2 end; var glowa, p1 : tr2; procedure wypisz(el: tr2); begin while el<>nil do writeln(el^.dana); el:=el^.nast end end;
begin new(p1); glowa := p1; { utwórz pierwszy element} p1^.dana := 10.0; p1^.nast := nil; {kolejny element} new(p1); { nowa zmienna dynamiczna! } p1^.nast := glowa; { wstawimy na początek listy } glowa := p1; { czyli to jest nowy początek listy } p1^.dana := 8.0; wypisz(glowa); { usun(glowa); } {wypada posprzątać } end.
Usuwanie listy procedure usun(el: tr2); var p: tr2; begin while el<>nil do p:=el; el:=el^.nast; dispose(p); { UWAGA: po tym nie można byłoby } { wykonać el:=el^.nast ! } end end;
Struktury dynamiczne Lista jednokierunkowa Lista dwukierunkowa Listy cykliczne Drzewo binarne Inne drzewa Lista a tablica – kiedy wybrać listę?
Wskaźniki (16-bit TurboPascal) Funkcja Addr(X):pointer. X może być zmienną lub identyfikatorem programu. Funkcja Ofs(X):word zwraca offset zmiennej (podprogramu) X. Funkcja Seg(X):word zwraca segment zmiennej (podprogramu) X. Funkcja Ptr(Seg, Ofs: Word): Pointer tworzy wskaźnik na podstawie podanych wartości segmentu i offsetu. Adresy wirtualne (kod 16-bit, maszyna 32-bit).
Wskaźniki (16-bit TurboPascal) Przypisania dozwolone pomiędzy różnymi typami wskaźników (nie sprawdzane)! funkcja memavail funkcja maxavail typ pointer
Wskaźniki (16-bit TurboPascal) Tablice predefiniowane mem* i port* (mem, memw, meml ) mem[$1234:$5678]:=85 Słowo kluczowe absolute var a: real; { nowa zmienna } b: integer absolute $b000:0000; { określenie konkretnego } {miejsca w pamięci } c: integer absolute a; { zmienna c w tym samym miejscu} { pamięci co zmienna a }
type tekran = array[0..24,0..79] of record znak, atrybut: byte end; tel = record k: integer; { klucz } wyst: integer; { liczba wystąpień } lewy,prawy: pel var ekran: tekran absolute $b800:0 { mode co80; dla mode mono: $b000:0 } e2: tekran; { np. do zapamiętywania zawartości ekranu } begin ekran[0,10].znak:=65; { litera A na 11 pozycji górnego wiersza ekranu } e2:=ekran; { zapamiętanie zawartości całego ekranu } {.....} ekran:=e2; { odtworzenie zawartości całego ekranu } end.
Wskaźniki UWAGA: struktura s zmniejszy dostępny obszar sterty o nie mniej niż sizeof (s) Fragmentacja pamięci – to jest problem: użyj maxavail
Przykłady Wstawianie elementu na koniec listy Lista dwukierunkowa. Wyszukiwanie elementu w liście. Lista cykliczna. Testowanie cykliczności listy. Drzewo binarne. Stos, kolejka. Uporządkowane drzewo binarne
Listy dynamiczne Uwaga na przypadki szczególne: lista jest pusta lista ma tylko jeden element jesteśmy na początku/końcu listy Uwaga na przypadek typowy: nie początek i nie koniec nie pustej listy
Przykład Uporządkowane drzewo binarne type pel = ^tel; tel = record k: integer; { klucz } wyst: integer; { liczba wystąpień } lewy,prawy: pel end; var korzen: pel;
procedure wstaw(var p:pel; dana: integer); begin if p=nil then new(p); with p^ do { problematyczne to with! } k:=dana; wyst:=1; lewy:=nil; prawy:=nil end else if dana=p^.k then inc(p^.wyst) if dana<p^.k then wstaw(p^.lewy,dana) wstaw(p^.prawy,dana) end;
procedure wypisz(p:pel); begin if p<>nil then wypisz(p^.lewy); writeln(p^.k:5,' (',p^.wyst,')'); wypisz(p^.prawy); end end; wstaw(korzen,3); wstaw(korzen,1); wstaw(korzen,4); { ....... } wstaw(korzen,5); wypisz(korzen); end.
Przykład Uporządkowane drzewo binarne – zalety tej struktury dynamicznej type pel = ^tel; tel = record k: integer; { klucz } wyst: integer; { liczba wystąpień } lewy,prawy: pel end; var korzen: pel;
Przykład Sortowanie listy przez proste wstawianie type tw = ^tel; tel = record dana: real; nast: tw end; const glowa: tw = nil; Mniej przypadków szczególnych: pomocniczy element na początku listy
type tw = ^tel; tel = record dana: real; nast: tw end; const glowa: tw = nil; procedure wstaw(co: tw); { wyszukuje miejsce i wstawia } procedure wypisz(p: tw); { wypisuje całą listę } procedure usun(var p: tw); { usuwa całą listę } function nowy(x:real):tw; { alokuje element i zwraca jego adres }
procedure wstaw(co: tw); { wyszukuje miejsce i wstawia } var p,p1:tw; begin { Zakładamy, że mamy na początku wartownika!!! } p1:=glowa; p:=glowa; while (p<>nil) and (p^.dana<co^.dana) do p1:=p; p:=p^.nast; end; { tutaj p=nil albo p^.dana>=co^.dana, czyli musimy wstawić PRZED p^, czyli ZA p1^ } wstaw_za(co,p1)
Jak wstawić za danym elementem? procedure wstaw_za(co, za_czym: tw); { wstawia co^ za za_czym^ } var p:tw; begin if za_czym = nil then writeln('Za nilem nie wstawiam'); halt end; co^.nast:=za_czym^.nast; za_czym^.nast:=co
procedure wypisz(p: tw); begin writeln('-----------'); while p<>nil do writeln(p^.dana:10:5); p:=p^.nast end end; procedure usun(var p: tw); var p1: tw; p1:=p^.nast; dispose(p); p:=p1
function nowy(x:real):tw; var p: tw; begin if maxavail<sizeof(p^) then writeln('Zabrakło pamięci'); halt end; new(p); p^.dana:=x; p^.nast:=nil; nowy := p
begin writeln('Początek: ',memavail); new(glowa); { to będzie element pusty} glowa^.dana:=-9999; { zakładamy, że nikt nie wstawi -10000 } glowa^.nast:=nil; wstaw(nowy(2)); { wstawianie w odpowiednie miejsce listy } wstaw(nowy(-333)); wstaw(nowy(4)); wypisz(glowa); usun(glowa); writeln('Koniec: ',memavail); end.