* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Jak na ukazatele * * ======================================================================= * * sepsal: Mircosoft (http://mircosoft.mzf.cz) * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Soubor doporucuji otevrit primo v Pascalu, zobrazi se tak specialni znaky a da se koukat do napovedy. Obsah: 1 - Zaklady (co to je ukazatel, co umi a jak se s nim pracuje) 2 - Vytvareni a ruseni dynamickych promennych (obecne) 3 - Dynamicke spojove seznamy 4 - Dynamicka pole 5 - Proceduralni promenne ****************************************************************************** 1 - Zaklady ****************************************************************************** Ukazatel je promenna, ktera obsahuje adresu, tedy vlastne "ukazuje" na nejake konkretni misto v pameti. Deklarace promenne typu ukazatel: var ukazatel:^Neco; nebo var ukazatel:pointer; Prvni moznost predstavuje ukazatel s typem. Ten se vyznacuje tim, ze je urcen k ukazovani pouze na jeden konkretni typ (zde typ Neco). Napr. ukazatel var Uk:^integer muze ukazovat na typ integer. Symbol ^ se v tomto pripade da cist "ukazatel na". Pristup k promenne, na kterou ukazatel ukazuje: Napr.: ukazatel^:=..., x:=ukazatel^, inc(ukazatel^) apod. Kdyz se odkazujeme na cil ukazatele, predpoklada se, ze ukladame vzdy ten typ dat, na jaky je ukazatel "staveny". Pokud tedy mame ukazatel na typ integer, muzeme psat uk^:=cislo, kde cislo je hodnota typu integer. Pri pokusu o vlozeni jineho typu je z toho chyba "Type mismatch". Druha moznost je beztypovy ukazatel. Ten muze ukazovat na cokoli, ale prace s nim uz je trochu tezsi. Nemuzeme se pres nej jen tak odkazovat na promennou nejakeho konkretniho typu, i kdyz na ni treba ve skutecnosti ukazuje. Ukazatel je totiz beztypovy a prekladac proto nepozna, jestli ta hodnota, kterou tam treba chceme ulozit, ma spravny typ a nezpusobi problemy, a tak nam to radsi zakaze. Takove situace se obvykle resi pretypovanim ukazatele na ukazatel na typ, s jakym zrovna chceme pracovat. Pozor! Kdyz napiseme ukazetel:=neco, menime hodnotu ukazatele (tedy menime adresu, na kterou ukazuje). Kdyz napiseme ukazatel^:=neco, menime hodnotu promenne, na kterou ukazatel ukazuje. Neplest!!! Kazdy ukazatel bez ohledu na typ je vzdy 4 B velky. Sklada se ze dvou hodnot typu word: segment a ofset adresy, na kterou ukazuje (to plati v realnem modu, ve kterem TP i cely DOS obvykle pracuje). Libovolny ukazatel se da pretypovat na ukazatel na libovolny jiny typ. Take se da (mimo jine) pretypovat na typ longint, coz se velice hodi, pokud s ukazatelem chceme provadet aritmeticke operace (napr. zvysit jeho hodnotu o nejake cislo, aby ukazoval o par bytu dal apod.). Ukazateli se da priradit hodnota ctyrmi zpusoby: 1) Vytvorenim dynamicke promenne (viz dalsi kapitolu). 2) Prirazenim adresy nejake promenne: ukazatel:=@promenna nebo jinym zapisem ukazatel:=Addr(promenna). Oba zapisy jsou naprosto rovnocenne. Operator @ se da cist jako "adresa". Timto zpusobem muzeme ukazatel jakehokoli typu nasmerovat na promennou taktez jakehokoli typu, treba i jineho nez ma ten ukazatel. Toho se nekdy s vyhodou vyuziva (napr.: jak rozlozit realne cislo na jednotlive byty a poslat ho pres COM? Nasmerovat na nej ukazatel na pole bytu a pak poslat toto pole), jindy to muze zpusobit zmatek a problemy, ale to jen v pripade nespravneho pouziti (napr. na promennou typu byte nasmeruji ukazatel na longint a priradim ji pres nej hodnotu. Jenze byte ma 1 B, zatimco longint ctyri, takze zbyvajici 3B se zapisou do pameti "za" promennou, coz se ale nesmi a byva to pricinou chyb typu "Program provedl nepovolenou operaci a bude ukoncen"). 3) Rucnim zadanim hodnot segmentu a ofsetu: ukazatel:=Ptr(Segment,Ofset), kde Segment a Ofset jsou hodnoty typu word. Za zminku stoji i funkce Seg a Ofs, ktere vraceji segment nebo ofset dane promenne. 4) Prirazenim hodnoty jineho ukazatele: ukazatel1:=ukazatel2 (ted ukazatel1 ukazuje na stejnou adresu jako ukazatel2). Pozor! Zapis ukazetel1^:=ukazatel2^ (promenne, na kterou ukazuje ukazatel1, prirad hodnotu promenne, na kterou ukazuje ukazatel2) je mozny pouze tehdy, kdyz oba ukazatele ukazuji na nejake promenne a radsi jenom kdyz oba jsou stejneho typu (jde to i s ruznymi typy, ale to uz jsou pokrocilejsi a slozitejsi techniky). Nikdy nesmime nic zapsat do pameti "nekam mimo". Ukazatel muze mit i hodnotu Nil. To je hodnota, pri ktere "neukazuje na nic". Prakticky to znamena, ze ukazuje na adresu 0:0. Tato specialni hodnota je velmi dulezita, protoze podle ni poznate, jestli ukazatel uz na neco ukazuje nebo ne. Ale pozor, vetsinou mu hodnotu Nil musite priradit sami rucne! Ukazatele muzeme i porovnavat: if ukazatel1=ukazatel2 then oba ukazuji na tu samou adresu (promennou). Take jdou porovnavat s hodnotou nil: if ukazatel=nil then... ma proste hodnotu nil, vyuzit to muzete jak chcete. Porovnavani <,>, >= a <= tu nefunguje. Pouze = a <>. Vsimnete si, ze jedine hodnoty, se kterymi muzeme nejaky ukazatel porovnat, jsou bud jine ukazatele nebo hodnota Nil. NIC JINEHO nejde! ****************************************************************************** 2 - Vytvareni a ruseni dynamickych promennych ****************************************************************************** Vytvorit dynamickou promennou znamena alokovat prislusne mnozstvi pameti na hromade (to je ta cast pameti pro tyto ucely vyhrazena, ma obvykle neco kolem 640 kB). K tomu, abychom k takto alokovanym promennym meli pristup, slouzi prave ukazatele. U bezne promenne deklarovane za slovem Var (staticke) je jeji adresa jasna, pamet pro ni vyhradi uz prekladac a za behu programu se nemeni. Proto muzeme pouzivat jeji identifikator (jmeno) a prislusnou adresu (ktera se pak misto tohoto jmena objevi ve strojovem kodu programu) za nas vsude dosadi prekladac. Dynamicke promenne vytvarime ale az za behu programu. Prekladac to nezaridi, protoze touhle dobou uz davno nepracuje. Navic nikdy nevime, kde presne je na hromade zrovna misto, kam muzeme novou promennou umistit. Z toho vyplyva, ze adresa dynamicke promenne muze byt pri kazdem spusteni programu jina a jedine, co nam zbyva, je vzdycky na ni nasmerovat nejaky ukazatel a pristupovat k ni jeho prostrednictvim. K vytvareni dynamickych promennych (alokaci pameti) slouzi procedury New a Getmem, k jejich ruseni (dealokaci, uvolneni pameti) slouzi Dispose a Freemem. Tyto procedury se postaraji o to nejslozitejsi, tj. nalezeni dostatecne velkeho neobsazeneho mista na hromade, jeho "zabrani" apod., my uz dostaneme jenom vysledek. Co chtit vic :-). Nejdrive se vzdycky vyplati zkontrolovat, jestli se dana dyn. promenna do pameti vejde. Na to jsou funkce Memavail a Maxavail. Prvni vrati souhrnnou velikost veskere volne pameti na hromade v bytech, coz je ale celkem k nicemu. Druha vrati velikost (opet v B) nejvetsiho souvisleho volneho useku pameti. Hromada totiz nemusi byt jen na jednom konci plna a na druhem prazdna, ale v datech mohou byt i "diry", ktere se sice zapocitaji do Memavail, ale nase nova promenna by se do takove diry nemusela vejit. Takze obecne: if sizeof(typ pozadovane dyn. promenne) <= Maxavail then OK, muzeme promennou bez obav alokovat else neni dost pameti, nejde to. Ukazatele s typem ------------------- Pro vytvareni dynamickych promennych se nejcasteji pouziva procedura New(P), kde P je promenna - ukazatel na nejaky konkretni typ. New vytvori dynamickou promennou prislusneho typu a ukazatel P na ni nasmeruje. Od tedka pozor: neprepiste necim ukazatel P, protoze jestli to udelate (a jeho hodnotu neulozite treba nekam jinam), ztratite k dynamicke promenne pristup a bude se vam az do konce programu valet na hromade a zabirat tam misto. Prakticky neexistuje zpusob, jak se pak k takoveto "ztracene" promenne dostat. Takto vytvorena dynamicka promenna se da zrusit procedurou Dispose(P), kde P je ukazatel na tu promennou. Dispose zrusi dyn. promennou, tj. uvolni pamet, kterou zabirala. Ale pozor, ukazatel P se nemeni, po skonceni procedury ukazuje porad tam, kde promenna predtim byla. Akorat ze ted uz na to misto nic zapisovat nesmime, protoze ta pamet uz neni "nase". Proto, aby bylo jasne, ze ukazatel uz na nic neukazuje, doporucuji mu hned pote priradit hodnotu nil: ukazatel:=nil. Potom, kdykoli neco s nejakym ukazatelem delam a nejsem si jist, jestli na neco ukazuje, vzdycky otestuji, jestli nahodou nema hodnotu nil. Jestli ano, vim, ze na nic neukazuje. Je vhodne pred prvnim pouzitim priradit hodnotu nil kazdemu ukazateli, nic se tim nezkazi. Pokud rusite promennou, ktera neexistuje (tedy: pokud volate Dispose(P), kde P zrovna na zadnou promennou neukazuje), program spadne s chybovou hlaskou "Invalid pointer operation". Pokud se chystate tvorit vice dynamickych promennych a pak je chcete rychle vsechny najednou zrusit, da se pouzit kombinace procedur Mark a Release: Mark(P) - do parametru P (typu pointer) ulozi aktualni pozici konce hromady. Ted si muzete vesele alokovat... Release(P) - zrusi najednou vsechny dynamicke promenne, ktere byly vytvoreny po zavolani procedury Mark(P) (P musi byt ten ukazatel, ktery vyplivl Mark). Tyto dve procedury by se nemely pouzivat na netypove ukazatele. Ja osobne tento system prilis v lasce nemam, protoze se pri nem snadno neco prehledne (co jsem alokoval pred Markem a co po nem?) a pak vznikaji chyby, nejcasteji "Invalid pointer operation". Ukazatele bez typu -------------------- Az doted jsme se nestarali o velikost vytvorene dynamicke promenne. To proto, ze byla jednoznacne urcena typem, na ktery ukazatel ukazoval. Ovsem beztypovy ukazatel uz nam praci nijak neusnadni, a tak musime velikost promenne zadat rucne. Pouziva se procedura Getmem(P,Velikost), kde P je promenna typu pointer a Velikost je velikost pozadovane dynamicke promenne v bytech. Dynamicka promenna se zrusi procedurou Freemem(P,Velikost), kde P je ukazatel ziskany z Getmem a Velikost je TA SAMA velikost (opet v B), jakou jsme predtim zadali do Getmem. Pokud P na zadnou dynamickou promennou neukazoval nebo nesouhlasi velikost, je z toho opet "Invalid pointer operation". Beztypove dynamicke promenne se obvykle vyuzivaji v procedurach typu Getimage nebo Putimage. Primo (rucne) jejich hodnotu zadavat nemuzeme, prave proto, ze jsou beztypove. Jestli to potrebujeme, musime ukazatel pretypovat na nejaky typovy ukazatel, aby prekladac pochopil, s jakymi hodnotami chceme pracovat. ****************************************************************************** 3 - Dynamicke spojove seznamy ****************************************************************************** Tyto datove struktury slouzi k ukladani datovych polozek, jejichz pocet nezname nejen pri psani programu, ale ani pred zacatkem ukladani, coz je velka vyhoda. V kazde polozce musi byt alespon jeden ukazatel, coz jsou v podstate "zbytecne" zaplacane 4 byty pameti. To je sice trochu nevyhoda, ale obvykle nevadi. Uplatni se zde hlavne typove ukazatele v kombinaci s typem record. Jedna zajimava vlastnost ukazatelu je, ze typ "ukazatel na NECO" (NECO je nejaky jiny typ) se da definovat drive, nez je definovan typ NECO (to je vyjimka z pravidla, ze vsechno se musi definovat drive, nez to pouzijeme, vytvorena specialne pro tyto pripady). Tedy napriklad: type Tady jsme definovali nejdrive typ UkNaZaznam jako ukazatel na typ Zaznam (ktery v te dobe UkNaZaznam = ^Zaznam; jeste neexistoval). Potom jsme definovali typ Zaznam, ve kterem uz je obsazena polozka Dalsi Zaznam = record typu UkNaZaznam. jmeno:string[30]; Na tomto priklade si ukazeme vytvoreni cislo:word; jednoducheho jednosmerneho dynamickeho seznamu. Dalsi:UkNaZaznam; Predpokladejme, ze deklarujeme ukazatel end; var Prvni: UkNaZaznam; a vytvorime dynamickou promennou, na kterou bude ukazovat: New(Prvni). Teto promenne priradime hodnoty: with Prvni^ do begin jmeno:='prvni prvek seznamu'; cislo:=1; dalsi:=nil; end; Prirazeni hodnoty Nil polozce (ukazateli) Dalsi je VELMI dulezite!!! Jen tak totiz muzeme potom poznat, ze uz na nic dalsiho neukazuje a tudiz tento prvek seznamu je posledni. Nyni mame v nasem seznamu jednu polozku. Druhou vytvorime nasledovne: New(Prvni^.Dalsi); - tim vznikla dynamicka promenna typu Zaznam, na kterou ukazuje polozka Dalsi promenne Prvni^. Nove dynamicke promenne muzeme opet priradit nejake hodnoty: with Prvni^.dalsi^ do begin jmeno:='druhy'; cislo:=2; dalsi:=nil; end; Vsimnete si, kde vsude se ty ^ pisou. Mozna to vypada napoprve slozite, takze to radsi nakreslim: Prvni^ Prvni^.dalsi^ ÚÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄ¿ Prvni ÄÄÄ>³ jmeno ³ ÚÄÄÄ>³ jmeno ³ ³ cislo ³ ³ ³ cislo ³ ³ dalsiÄÅÄÄÄÙ ³ dalsiÄÅÄÄÄ> nil ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ Prvky seznamu muzeme tvorit dale: New(Prvni^.dalsi^.dalsi) atd. Protoze by bylo neprakticke dostavat se k dalsim prvkum seznamu takto pres prvni (asi bychom se uteckovali a u^ovali k smrti), je vyhodne deklarovat jeste jeden ukazatel stejneho typu (zde tedy UkNaZaznam), nazvat ho treba Vybrany a jim se pohybovat v seznamu: Vybrany:=Prvni - Vybrany ukazuje na tu samou dyn. promennou jako Prvni, Vybrany:=Vybrany^.dalsi - skocili jsme o prvek dal, Vybrany ted ukazuje na Prvni^.dalsi^ (druhy prvek seznamu). Pokud bychom toto zopakovali, dostal by Vybrany hodnotu Nil (mame vytvorene jen dve polozky a ukazatel Dalsi u te posledni ma hodnotu Nil) a tim bychom bezpecne zjistili, ze jsme opravdu na konci seznamu. Ovsem take tim konec seznamu "prejedeme" (nelze se vratit "o prvek zpet"). Chceme-li misto toho vytvorit novy prvek na konci seznamu, zustaneme s ukazatelem Vybrany na predposlednim prvku a napiseme: New(vybrany^.dalsi). To uz je treti prvek. Posuneme na nej ukazatel: Vybrany:=Vybrany^.dalsi; a vytvorime ctvrty: New(vybrany^.dalsi). Jednoduche a ucinne. Jak bychom se k obsahu ctvrteho prvku dostali bez promenne Vybrany? Takto: Prvni^.dalsi^.dalsi^.dalsi^.polozka_prvku. Ano, velmi odporne :-). Nezapominejte vzdycky ukazateli Dalsi v poslednim prvku seznamu priradit hodnotu Nil, abyste vedeli, ze tam seznam konci. Porovnavat ukazatele muzete vzdy jen s jinymi ukazateli nebo s Nil! Kdyz chceme ukazatel Vybrany posunout o prvek zpet, musime skocit na zacatek a pak opet lezt po jednom prvku dopredu. Jina moznost je vytvorit si obousmerny seznam, kde v kazdem prvku je krome polozky Dalsi jeste polozka Predchozi (napr.), ktera ukazuje na predchozi prvek. Pak je sice pri vytvareni novych prvku trochu vic prace, ale jsou pripady, kdy to za to stoji. Princip prace s timto seznamem je podobny jako u jednosmerneho, takze ho tu zvlast popisovat nebudu. Manipulace s prvky seznamu: --------------------------- Pri vkladani prvku doprostred seznamu budeme potrebovat jeden pomocny ukaza- tel (Pom:uknazaznam). Vytvorime New(Pom). Ukazatelem Vybrany najedeme na prvek, za ktery chceme vlozit novy a pak postupujeme takto: Pom^.dalsi:=Vybrany^.dalsi; Vybrany^.dalsi:=Pom; a to je cele. U obousmerneho seznamu musime postupovat trochu jinak: Vybrany^.dalsi^.predchozi:=Pom; Pom^.dalsi:=Vybrany^.dalsi; Pom^.predchozi:=vybrany; Vybrany^.dalsi:=Pom; Vsimnete si, ze dost zalezi na poradi operaci, protoze do posledni chvile musime mit pristup k prvku za tim vkladanym prvkem. Schema pro jednosmerny seznam: na zacatku: ÚÄÄÄÄÄÄÄ¿ PomÄÄ>³ ... ³ ³ dalsiÄÅÄ>nil ÀÄÄÄÄÄÄÄÙ ÚÄ sem nekam ho chceme dostat ³ ¿ ÚÄÄÄÄÄÄÄ¿ V ÚÄÄÄÄÄÄÄ¿ Ú ³ ÚÄÄ>³ ... ³ ÚÄÄÄ>³ ... ³ ÚÄÄ>³ ÅÄÙ ³ dalsiÄÅÄÄÄÙ ³ dalsiÄÅÄÙ ³ Ù ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ À vysledek: ÚÄÄÄÄÄÄÄ¿ PomÄÄ>³ ... ³ ÚÄ>³ dalsiÄÅ¿ ³ ÀÄÄÄÄÄÄÄÙ³ ÀÄÄÄÄÄÄ¿ ³ ³ ³ ¿ ÚÄÄÄÄÄÄÄ¿³ ³ ÚÄÄÄÄÄÄÄ¿ Ú ³ ÚÄÄ>³ ... ³³ ÀÄ>³ ... ³ ÚÄÄ>³ ÅÄÙ ³ dalsiÄÅÙ ³ dalsiÄÅÄÙ ³ Ù ÀÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÙ À Pro obojsmerny seznam: na zacatku: ÚÄÄÄÄÄÄÄÄÄ¿ PomÄÄÄÄÄÄ>³ ... ³ nil<ÄÄÅpredchozi³ ³ dalsiÄÄÅÄ>nil ÀÄÄÄÄÄÄÄÄÄÙ ÚÄ sem ³ V ¿ ÚÄÄÄÄÄÄÄÄÄ¿ ÚÄÄÄÄÄÄÄÄÄ¿ Ú ³ ³ ... ³ ³ ... ³ ³ ³<ÄÄÄÄÅpredchozi³<ÄÄÄÄÄÄÅpredchozi³<ÄÄÄÄÄÄÅ ÅÄÄÄÄ>³ dalsiÄÄÅÄÄÄÄÄÄ>³ dalsiÄÄÅÄÄÄÄÄÄ>³ Ù ÀÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÙ À vysledek: ÚÄÄÄÄÄÄÄÄÄ¿ PomÄÄÄÄÄÄ>³ ... ³ ÚÄÅpredchozi³ ³ ³ dalsiÄÄÅÄÄÄÄ¿ ³ ÀÄÄÄÄÄÄÄÄÄÙ ³ ÀÄÄÄÄÄÄÄÄ¿^^ ³ ³³³ V ¿ ÚÄÄÄÄÄÄÄÄÄ¿ ³³³ ÚÄÄÄÄÄÄÄÄÄ¿ Ú ³ ³ ... ³<Ù³³ ³ ... ³ ³ ³<ÄÄÄÄÅpredchozi³ ³ÀÄÄÄÅpredchozi³<ÄÄÄÄÄÄÅ ÅÄÄÄÄ>³ dalsiÄÄÅÄÄÙ ³ dalsiÄÄÅÄÄÄÄÄÄ>³ Ù ÀÄÄÄÄÄÄÄÄÄÙ ÀÄÄÄÄÄÄÄÄÄÙ À Likvidace jednotlivych polozek seznamu se deje takto (napr.): kdyz chceme odstranit posledni prvek, staci najet ukazatelem Vybrany na predposledni prvek a napsat Dispose(Vybrany^.dalsi). Proc zrovna takhle? Protoze pak jeste napiseme Vybrany^.dalsi:=nil. A proc tohle? Protoze kdybychom to neudelali, uz by se nam velmi spatne trefovalo na posledni prvek. Na takovou operaci se nejlepe hodi prikaz while Vybrany^.dalsi<>nil do Vybrany:=Vybrany^.dalsi. Protoze procedura Dispose neprirazuje ukazateli hodnotu nil, nedal by se bez toho konec seznamu nejak jednoduse najit (museli bychom napred znat pocet prvku). Takze kdybychom nastavili Vybrany na posledni prvek a spustili Dispose(Vybrany), tak si pod sebou podrizneme vetev a uz se nedostaneme zpatky na predposledni prvek, abychom jeho polozce Dalsi priradili Nil. Toto plati i pro obousmerne seznamy, protoze pokud zrusite vybrany prvek, nemuzete se vracet pres jeho prave zrusenou polozku Predchozi. Mazani prvniho prvku je celkem jednoduche: Vybrany nastavime na druhy prvek, odstranime prvni (Dispose(Prvni);) a pak Prvni prepojime na Vybrany (Prvni:= Vybrany). Pozor, ze jestli si vymazeme nejaky prvek seznamu a nepostarame se o propojeni prvku pred nim a za nim, seznam se prerusi a uz neexistuje zadny zpusob, jak se k "odriznutym" prvkum dostat (snad jen pres primou adresu v pameti, ale to bychom ji nejdriv museli znat). Mazani od prostredka je o trochu slozitejsi. Na prvek ke smazani nastavime Vybrany. Dale potrebujeme jeden pomocny ukazatel (napr. Pom), ktery umistime na prvek pred nim. A jedeme: Pom^.dalsi:=Vybrany^.dalsi; Dispose(Vybrany); & je to. (museli jsme predchazejici prvek upravit tak, aby ukazoval az na ten za mazanym prvkem, jinak by se seznam prerusil) Trochu jinak komplikovane je to u obousmernych seznamu: Vybrany nastavime na prvek, ktery chceme smazat, a zacina motanice: Vybrany^.predchozi^.dalsi:=Vybrany^.dalsi; Vybrany^.dalsi^.predchozi:=Vybrany^.predchozi; a konecne Dispose(Vybrany). Temi dvema prikazy jsme zajistili "prepojeni" ukazatelu z prvku pred a za mazanym prvkem tak, ze ho jakoby "obejdou". Nebo muzete v jistem okamziku pouzit Mark a pak pres Release vyhodit treba cely seznam najednou, zalezi na okolnostech. Dodatek na zaver: doporucuji vsechny operace se seznamy si kreslit na papir (kolecka a sipky), protoze je velmi snadne se v nich dokonale zamotat (mam to mnohokrat vyzkousene :-) ). Pokud hledate nejaky prakticky priklad, podivejte se treba do zdrojaku nekterych mych jednotek. Napr. Woknows 3. Tam se do spojoveho seznamu ukladaji napr. jednotlive radky textu v dialogovych oknech (kde neni predem dano, kolik tech radku bude). Nebo VESA, procedury _Pushtext a _Poptext pouzivaji zasobnik tvoreny jednosmernym spojovym seznamem. Pozor! Kdyz vytvorite dynamickou promennou, bude jeji velikost zaokrouhlena nahoru na nejblizsich 16 B. To kvuli tomu, ze pocitac chce, aby kazda promenna mela ofsetovou cast adresy rovnou nule (a segmenty maji granularitu 16 B). Pokud tedy chcete delat spojovy seznam polozek o velikosti nekolik malo B (treba jedno cislo plus ukazatele, dohromady treba 6 B), vzdy bude nakonec kazda polozka zabirat minimalne 16 B. Pokud ma polozka treba 17 B, zaokrouhli se to zase nahoru - na 32. Takze si dobre rozmyslete, kdy se vyplati spojovy seznam a kdy je lepsi dynamicke pole. ****************************************************************************** 4 - Dynamicka pole ****************************************************************************** Tyto datove struktury slouzi k ukladani datovych polozek, jejichz pocet sice nezname pri psani programu, ale pozdeji ho za behu programu jeste pred zacatkem ukladani dat nejakym zpusobem zjistime. Oproti spojovemu seznamu se setri pameti (nepotrebujeme ukazatel v kazde polozce) a dosahuje se rychlejsiho pristupu (prosty index pole misto postupneho prochazeni spojoveho seznamu). Dynamicka pole v podstate funguji jako obycejna staticka pole (var pole: array[...] of ...), jenomze je alokujeme dynamicky (na hromade a ne v Data segmentu) za chodu programu a pri pristupu k nim musime napsat znak ^ navic. Jak na to: 1) Definujeme si "sablonu" pole: type SablonaPole=array[Min..Max] of PozadovanyTypPrvku; Je potreba, aby typ prvku odpovidal budoucimu dynamickemu poli. Cislo Min (minimalni index pole) musi odpovidat skutecnemu pozadovanemu minimalnimu indexu dynamickeho pole, cislo Max muze byt cokoli od Min vys. 2) Definujeme druhy pomocny typ - ukazatel na jiz definovanou sablonu: type UkazatelNaSablonu=^SablonaPole; 3) Vypneme kontrolu rozsahu: bud direktivou {$R-} nebo nastavenim Options -> -> Compiler -> Range checking. Bude to potreba, protoze prave to nam umozni zapisovat do budouciho dynamickeho pole kamkoli, treba i za index Max, pokud to SKUTECNA velikost alokovane pameti dovoli. 4) Deklarujeme prislusnou promennou - ukazatel: var DynPole:UkazatelNaSablonu; To mame zatim 4 "zbytecne zaplacane" byty (jeden ukazatel), ale vic uz jich, na rozdil od spojovych seznamu, nebude. 5) Alokujeme pamet pro pole. Nejdriv si vypocitame, kolik ji budeme potrebovat. Vezmeme velikost jednoho prvku (napr. pomoci sizeof(typ prvku)) a vynasobime ji pozadovanym poctem prvku (ktery uz v tomto okamziku znat musime). Ted pomoci naseho ukazatele alokujeme dynamickou promennou prislusne velikosti: Getmem(DynPole,velikost). Ukazatel DynPole sice je typovy, ale procedura Getmem je univerzalni a jde pouzit i v tomto pripade. Kdybychom pouzili New, alokovalo by se tak velke pole, jak jsme definovali v typu SablonaPole, coz ale nechceme, ze jo, to by pak nebylo nic skutecne dynamickeho. 6) A to je vsechno. Nyni mame pole, do ktereho muzeme ukladat prislusne hodnoty jako do kazdeho jineho pole, jenom nesmime zapomenout na to, ze jdeme pres ukazatel: DynPole^[index]:=hodnota apod. Pozor na indexy. Je vypnuta kontrola rozsahu, takze ne abyste neco zapsali mimo alokovane pole! Rozmery pole si musite celou dobu pamatovat. 7) Zruseni pole: Freemem(DynPole,velikost) (velikost stejna jako pri alokaci). Dvojrozmerne dynamicke pole ---------------------------- Skutecne dvojrozmerne dynamicke pole vyrobit nejde. Misto toho si musime pomoci jinak, ale porad jeste pomerne jednoduse. Predstavme si, ze 2D pole se do pameti uklada po jednotlivych prvcich, radek po radku - ulozime jeden radek a hned za nej (vlastne vedle nej) ulozime druhy atd.. Presne takhle to totiz opravdu v pameti pocitace vypada. Ted pouzijeme analogii s obrazovkou. Ta je v pameti ulozena uplne stejne: zacina levym hornim rohem a pak pixel po pixelu, radek po radku az po pravy dolni roh. Kdyz chceme spocitat linearni adresu (ofset) nejakeho pixelu, delame to takto: adresa=(SirkaObrazovky * y) + x. To ovsem predpoklada, ze prvni radek ma index 0 a prvni sloupec taky. Takze vysledny postup: 1) Sablonu definujeme jako obycejne jednorozmerne pole s indexem Min=0: type SablonaPole=array[0..cokoli] of PozadovanyTypPrvku; 2) Ukazatel na sablonu je porad stejny (type UkazatelNaSablonu=^SablonaPole;) 3) Kontrola rozsahu: vypnout (abych se priznal, ja uz ji vubec nezapinam :-)). 4) Promenna - ukazatel: var DynPole:UkazatelNaSablonu; (taky stejne) 5) Pamet pro pole: velikost = sirka * vyska * sizeof(typ prvku pole) a opet Getmem(DynPole,velikost). 6) No, a ted jak se k te dvojrozmernosti dostat: DynPole^[y*sirka+x]:=hodnota apod. atd. Vicerozmerna pole by se delala podobne: usporadani po radcich, po vrstvach, po "krychlich" atd., index prvku (napr. pro 3D) = z*(pocet prvku na vrstvu) + + y*(delka radku ve vrstve) + x. Prakticky priklad vyuziti najdete treba v mem programu Eddie (editor na rastrove obrazky) nebo v jednotce Matyka (matice s obecnou velikosti i typem prvku). ****************************************************************************** 5 - Proceduralni promenne ****************************************************************************** Zni to zvlastne, ale je mozne definovat promennou typu procedura, napr.: var Proc1:procedure;. Beztypove ukazatele se daji pouzit i k tomu, aby ukazo- valy na proceduru (proceduralni promenna je tachnicky vlastne taky ukazatel). Nejlepsi bude predvest priklad: {male demo na pouziti procedury jako promenne a jak se s tim pracuje} program pokus; uses crt; var Proc:procedure; p:pointer; {$F+} procedure NapisNeco; Begin textcolor(random(14)+1); writeln('Procedura NapisNeco se hlasi. Stiskni Enter.'); readln; End;{napisneco} {$F-} procedure Pipni; far; Begin textcolor(random(14)+1); write('Pipam, cekejte prosim... '); sound(200); delay(500); nosound; writeln('hotovo. Stiskni Enter.'); readln; End;{pipni} BEGIN{program} clrscr;{vymazeme obrazovku} randomize;{to jenom kvuli nahodne barve textu} p:=@NapisNeco; {ukazatel p ted ukazuje na proceduru NapisNeco} p:=addr(napisneco);{to same, jen jiny zapis, ukazatel p porad ukazuje na proceduru NapisNeco} @Proc:=p; {adrese procedury Proc jsme priradili adresu, na kterou ukazuje p} proc; {zavolame Proc, spusti se procedura NapisNeco} addr(proc):=addr(pipni);{asi jediny pripad, kdy muzete funkci priradit hodnotu jinde nez ve zdrojovem kodu teto funkce. Funkce addr se totiz chova presne stejne jako operator @} proc; {ted se zavola procedura Pipni} @Proc:=@NapisNeco; {adrese procedury Proc jsme priradili adresu procedury NapisNeco} Proc; {zavola se opet procedura NapisNeco} END. {konec prikladu} Pokud treba ukazatel P ukazuje na proceduru, neda se napsat P; jako prikaz k volani te procedury, to jde jen s promennymi typu procedura. Timto zpusobem se da pracovat i s procedurami s parametry, napr.: var procedura:procedure(x:word); Takovou proceduralni promennou pak muzeme nasmerovat jen na takovou proceduru, ktera ma prave jeden parametr typu word. Jo, abych nezapomnel - kdyz napisu var p:procedure, muzu sem dosazovat jen procedury bez parametru. U funkci je to skoro to same, pribyde jen udani typu navratove hodnoty: var funkce:function(x:byte):boolean; Vsechny procedury a funkce, ktere chceme volat pomoci proceduralnich promennych, musi byt definovany v rezimu dalekeho volani - Far. Nebudu se rozepisovat o tom, co to presne znamena, staci vedet, jak se to zaridi: bud klicovym slovem Far za hlavickou te procedury nebo direktivou prekladace {$F+} (pokud je tech procedur vic a nechcete pracne psat za kazdou hlavickou far) nebo v Options -> Compiler -> Force far calls. Podobne veci se daji vyuzit pro nastaveni vektoru preruseni (procedury GetIntVec, SetIntVec a klicove slovo Interrupt - viz napovedu Pascalu, jako priklad muze poslouzit napr. vylepsene cekani v me jednotce Cas). Pokud je procedura oznacena slovem interrupt, uz k ni nemusite pripisovat far, to je zahrnuto v tom interrupt. Dalsi priklady pouziti proceduralnich promennych najdete napr. v jednotce SVGA 4 (SVGA.PAS), kde se podle typu graficke karty nastauvji procedury pro jeji obsluhu, a v me stare jednotce Mys (1), kde mel kazdy graficky kurzor svou vlastni kreslici procedurku. ... a to je vse, pratele ... (c) Mircosoft 2006