NMIN102 Programování 2 --- 2/2 Z, Zk Pavel Töpfer Katedra softwaru a výuky informatiky MFF UK MFF Malostranské nám., 4. patro, pracovna 404 pavel.topfer@mff.cuni.cz http://ksvi.mff.cuni.cz/~topfer Pavel Töpfer, 2019 Programování 2-1 1
Přehled učiva Ukazatel, dynamicky alokované proměnné, dynamické datové struktury Operace s lineárními spojovými seznamy Halda, další algoritmy vnitřního třídění, K-tý nejmenší prvek Binární soubory, vnější třídění Dynamické programování Grafy základní pojmy, reprezentace grafu v programu Programová realizace základních grafových algoritmů Binární stromy, binární vyhledávací stromy, obecné stromy Aritmetické notace, vyhodnocení aritmetického výrazu Vyvážené stromy (AVL-stromy, B-stromy) Hešování, hešovací tabulky Modulární programování Objektové programování Pavel Töpfer, 2019 Programování 2-1 2
Studijní zdroje Prezentace z přednášek (často rozšířené a doplněné) a ukázkové programy - aktuálně vždy po přednášce na webu přednášejícího Pavel Töpfer: Algoritmy a programovací techniky Prometheus Praha 1995, 2. vydání 2007 tištěná v knihovnách, k zakoupení pouze jako e-kniha https://www.prometheus-eknihy.cz/ Martin Mareš, Tomáš Valla: Průvodce labyrintem algoritmů CZ.NIC Praha 2017 text pdf zdarma ke stažení http://pruvodce.ucw.cz/ https://knihy.nic.cz/ Pavel Töpfer, 2019 Programování 2-1 3
Zápočet - o udělení rozhoduje cvičící * domácí úkoly, příp. písemky, práce na cvičeních * zápočtový program (náročnější než v ZS) + písemná dokumentace k zápočtovému programu Zkouška - k účasti na zkoušce není nutné předchozí získání započtu - zahrnuje i učivo předmětu NMIN101 Programování 1 - část písemná a ústní (zkouška není u počítačů) * písemná část malý příklad operace s ukazateli a dynamickými datovými strukturami * písemná část velký příklad vyřešení rozsáhlejšího algoritmického problému * v ústní části diskuse o řešení písemky + teorie Pavel Töpfer, 2019 Programování 2-1 4
Alokace paměti - způsob přidělování paměťového prostoru proměnným: 1. statická alokace - paměť přidělena pevně na celou dobu výpočtu - překladač zná umístění efektivní kód při přístupu k proměnné - použití pro proměnné deklarované v hlavním programu - překročení kapacity prostoru pro statická data ohlásí překladač Pavel Töpfer, 2019 Programování 2-1 5
2. alokace na zásobníku (stack) - pro proměnné deklarované v procedurách a funkcích - paměť je přidělována v aktivačních záznamech na zásobníku (je v nich také návratová adresa a další technické údaje) - paměť přidělena vždy při zavolání procedury, při ukončení je opět uvolněna (lokální proměnné zanikají, jejich hodnoty se ztrácejí) - výhoda: úspora paměti (proměnné nemající momentálně význam nezabírají místo), umožnění rekurzivních volání (více exemplářů) - překročení kapacity zásobníku běhová chyba Stack overflow Pavel Töpfer, 2019 Programování 2-1 6
3. dynamická alokace na haldě (heap) - přidělování paměti na explicitní žádost programu během výpočtu - na přidělené úseky paměti program odkazuje pomocí speciálních proměnných typu ukazatel (paměťových adres) - nepotřebnou paměť program opět sám explicitně uvolňuje - uvolňování úseků paměti nemusí být v opačném pořadí než jejich přidělování jako je tomu u zásobníku vznikají díry (fragmentace paměti) - překročení kapacity haldy běhová chyba Heap overflow Pavel Töpfer, 2019 Programování 2-1 7
Výhody dynamické alokace - proměnné existují jen po dobu, kdy jsou skutečně potřeba - velikost podle skutečné potřeby (až po zjištění velikosti vstupních dat) - na haldě může být k dispozici více paměti než ve statickém segmentu a na zásobníku - práce s ukazateli, možnost vytvářet dynamické datové struktury Nevýhody dynamické alokace - dynamicky alokované proměnné nemají svůj jednoznačný identifikátor, přistupuje se k nim prostřednictvím ukazatelů pomalejší přístup k datům větší nebezpečí chyby při psaní programu Pavel Töpfer, 2019 Programování 2-1 8
Práce s dynamicky alokovanými proměnnými - program žádá o přidělení paměti určité velikosti (buď přímo uvede počet bytů, nebo chce alokovat proměnnou určitého typu) - paměťovou adresu přidělené paměti program převezme do speciální proměnné typu ukazatel - pomocí ukazatele program k dynamicky alokované proměnné přistupuje a manipuluje s ní jako s každou jinou proměnnou příslušného typu - když už proměnná není potřeba, program ji uvolní ( možnost využití uvolněné paměti pro následné alokace) Pavel Töpfer, 2019 Programování 2-1 9
Halda (heap) - paměť určená pro realizaci dynamických alokací - správce haldy = program na evidenci přidělených a uvolněných úseků, sousední volné úseky spojuje (aby byly větší souvislé kusy) - obsazené úseky nelze v paměti přesouvat (vedou na ně odkazy z programu) fragmentace paměti - nové žádosti o alokaci se realizují v uvolněných úsecích haldy, strategie přidělování paměti first-fit (první použitelný) a best-fit (nejmenší použitelný úsek) - automatické uvolňování a posouvání úseků = garbage collector v Pascalu není - nelze-li uspokojit žádost o alokaci běhová chyba Heap overflow Pavel Töpfer, 2019 Programování 2-1 10
Dynamicky alokované proměnné v Pascalu Typ ukazatel pro uložení jedné paměťové adresy proměnná může ukazovat vždy jen na data určitého typu type T = ; Uk = ^T; var P, Q: Uk; {co chceme dynamicky alokovat} {typ ukazatel na T } {proměnné typu ukazatel na T } Lze psát přímo var P, Q: ^T; například type T1 = record X: real; Y: char end; var P1: ^T1; type T2 = array[1..10] of integer; var P2: ^T2; Pavel Töpfer, 2019 Programování 2-1 11
Alokace paměti procedura new(p) - velikost požadované paměti je určena datovým typem, pro který je deklarován ukazatel P (doplní překladač) - alokuje se zpravidla nejbližší násobek 8 bytů (věc implementace) - ve výstupním parametru P procedura vrací adresu přidělené paměti - pozor původní hodnota proměnné P se ztratí Uvolnění paměti procedura dispose(p) - velikost uvolňované paměti je určena datovým typem, pro který je deklarován ukazatel P (doplní překladač) - ve vstupním parametru P se proceduře předává adresa uvolňované paměti - pozor hodnota proměnné P se přitom nezmění (P dál ukazuje na uvolněnou paměť) Pavel Töpfer, 2019 Programování 2-1 12
Zjištění stavu paměti funkce MemAvail velikost volné paměti na haldě v bytech funkce MaxAvail velikost největšího souvislého volného úseku paměti na haldě v bytech Operace s ukazateli - získání nové hodnoty (dosavadní hodnota ukazatele se ztratí) * voláním new new(p) * dosazením Q:=P dosazovat lze jen mezi ukazateli téhož typu - konstanta nil = neukazuje nikam Q:=nil kompatibilní se všemi ukazatelovými typy význam: speciální hodnota těch ukazatelů, které momentálně nikam neukazují prevence před možným chybným odkazem, zakončení dynamických datových struktur (bude později) Pavel Töpfer, 2019 Programování 2-1 13
- porovnání ukazatelů pouze relační operátory =, <> if P = Q then while P <> nil do porovnávat lze jen ukazatele téhož typu - přístup k dynamicky alokované proměnné, na kterou ukazuje P: P^ P1^.X := 3.14; write(p1^.y); for I:=1 to 10 do read(p2^[i]); - k jedné dynamicky alokované proměnné můžeme přistupovat zároveň (nebo postupně) pomocí více různých ukazatelů new(p); Q := P; Q^.A := 15; write(p^.a); {vypíše 15 } dispose(q); {uvolní paměť alokovanou pomocí new(p)} Pavel Töpfer, 2019 Programování 2-1 14
Rozlišujte: Q := P; dosazení hodnoty ukazatele P do ukazatele Q, tzn. Q začne ukazovat na tu proměnnou, na kterou právě ukazuje P (neprovádí se nová alokace) Q^ := P^; dosazení hodnoty dynamicky alokované proměnné P^ do proměnné Q^ (ta musí být naalokována), hodnoty ukazatelů P, Q se tím nezmění if Q = P then test, zda ukazatele P, Q ukazují na stejné místo v paměti if Q^ = P^ then test, zda hodnoty dynamicky alokovaných proměnných P^, Q^ jsou stejné (mohou to ale být různé proměnné) Pavel Töpfer, 2019 Programování 2-1 15
Pozor na ztrátu přístupu k dynamicky alokované proměnné! Provedením new(p) nebo P:=Q se ztratí dosavadní hodnota proměnné P. Pokud na proměnnou P^ neukazuje ještě jiný ukazatel, stane se proměnná P^ nedostupnou nelze nadále pracovat s její hodnotou, ale není ani možné uvolnit ji ( smetí v paměti). Pozor na chybné použití ukazatelové proměnné, která ukazuje na uvolněné místo v haldě! Dispose(P); P^.A:=10; Po provedení dispose(p) se hodnota P nezmění, P nyní ukazuje do haldy na místo evidované jako uvolněný úsek. Obsah této proměnné využívá správce haldy pro svoji organizaci seznamu uvolněných úseků zápis do P^ může znamenat destrukci seznamu uvolněných úseků haldy. Pavel Töpfer, 2019 Programování 2-1 16
Procedury mark release - alternativní způsob uvolňování paměti (alternativa k proceduře dispose) - volání mark(p) do ukazatele P se zaznamená adresa vrcholu obsazené části haldy (P je libovolného ukazatelového typu) - volání release(p) uvolní se obsazená část haldy od adresy zaznamenané v ukazateli P - s haldou se tedy pracuje tak trochu jako se zásobníkem - příklad použití: několik procedur v programu používá schéma * na začátku procedury mark * v proceduře podle potřeby opakovaně new * na konci procedury najednou uvolnit pomocí release všechno, co procedura alokovala - nekombinovat dispose a release v jednom programu! syntaxe to připouští, ale výpočet může být nekorektní Pavel Töpfer, 2019 Programování 2-1 17
Dynamické datové struktury Vytvářejí se během výpočtu z dynamicky alokovaných proměnných, průběžně mohou podle potřeby měnit svoji velikost. Realizace: dynamicky se alokuje záznam, který má mezi svými položkami jednu nebo více položek typu ukazatel, ty ukazují na další dynamicky alokované záznamy, atd. (ukončení pomocí nil). V jedné dynamické struktuře mohou být provázány záznamy téhož typu nebo různých typů (méně časté). Příklady: lineární spojový seznam, binární strom, obecný strom, graf. Pavel Töpfer, 2019 Programování 2-1 18
Dynamická pole - v Pascalu existují standardně pouze statická pole počet prvků musí být znám už v době překladu - pomocí New lze sice pole alokovat dynamicky, ale musí mít rovněž předem známou velikost - dynamické pole lze vytvořit v TP pomocí alokační procedury GetMem (a uvolnit pomocí FreeMem) až v době výpočtu určíme potřebný počet prvků - i v tomto případě je velikost alokovaného pole dále neměnná (nelze ho podle potřeby třeba dodatečně zvětšit, jako v některých jiných programovacích jazycích) Pavel Töpfer, 2019 Programování 2-1 19
GetMem(P, N) žádost o dynamické přidělení N bytů paměti, adresu přidělené paměti chceme převzít do ukazatele P P může být ukazatel libovolného typu, jeho typ neurčuje velikost alokace jako v případě procedury New FreeMem(P, N) žádost o uvolnění N bytů paměti, adresu uvolňované paměti předáváme v ukazateli P P může být opět ukazatel libovolného typu, jeho typ neurčuje velikost uvolňované paměti jako v případě procedury Dispose Pavel Töpfer, 2019 Programování 2-1 20
program DynamickePole; {příklad: jednorozměrné pole celých čísel} type Pole = array[1..maxint] of integer; Uk = ^Pole; var P : Uk; N : integer; {potřebný počet prvků pole} V : integer; {potřebná velikost alokace paměti} I : integer; begin N := 200; {libovolná hodnota, kterou získáme na základě vstupních dat a předchozího výpočtu} V := N * SizeOf(integer); GetMem(P,V); {vytvoření pole o N položkách} for I:=1 to N do P^[I] := I; {libovolná práce s prvky pole P^} for I:=1 to N do write(p^[i]:4); FreeMem(P,V); {uvolnění paměti - zrušení pole} end. Pavel Töpfer, 2019 Programování 2-1 21