Základy programování Ingrid Nagyová
Obsah 1 1.1 1.2 1.3 1.4 1.5
ÚVOD DO PROSTŘEDÍ 3 Proč se budeme učit programovat právě v Delphi? .............................................. 3 Programátorské prostředí Delphi........................................................................... 3 Tlačítka, přiřazení akcí tlačítkům .......................................................................... 5 Grafická plocha Image, grafické příkazy .............................................................. 6 Nastavení prostředí Delphi, soubory a přípony..................................................... 9
2 2.1 2.2 2.3 2.4
PROMĚNNÉ A JEDNODUCHÉ CYKLY 11 Celočíselné proměnné, deklarace proměnných ................................................... 11 FOR-cyklus ............................................................................................................. 12 Barvy, míchání barev ............................................................................................. 14 Vnořené cykly ......................................................................................................... 16
3 3.1 3.2 3.3 3.4
OBJEKT ŽELVA 19 Kreslení kruhových objektů.................................................................................. 19 Objekt Želva ........................................................................................................... 20 Kreslení geometrických útvarů – n-úhelník, kružnice, spirála.......................... 23 Víc želv v jednom programu ................................................................................. 25
4 4.1 4.2 4.3
PODMÍNKY – PODMÍNĚNÝ CYKLUS A PODMÍNĚNÝ PŘÍKAZ 28 Podmíněný cyklus................................................................................................... 28 Podmíněný příkaz .................................................................................................. 30 Výběr z více možností............................................................................................. 32
5 5.1 5.2 5.3
PODPROGRAMY - PROCEDURY A FUNKCE 37 Deklarace procedur................................................................................................ 37 Formální parametry............................................................................................... 39 Funkce ..................................................................................................................... 43
6 6.1 6.2 6.3
ZNAKOVÉ PROMĚNNÉ, TEXTOVÉ SOUBORY 46 Typy proměnných, znakové proměnné ................................................................ 46 Textové soubory...................................................................................................... 48 Textové soubory a objekt Želva ............................................................................ 54
7 7.1 7.2 7.3
TYP POLE 58 Typ interval............................................................................................................. 58 Strukturovaný typ pole.......................................................................................... 59 Pole želv – animace pomocí želv, honičky želv .................................................... 61
8 8.1 8.2 8.3 8.4
ZNAKOVÉ ŘETĚZCE, DVOUROZMĚRNÁ POLE 66 Znakové řetězce ...................................................................................................... 66 Dvourozměrné pole, matice ................................................................................... 69 Bitmapové obrázky, práce s myší, editovací řádek ............................................. 72 Objekt Bitmapa ...................................................................................................... 75
1 Úvod do prostředí 1.1
Proč se budeme učit programovat právě v Delphi?
Delphi je pojmenování prostředí, které umožňuje navrhovat a vytvářet programy v jazyce Objektový Pascal. Objektový Pascal se v průběhu času (v polovině osmdesátých let) vyvinul z jazyka Pascal. Jazyk Pascal, podobně jako jazyk C, vznikl počátkem 70-tých let. Oba tyto programovací jazyky (Pascal i jazyk C) vzešly ze struktury jazyka Algol a oba se inspirovali tehdy používanými jazyky Fortran a PL/1. Oba jazyky jsou tedy postaveny na společných základech a jsou si tedy podobné. Pokud ale jazyk Pascal vznikl na akademické půdě jako nástroj pro výuku principů tvorby algoritmů, programování, algoritmického myšlení apod., jazyk C byl od počátku určen pro zkušené systémové programátory. Jazyk Pascal je navíc natolik jednoduchý a jednoznačný, že pozdější přechod na libovolný jiný programovací jazyk Vám jistě nebude činit velké potíže. Mnoho nových programovacích jazyků se jazykem Pascal inspirovalo, proto při jejich zvládání jistě využijete metody a programátorský styl, které jste se naučili při práci s jazykem Pascal. Programové prostředí Delphi využijete k výuce jazyka Pascal. Bude sice nutné seznámit se s tímto prostředím a jeho nástroji. Získáme tím však jednak možnost vytvářet programy pro operační systém MS Windows (na rozdíl od Turbo Pascalu) a toto prostředí nás povede při definicích a používání jednotlivých prvků jazyka Pascal (členění programu do podprogramů, strukturovaný zápis programu, nastavování parametrů procedur apod.). 1.2
Programátorské prostředí Delphi
Programátorské prostředí Delphi je IDE (Integrated Development Environment). To již podle názvu znamená, že toto prostředí v sobě integruje všechny prvky, které programátor potřebuje pro návrh a vývoj programů, pro jejich kompilaci (překlad), testování a ladění. Prostředí je založeno na vizuálním principu: všechno, co bude v běžícím programu vizuálně zobrazeno, programátor již během návrhu programu vizuálně „skládá“ z předpřipravených částí (komponent). Programátorské prostředí Delphi sestává z následujících částí: – panel ovládacích tlačítek, například Otevři, Nová položka, Načti, Zapiš, Spusť apod. – editovací okno pro zápis kódu programu (popis činnosti programu v jednotlivých situacích), – formulář pro vizuální návrh prostředí budoucího programu, – paleta komponent obsahuje předpřipravené části (komponenty) programu, které můžeme přímo vkládat na plochu navrhovaného formuláře, například tlačítka, editovací pole, různé grafické komponenty, komponenty pro načtení souboru, kalendář apod. – objektový inspektor pro nastavení vlastností a parametrů jednotlivých komponent umístěných na formuláři, – prostředí obsahuje i další části, se kterými se seznámíme později.
Poznámka 1.2.1: Editovací okno programu a formulář spolu navzájem souvisí. Pokud se vzájemně překryjí, přepínáme se mezi nimi pomocí klávesy F12. Formulář Panel ovládacích tlačítek
Objektový inspektor
Paleta komponent
Editovací okno
Programové prostředí Delphi První program – spouštění a zastavování aplikací Než se pustíme do návrhu a psaní složitějších programů (aplikací, projektů) v Delphi, uvedeme obecný postup, kterým při vytváření nových aplikací, jejich spouštění a zastavení postupovat: – Spustíme Delphi. – V menu zvolíme File → New → Application. Tím se vytvoří základ nové aplikace (programu). Editovací okno obsahuje pouze několik základních konstrukcí, formulář aplikace je prázdný. – I když máme zatím pouze prázdnou aplikaci, je dobré jí v tomto okamžiku uložit na disk počítače, abychom předešli pozdější ztrátě částí programu, které jsme již vytvořili. Aplikaci uložíme výběrem volby File → Save Project As… z menu. Jsme vyváni k vložení názvu dvou souborů – Unit1.pas, který obsahuje vlastní program a navržený formulář a Project1.dpr obsahující projektový soubor. Projektový soubor se
vytváří automaticky a obsahuje další informace o struktuře projektu. Pro začátečníky doporučujeme názvy těchto souborů neměnit, pro každý projekt (program) vytvořit novou složku, do které projektový soubor, vlastní program a formulář uložíme. – Uloženou aplikaci můžeme spustit, i když jsme ještě nic nenaprogramovali. Programové prostředí Delphi za nás připravilo a definovalo konstrukce, potřebné pro běh programu. Aplikaci spustíme stiskem klávesy F9 nebo výběrem volby Run → Run z menu. Na obrazovce se zobrazí prázdné šedé okno (námi navržený formulář) nahoře s modrým pásem s nápisem Form1 a tlačítky pro minimalizaci a maximalizaci okna a pro ukončení aplikace. – Aplikaci ukončíme tlačítkem pro uzavření okna nebo kombinací kláves Alt-F4. Tím se vrátíme zpět do programového prostředí Delphi a můžeme pokračovat v návrhu programu. Úkol k textu 1.2.1: Vytvořte svoji první prázdnou aplikaci podle uvedeného návodu. Aplikaci uložte do adresáře PrvniPokus na disk svého počítače. Aplikaci přeložte a spusťte. Zkontrolujte obsah adresáře PrvniPokus. Který ze souborů obsahuje projektový soubor, ve kterém je uložen zdrojový kód programu, ve kterém formulář? 1.3
Tlačítka, přiřazení akcí tlačítkům
Jak již bylo uvedeno, jsou tlačítka (podobně jako mnoho dalších prvků) komponenty, nalezneme je proto v paletě komponent. Paleta komponent má více záložek, například Standard, Additional, System apod. Pokud chceme vybrat nějakou komponentu, musíme nejprve zvolit odpovídající záložku, čímž určíme skupinu komponent, ke které potřebná komponenta náleží.
Pro umístění tlačítka na plochu formuláře vybereme v paletě komponent záložku Standard a v ní vyhledáme komponentu s názvem Button (tlačítko) – malé tlačítko s nápisem OK. Klikneme na tuto komponentu a následně na plochu formuláře. Tam se objeví tlačítko s nápisem Button1 ohraničené černými čtverečky, které označují vybrané objekty. Myší můžeme změnit velikost tlačítka (pomocí černých čtverečků) a jeho umístění (taháním myši) na ploše formuláře. Současně se změní údaje v objektovém inspektoru (Object Inspector). Ty vždy aktuálně popisují vybrané objekty, označené černými čtverečky. Pokud máme vybrané tlačítko (viz první řádek v objektovém inspektoru Button1: TButton), objektový inspektor obsahuje informace o tomto objektu. Můžeme pozorovat, že při posouvání tlačítka po ploše formuláře se automaticky mění jeho vlastnosti Top (nahoře) a Left (vlevo), udávající souřadnice levého horního rohu tlačítka. Při změně velikosti tlačítka se mění vlastnosti Heght (výška) a Width (šířka). Vlastnost Caption (nápis, popis) umožňuje změnit nápis na tlačítku. Naproti tomu, vlastnost Name (jméno)
nese název objektu, kterým se na objekt odkazujeme v programu a doporučujeme jí proto neměnit! Úkol k textu 1.3.1: Změňte nápis na tlačítku Button1 na Tlačítko (můžete použít i české znaky s diakritikou). Umístěte na plochu formuláře další tlačítka, seřaďte je pod sebe a vhodně změňte nápisy na tlačítkách. Spusťte takto vytvořený program klávesou F9 a ověřte jeho funkci. Tak, jak jsme se prozatím naučili tlačítka používat, tyto sice udělají výsledný program zajímavějším, avšak po jejich stlačení nenásleduje žádná akce – program nic nedělá. Tlačítku můžeme přiřadit akci, která se vykoná při každém stisku tlačítka během běhu programu, a to následovně: – při návrhu programu dvakrát klikneme na dané tlačítko (nechť je to tlačítko s názvem Button1). – zobrazí se editovací okno, ve kterém se automaticky vypíšou následující řádky procedure TForm1.Button1Click(Sender: TObject); begin end;
–
název Button1Click označuje, že následující akce se provede po stisku tlačítka se jménem (Name) Button1, popis akce provedeme mezi příkazy begin a end.
Příklad 1.3.1: Změnu popisu tlačítka provedeme úpravou kódu, zapsaného v editovacím okně: procedure TForm1.Button1Click(Sender: TObject); begin Button1.Caption:=’Nový popis’; end;
Umístíme-li na plochu formuláře další tlačítko Button2 s popisem Konec, můžeme mu přiřadit akci ukončení programu následovně: procedure TForm1.Button2Click(Sender: TObject); begin Close; end;
Upozornění 1.3.1: Platí pravidlo, že veškeré zápisy, které provede programové prostředí v editovacím okně automaticky nikdy nemažeme! Pokud chceme odstranit naprogramovanou akci, smažeme pouze námi připsané příkazy mezi begin a end. Vlastní odstranění přebytečných konstrukcí se provede při nejbližším uložení projektu (například Ctrl-S). 1.4
Grafická plocha Image, grafické příkazy
Pro kreslení na formulář používáme grafickou plochu Image, která umožňuje vykreslit jednoduchý obrázek, načíst a zobrazit obrázek ze souboru, vytvářet animace, vizualizovat různé algoritmy apod. Vyhýbáme se kreslení přímo na plochu formuláře, které je velice podobné práci s grafickou plochou, nemá však všechny její vlastnosti.
Začneme položením grafické plochy Image na plochu formuláře. Najdeme jí v paletě komponent na záložce Additional. Po umístění na plochu formuláře má Image velikost přibližně 100x100 bodů. Pokud jí chceme zvětšit, můžeme to udělat buď roztažením pomocí myši, nebo nastavením vlastnosti Align v objektovém inspektoru na alClient. Velikost grafické plochy se tak přizpůsobí velikosti formuláře (a to i při změně velikosti formuláře), tlačítka zůstávají nad grafickou plochou. Grafická plocha nese název (Name) Image1, Image2 ... Má svoje plátno Canvas, na kterém každý bod má svoje souřadnice. Bod nejvíce vlevo nahoře má souřadnice [0,0]. Přesunem bodu doprava roste jeho první (x-ová) souřadnice, přesunem bodu směrem dolů roste jeho druhá (y-ová) souřadnice v kladné části osy (záporné souřadnice popisují body mimo grafickou plochu). Na plátno kreslíme pomocí pera Pen a plochy vybarvujeme pomocí štětce Brush. Pero i štětec mají svoji barvu Color, pero má svoji tloušťku Width a štětec styl výplně Style (průhledný, neprůhledný, horizontální, vertikální čáry apod.). Na grafickou plochu budeme většinou kreslit pomocí akcí, přiřazených tlačítkům (například Button1Click) a grafické příkazy budeme zapisovat mezi begin a end. Nejjednodušším příkazem je příkaz pro vykreslení obdélníku: Image1.Canvas.Rectangle(100, 150, 200, 300);
který se skládá s názvu grafické plochy Image1, názvu plátna Canvas, na které budeme kreslit a příkazu pro vykreslení obdélníku Rectangle. Příkaz má čtyři celočíselné parametry – první dva jsou x-ová a y-ová souřadnice levého horního rohu obdélníku, další dva souřadnice pravého dolního rohu tohoto obdélníku. Úkol k textu 1.4.1: Definujte akci na smáčknutí tlačítka, která vykreslí tři obdélníky – první dva přes sebe do kříže a třetí mimo ně. Program vylaďte a otestujte. Jakou barvou se vykreslují hranice obdélníků a jakou jejich vnitro? Při spuštění programu má pero černou barvu a tloušťku 1, štětec má bílou barvu a neprůhledný styl výplně. Chceme-li je změnit, použijeme přiřazovací příkazy: Image1.Canvas.Pen.Color:=clRed; Image1.Canvas.Pen.Color:=clGreen; Image1.Canvas.Pen.Width:=5; Image1.Canvas.Brush.Color:=clBlue; Image1.Canvas.Brush.Style:=bsClear;
// // // // //
červená barva pera zelená barva pera tloušťka pera 5 modrá barva štětce průhledný styl výplně štětce
Princip těchto přiřazovacích příkazů spočívá v tom, že objektům pero a štětec (Pen, Brush) na plátně Canvas změníme (přiřadíme přiřazovacím příkazem := dvojtečka, rovná se) barvu, tloušťku nebo styl výplně. Možné hodnoty barvy a stylu výplně můžeme zjistit z nápovědy po stisku klávesy F1. Úkol k textu 1.4.2: K tlačítku, umístěnému na ploše formuláře definujte akci, jenž vykreslí tři čtverce do řady vedle sebe perem modré barvy a tloušťky 5 s různobarevnou výplní. Úkol k textu 1.4.3: Grafickou plochu Canvas mažeme tím způsobem, že přes ní vykreslíme bílou barvou neprůhledný obdélník. Vytvořte akci na tlačítko formuláře pro vymazání grafické plochy.
Přehled grafických příkazů Uváděné grafické příkazy jsou odvozeny od plátna Canvas grafické plochy Image. Proto při jejich použití musíme uvádět název grafické plochy, na kterou budeme kreslit a také název plátna (například Image1.Canvas pokud se jedná o plátno grafické plochy Image1). Nastavení pera a štětce Pen.Color:=clRed; Pen.Width:=5; Pen.Style:=psSolid; Brush.Color:=clOlive; Brush.Style:=bsSolid;
// barva pera, další barvy clBlue, clYellow, clGreen, clWhite, clBlack atd. // tloušťka pera od 1 výše // styl čáry – tečkovaná, čárkovaná: psDash, PsDot // barva štětce // styl výplně, bsSolid – neprůhledné, bsClear – bez výplně, bsVertical, bsHorizontal atd.
Kreslení Rectangle(x1,y1,x2,y2); Ellipse(x1,y1,x2,y2);
MoveTo(x,y); LineTo(x,y);
// obdélník // elipsa (i kružnice) vepsaná do obdélníku, x1,y1,x2,y2 – stejné souřadnice jako u obdélníku // přesun kreslícího pera do bodu [x,y] // úsečka (čára) z aktuální pozice kreslícího pera do bodu [x,y]
Práce s textem TextOut(x,y,’text’); TextOut(x,y,IntToStr(25)); Font.Name:=’Ariel’; Font.Height:=16; Font.Style:=[fsBold]; Font.Color:=clYellow;
// // // // //
vypíše text v apostrofech od bodu [x,y] vypíše číslo 25 od bodu [x,y] typ písma velikost písma řez písma – tučné, šikmé, podtržené: fsBold, fsItalic, fsUnderLine // barva písma
Kreslení nepravidelných tvarů PolyLine(Body: array of TPoint); // postupně spojí body uvedené v Polygon(Body: array of TPoint); // postupně spojí body uvedené v spojí poslední
čárami jednotlivé závorkách čárami jednotlivé závorkách a nakonec bod s prvním
Příkaz PolyLine kreslí pouze křivou čáru (křivku) barvou pera. Příkaz Polygon slouží k vykreslení nepravidelních n-úhelníků, uzavřených tvarů, kterých vnitro je vybarveno barvou štětce. Příklad 1.4.1: Vyzkoušejte následující grafické příkazy: PolyLine([Point(10,10),Point(100,20),Point(100,100), Point(190,100)]); Polygon([Point(10,10), Point(100,20), Point(100,100)]);
Jaký je v těchto příkazech rozdíl? Nastavte rozdílné barvy pera a štětce a sledujte účinky těchto změn. Která nastavení mají vliv na který z příkazů? Úkol k textu 1.4.4: Vyzkoušejte všechny uvedené příkazy při vytváření programu, který po stisku tlačítka vykreslí jednoduchého panáčka. Panáčka pojmenujte a jeho jméno vypište také na grafickou plochu. Nezapomeňte panáčka vybarvit, můžete mu namalovat vlasy, oči a nos (viz obrázek).
1.5
Nastavení prostředí Delphi, soubory a přípony
I když lze v programovém prostředí Delphi pracovat s jeho nastavením inicializovaným instalací programu, zkušenosti Vám jistě ukážou, že některé věci je lépe změnit. Uvedeme dvě nastavení, které budou důležité i při hodnocení Vámi odevzdávaných programů. Prvním důležitým nastavením je Range checking, které naleznete v menu Project → Options…na záložce Compiler. Jedná se o kontrolu přetečení indexů polí a alespoň v začátcích Vám toto nastavení pomůže i při tvorbě programů. Na stejné záložce doporučujeme nastavit také přepínače I/O checking (kontrola vstupně-výstupných operací) a Owerflow checking (kontrola přetečení při různých výpočtech, aritmetických operacích apod.).
Další nastavení je důležité při ladění programu. Jedná se o nastavení automatického ukládání souborů při spouštění programu. Toto nastavení oceníte, pokud se Vám při běhu programu zasekne počítač, poslední změny máte aktuálně uloženy a můžete se k nim kdykoliv vrátit. Nastavení naleznete po výběru volby Tools → Environmental Options… na záložce Preferences, kde zaškrtnete políčko Editor Files.
Jak již bylo uvedeno, je dobré každý projekt (program) ukládat do jiného adresáře. Při přenosu projektu na jiný počítač není nutné přenášet všechny zde ukládané soubory (mnohé z nich pouze průběžně zálohují Vaši práci). Důležité jsou soubory s následujícími příponami: – *.dpr – projektový soubor, – *.pas – soubory se zdrojovým kódem programu, – *.dfm – formuláře programu, – *.exe – spustitelný soubor, který lze pomocí programového prostředí Delphi kdykoliv z předchozích souborů vytvořit. Poznámka 1.5.1: Při odesílání vypracovaných úkolů ke kontrole je nutné odeslat všechny soubory s příponami *.dpr, *.pas a *.dfm. Cvičení: 1. Pomocí grafických příkazů vykreslete na grafickou plochu barevnou květinu. 2. Pomocí příkazu Polygon vykreslete na grafickou plochu vybarvený rovnostranný trojúhelník – viz obrázek A. 3. Pomocí grafických příkazů MoveTo a LineTo vykreslete pyramidu – viz obrázek B. 4. Pomocí příkazu Polygon vykreslete vybarvenou pyramidu sestávající z rovnostranných trojúhelníků – viz obrázek C. 5. Vykreslete na grafickou plochu deset různobarevných soustředních kružnic (všechny kružnice mají stejný střed). Řešení úkolu k textu 1.4.3: Smazání grafické plochy můžeme provést následovně: procedure TForm1.Button2Click(Sender: TObject); begin Image1.Canvas.Brush.Color:=clWhite; // bílá barva štětce Image1.Canvas.Brysh.Style:=bsSolid; Image1.Canvas.Rectangle(0, 0, Image1.Width, Image1.Height); end;
nebo můžeme využít příkaz FillRect pro vymazání grafické plochy barvou štětce (v našem případě bílou barvou): procedure TForm1.Button2Click(Sender: TObject); begin Image1.Canvas.Brush.Color:=clWhite; // bílá barva štětce Image1.Canvas.FillRect(Image1.ClientRect); end;
Parametrem Image1.ClientRect je dána velikost grafické plochy Image1.
2 Proměnné a jednoduché cykly Pokud jste řádně plnili úkoly, uvedené ve cvičeních předchozí lekce, jistě jste si povšimli, že by bylo užitečné mít nástroj, kterým by se zadávání souřadnic zjednodušilo. Jistě jste často danou souřadnici měnili pouze o jistý počet bodů, přitom celý zbytek příkazu zůstával beze změny. V této kapitole si ukážeme, jak takovéto problémy řešit. Zavedeme pojem proměnné, hodnoty které budeme moci jednoduše měnit. Změna hodnoty proměnné v cyklu (několikanásobní změna hodnoty proměnné) nám umožní kreslit další zajímavé obrázky. 2.1
Celočíselné proměnné, deklarace proměnných Využití proměnných ukážeme nejprve na konkrétním příkladu.
Příklad 2.1.1: Vykreslíme čtverec o velikosti strany 100 bodů. Souřadnice levého horního rohu čtverce budou [50,70]. Vykreslení provedeme po stisku tlačítka Button1. procedure Button1Click(Sender: Tobject); var a, x, y: Integer; begin a := 100; x := 50; y := 70; Image1.Canvas.Pen.Width := 5; Image1.Canvas.Rectangle(x, y, x+a, y+a); end;
Vytvořte aplikaci s grafickou plochou Image1 a jedním tlačítkem Button1. Ověřte funkci uvedené procedury. Změňte uvedenou proceduru tak, aby souřadnice levého horního rohu čtverce byly [120,120]. Pomocí stejné procedury vykreslete větší čtverec nebo kružnici. V uvedeném příkladu jsou a, x a y proměnné. Pokud je chceme v programu použít, musíme je nejprve zadeklarovat: za slovo var zapíšeme seznam proměnných a protože se jedná o celočíselné proměnné, zapíšeme za dvojtečku Integer (var a, x, y: Integer;). Tím, že jsme proměnné zadeklarovali, vyhradí se pro ně při spuštění procedury (při slově begin) místo v paměti počítače. Pokud se jedná o celočíselné proměnné, je každé proměnné přiřazeno místo 4B (32 bitů), a tedy deklarovaná celá čísla mohou nabývat hodnot z intervalu <–2 147 483 648, 2 147 483 647>, co je přibližně + nebo - 231 (4Byte = 32 bit = 31 bitů pro číslo + bit pro znaménko). Tím, že se proměnné vyhradí potřebné místo v paměti počítače, tato proměnná ještě nemá přiřazenou hodnotu. Říkáme, že její hodnota je nedefinovaná. Předtím, než tuto proměnnou použijeme, musíme jí nějakou hodnotu přiřadit pomocí přiřazovacího příkazu (:=) – viz příklad. Hodnota proměnné je pak uchována na vyhrazeném místě během celé procedury. Můžeme s ní pracovat, můžeme jí i několikrát měnit. Při skončení procedury (slovo end) se však všechny deklarované proměnné zruší a jejich hodnoty se nenávratně ztratí. Každá proměnná má své jméno (název, identifikátor). V příkladu jsem použili pouze jednopísmenová jména proměnných (a, x, y). Obecně může být jméno proměnné jakékoliv slovo, které může obsahovat i číslice (ne na začátku jména) a podtrhovátko (znak _). Jména proměnných nesmí obsahovat písmena s diakritikou. V jednoduchých programech vystačíme
obvykle s krátkými jmény (jedno-, dvoj- písmenová pojmenování), ve složitějších a delších programech použijeme pro lehčí orientaci spíše delší pojmenování (i více než 10 znaků). Vraťme se zpátky k našemu programu. Očekávali jsme, že zavedení proměnných přispěje k variabilitě programu. Skutečnost je ale taková, že proměnné a, x a y (viz příklad) mají hodnoty pevně přiřazené na začátku procedury (hodnoty 100, 50 a 70). Tyto hodnoty jsou při každém volání procedury stejné, nemění se ani při běhu procedury. Proto se při kliknutí na tlačítko Button1 vykreslí pokaždé stejný čtverec na stejném místě grafické plochy. Pokud bychom chtěli při každém stisku tlačítka vykreslovat čtverec na jiném místě grafické plochy, museli bychom měnit souřadnice x a y levého horního rohu čtverce. Provedeme to pomocí náhodného generátoru – funkce Random a program upravíme následovně. procedure Button1Click(Sender: Tobject); var a, x, y: Integer; begin a := 100; x := Random(350); y := Random(300); Image1.Canvas.Pen.Width := 5; Image1.Canvas.Rectangle(x, y, x+a, y+a); end;
Funkce Random(n) vybere (vygeneruje) náhodně celé číslo z intervalu <0, n-1>. Příkazem x := Random(350) přiřadíme proměnné x náhodné celé číslo v rozmezí 0 až 349. Podobně přiřadíme proměnné y náhodné číslo v rozmezí 0 až 299. Uvedená procedura tedy při každém stisku tlačítka Button1 vykreslí čtverec o straně délky 100 bodů na jiném místě grafické plochy (viz obrázek). Generátor náhodných čísel generuje při opětovném spuštění programu pokaždé stejná čísla (ověřte). Proto je dobré generátor inicializovat příkazem Randomize. Při dvojnásobném poklepání na formulář (na místo mimo záhlaví a umístěných komponent) můžeme zapsat kód programu, jenž se provede při vytváření formuláře (při spouštění programu). Inicializujeme zde náhodný generátor: procedure TForm1.FormCreate(Sender: TObject); begin Randomize; end;
2.2
FOR-cyklus Začneme opět příkladem.
Příklad 2.2.1: Vykreslíme 8 svislých čar vedle sebe. procedure Button1Click(Sender: Tobject); begin Image1.Canvas.Pen.Width := 5; Image1.Canvas.MoveTo(10, 10); Image1.Canvas.LineTo(10, Image1.Canvas.MoveTo(20, 10); Image1.Canvas.LineTo(20, Image1.Canvas.MoveTo(30, 10); Image1.Canvas.LineTo(30, Image1.Canvas.MoveTo(40, 10); Image1.Canvas.LineTo(40, Image1.Canvas.MoveTo(50, 10); Image1.Canvas.LineTo(50, Image1.Canvas.MoveTo(60, 10); Image1.Canvas.LineTo(60,
110); 110); 110); 110); 110); 110);
Image1.Canvas.MoveTo(70, 10); Image1.Canvas.LineTo(70, 110); Image1.Canvas.MoveTo(80, 10); Image1.Canvas.LineTo(80, 110); end;
Pokud jste uvedené řešení opravdu přepisovali do počítače, určitě Vás napadlo, zda neexistuje konstrukce, která by automaticky zvyšovala x-ovou souřadnici příkazů MoveTo a LineTo a y-ovou souřadnici by neměnila. Uvedený příklad lze přepsat pomocí FOR-cyklu následovně: procedure Button1Click(Sender: Tobject); var i: Integer; begin Image1.Canvas.Pen.Width := 5; for i:=1 to 8 do begin Image1.Canvas.MoveTo(i*10, 10); Image1.Canvas.LineTo(i*10, 110); end; end;
Z konstrukce uvedeného příkladu je zřejmé, že FOR-cyklus slouží k opakování částí programu – jednoho nebo několika příkazů uzavřených mezi begin a end. Konstrukce tohoto typu cyklu začíná slovem for, za kterým následuje přiřazení počáteční hodnoty (v našem případě 1) pomocné proměnné i, tzv. proměnné cyklu. Za slovem to následuje koncová hodnota, kterou nanejvýš může proměnná cyklu nabývat. Slovo do končí úvodní deklaraci FOR-cyklu a uvádí seznam příkazů pro opakování, tzv. tělo cyklu – příkazy uzavřené mezi begin a end. Proměnná FOR-cyklu musí být deklarována před cyklem v části var. S její hodnotou můžeme v příkazech těla cyklu počítat a používat jí (viz příkazy MoveTo a LineTo). Hodnotu proměnné cyklu však nesmíme v těle cyklu měnit! Po skončení cyklu ztrácí proměnná cyklu svoji hodnotu, její hodnota je nedefinovaní a pro další použití musíme této proměnné nějakou hodnotu přiřadit. Tělo cyklu se opakuje pro proměnnou cyklu od její počátečné po koncovou hodnotu. Pokud označíme počátečnou hodnotu proměnné cyklu A a koncovou hodnotu B, platí: – pro A ≤ B se tělo cyklu opakuje pro hodnoty A, A+1, A+2, …, B, tj. B-A+1 krát, – pro A = B se tělo cyklu zopakuje právě jednou pro hodnotu A proměnné cyklu, – pro A > B se tělo cyklu vůbec nevykoná. Počátečná a koncová hodnota proměnné cyklu nemusí být zadána pouze konstantou, jak tomu bylo v našem případě. Může být zadána i složitým aritmetickým výrazem. V takovém případě se tyto výrazy před prvním vykonáním těla cyklu vyhodnotí a tělo cyklu se opakuje podle vypočtené počáteční a koncové hodnoty. Příklad 2.2.2: Vykreslíme 15 čtverců se společným středem v bodě [250, 200]. Budeme vycházet z programu z příkladu 2.1.1. procedure TForm1.Button1Click(Sender:TObject); var a, x, y, i: Integer; begin Image1.Canvas.Pen.Width := 5; x := 250; // souřadnice středu y := 200; for i := 1 to 15 do begin
a := 158-8*i; // polovina délky strany čtverce Image1.Canvas.Rectangle(x-a,y-a,x+a,y+a); end; end;
• •
•
Podívejme se podrobněji na uvedené řešení: Bod [x, y] v tomto případě není (na rozdíl od příkladu 2.1.1) levým horním rohem čtverce, ale jeho středem. Proměnným x a y jsme přiřadili hodnoty před cyklem, dále se hodnoty těchto proměnných nemění. V proměnné a není uložena délka strany čtverce (viz příklad 2.1.1), ale pouze její polovina. Pro její výpočet jsme použili výraz a := 158-8*i, a tedy z ohledem na počátečnou a koncovou hodnotu proměnné cyklu i (i nabývá hodnoty od 1 do 15) je hodnota proměnná a postupně 150, 142, 134, 126, ..., 38. Uvedené řešení vykreslí čtverce od největšího po nejmenší. Pokud bychom chtěli postup obrátit a vykreslit čtverce od nejmenšího po největší, museli bychom upravit vzorec pro výpočet hodnoty proměnné a následovně a := 30+8*i. Víme, že předdefinované hodnota stylu štětce je neprůhledné kreslení, a tedy větší čtverec by v tomto případě překreslil menší (již vykreslený). Pokud bychom chtěli vykreslovat čtverce od nejmenšího po největší, museli bychom nastavit styl štětce na bsClear (průhledný). Pokuste se tuto myšlenku prakticky realizovat.
Vidíme, že v programech můžeme používat nejrůznější, často i značně komplikované, výrazy. Při zapisování těchto výrazů je nutné mít na paměti prioritu operací (násobení a dělení má větší prioritu nežli sčítání a odečítání), která je při jejich vyhodnocování dodržována. Kromě základních operací pro celá čísla +, -, * (výsledek dělení není celočíselný) můžeme použít celočíselné dělení div a zbytek po dělení mod. 2.3
Barvy, míchání barev
Prozatím jsme se naučili používat předdefinované barvy, jako například clWhite, clYellow, clRed, clBlue, clGreen, clOlive, clBlack a podobně. Samozřejmě, že ve svých programech můžeme pro vykreslení použít i řadu jiných barev. Ty si namícháme ze tří základních složek – červené, zelené a modré – přes tzv. RGB paletu (RGB = red, green, blue = červená, zelená, modrá). Hodnoty RGB nejpoužívanějších barev RGB
Název barvy
RGB(255, 0, 0)
clRed
RGB(128, 0, 0)
clMaroon
RGB(0, 255, 0)
clLime
RGB(0, 128, 0)
clGreen
RGB(0, 0, 255)
clBlue
RGB(0, 0, 128)
clNavy
RGB(0, 0, 0)
clBlack
RGB(255, 255, 255) clWhite RGB(128, 128, 128) clGray
Barva červená tmavě červená zelená tmavě zelená modrá tmavě modrá černá bílá šedá
RGB(255, 255, 0)
clYellow
RGB(0, 255, 255)
clAqua
RGB(255, 0, 255)
clFuchsia
RGB(153, 51, 0)
žlutá tyrkysová cyklámenová hnědá
Příklad 2.3.1: Vykreslete čáru, která bude plynule měnit barvu od bílé, přes červenou, do černé. Čáru vykreslíme ve dvou fázích: – přechod od bílé RGB(255, 255, 255) do červené RGB(255, 0, 0) – červená složka zůstane konstantní (rovna 255), zelená a modrá složka se budou současně měnit od maximální hodnoty 255 do 0, – přechod od červené RGB(255, 0, 0) do černé RGB(0, 0, 0) – zelená a modrá složka zůstanou konstantní (nulové), červená složka bude klesat k 0 (k černé barvě). procedure TForm1.Button1Click(Sender: TObject); var i: Integer; begin Image1.Canvas.Pen.Width := 5; Image1.Canvas.MoveTo(0, 100); for i := 0 to 255 do // změna od bílé do červené begin Image1.Canvas.Pen.Color := RGB(255,255-i,255-i); Image1.Canvas.LineTo(0+i, 100); end; for i := 0 to 255 do // změna od červené do černé begin Image1.Canvas.Pen.Color := RGB(255-i,0,0); Image1.Canvas.LineTo(255+i, 100); end;
end; Úkol k textu 2.3.1: Řešte problém z příkladu 2.3.1 pro čáru měnící se od bílé, přes zelenou, do černé a pro čáru, která se bude měnit od bílé, přes modrou až do černé. Náhodnou barvu můžeme namíchat například takto: Image1.Canvas.Pen.Color := RGB(random(256),random(256),random(256)); Image1.Canvas.Pen.Color := RGB(random(256),0,random(256)); Image1.Canvas.Pen.Color := RGB(150+random(106),0,200);
Příklad 2.3.2: Vykreslete soustředné barevné kružnice měnící postupně směrem do středu barvu. Kružnice budeme vykreslovat bez hraniční čáry pera (styl pera nastavíme na psClear). Barvu výplně budeme měnit od modré RGB(0, 0, 255) do bílé.
procedure TForm1.Button1Click(Sender: TObject); var a, x, y, i: Integer; begin Image1.Canvas.Pen.Style := psClear; x := 260; y := 260; for i := 0 to 255 do begin a := 255-i; // poloměr kružnice Image1.Canvas.Brush.Color:=RGB(i,i,255); Image1.Canvas.Ellipse(x-a, y-a, x+a, y+a); end; end;
2.4
Vnořené cykly
Tam, kde při kreslení a výpisech potřebujeme měnit obě souřadnice (x-ovou i y-ovou), se uplatní vnořené cykly. Jejich využití si předvedeme na příkladech. Příklad 2.4.1: Vykreslíme hvězdičky do čtverce – 10 x 10 hvězdiček. Použijeme vnořené cykly, přičemž proměnná i vnějšího cyklu bude procházet řádky (odvine se od ní y-ová souřadnice) a proměnná j vnitřního cyklu bude procházet sloupce (a také xovou souřadnici). procedure TForm1.Button1Click(Sender: TObject); var i, j: Integer; begin Image1.Canvas.Font.Color := clGreen; Image1.Canvas.Font.Size := 10; for i := 1 to 10 do for j := 1 to 10 do Image1.Canvas.TextOut(j*10, i*10, '*'); end;
Úkol k textu 2.4.1: U vnořených cyklů se vnitřní cyklus (v našem případě cyklus s proměnnou j) vykoná pro každou hodnotu proměnné vnějšího cyklu (proměnné i). Vytvořte si jednoduchou tabulku, do které budete zapisovat aktuální hodnoty proměnných i a j. Procházejte postupně krok za krokem programem z příkladu 2.4.1. Při každé změně hodnoty jedné z proměnných evidujte tuto změnu v tabulce. Jak se postupně mění hodnoty proměnných i a j? Kolikrát v průběhu výpočtu dosáhne maximální hodnoty (hodnoty 10) proměnná i a kolikrát proměnná j? Příklad 2.4.2: Vykreslete hvězdičky do trojúhelníku – v prvním řádku bude pouze jedna hvězdička, ve druhém dvě, ve třetím tři, atd. Vyjdeme-li z řešení příkladu 2.4.1, bude počet opakování vnitřního cyklu záviset na aktuálním řádku – počet opakování vnitřního cyklu bude přesně odpovídat hodnotě proměnné i vnějšího cyklu. procedure TForm1.Button1Click(Sender: TObject); var i, j: Integer; begin Image1.Canvas.Font.Color := clGreen;
Image1.Canvas.Font.Size := 10; for i := 1 to 10 do for j := 1 to i do Image1.Canvas.TextOut(j*10, i*10, '*'); end;
Pokud bychom chtěli vypsat trojúhelník hvězdiček symetricky podle svislé osy, museli bychom změnit souřadnice pro výpis hvězdiček: procedure TForm1.Button1Click(Sender: TObject); var i, j: Integer; begin Image1.Canvas.Font.Color := clGreen; Image1.Canvas.Font.Size := 10; for i := 1 to 10 do for j := 1 to i do Image1.Canvas.TextOut(110-j*10, i*10, '*'); end;
Úkol k textu 2.4.2: Upravte předchozí programy tak, aby se trojúhelníky hvězdiček vypisovaly jedním svým cípem směrem dolů (oproti příkladu 2.4.2 překlopené podle vodorovné osy). Jak je potřeba upravit počátečnou a koncovou hodnotu proměnné vnitřního cyklu? Cvičení: 1. Vraťte se k programu, který jste vytvořili v minulé lekci pro vykreslení jednoho panáčka. Program upravte tak, aby vykreslil několik panáčků vedle sebe držících se za ruku. K vykreslování použijte FOR-cyklus.
2. Napište program, který po stisku tlačítka vypíše tabulku malé násobilky.
3. Napište program, který po stisku tlačítka vypíše text s dlouhým barevným stínem. Stín půjde zprava doleva a bude plynule měnit barvu. Na obrázku jsme vyšli z modré barvy (clBlue) a měnili její prostřední složku RGB palety. Pro vykreslení stínu je potřeba nastavit průhledný styl štětce (bsClear). Nezapomeňte pro zvýšení efektu vypsat nakonec stejný text původní barvou.
4. Upravte příklad 2.3.2 tak, že nebudete vykreslovat kružnice, ale čtverce. Vyzkoušejte i jiné plynulé barevné změny. 5. Vykreslete malé čtverečky (3x3 bodů) do sítě 256 x 256 čtverečků. Každý čtvereček bude vykreslen jinou barvou – jednu složku v RGB paletě zafixujte, druhou budete měnit s proměnnou vnějšího cyklu a třetí se změnou proměnné vnitřního cyklu (viz obrázek).
6. Vykreslete šachovnici 8x8 čtverců. Pozor na pravidelné střídání bílých a černých políček.
3 Objekt Želva Předtím, než se seznámíme s objektem Želva, který nám umožní kreslit jednoduchým způsobem složité geometrické tvary, si ukážeme, jak využít ke kreslení kruhových objektů a obrázků uspořádaných do kruhu goniometrické funkce sinus a cosinus. 3.1
Kreslení kruhových objektů
Z matematiky je obecně známo, že v pravoúhlém trojúhelníku (viz obrázek) platí pro sinus a cosinus úhlu α následující vztahy: sin (α ) =
protilehlá _ strana a = přepona c
cos(α ) =
přílehlá _ strana b = přepona c
Převedeme-li tyto vztahy na body, ležící na kružnici, zjišťujeme, že každý bod ležící na kružnici se středem v bodě [x, y] a poloměrem r má souřadnice [x + r * cos(α ), y + r * sin (α )] , kde α je úhel, který svírá polopřímka začínající ve středu kružnice a procházející daným bodem kružnice s kladnou částí x-ové osy.
Příklad 3.1.1: Vytvoříme program, který po stisku tlačítka vykreslí 24 paprsků rovnoměrně rozdělených po kružnici. Paprsky budeme kreslit jako čáry, které budou vycházet ze středu kružnice a budou končit na jejím obvodu. procedure TForm1.Button1Click(Sender: TObject); var i, x, y, r: Integer; a: Real; begin Image1.Canvas.Pen.Width := 2; Image1.Canvas.Pen.Color := clNavy; x := 100; // souřadnice středu paprsků y := 100; r := 70; // délka paprsků for i:=1 to 24 do begin a := 360/24*i*pi/180; // úhel přepočtený na radiány Image1.Canvas.MoveTo(x, y); Image1.Canvas.LineTo(x+Round(r*cos(a)), y+Round(r*sin(a))); end end;
Při řešení příkladu 3.1.1 jsme poznali, že pokud použijeme goniometrické funkce sinus a cosinus, nevystačíme pouze s celočíselnými proměnnými. I v případě, že sami budeme počítat úhly ve stupních (celočíselných), musíme tyto hodnoty převádět pro jejich použití v goniometrických funkcích na radiány podle vztahu: α *π α° = rad 180 Proměnné, jejichž hodnoty jsou reálná čísla, deklarujeme jako proměnné typu Real (var a: Real;). Při deklaraci proměnné typu Real se jí v paměti vyhradí místo 6B, a tedy reálná čísla mohou nabývat hodnot z intervalu <2.9 x 10-39, 1.7 x 1038>. Reálná čísla nejsou v počítači obvykle reprezentována úplně přesně (nenabývají přesně svých hodnot). Při počítání a operacích s reálnými čísly se tyto chyby a nepřesnosti ještě zvyšují, proto se snažíme jejich použití v programech minimalizovat a výpočty převádět v oboru celých čísel. Pro převod reálných čísel na celá čísla můžeme využít následující funkce: – Trunc – z reálného čísla vybere pouze jeho celou část (desetinnou část odstraní), – Round – zaokrouhlí reálné číslo na celá čísla (viz příklad 3.1.1). Upozornění 3.1.1: Reálná čísla píšeme v programech s desetinnou tečkou!!! (Viz zápis intervalu přípustných reálných čísel.) Desetinnou čárku můžeme použít pouze ve výstupech (ve výpisech na obrazovku, při tisku apod.). Úkol k textu 3.1.1: Upravte program z příkladu 3.1.1 tak, aby se při stisku tlačítka vymazala grafická plocha a vykreslilo se nové „sluníčko“. Počet paprsků sluníčka náhodně generujte v rozmezí 20-50 paprsků. 3.2
Objekt Želva
Kreslení složitějších grafických struktur nám usnadní speciálně cvičené želvy, které se po grafické ploše budou různým způsobem pohybovat a zanechávat za sebou čáru. Tyto želvy, přímé následovatelky želv LOGO, nejsou součástí programového prostředí Delphi. Do tohoto prostředí musíme želvy nejprve přidat: – Otevřeme novou aplikaci, kterou uložíme do zvláštního adresáře. Do tohoto adresáře uložíme také soubor ZelvaUnit.pas, jenž obsahuje potřebné údaje pro pohyb želv. – Na formulář aplikace umístíme grafickou plochu a tlačítko. – V editovacím okně zapíšeme za řádek {$R *.DFM} uses ZelvaUnit; řádek: Tento zápis předpokládá, že máme soubor ZelvaUnit.pas uložen ve stejné složce, ve které je uložena celá aplikace. Pokud budeme vytvářet víc aplikací s použitím želv, budeme muset tento soubor kopírovat do složky každé z nich. – Použití nové želvy definujeme přímo v programu. Poklepeme na tlačítko na formuláři a vepíšeme následující kód:
procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i: Integer; begin z := TZelva.Create; z.Smaz; for i := 1 to 4 do begin z.vpred(150); z.vpravo(90); end; end;
V programu nejprve deklarujeme proměnnou z typu TZelva, z jejíž pomocí budeme želvu ovládat. Příkazem TZelva.Create vytvoříme novou želvu. Želva se vytvoří (narodí) ve středu grafické plochy a je otočena směrem nahoru (je připravena pohybovat se směrem nahoru). Příkaz z.Smaz smaže grafickou plochu a připraví jí tak pro další kreslení. Ve FOR-cyklu se opakují dva příkazy: – z.vpred – řídí pohyb želvy z dopředu o počet kroků uvedených v závorce (v našem případě o 150 kroků), – z.vpravo – otočí želvu vpravo ve směru hodinových ručiček o úhel daný v závorkách (v našem případě o 90°). Princip želvy je následující: Želva je umístěna na grafické ploše a je natočena určitým směrem. V této situaci jí není vidět. Želva se může procházet po grafické ploše a pokud má spuštěné pero, kreslí cestou, kterou prochází. Pokud pero zvedne, nekreslí, pouze se neviditelně přesouvá. Aby kreslení bylo zajímavější, můžeme měnit barvu a tloušťku pera želvy. Abychom mohli lépe pozorovat, co želva provádí, můžeme vykonávání programu zpomalit pomocí příkazu Cekej, který je součástí želvího prostředí. Parametrem příkazu Cekej je počet milisekund, po které se má vykonávání programu pozastavit. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i: Integer; begin z := TZelva.Create; z.Smaz; for i := 1 to 4 do begin z.vpred(150); z.vpravo(90); Cekej(500); // pozastavení programu na 0,5 s end;
end; Použití příkazu Cekej může v prostředí MS Windows činit jisté potíže. Z toho důvodu jej nedoporučujeme používat ve větších aplikacích, pro naše potřeby však postačí. Želva se může otáčet nejenom vpravo, ale také vlevo, proti směru hodinových ručiček. K tomu jí slouží příkaz z.vlevo, ve kterém opět zadáme počet úhlových stupňů, o které se má želva vlevo otočit. Pokud v uvedeném programu budeme měnit počet opakování a úhel otočení želvy, dostaneme mnoho zajímavých obrázků a tvarů. S některými z nich se seznámíme při řešení dalších úkolů.
Příklad 3.2.1: Vykreslíme pěticípou hvězdu. Budeme muset zvětšit počet opakování FOR-cyklu na pět a také změnit úhel otáčení želvy na 360/5 = 144°. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i: Integer; begin z := TZelva.Create; z.Smaz; for i:=1 to 5 do begin z.vpred(100); z.vlevo(144); Cekej(300); end end;
Přehled želvích příkazů K tomu, abychom želví příkazy mohli použít, musíme mít na formuláři umístěnou grafickou plochu. Želvu musíme nejprve vytvořit příkazem z := TZelva.Create; Želva se vytvoří ve středu grafické plochy a je natočena směrem nahoru. Želva zná následující příkazy: Vytvoření želvy z:=Tzelva.Create;
// vytvoří želvu uprostřed grafické plochy otočenou směrem vzhůru (na sever)
Poloha želvy z.X, z.Y z.H
// x-ová a y-ová souřadnice aktuální polohy želvy // aktuální úhel natočení želvy
Pohyb želvy // želva se otočí vpravo o 120 stupňů // želva se otočí vlevo o 45 stupňů // želva se na místě otočí do úhlu 330 stupňů 0° - směr kladné části x-ové osy úhly měříme proti směru hodinových ručiček z.vpred(100); // želva se posune o 100 kroků vpřed z.movexy(150, 280); // želva se přesune se zdviženým perem do bodu se sou5adnicemi [150, 180] - nekreslí čáru z.setxy(150, 280); // želva se přesune do bodu se sou5adnicemi [150, 180] - kreslí čáru z.bod; // želva vykreslí bod velikosti tloušťky pera z.vpravo(120); z.vlevo(45); z.setuhel(330);
Nastavení pera želvy z.pd; z.ph; z.pc := clRed; z.pw := 5;
// želva spustí pero // želva zvedne pero, nebude při dalším pohybu kreslit // změna barvy pera na červenou // změna tloušťky pera na 5 bodů
Ostatní příkazy z.smaz; z.vypln(clBlue); z.vzdal(100,150); Cekej(300);
// želva smaže grafickou plochu // želva vyplní ohraničenou plochu modrou barvou // vzdálenost bodu [100, 150] od momentální polohy želvy // pozastaví běh programu na 300 milisekund
Příklad 3.2.2: Vykreslíme rovnostranný trojúhelník čárkovanou (program vlevo) a tečkovanou čárou (viz program vpravo).
procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i, j: Integer; begin begin z := TZelva.Create; z.smaz; z := TZelva.Create; z.smaz; for i:=1 to 3 do z.ph; begin for i:=1 to 3 do for j:=1 to 10 do begin begin for j:=1 to 20 do z.pd; z.vpred(7); begin z.ph; z.vpred(7); z.bod; z.vpred(7); end; end; z.vlevo(120); z.vlevo(120); end end end; end;
Srovnejte zápisy obou programů a vyzkoušejte je. Který kreslí čárkovanou čáru a který tečkovanou? Poznáte to ještě před spuštěním programu? 3.3
Kreslení geometrických útvarů – n-úhelník, kružnice, spirála Vykreslení jednotlivých geometrických útvarů předvedeme prakticky na příkladech.
Příklad 3.3.1: Vyzkoušeli jsme již kreslení trojúhelníku, čtverce, pěticípé hvězdy. Nyní vykreslíme pravidelný n-úhelník procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i, n: Integer; begin z := TZelva.Create; z.Smaz; n:=9; for i:=1 to n do begin z.vpred(50); z.vpravo(360/n); end end;.
Příklad 3.3.2: Kružnici kreslíme jako speciální 360-úhelník s malou délkou strany. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i: Integer; begin z := TZelva.Create; z.Smaz; for i:=1 to 360 do begin z.vpred(2); z.vpravo(1); end end;.
Pokud bychom FOR-cyklus opakovali pouze 180-krát nebo 90-krát, nedostali bychom kružnici, ale pouze její část (půlkružnice, čtvrtkružnice). Následující příklad ukáže, jak pomocí čtvrtkružnic vykreslit květinu o 9-ti lupíncích. Příklad 3.3.3: Každý lupínek květiny bude sestávat ze dvou čtvrtkružnic proti sobě (mezi nimi se želva otočí o 90°). Jednotlivé lupínky umístíme stejnoměrně po kružnici (želva se před vykreslením každého z 9-ti lupínků otočí o 360/9°). procedure TForm1.Button1Click(Sender:TObject); var z: TZelva; i, j, k: Integer; begin z:=TZelva.Create; z.Smaz; z.PC:=clRed; z.PW:=3; for k:=1 to 9 do begin for j:=1 to 2 do // jeden lupínek begin for i:=1 to 90 do // čtvrtkružnice begin z.vpred(2); z.vpravo(1) end; z.vpravo(90) end; z.vpravo(360/9) end; end;
Úkol k textu 3.3.1: Doplňte řešení příkladu 3.3.3 o vykreslení celé květiny – vybarvěte lupínky (použitím příkazu z.vypln(clYellow)), přikreslete stonek a zelený lístek (viz obrázek). Pokud umíme namalovat kruhové objekty, vytvoříme z nich spirálovitě zatočené objekty tak, že budeme ve FOR-cyklu měnit se změnou proměnné cyklu měnit i délku kroku želvy. Situaci předvedeme opět na příkladu. Příklad 3.3.4: Čtverec do spirály. procedure TForm1.Button1Click (Sender: TObject); var z: TZelva; i: Integer; begin z:=TZelva.Create; for i:=1 to 50 do begin z.vpred(i*2+10); z.vpravo(90); // vyzkoušejte i jiné úhly 91, 71, 123, 145 apod. end end;
Spirálový objekt dostaneme i tehdy, když budeme s proměnnou cyklu měnit úhel otočení želvy. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; r: Real; i: Integer; begin z:=TZelva.Create; r:=20; // vyzkoušejte 5, 10 apod. for i:=1 to 2000 do begin z.vpred(2); z.vpravo(r); r:=r*0.99; // vyzkoušejte 0.995, 0.999 apod. end end;
Nebo také jiná možná změna úhlu: procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i: Integer; begin z:=TZelva.Create; for i:=1 to 2000 do begin z.vpred(5); z.vpravo(i); // vyzkoušejte i+0.1, i+0.2 apod. end end;
3.4
Víc želv v jednom programu
V jednom programu (proceduře) nemusíme obecně použít pouze jednu želvu. Příkazem z:=TZelva.Create; můžeme vytvořit libovolný počet žel. Každá z nich může kreslit něco jiného a společně vytvořit zajímavý obrázek. Příklad 3.4.1: Vyzkoušejte činnost následujícího programu, který využívá tří želv ke lreslení tří různých kružnic. Porovnejte parametry nastavené pro jednotlivé želvy. procedure TForm1.Button1Click(Sender: TObject); var z1, z2, z3: TZelva; i: Integer; begin z1:=TZelva.Create; z1.PC:=clRed; z1.PW:=2; z1.movexy(150, 150); z2:=TZelva.Create; z2.PC:=clBlue; z2.movexy(150, 190); z3:=TZelva.Create; z3.PC:=clGreen; z3.PW:=5; z3.movexy(300, 150); for i:=1 to 360 do begin z1.vpred(2); z1.vlevo(1); z2.vpred(150); z2.vlevo(91); z3.vpred(2); z3.vlevo(1); Cekej(100) end; end;
Cvičení: 1. Upravte program pro vykreslování paprsků (příklad 3.1.1) tak, aby se délka paprsků měnila se změnou hodnoty proměnné FOR-cyklu.
2. Upravte program pro vykreslování paprsků (příklad 3.1.1 tak, aby se paprsky do poloviny kruhu prodlužovaly a pak se zase zpět zkracovaly. Vznikne tvar podobný srdíčku (viz obrázek).
3. Vykreslete paprsky (příklad 3.1.1) pomocí objektu Želva. Paprsky použijte pro vykreslení sluníčka.
4. Z dvou čtvrtkružnic (objekt Želva) lze vytvořit siluetu letícího ptáka (viz obrázek). Zabarvěte grafickou plochu bleděmodrou barvou a vytvořte program, který po stisku tlačítka vykreslí na nebe (modrou grafickou plochu) ptáka na náhodné pozici. Můžete měnit také sklon ptáka – pozor však, aby neletěl hlavou dolů. 5. Pomocí želví grafiky vykreslete osmičku (dvě kružnice nad sebou). Úkol řešte pro různé počáteční pozice želvy v osmičce – viz červená kolečka na obrázku. Pokuste se vykreslit osmičku také naležato.
6. Vytvořte program, který po stisku tlačítka vykreslí rozkvetlou louku s deseti náhodně rozmístěnými květy. Při vykreslování květů vyjděte z příkladu 3.3.3, počet lupínků jednotlivých květů můžete generovat náhodně. 7. Vykreslete spirálu z příkladu 3.3.4 barevně – dvě složky RGB palety fixujte, jednu složku měňte se změnou proměnné FOR-cyklu. 8. Vytvořte program, který pomocí objektu Želva vykreslí po stisku tlačítka 3, 5, 10 nebo jiný počet čtverců stejnoměrně do kruhu (viz obrázek). Upravte program tak, aby se místo čtverců vykreslovali trojúhelníky. Jak se změní výsledný obrazec?
9. Pomocí objektu Želva vykreslete následující ornament: 10. Nakreslete graf funkce f(x) = sin(x) tak, že jedna želva bude chodit po kružnici a druhá bude přebírat její y-ovou souřadnici a x-ovou bude rovnoměrně zvyšovat (viz obrázek). Pro větší názornost můžou želvy své cesty vykreslit barevně.
11. Pomocí objektu Želva vykreslete mrak. Obvod mraku tvoří půlkružnice stejnoměrně rozmístěné po kruhu. Mrak vybarvěte vhodnou barvou příkazem želví grafiky. 12. Vygenerujte tři želvy na grafickou plochu a vytvořte program, ve kterém tyto želvy budou kreslit následovně: jedna spirálu do čtverce, druhá spirálu do trojúhelníku a třetí bude kráčet po spirále sem a tam. Každá želva kreslí jinou barvou a jinou tloušťkou pera, každá začíná v jiné části grafické plochy, aby si při kreslení co nejméně zavazeli.
4 Podmínky – podmíněný cyklus a podmíněný příkaz Prozatím jsme se seznámili s objektem Želva. Naučili jsme se deklarovat číselné proměnné (celá a reálná čísla) a přiřazovat jim hodnoty. Seznámili jsme se s FOR-cyklem a naučili se ho využívat při kreslení jednoduchých grafických objektů. Vytvářené grafické objekty sestávaly z jednoduchých geometrických útvarů – čtverec, trojúhelník, kružnice apod. Tyto jsme různě natáčeli, posouvali a překreslovali. Snad jste zde také přišli na myšlenku definovat pro tyto jednoduché útvary „příkazy“, kterým by se želva na grafické ploše „doučila“. Mohla by je využívat při kreslení dalších objektů. V této kapitole si ukážeme, jak toto provést. Zavedeme pojem podprogramu, naučíme se deklarovat a volat procedury. Předtím se ale naučíme definovat podmínky, které využijeme v deklaraci podmíněných cyklů a pro řízení pohybu želvy při jejich náhodných procházkách po grafické ploše. 4.1
Podmíněný cyklus
FOR-cyklus, s kterým jsme až doposud pracovali, opakoval posloupnost příkazů (tělo cyklu) podle hodnoty proměnné cyklu. Podle počátečné a koncové hodnoty proměnné cyklu jsme předem věděli, kolikrát se tělo cyklu zopakuje. To bylo za jistých okolností výhodné. V jiných situacích se však může stát, že potřebujeme opakovat tělo cyklu pouze při splnění jisté podmínky. Pokud tato podmínka přestane platit, tělo cyklu by se již nemělo opakovat. Počet opakování těla cyklu zde není pevně určen a dokonce nemusí být ani shora ohraničen (nemusíme znát maximální počet opakování těla cyklu). V takových případech nevystačíme pouze s FOR-cyklem. Příklad 4.1.1: Následující program vykreslí hvězdu do spirály. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; d: Integer; begin z:=TZelva.Create; z.movexy(250,200); d:=5; while d<300 do begin z.vpred(d); z.vpravo(144); d:=d+5 end end;
Při řešení příkladu 4.1.1 jsme použili WHILE-cyklus. V jeho deklaraci je za slovem while podmínka, tj. nějaký výraz, který je buď pravdivý nebo nepravdivý. Za slovem do následuje tělo cyklu umístěné mezi begin a end. Tělo WHILE-cyklu se vykoná tehdy, pokud podmínka v jeho deklaraci je pravdivá. Po vykonaní všech příkazů těla cyklu se opětovně kontroluje podmínka WHILE-cyklu. Pokud je pravdivá, tělo cyklu se vykoná znovu. Pokud podmínka není pravdivá, vykonávání programu pokračuje za cyklem (pokračuje příkazy za slovem end, které ukončuje tělo cyklu).
Pokud je podmínka v deklaraci WHILE-cyklu nepravdivá již při prvním testování, tělo cyklu se nevykoná ani jednou. Pokud je tato podmínka pravdivá vždy, cyklus nikdy neskončí – takový cyklus nazýváme nekonečný cyklus. Úkol k textu 4.1.1:i V příkladu 4.1.1 není použití WHILE-cyklus nutné. Pokuste se přepsat uvedené řešení pomocí FOR-cyklu. Vylaďte program tak, aby počet opakování těla cyklu byl v obou příkladech stejný. Podmínky v deklaraci WHILE-cyklu jsou logické výrazy. Jak bylo již uvedeno, mohou být pro dané hodnoty použitých proměnných buď pravdivé – nabývají pravdivostí hodnoty true (pravda) – nebo nepravdivé – pak nabývají pravdivostní hodnoty false (nepravda). Při definici podmínek můžeme využít aritmetické, logické a relační operátory, přičemž dbáme priority jednotlivých operátorů: - největší prioritu má unární operátor not (negace), - následují multiplikativní operátory * (násobení), / (dělení), div (celočíselné dělení), mod (zbytek po celočíselném dělení) a and (logická spojka „a“), - aditivní operátory + (sčítání), - (odčítání) a or (logické spojka „nebo“), - nejnižší prioritu mají relační operátory =, <, <=, >, >=, <> (uvádíme zápis jednotlivých relačních operátorů jak je používáme při zápisu programů). Naznačenou prioritu jednotlivých operací můžeme ovlivnit vhodným uzávorkováním jednotlivých částí podmínek pomocí kulatých závorek ( ) – jiné typy závorek při konstrukci podmínek a výrazů nepoužíváme (jsou vyhrazeny pro jiné účely). Příklad 4.1.2: Podmínku x < 50 or x >= 100 vyhodnotíme podle naznačených priorit tak, že nejprve vyhodnotíme výraz 50 or x. Jeho výsledek srovnáme s x (zda je větší) a získanou hodnotu porovnáme s číslem 100. Protože toto řešení je možné a odpovídá naznačené prioritě operací, Delphi neohlásí chybu. Přesto jsme tento výraz sestavili pravděpodobně s jiným úmyslem, který programovému prostředí Delphi předáme vhodným uzávorkováním daného výrazu: (x < 50) or (x >= 100). Příklad 4.1.3:i Upravíme řešení příkladu 4.1.1 tak, aby vykreslované spirálovité útvary měly pokaždé jiný tvar – budeme náhodně měnit úhel pohybu želvy. Aby nám želva neutekla z grafické plochy, upravíme zároveň podmínku ukončení WHILE-cyklu. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; i, d, u: Integer; begin z:=TZelva.Create; z.Smaz; z.movexy(250,200); d:=5; u:=90+random(100); // náhodné úhly v rozmezí 90° až 189° while sqrt(sqr(z.X-250)+sqr(z.Y-200))<200 do begin z.vpred(d); z.vpravo(u); d:=d+5
end end;
Abychom se vyhnuli příliš malým úhlům, při kterých by vykreslený objekt mohl být málo zajímavý, generujeme velikost úhlů příkazem u:=90+random(100). Tím dostáváme náhodná čísla v rozmezí 90° až 189°. Podmínka, která nyní omezuje vykonávání WHILE-cyklu vyjadřuje potřebu, aby vzdálenost aktuální pozice želvy (bodu [z.X, z.Y]) od bodu [250, 200] (přibližného středu grafické plochy) byla menší než 200:
(z.X −250) + (z.Y −200) < 200 2
2
Srovnáním uvedeného vzorce s jeho zápisem v programu (viz příklad) zjistíme, že druhé odmocnině odpovídá funkce sqrt a druhé mocnině funkce sqr. U obou funkcí uvádíme v závorce hodnotu, z níž má být výsledek vypočítán. Objekt Želva má pro výpočet vzdálenosti vlastní prostředky s jejichž pomocí můžeme podmínku ve WHILE-cyklu upravit na while z.vzdal(250, 200) < 200 do. Vyzkoušejte navrhovanou úpravu. 4.2
Podmíněný příkaz
Podmíněné příkazy (jejich smysl a použití) si přiblížíme na náhodných procházkách. Želvu necháme „donekonečna“ procházet se po grafické ploše: v každém bodu své cesty si želva náhodně vybere směr, kterým vykoná nejbližší krátký krok (viz příklad). Příklad 4.2.1: Náhodná procházka. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz; while true do begin z.vpravo(random(360)); z.vpred(5); Cekej(1); end end;
Pokud jste se pokusili uvedený program spustit, možná jste byli překvapeni, že nešel uzavřít křížkem na liště vpravo nahoře. WHILE-cyklus použitý v příkladu má v podmínce uvedeno pouze slovo true, a tedy podmínka je vždy splněna. Jedná se o nekonečný cyklus. Přerušení takovéhoto programu je možné pouze z programového prostředí Delphi stiskem kombinace kláves Ctrl-F2 nebo násilným přerušením běhu programu (restartem počítače). Abychom se vyhnuli problémům s ukončováním aplikace, umístíme na plochu formuláře tlačítko pro ukončení programu. Aplikaci ukončíme příkazem halt, který násilně přeruší všechny běžící procesy a aplikaci přeruší. Takovéto ukončení ve slušných aplikacích nepoužíváme, nebo pouze výjimečně. Od této zásady upustíme pouze pro naše potřeby náhodných procházek. procedure TForm1.Button2Click(Sender: TObject); begin Halt end;
Pokud jste chvíli sledovali náhodnou procházku želvy, nakonec Vám z grafické plochy unikla a již se nevrátila. Dále se pokusíme omezit prostor pro pohyb želvy pouze na vhodné části grafické plochy. Použijeme k tomu podmíněný příkaz if (má dvě varianty, použijeme zatím pouze tu jednodušší): if podmínka then vnořený_příkaz
Podobně jako ve WHILE-cyklu: Je-li podmínka splněna, vykoná se vnořený příkaz. Pokud by podmínka splněna nebyla, vnořený příkaz se nevykoná a vykonávání programu pokračuje za vnořeným příkazem. Vnořený příkaz může být uzavřen mezi begin a end. Příklad 4.2.2: Náhodná procházka v níž se želva nevzdálí od bodu [250, 200] (z kterého vychází) o více než 50 kroků. V opačném případě se vrátí zpět. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz; z.moveto(250, 200); while true do begin z.vpravo(random(360)); z.vpred(5); if z.vzdal(250, 200)>50 then z.vpred(-5); Cekej(1); end end;
Protože množina bodů, jejichž vzdálenost od bodu [250, 200] je menší nebo rovna 50, tvoří kruh, vyplní postupně při své náhodné procházce želva právě tento prostor (viz obrázek). Úkol k testu 4.2.1: Podmínky v podmíněném příkazu mohou být i komplikovanější. Uvedeme několik příkladů: – kosočtverec se středem v bodě [250, 200]: if abs(z.X-250)+abs(z.Y-200) > 100 then z.vpred(-5);
–
obdélník se středem v bodě [250, 200] a stranami 2*70 a 2*50: if (abs(z.X-250) > 70) or (abs(z.Y-200) > 50) then z.vpred(-5);
–
sněhulák jako sjednocení tří kružnic: if (z.vzdal(250, 350) > 100) and (z.vzdal(250, 190) > 80) and (z.vzdal(250,60) > 60) then z.vpred(-5);
Vyzkoušejte uvedené podmínky a pozorujte plochy, které vyplní želva náhodnou procházkou. Poznámka 4.2.1: Příkaz Cekej(1) je v programu nezbytný. Pokud bychom ho vyhodili, aplikaci by se nám nepodařilo zastavit ani příkazem halt (k vykonání příkazu halt by vůbec nemohlo dojít). Abychom urychlili náhodnou procházku želvy, můžeme omezit počet volání příkazu Cekej. To můžeme udělat například tak, že v jisté proměnné budeme počítat počet přechodů WHILE-cyklu a příkaz Cekej zavoláme pouze pokud tato hodnota bude dělitelná tisíci. Jiná možnost je náhodně generovat čísla v rozmezí 0-999 a příkaz Cekej zavolat pokud vygenerujeme jedničku. Účinek bude stejný jako v prvním případě, program však zde bude jednodušší. Příkaz Cekej tedy v programu nahradíme příkazem: if random(1000)=1 then Cekej(1);
Příklad 4.2.3: Náhodnou procházkou vyplníme plochu ve tvaru měsíce. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz; z.movexy(300, 200); z.vypln(clBlue); // modré pozadí z.PC:=clYellow; // žlutý měsíc while true do begin z.vpravo(random(360)); z.vpred(5); if (z.vzdal(250, 200) > 100) or (z.vzdal(150, 200) < 100) then z.vpred(-5); if random(1000)=1 then Cekej(1); end end;
4.3
Výběr z více možností Prozatím jsme se seznámili s podmíněným příkazem pouze v jeho jednodušší variantě if podmínka then vnořený_příkaz1;
(je-li podmínka splněna, vykoná se vnořený příkaz 1, v opačném případě se pokračuje vykonáváním příkazů za podmíněným příkazem). Mají-li se některé příkazy vykonat v případě, že podmínka podmíněného příkazu neplatí, můžeme uvedenou konstrukci upravit následovně: if not podmínka then vnořený_příkaz2;
(není-li podmínka splněna, vykoná se vnořený příkaz 2). Obě uvedené konstrukce můžeme spojit v možné variantě podmíněného příkazu if podmínka then vnořený_příkaz1 else vnořený_příkaz2;
Je-li podmínka podmíněného příkazu splněna, vykoná se vnořený příkaz 1, jinak (slovo else) se vykoná vnořený příkaz 2. Takovýto podmíněný příkaz nabízí výběr ze dvou možností: pokud podmínka platí a pokud podmínka neplatí. Využití předvedeme na příkladu. Upozornění 4.3.1: Před slovem else středník nedáváme!!! Pokud bychom za vnořený příkaz 1 dali středník, předpokládá se ukončení podmíněného příkazu (bez části else). Část za else ztrácí v programu smysl a programové prostředí Delphi ohlásí chybu. Příklad 4.3.1: Želva se bude náhodně procházet v kruhu. Barva jejího pera se bude měnit podle její vzdálenosti od středu kruhu – do poloviny bude žlutá, od poloviny červená. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz;
z.movexy(200,200); z.PC:=clYellow; while true do begin z.vpravo(random(360)); z.vpred(5); if z.vzdal(200,200)>100 then z.vpred(-5); if z.vzdal(200,200)<50 then z.PC:=clYellow else z.PC:=clRed; if random(1000)=1 then Cekej(1); end end;
První podmíněný příkaz (if z.vzdal(200,200) > 100 then z.vpred(-5)) nepustí želvu za hranice kruhového prostoru (střed kruhu je v bodě [200, 200], poloměr kruhu je 100). Druhý podmíněný příkaz mění barvu pera želvy podle její vzdálenosti od středu kruhu – pokud se želva nachází ve vnitřním kruhu (poloměr vnitřního kruhu je 50), je barva pera žlutá, ve vnějším kruhu želva kreslí červenou barvou. Příklad 4.3.2: Upravíme program z příkladu 4.3.1 tak, vznikly tři soustředné kruhy – vnější zelený, prostřední žlutý a vnitřní červený (výběr ze tří možností barvy pera). procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz; z.movexy(200,200); z.PC:=clRed; while true do begin z.vpravo(random(360)); z.vpred(5); if z.vzdal(200, 200)>100 then z.vpred(-5); if z.vzdal(200, 200) < 30 then z.PC:=clRed else if z.vzdal(200,200) < 70 then z.PC:=clYellow else z.PC:=clLime; if random(1000)=1 then Cekej(1); end end;
Pokud chceme použít výběr ze tří možností, použijeme dva podmíněné cykly vnořené do sebe (viz řešení příkladu). Úkol k textu 4.3.1: Upravte řešení příkladu 4.3.2 tak, aby vznikly čtyři soustředné kruhy vybarvené střídavě červenou a žlutou barvou. Kolik podmíněných cyklů použijete? Lze tento počet zmenšit? Pokud ano, navrhněte způsob řešení. Příklad 4.3.3: Upravíme program z příkladu 4.3.1 tak, aby vzniklo deset soustředních kruhů v barvách duhy. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz; z.movexy(200,200); z.PC:=clRed;
while true do begin z.vpravo(random(360)); z.vpred(5); if z.vzdal(200, 200)>100 then z.vpred(-5); if z.vzdal(200, 200) < 10 then z.PC:=clRed else if z.vzdal(200, 200) < 20 then z.PC:=RGB(255,150,0) else if z.vzdal(200, 200) < 30 then z.PC:=clYellow else if z.vzdal(200, 200) < 40 then z.PC:=clLime else if z.vzdal(200, 200) < 50 then z.PC:=clGreen else if z.vzdal(200, 200) < 60 then z.PC:=RGB(0,255,255) else if z.vzdal(200, 200) < 70 then z.PC:=RGB(0,150,255) else if z.vzdal(200, 200) < 80 then z.PC:=clBlue else if z.vzdal(200, 200)< 90 then z.PC:=RGB(255,0,255) else z.PC:=clRed; if random(1000)=1 then Cekej(1); end end;
V tomto případě jsme museli použít již devět do sebe vnořených podmíněných příkazů. Program tím ztrácí přehlednost. Rovněž vzdálenost aktuální pozice želvy od středu kruhu se vyčísluje i několikrát (podle velikosti této vzdálenosti). Uvádíme elegantnější způsob řešení daného příkladu pomocí příkazu case. Tento příkaz umožňuje podle nějaké hodnoty vykonat jeden z možných alternativních příkazů (nebo také žádný alternativní příkaz). procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; begin z:=TZelva.Create; z.Smaz; z.movexy(200,200); z.PC:=clRed; while true do begin z.vpravo(random(360)); z.vpred(5); if z.vzdal(200, 200) > 100 then z.vpred(-5); case Round(z.vzdal(200,200)) div 10 of 1 : z.PC:=RGB(255,150,0); 2 : z.PC:=clYellow; 3 : z.PC:=clLime; 4 : z.PC:=clGreen; 5 : z.PC:=RGB(0,255,255); 6 : z.PC:=RGB(0,150,255); 7 : z.PC:=clBlue; 8 : z.PC:=RGB(255,0,255) else z.PC:=clRed end; if random(1000)=1 then Cekej(1); end end;
Výraz Round(z.vzdal(200,200)) div 10 určuje pořadí daného mezikruží. Kruh nejvíc vevnitř (s poloměrem 10) má číslo 0, vnější kruh má číslo 9. Kruhy nejvíc vevnitř a nejvíc vně jsou vybarveny červenou barvou, kterou nastavujeme peru želvy v možnosti else (jinak). Obecní tvar příkazu case: case výraz of konstanta1: příkaz1; konstanta2: příkaz2; ... konstantaN: příkazN else příkazy; end;
Podle hodnoty výraz rovnající se konstatnta1, konstatnta2, … se vykoná jeden z alternativních příkazů příkaz1, příkaz2, … Příkazy za slovem else se vykonají v případě, že výraz nenabývá žádnou z hodnot konstatnta1, konstatnta2, …Část else může v příkazu chybět. Samostatný výraz i hodnoty všech konstant konstatnta1, konstatnta2, …musí být celočíselného typu (později uvidíme, že mohou být i jiných typů). Cvičení: 1. Naprogramujte náhodnou procházku tři želv. Všechny se budou pohybovat ve stejném kruhu o poloměru 50. Želvy budou mít tlusté pero s barvami žlutou, červenou a modrou.
2. Upravte program (viz příklad 4.2.3) pro vykreslení měsíce tak, že zobrazíte i tu část měsíce, která není osvětlena (viz obrázek).
3. Naprogramujte pomocí náhodné procházky jedné želvy kříž (dva obdélníky zkřížené přes sebe).
4. Naprogramujte pomocí náhodné procházky jedné želvy květinu (viz obrázek). Žlutý (barva clYellow) středový kruh má střed v bodě [200, 200], červené kruhy (barva clRed) mají středy v bodech [120, 200], [240, 130], [240, 270] a růžové kruhy (barva RGB(255, 204, 255)) mají středy v bodech [280, 200], [160, 130], [160, 270]. Všechny kruhy mají poloměr 50. Dbejte na vhodné překrývání kruhů.
5. Pomocí náhodné procházky jedné želvy vykreslete olympijské kruhy.
6. Zjistěte vzhled vlajek jednotlivých států. Vykreslete některé z vlajek pomocí náhodné procházky jedné želvy. Pod vlajku připište název státu, jemuž vlajka náleží. 7. Definovali jsme podmínky pro vykreslování nejrůznějších obrazců pomocí náhodných procházek. Vyberte si pět z těchto podmínek (obrázků) a pomocí příkazu case vykreslete po stisku tlačítka náhodně jeden z nich – vygenerujte náhodně číslo od 1 do 5 a vykreslete příslušný tvar. Při opětovném stisku tlačítka se vykreslí jiný z pěti tvarů.
5 Podprogramy - procedury a funkce Prozatím jsme vytvářeli většinou pouze jednoduché krátké programy, jejichž rozsah nepřesáhl 10-20 řádků. Pokud některý z Vámi vytvářených programů přesahoval tento rozsah, stával se nepřehledným, těžko se v něm nacházely a opravovaly chyby. Snad jste v této souvislosti uvažovali o možnosti rozdělit program na menší, přehlednější části. Těmto částem se říká podprogramy. Jejich deklaraci a využití je věnována tato kapitola. Podprogramy jsou části programu, jenž mají své jméno, pomocí kterého je můžeme zavolat. Podprogramy nám umožňují řešit problém jeho rozdělením na dílčí podúlohy – v této souvislosti hovoříme o programování shora dolů. Podprogramy v Pascalu mohou být dvou typů: – procedury – posloupností příkazů, – funkce – posloupnosti příkazů, jejichž výsledkem je nějaké hodnota. Podprogramy využijeme tehdy, když: – nějakou část programu chceme kvůli přehlednosti a logice algoritmu vytáhnout do samostatné části mimo vlastní program – program se zpřehlední, lehčeji ho odladíme, – se nějaká část programu víckrát opakuje na různých místech (není myšleno pouze v cyklu) – definovaný podprogram zavoláme jeho jménem z různých části vlastního programu, – nějakou část programu naprogramoval někdo jiný – podprogram ke své práci připojíme a ve vhodný okamžik jej zavoláme jeho jménem, – použijeme rekurzivní volání (bude vysvětleno později). 5.1
Deklarace procedur Obecný tvar deklarace procedury: procedure jméno; ... // lokální deklarace proměnných náležících proceduře begin ... // tělo procedury end;
Při deklaraci procedur platí následující pravidla: – procedura má své jméno, které podobně jako pojmenování proměnné sestává z písmen abecedy bez diakritiky a číslic (číslice nesmí být na prvním místě), – hlavička procedury začíná slovem procedure, za kterým následuje jméno procedury (například procedure ctverec;), – za hlavičkou procedury následují deklarace lokálních proměnných, tj. takových proměnných, které se používají pouze v dané proceduře, – tělo procedury mezi begin a end obsahuje příkazy procedury. Deklaraci a volání procedury předvedeme na konkrétním příkladu. Deklarujeme proceduru ctverec, pomocí které vykreslíme čtyři čtverce. Příklad 5.1.1: Proceduru ctverec deklarujeme vevnitř procedury TForm1.Button1Click. Všimněte si, že proměnnou pro želvu deklarujeme ještě před deklaraci procedury ctverec, která pak želvu využívá pro vyreslení.
procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; procedure ctverec; var i: Integer; begin for i:=1 to 4 do begin z.vpred(100); z.vpravo(90); end end; begin z:=TZelva.Create; z.movexy(100, 150); z.movexy(210, 150); z.movexy(100, 260); z.movexy(210, 260); end;
ctverec; ctverec; ctverec; ctverec;
// ***
Proceduru ctverec jsme deklarovali pouze jednou a použili (volali) čtyřikrát. Postup práce počítače při volání procedury (viz například řádek označený v poznámce ***) je následující: – počítač si zapamatuje návratovou adresu – místo volání procedury a místo, kam se po jejím vykonání počítač opět vrátí, – vytvoří se místo v paměti pro lokální proměnné (ty zatím nemají definovanou hodnotu) a pro formální parametry (jak uvidíme později), – řízení programu se převede do těla procedury – za příslušné begin, – vykoná se tělo procedury – všechny příkazy až po příslušné end, – zruší se všechny lokální proměnné (vymažou se z paměti) a všechny formální parametry (jak bude vysvětleno později), – řízení programu se vrátí na místo návratové adresy, vykonávání programu následuje příkazem za příkazem volání procedury. Důležité je rovněž si uvědomit vnoření jednotlivých procedur do sebe a rozsah platnosti procedur a proměnných definovaných v programu. Zde platí následující pravidla: – Proměnné deklarované v procedurách mají platnost pouze v dané proceduře – nazýváme je lokální proměnné. Například proměnná i (viz příklad 5.1.1) platí pouze v proceduře ctverec. Proměnná z (typu TZelva) platí v celé proceduře TForm1.Button1Click. Procedura ctverec je do ní vnořena, a tedy proměnná z platí i v této proceduře. Pokud bychom deklarovali v proceduře ctverec proměnnou z (typu TZelva), program by vypsal chybové hlášení. Příkazy, použité v této proceduře pro práci se želvou, počítají s deklarovanou lokální „želvou“, která však zde nebyla vytvořena (příkazem z:=TZelva.Create), tudíž se nemůže posunout vpřed ani otočit vpravo. – Proměnné a procedury můžeme použít pouze tehdy, byli-li předtím zadeklamovány. Proměnnou z (viz příklad 5.1.1) bychom nemohli v proceduře ctverec použít, pokud by nebyla deklarována před touto procedurou. – Standardní deklarace (standardní typy, procedury, funkce, které programová prostředí Delphi nabízí) jsou pro program deklarovány ještě před jeho začátkem. Říkáme, že jsou deklarovány na 0. úrovni. Na této úrovni je pro naše programy deklarován i celý objekt Želva. Tyto deklarace nazýváme globální deklarace.
–
Formulář a všechny procedury formuláře (procedury označené typem formuláře TForm1) jsou na 1. úrovni. Procedura ctverec a proměnná z jsou deklarovány na 2. úrovni, proměnná i procedury ctverec je deklarována až na 3. úrovni. – Proměnné a procedury deklarované na vyšších úrovních (na úrovních s nižším číslem) jsou „viditelné“ a použitelné v procedurách deklarovaných na nižších úrovních. Proměnné a procedury deklarované na vyšších úrovních nazýváme globální proměnné, resp. globální procedury. – Používání globálních proměnných a procedur se snažíme minimalizovat – z důvodů minimalizace použité paměti, kvůli přehlednosti programového kódu, pro vyšší „nezávislost“ procedur (zapouzdření) od ostatního programového kódu apod.
Příklad 5.1.2: Vraťme se ještě jednou k příkladu 5.1.1. Strana vykreslovaného čtverce je v tomto případě konstantní, rovna 100. Pokud bychom měnit délku strany čtverce, museli bychom pro tento parametr použít globální proměnnou (vzhledem k proceduře ctverec). procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; d: Integer; procedure ctverec; var i: Integer; begin for i:=1 to 4 do begin z.vpred(d); z.vpravo(90); end end; begin z:=TZelva.Create; z.movexy(100, 150); z.movexy(210, 150); z.movexy(100, 260); z.movexy(210, 260); end;
5.2
d:=100; ctverec; d:=70; ctverec; d:=80; ctverec; d:=90; ctverec;
Formální parametry
Abychom nemuseli používat globální proměnné (například pro určení velikosti strany čtverce v příkladu 5.1.2), jejichž používání se snažíme minimalizovat, můžeme při volání posílat podprogramu nějaké hodnoty pomocí tzv. formálních parametrů. Situaci předvedeme nejprve na příkladu. Příklad 5.2.1: Různě velké čtverce z příkladu 5.1.2 vykreslíme pomocí procedury, které předáme délku strany čtverce pomocí formálního parametru. procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; procedure ctverec(d: Integer); var i: Integer; begin
for i:=1 to 4 do begin z.vpred(d); z.vpravo(90); end end; var j: Integer; begin z:=TZelva.Create; for j:=1 to 24 do begin ctverec(j*5); z.vlevo(360/24); Cekej(1000) end end;
Formální parametry procedury definujeme v závorce za názvem procedury. Zde kromě pojmenování formálního parametru uvádíme i jeho typ – viz hlavička procedury procedure ctverec(d: Integer);
Parametr d procedury ctverec je pouze formálním (zástupným) parametrem. Jeho hodnota není známa při spouštění programu. Formálnímu parametru se přiřazuje hodnota až při volání podprogramu! Tento parametr se vytvoří až při volání podprogramu (podobně jako lokální proměnné) a přiřadí se mu hodnota odpovídajícího výrazu (v našem případě j*5). Formální parametry jsou v těle podprogramu (procedury) obyčejnými lokálními proměnnými, jenž mají na začátku procedury (slovo begin) inicializovanou hodnotu podle skutečného parametru, podle hodnoty odpovídajícího výrazu ve volání procedury. Formální parametr je pouze zástupnou proměnnou skutečného parametru (výrazu ve volání procedury). V těle procedury odpovídá formální parametr svým postavením lokálním proměnným. Procedura může mít i více formálních parametrů. Příklad 5.2.2: Na grafické ploše vytvoříme dvě želvy, které se budou pohybovat po kružnicích (každá jinou rychlostí, po kružnici s jiným poloměrem). Třetí želvu vytvoříme uprostřed mezi nimi. Třetí želva se bude neustále snažit být mezi zbývajícími dvěma – přesně uprostřed úsečky vymezené prvními dvěmi želvami. Pro výpočet středu úsečky použijete proceduru. procedure TForm1.Button1Click(Sender: TObject); var z1, z2, z3: TZelva; b1, b2: Integer; // barva pera želvy z1 a z2 x, y: Real; procedure UrciStred; // souřadnice středu úsečky mezi z1 a z2 begin x:=(z1.X+z2.X)/2; y:=(z1.Y+z2.Y)/2; end;
begin z1:=TZelva.Create; z1.movexy(200,200); b1:=0; z2:=TZelva.Create; z2.movexy(300,200); b2:=0; z3:=TZelva.Create; z3.PC:=clRed; UrciStred; z3.movexy(x,y); while true do begin z1.vpred(4); z1.vlevo(3); // pohyb želvy z1 if Round(z1.H)=90 then begin b1:=150-b1; z1.PC:=RGB(b1, b1, b1) end; z2.vpred(3); z2.vpravo(2); // pohyb želvy z2 if Round(z2.H)=90 then begin b2:=150-b2; z2.PC:=RGB(b2, b2, b2) end; UrciStred; z3.setxy(x,y); // pohyb želvy z3 Cekej(10); end; end;
Proměnné b1 a b2 slouží ke změně barvy pera želv z1 a z2 tak, aby bylo možno lépe sledovat jejich pohyb – želvy po oběhnutí kružnice mění barvu pera z černé na šedou a naopak. V dalších úpravách programu tyto proměnné nebudeme používat. Úkol k textu 5.2.1: Podle uvedeného programu vykreslí prostřední želva „mašličkovou“ pěticípou hvězdu (viz obrázek). Změnou úhlů otáčení želv z1 a z2 se mění i obrazec vykreslený prostřední želvou. Vyzkoušejte možné změny. Upravte program z příkladu 5.2.2 tak, aby želvy, jdoucí po kružnicích, šli obě stejným směrem. Jak se v tomto případě změní dráha třetí želvy? Procedura UrciStred využívá proměnné z1, z2, x, y, které nejsou deklarovány v této proceduře (nejsou lokálními proměnnými procedury). Tyto proměnné jsou pro proceduru UrciStred globální. Použití globálních proměnných nemusí být vždy možné a optimální – dané globální proměnné se budeme snažit z programu odstranit. procedure TForm1.Button1Click(Sender: TObject); procedure UrciStred(z1, z2: TZelva; xx, yy: Integer); begin xy:=(z1.X+z2.X)/2; yy:=(z1.Y+z2.Y)/2; end; var z1, z2, z3: TZelva; x, y: Real; begin z1:=TZelva.Create; z1.movexy(200,200); z2:=TZelva.Create; z2.movexy(300,200); z3:=TZelva.Create; z3.PC:=clRed; UrciStred; z3.movexy(x,y); while true do begin z1.vpred(4); z1.vlevo(3); z2.vpred(3); z2.vpravo(2); UrciStred(z1, z2, x, y); z3.setxy(x,y); Cekej(10); end; end;
Vyzkoušíte-li takto zapsaný program zjistíte, že nebude fungovat. Použití formálních parametrů z1 a z2 je v pořádku – tyto proměnné používáme v proceduře UrciStred pouze pro čtení x-ové a y-ové souřadnice aktuální polohy želvy, tedy pouze jako lokální proměnné. Proměnné xx a yy jsou v uvedené deklaraci rovněž pouze lokální proměnné procedury UrciStred. Hodnoty těchto proměnných bychom však potřebovali po skončení procedury UrciStred přiřadit proměnných x a y pro nastavení aktuální pozice želvy z3. Zde již nevystačíme s formálními parametry tak, jak jsme se s nimi seznámili doposud. V proceduře UrciStred musíme použít tzv. var parametry: procedure UrciStred(z1, z2: TZelva; var xx, yy: Integer); begin xy:=(z1.X+z2.X)/2; yy:=(z1.Y+z2.Y)/2; end;
Tři typy formálních parametrů v Pascalu Podle možnosti přístupu k parametrů procedury rozeznáváme následující typy parametrů: – Hodnotové parametry – volání hodnotou – umožňují přístup pouze k duplikátu skutečného parametru. Při volání procedury se hodnotové parametry vytvoří (podobně jako lokální proměnné) a jejich hodnota se inicializuje podle hodnoty skutečného parametru. Po skončení volané procedury jsou tyto parametry zrušeny a jejich hodnoty nenávratně zapomenuty. – Proměnné parametry (var parametry) – volání adresou – umožňují úplný přístup k hodnotám parametrů. Proměnné parametry se při volání procedury nevytváří, jsou to pouze dočasná jména skutečných parametrů. Při volání procedury musí být na místech proměnných parametrů proměnné (u nás x a y), jejichž hodnoty se prostřednictvím zástupních jmen proměnných parametrů (xx a yy) mohou v průběhu procedury měnit. Po skončení procedury jsou hodnoty skutečných parametrů rovny hodnotách zástupných proměnných parametrů. – Konstantní parametry (const parametry) – volání konstantou – umožňují přístup pouze pro čtení hodnoty skutečného parametru. Jejich hodnota se v těle procedury nemění. Ve všech uvedených případech se jedná o formální (zástupné) parametry. Skutečná hodnota je parametrům přiřazena při volání procedury.
– –
– –
Postup práce počítače při volání procedury můžeme tedy upravit následovně: počítač si zapamatuje návratovou adresu, vytvoří se místo v paměti pro lokální proměnné: • opravdové lokální proměnné dostávají nedefinovanou hodnotu, • hodnotové formální parametry jsou lokální proměnné, jejichž hodnota je rovna hodnotě skutečného parametru (duplikát), • konstantní formální parametry jsou lokální konstanty, jejichž hodnota je rovna skutečnému parametru a tato hodnota se v průběhu procedury nemění, • proměnné formální parametry se nevytváří, jsou pouze dočasnými jmény skutečných parametrů, řízení programu se převede do těla procedury, vykonají se všechny příkazy těla procedury,
– –
zruší se všechny lokální proměnné (vymažou se z paměti) a hodnotové a konstantní formální parametry, řízení programu se vrátí na místo návratové adresy, vykonávání programu následuje příkazem za příkazem volání procedury.
Příklad 5.2.3: Proceduru UrciStred z příkladu 5.2.2 „pozvedneme“ na 1.úroveň a pohyb želv upravíme tak, že jedna se bude pohybovat po kružnici a druhá po čtverci (trojúhelníku, pěticípé hvězdě apod.). Postavení třetí želvy se nemění. procedure UrciStred(z1, z2: TZelva; var xx, yy: Real); begin xx:=(z1.X+z2.X)/2; yy:=(z1.Y+z2.Y)/2; end; procedure TForm1.Button1Click(Sender: TObject); var z1, z2, z3: TZelva; x, y: Real; i: Integer; begin z1:=TZelva.Create; z1.movexy(200,200); z2:=TZelva.Create; z2.movexy(300,200); z3:=TZelva.Create; z3.PC:=clRed; UrciStred(z1,z2,x,y); z3.movexy(x,y); while true do begin for i:=1 to 30 do // zkuste jiný počet opakování: 20, 40... begin z1.vpred(4); z2.vpred(3); z2.vpravo(2); UrciStred(z1,z2,x,y); z3.Setxy(x,y); Cekej(10); end; z1.vlevo(90) // vyzkoušejte jiné úhly: 120, 144 apod. end; end;
5.3
Funkce
Pomocí proměnných parametrů můžeme získat hodnoty proměnných, jenž byly vypočteny v dané proceduře. Pokud daná procedura počítá pouze jednu hodnotu (má jeden proměnný formální parametr), je většinou výhodnější použít funkci. Příklad 5.3.1: Procedura s proměnným parametrem vracejícím hodnotu většího ze dvou čísel. procedure max(a,b:integer; var m:integer); begin if a>b then m:=a else m:=b; end;
Stejná úloha řešená pomocí funkce – funkce, která vrací větší ze dvou čísel a a b.
function max(a,b:integer): Integer; begin if a>b then Result:=a else Result:=b; end;
Funkce je speciálním druhem procedury, která vrací jednu hodnotu daného typu. Hlavička funkce začíná slovem function, za kterým následuje jméno a parametry funkce. Hlavička končí udáním typu funkce (typu výsledku funkce). Funkce má – na rozdíl od obvyklé procedury – automaticky definovanou lokální proměnnou Result, jenž je výsledkem funkce. Proměnná Result je stejného typu jako je typ funkce. Při volání funkce má nedefinovanou hodnotu, pokud jí nějakou hodnotu v těle funkce nepřiřadíme, výsledkem bude nesmyslná hodnota (může to být cokoliv). Příklad 5.3.2: Funkce vracející součet prvních n čísel. function Suma(n:integer): Integer; var i: Integer; begin Result:=0; for i:=1 to n do Result:=Result+i end;
Funkce, která počítá průměr prvních n čísel. function Prumer(n:integer): Real; begin Result:=Suma(n)/n; end;
Funkce nahodne – náhodné číslo z intervalu
. function Nahodne(a, b:integer): Integer; begin Result:=random(b-a+1)+a; end;
Cvičení: 1. Pomocí
procedury
pro vykreslení čtverce ctverec(a: Integer) (a je délka strany čtverce) vykreslete několik čtverců v sobě. Nezapomeňte se směnou délky strany čtverce měnit také tloušťku pera želvy.
2. Pomocí
objektu
Želva napište proceduru která vykreslí kružnici – a je délka kroku želvy při jejím pochodu dokola. Napište proceduru vicekruzic(p: Integer), která vykreslí p kružnic v sobě (viz obrázek). kruznice(a: Integer),
3. Napište proceduru uhelnik(a, n: Integer), která vykreslí n-úhelník s počtem vrcholů n a délkou strany a. Proceduru využijte k tvorbě procedury viceuheniku(k: Integer) pro vykreslení k úhelníků – viz obrázek. Barvu jednotlivého n-úhelníku generujte náhodně.
4. Napište proceduru koso(a, u: Integer), která vykreslí kosočtverec s délkou strany a. Menší z úhlů kosočtverce je u. Barvu kosočtverce vygenerujeme náhodně. Proceduru koso využijte při kreslení květiny (procedura kvet(n, a, u: Integer)) o n lupíncích. Proměnné a a u mají význam při volání procedury koso. 5. Pomocí procedury ctverec(a: Integer) vykreslete schody – vytvořte procedury leveschody(n: Integer) a praveschody(n: Integer), n je počet poschodí (viz obrázek).
6. Napište funkci NSD(a, b: Integer): Integer, která vypočte největšího společného dělitele dvou přirozených čísel Euklidovým algoritmem. 7. Napište funkci NSN(a, b: Integer): Integer, která vypočte nejmenší společný násobek dvou přirozených čísel. 8. Napište proceduru Zkrat(a,b:Integer; var p,q:Integer) s celočíselnými parametry a p (b<>0), která zkrátí zlomek na základní tvar . Použijte funkci NSD z úlohy 6. b q 9. Napište funkci Prepona(a, b: Integer): Real, která vypočte přeponu v pravoúhlém trojúhelníku s odvěsnami a a b. 10. Napište funkci Prvocislo(a: Integer): Integer, která o daném čísle a zjistí, zda je prvočíslo. Pokud číslo a je prvočíslo, vrátí funkce hodnotu 1, v opačném případě vrátí hodnotu 0. 11. Napište funkci Fibonaci(n: Integer), která vrátí n-tý prvek Fibonaciho posloupnosti definované: Fibonaci(0) = 1, Fibonaci(1) = 1, Fibonaci(n) = Fibonaci(n-2) + Fibonaci(n-1)
6 Znakové proměnné, textové soubory Proměnné, které jsme ve vytvářených programech používali, byly obvykle celočíselného typu (typu Integer). Zavedli jsme i jiný typ číselných proměnných – typ Real. V této kapitole v úvodu shrneme znalosti a zkušenosti s používáním těchto typů a zavedeme další jednoduché typy proměnných – typ Boolean (logická proměnná) a typ Char (znaková proměnná). Typ TextFile (textový soubor) je komplexnější a složitější. Kromě základní informace o typu (deklarace, přiřazení hodnoty proměnné typu TextFile) vyžaduje rovněž vysvětlení plavidel pro práci s tímto typem: čtení a zápis do textových souborů, sekvenční přístup k textovým souborům apod. Textové soubory využijete při tvorbě jednoduchých aplikací a také ve spojení s objektem Želva. 6.1
Typy proměnných, znakové proměnné
Typ Integer Proměnná typu Integer – celočíselná proměnná: – nabývá hodnot z intervalu -2147483648 .. 2147483647, – zabírá 4 Byte = 32 bitů (= 31 bitů pro číslo + 1bit na znaménko), – přiřazovací příkaz: na obou stranách musí být celá čísla (nebo celočíselné proměnné), – aritmetické operace: +, -, *, div, mod, výsledek operací je celé číslo, pokud oba operandy jsou celá čísla. – relační operace: <, <=, =, =>, >, <>, – procedury: inc (inkrement – zvýší hodnotu dané proměnné o jedničku), dec (dekrement – sníží hodnotu dané proměnné o jedničku), – funkce: pred (předchůdce daného čísla), succ (následovník daného čísla). Typ Real Proměnná typu Real – reálná proměnná: – jsou čísla vyjádřena desetinným zápisem (s desetinnou tečkou) s exponentem, například 3,14 zapíšeme 3.14, 1,10347x10-5 zapíšeme 1.10347E-05, 8,89x1023 zapíšeme 8.89E+23 – nabývají hodnot ± (5.0x10–324 až 1.7 x 10308 ), – obsahují 15-16 platných číslic, zabírají 8 Byte, – speciální reálná konstanta pi pro Ludolfovo číslo, – přiřazovací příkaz: real:=real nebo real:=integer, není možné přiřazení integer:=real! – automatická konverze celého čísla na reálné při přiřazení (real:=integer) a v reálných aritmetických operacích (1+0.5 = 1.0+0.5, 1/3 = 1.0/3.0), – funkce pro konverzi reálných čísel na celá čísla: round (zaokrouhlení), trunc (celá část), – aritmetické operace: +, -, *, /, výsledkem je reálné číslo, – relační operace: <, <=, =, =>, >, <>, – procedury inc a dec a funkce pred a succ nemají smysl, pro reálná čísla nefungují, – operace s reálnými čísly mají často chyby způsobené jejich realizací v počítači – reálná čísla používáme pouze v odůvodněných případech, – chyby při operacích s reálnými čísly mohou ovlivnit výsledky relačních operací! – standardní funkce: sgrt (druhá odmocnina), sgr (druhá mocnina), abs, sin, cos, tan atd.
Typ Boolean Proměnná typu Boolean – logická proměnná: – nabývá jednu z hodnot true (pravda) nebo false (nepravda), – zabírá 1 Byte, – přiřazovací příkaz: na obou stranách přiřazovacího příkazu musí být logické proměnné, – logické operace: and (a), or (nebo), not (negace), xor, – relační operace: false< true, false <> true, – funkce: pred (pred(false) = true, pred(true) = false), succ (succ(false) = true, succ(true) = false), ord (ord(false) = 0, ord(true) = 1), boolean (boolean(0) = false, boolean(1) = true), Typ Char Proměnná typu Char – znaková proměnná: – její hodnotou je jeden z 256 znaků (tzv. ASCII znaků), které jsou uspořádané, – zabírá 1 Byte, protože je vyjádřena celým číslem v rozmezí 0 až 255 (256 znaků), – konstanty typu Char píšeme v apostrofech (například ‘A’, ‘§’) nebo pomocí číselného kódu ve tvaru #kód (například #64, #13), – přiřazovací příkaz: na obou stranách přiřazovacího příkazu musí být znaková proměnná, – znakové operace: neexistují, – relační operace: ‘ ‘<...<’0’<’1’<...<’9’<...<’A’<’B’<...<’Z’<...<’a’<’b’...<’z’, – znakové funkce: pred (předchozí znak), succ (následující znak), char nebo chr (převede daný celočíselný ASCII kód na znak), ord (vrátí ASCII kód daného znaku), – ord(‘ ‘)=32; ord(‘0’)=48; ord(‘1’)=49, nebo obráceně chr(32)=’ ‘; chr(48)=’0’; chr(49)=’1’ co značí, že #32=’ ‘; #48=’0’; #49=’1’, – ord(‘A’)=65; ord(‘a’)=ord(‘A’)+32=97, a tedy #65=‘A‘; #97=‘a‘, – obsahuje-li znaková proměnná c znak cifry (‘0’..’9’), pak ord(c)-ord(‘0’)=cifra (celočíselná proměnná, jednociferné číslo), – obsahuje-li proměnná c znak velkého písmena (‘A’..’Z’), pak ord(c)-ord(‘A’)=cifra (pořadové číslo znaku v abecedě), podobně pro malá písmena, – převod malých písmen na velká: ord(c)-32, převod velkých písmen na malá: ord(c)+32, – procedury: inc (do dané proměnné přiřadí následující znak), dec (do dané proměnné přiřadí předcházející znak). Typy Integer, Boolean a Char jsou tzv. ordinální typy. Jsou uspořádány, proměnné těchto typů mají své následovníky (succ) a předchůdce (pred). Protože mají tuto pevnou hierarchickou strukturu, můžeme pro ně použít FOR-cyklus. Příklad 6.1.1: Příklady FOR-cyklu pro různé ordinální typy proměnných: var i: Integer; b: Boolean; z: Char; begin for i:=1 to 50 do for b:=false to true do
// cyklus projde 50-krát // cyklus projde 2-krát
for for for for
6.2
b:=i=1 to i=2 do z:=’A’ to ‘Z’ do i:=succ(‘a’) to pred(‘z’) do z:=’0’ to ‘9’ do
// // // //
cyklus cyklus cyklus cyklus
projde projde projde projde
0,1 nebo 2-krát 26-krát 24-krát 10-krát
Textové soubory
Vypisovat znaky a textové informace na grafickou plochu Image pomocí příkazu TextOut je zdlouhavé a složité. Pro práci a výpis textových informací budeme proto používat textovou plochu Memo (typu Tmeno). Textovou plochu Memo umístíme na plochu formuláře z palety komponent ze záložky Standard a pomocí myši vyznačíme její velikost. Na ploše formuláře se objeví textová plocha. Ta je pomyslně rozdělena do řádků – Lines. V prvním řádku je při umístění textové plochy na plochu formuláře vypsán text Memo1. Pro práci s textovou plochou budeme používat následující příkazy: – Memo1.Lines.Clear – pro smazání obsahu všech řádků, – Memo1.Lines.Add(‘nějaký text’) – pro přidání řádku s textem nějaký text na textovou plochu, – Memo1.Lines.LoadFromFile(‘text.txt’) – načtení obsahu souboru text.txt a jeho vypsání na textovou plochu Memo1, – Memo1.Lines.SaveToFile(‘text.txt’) – zápis obsahu textové plochy Memo1 do souboru text.txt. Příklad 6.2.1: Vypíšeme seznam velkých písmen abecedy spolu s jejich ASCII kódy. procedure TForm1.Button1Click(Sender: TObject); var z: Char; begin Memo1.Lines.Clear; for z:='A' to 'Z' do Memo1.Lines.Add(z+' '+IntToStr(ord(z))); // k písmenu (proměnná z) připojí mezeru a číselný kód znaku end;
Soubor je posloupnost (sekvence) prvků stejného typu. Je umístěn na nějakém vnějším zařízení, ve složce (adresáři) na disku. K údajům v souboru můžeme přistupovat: – sekvenčně – postupně, podle pořadí, v jakém se prvky v souboru nachází, – přímo – k libovolnému prvku, bez ohledu na uspořádání prvků v souboru. Základní operace pro práci se soubory: – čtení – ze vstupného souboru můžeme číst jednotlivé prvky souboru, – zápis – do výstupného souboru můžeme zapisovat potřebné údaje. Aktuální místo v souboru, z něhož můžeme hodnotu přečíst nebo kam jí lze zapsat, je označeno ukazatelem. Při sekvenčním přístupu k souboru (viz textové soubory) se ukazatel souboru postupně přesouvá od prvního k poslednímu prvku souboru. Ukazatel lze v tomto případě nastavit na zvolené místo pouze jeho postupným přesunem ze začátku souboru k tomuto místu. Přímý přístup k souboru umožňuje přímo určit aktuální místo ukazatele (místo čtení, resp. zápisu).
Textový soubor: V textovém souboru jsou všechny údaje zapsány formou ASCII znaků. Textové soubory umožňují pouze sekvenční přístup a neumožňují současný zápis a čtení údajů – do textového souboru můžeme zapisovat údaje nebo (ale ne současně) z něho můžeme údaje číst. Textový soubor je posloupnost řádků (i prázdná posloupnost = prázdný soubor), jenž je ukončena znakem <Eof> konce souboru. Každý řádek textového souboru je posloupností znaků (i prázdnou posloupností = ‘’) ukončenou znakem <Eoln> konce řádku. Navíc se znak <Eoln> vnitřně kóduje dvěma znaky #13 a #10 (označujeme je také symboly CR a LF) – při čtení souboru po jednotlivých znacích musíme na konci řádku přečíst dva znaky. Rozdíl mezi tím, jak se textový soubor jeví při prohlížení a tím, co je v něm ve skutečnosti zapsáno ukazuje tabulka: Jak to vidíme při Skutečný zápis znaků v souboru prohlížení souboru? Abc Abc<Eoln><Eoln>de<Eoln>f<Eof> nebo přesněji při čtení po znacích de Abc#13#10#13#10de#13#10f<Eof> f Ukazatel je při otevření souboru nastaven na začátek (v našem případě na znak ‘A’). Při práci se souborem se postupně přesouvá na další znaky v tom pořadí, jak jsou postupně v souboru zapsány (v našem případě b, c, #13, #10, #13, #10, d, …) Pro práci s textovými soubory používáme následující příkazy: – deklarace proměnné typu textový soubor: var f: TextFile;
–
přiřazení fyzického souboru umístěného na disku textové proměnné: AssignFile(f, ‘jméno souboru’);
přičemž jméno souboru je celé jméno souboru včetně přípony. Pokud jméno souboru neobsahuje cestu k souboru, počítá program s jeho umístěním v aktuálním adresáři, kterým je obvykle adresář spuštění programu. Jméno souboru může obsahovat cestu k souboru podle jeho umístění na disku. Vyhýbáme se uvádění plné cesty k souboru na disku, protože při přenosu programu na jiný počítač by tyto absolutní cesty k souborům mohli být neplatné. Používáme raději relativní cesty – od momentálního umístění programu (souboru s příponou EXE). Textové soubory, potřebné pro práci programů, je nejlépe definovat (vytvářet) přímo v programovém prostředí Delphi. Nový textový soubor vytvoříme volbou File → New → Text. Před čtením ze souboru je potřeba soubor uložit na disk do zvoleného adresáře. – otevření souboru: • pro čtení – příkaz Reset(f); – takový soubor musí již na disku existovat, • pro zápis – příkaz Rewrite(f); – otevření nového souboru, pokud soubor na disku již existoval, nejprve se vyprázdní, – práce se souborem: • čtení read(f,…); readln(f,…); • zápis: write(f,...); writeln(f,...); – testování konce řádku a konce souboru:
•
logická funkce Eof(f) (zkratka End Of File) vrátí hodnotu true, je-li ukazatel nastavený za posledním znakem souboru = na konci souboru, • logická funkce Eoln(f) (zkratka End Of LiNe) vrátí hodnotu true, je-li ukazatel nastavený na značce <Eoln> nebo za posledním znakem souboru (pak platí eof(f) = true, eoln(f) = true), – uzavření souboru a ukončení práce se souborem: CloseFile(f);
Čtení z textového souboru Pro čtení ze souboru musí tento textový soubor již existovat. V opačném případě pokus o jeho otevření příkazem reset(f) způsobí vstupně-výstupnou chybu (I/O error). Pro čtení ze souboru používáme příkazy read a readln. Příkazem read(f, z) se postupně: – přečte jeden znak ze souboru f z pozice ukazatele, – přečtená hodnota se přiřadí do proměnné z, – ukazatel se přesune na další znak textového souboru. Příkaz readln(f) slouží k přechodu na následující řádek souboru. Pomocí něho přeskočíme všechny znaky v souboru po nejbližší značku <Eoln> a přesuneme ukazatel za tuto značku, tj. na první znak následujícího řádku. Příkaz readln(f, z) je zkrácený zápis pro posloupnost příkazů read(f, z); readln(f). Čtení za koncem souboru (značka <Eof>) způsobí vstupně-výstupnou chybu (I/O error). Upozornění 6.2.1: Příkaz read(f, z) čte i značku <Eoln>, která se v tomto případě chápe jako dva znaky #13 a #10, ne jako jeden speciální znak <Eoln>. Na konci řádku musíme proto pro přechod na další řádek použít příkaz read dvakrát. Příklad 6.2.2: Zjistíme počet mezer v textovém souboru. Výsledek vypíšeme do textového pole. procedure TForm1.Button1Click(Sender: TObject); var z: Char; f: TextFile; pocet: Integer; begin AssignFile(f, 'text.txt'); Reset(f); pocet:=0; while not Eof(f) do begin read(f, z); if z=' ' then Inc(pocet) end; CloseFile(f); Memo1.Lines.Add('Počet mezer v souboru TEXT.TXT je '+IntToStr(pocet)); end;
Pro správnou funkci programu musí soubor text.txt existovat. Protože jsme nezadali cestu k tomuto souboru (viz příkaz AssignFile(f, ‘text.txt’)) musí být umístěn v adresáři, ve kterém se nachází přeložený program (soubor s příponou EXE). Funkce IntToStr(i) (INTeger TO STRing) převede celé číslo i do formy vhodné pro výpis do textového pole (později uvidíme, že tato funkce převádí celé číslo na řetězec).
Příklad 6.2.3: Zjistíme počet řádků v textovém souboru a výsledek opět vypíšeme do textového pole. procedure TForm1.Button1Click(Sender: TObject); var f: TextFile; pocet: Integer; begin AssignFile(f, 'text.txt'); Reset(f); pocet:=0; while not Eof(f) do begin readln(f); Inc(pocet) end; CloseFile(f); Memo1.Lines.Add('Počet řádků v souboru TEXT.TXT je '+IntToStr(pocet)); end;
Příklad 6.2.4: Do textového pole vypíšeme pro každý řádek textového souboru jeho délku. procedure TForm1.Button1Click(Sender: TObject); var f: TextFile; z: Char; delka, pocet: Integer; begin AssignFile(f, 'text.txt'); Reset(f); pocet:=0; // číslo aktuálně čteného řádku while not Eof(f) do begin delka:=0; // výpočet délky řádku while not Eoln(f) do begin read(f, z); Inc(delka) end; Inc(pocet); // výpis délky aktuálního řádku Memo1.Lines.Add(IntToStr(pocet)+'. řádek má délku '+IntToStr(delka)); readln(f); // přechod na další řádek end; CloseFile(f); end;
Zápis do textového souboru Soubor určený pro zápis dat nemusí na disku existovat – v tom případě se při jeho otevření příkazem rewrite vytvoří nový soubor s prázdným obsahem. Pokud soubor již existoval, jeho otevřením příkazem rewrite se zruší jeho obsah zruší (všechna data se smažou). Při zápisu do souboru je ukazatel nastaven vždy na konec souboru. Pro zápis do souboru používáme příkazy write a writeln. Příkaz write(f, z), kde z je znaková proměnná typu Char, zapíše do souboru znak z. Příkaz write(f, r), přičemž r je řetězec uzavřený v apostrofech, zapíše do souboru řetězec r. Příkaz writeln(f) zapíše do souboru značku <Eoln> konce řádku. Místo přímého zápisu značky <Eoln> můžeme konec řádku zapsat do souboru i zápisem odpovídajících znaků #13 a #10 – příkazem write(f, #13#10). Příkaz writeln(f, r) (r je řetězec) je zkráceným zápisem pro posloupnost příkazů write(f, r); writeln(f), resp. write(f, r,#13#10). Soubor uzavřeme příkazem CloseFile(f), čímž se všechna zapsaná data uloží na disk .
Příklad 6.2.5: Vytvoříme soubor text.txt ze znaků ‘A’ až ‘Z’. Obsah souboru vypíšeme do textové plochy příkazem Memo1.Lines.LoadFromFile(‘text.txt’). procedure TForm1.Button1Click(Sender: TObject); var z, z1: Char; f: TextFile; begin AssignFile(f, 'text.txt'); Rewrite(f); for z:='A' to 'Z' do begin for z1:='A' to z do write(f, z1); writeln(f) end; CloseFile(f); Memo1.Lines.LoadFromFile('text.txt'); end;
Příklad 6.2.6: Zkopírujeme obsah souboru text.txt do souboru kopie.txt. Ze souboru text.txt budeme číst, soubor musí na disku existovat. A)
B)
procedure TForm1.Button1Click(Sender: TObject); var z: Char; f1, f2: TextFile; begin AssignFile(f1, 'text.txt'); Reset(f); AssignFile(f2, 'kopie.txt'); Rewrite(f); while not Eof(f1) do if Eoln(f1) then begin readln(f1); writeln(f2) end else begin read(f1, z); write(f2, z) end; CloseFile(f1); CloseFile(f2); Memo1.Lines.LoadFromFile('kopie.txt'); end; // verze bez čtení konce řádku procedure TForm1.Button1Click(Sender: TObject); var z: Char; f1, f2: TextFile; begin AssignFile(f1, 'text.txt'); Reset(f); AssignFile(f2, 'kopie.txt'); Rewrite(f); while not Eof(f1) do begin read(f1, z); write(f2, z) end; CloseFile(f1); CloseFile(f2); Memo1.Lines.LoadFromFile('kopie.txt'); end;
Úkol k textu 6.2.1: Upravte procedury z příkladu 6.2.6 tak, že při kopírování souborů budete malé písmena ‘a’ až ‘z’ převádět na písmena velká. Nepoužívejte proceduru Upcase, použijte vzorečky pro převod znaků – jak byly uvedeny na začátku kapitoly.
Čtení a zápis čísel Pomocí příkazů read a write můžeme číst a zapisovat nejenom znaky, ale také čísla. Předpokládejme, že je deklarovaná číselná proměnná c (typu Integer nebo Real). Pak: – Příkaz read(f, c) nejprve přeskočí všechny mezerové znaky (mezera, <Eoln> a #9-tabulátor) a následně překonvertuje všechny nemezerové znaky na číslo. Pokud převáděné znaky neodpovídají číslu nebo je zapsané číslo ukončeno nemezerovým znakem (například i ‘;’ anebo ‘,’), příkaz skončí s chybou Invalid numeric format. Pokud čteme před koncem souboru pouze mezerové znaky, příkaz chybu neohlásí, vrátí hodnotu 0, která v souboru nemusí být vůbec zapsána. – Příkaz write(f, c) umožní zapsat do textového souboru hodnotu číselné proměnné: • Celá čísla se zapíšou bez mezer před i za číslem – například write(f, n, n+1) zapíše pro n=12 do souboru posloupnost znaků ‘1213’. • Reálná čísla se zapíšou v semilogaritmickém tvaru s mezerou před číslem – například číslo 0,1 se zapíše jako posloupnost znaků ‘ 1.00000000000022E-0001‘ i s nepřesností jeho reprezentace v počítači (viz hodnota 22 v zápisu čísla) . Při čtení čísel z textových souborů používáme místo funkcí Eof(f) a Eoln(f) funkce SeekEof(f) a SeekEoln(f). Ty se od původních funkcí liší pouze v tom, že před vlastním vyhodnocením konce souboru, resp. konce řádku odfiltrují všechny mezerové znaky a znaky tabulátoru a funkce SeekEof i konce řádků. Tím se vyvarujeme chyb, které mohou při čtení a zápisu čísel do textového souboru vznikat. Příklad 6.2.7: Máme vytvořený textový soubor text.txt, který obsahuje pouze celá čísla navzájem oddělena. mezerou nebo koncem řádku (znakem <Eoln>). Obsah souboru text.txt překopírujeme do souboru kopie.txt, přičemž čísla uspořádáme po trojicích na jeden řádek. procedure TForm1.Button1Click(Sender: TObject); var poc, c: Integer; f, f1: TextFile; begin AssignFile(f, 'text.txt'); Reset(f); AssignFile(f1, 'kopie.txt'); Rewrite(f1); poc:=0; // počet čísel aktuálně zapsaných ba řádku while not SeekEof(f) do begin read(f, c); if poc=3 then // pokud máme již tři čísla na řádku, přejdeme // pro zápis čísel na další řádek begin writeln(f1); poc:=1 end else begin if poc>0 then write(f1, ' '); Inc(poc) end; write(f1, c) end; CloseFile(f); CloseFile(f1); Memo1.Lines.LoadFromFile('kopie.txt') end;
Vyzkoušejte nahradit funkci SeekEof funkcí Eof. Co se změní a proč? Jaká chyba zde může vznikat? Prověřte!
Příklad 6.2.8: V textovém souboru reálných čísel (reálná čísla zapisujeme s desetinnou tečkou) text.txt zjistíme počet čísel větších než průměr všech čísel. procedure TForm1.Button3Click(Sender: TObject); var poc: Integer; suma, prumer, c: Real; f: TextFile; begin AssignFile(f, 'text.txt'); Reset(f); suma:=0; poc:=0; while not SeekEof(f) do // výpočet průměru begin read(f, c); suma:=suma+c; Inc(poc) end; prumer:=suma/poc; poc:=0; Reset(f); while not SeekEof(f) do // počet nadprůměrných hodnot begin read(f, c); if c>prumer then Inc(poc) end; CloseFile(f); Memo1.Lines.Add('Průměr hodnot je '+FLoatToStr(prumer)); Memo1.Lines.Add('Počet nadprůměrných hodnot je '+IntToStr(poc)); end;
Upozornění 6.2.2: Uvedený způsob práce s čísly uloženými v textových souborech se v profesionální praxi téměř nepoužívá. Může být totiž zdrojem množství chyb, které vznikají pokud textový soubor není korektní. My tento způsob budeme využívat pouze tehdy, když to bude vyžadovat zadání příkladu a pokud bude známo, že textový soubor je pro práci s čísly vhodně připraven. Formátovací parametr v příkazu write Výpis příkazem write můžeme jistým způsobem ovlivnit pomocí formátovacích parametrů. – Formátovací parametr za znakem nebo řetězcem znaků označuje šířku výpisu daného textu. Například write(f, ‘*’:10) vypíše znak ‘*’ na šířku 10 znaků, tj. vypíše 9 mezer a ‘*’. Pokud je řetězec delší než formátovací parametr (například write(f, ‘AHOJ’:3)), zapíše se kompletní řetězec, formát se ignoruje. – Formátovací parametr za celým číslem šířku výpisu daného čísla. Například write(f, 5*5:5) vypíše 3 mezery a číslo 25 (celkem 5 znaků). Pokud by se číslo do dané šířky nevešlo, formát se ignoruje. – Formátovací parametr za reálným číslem označuje šířku výpisu čísla v semilogaritmickém tvaru; druhý formátovací parametr označuje počet desetinných míst (čísla se vypisují v desetinném tvaru). Například write(f, sin(2):15) vypíše číslo v semilogaritmickém tvaru 9.092974E-0001; write(t, cos(2):7:4) vypíše číslo v desetinném tvaru -0.4161. 6.3
Textové soubory a objekt Želva
Textové soubory využijeme na zaznamenávání údajů o pohybu želvy po grafické ploše a na opětovnou rekonstrukci tohoto pohybu. Situaci popíšeme a předvedeme na příkladech.
Příklad 6.3.1: Je daný textový soubor text.txt, ve kterém je uložena posloupnost příkazů pro želvu: - d číslo – udává o kolik má jít želva dopředu, - p číslo – udává o kolik stupňů se má želva otočit vpravo, - l číslo – udává o kolik stupňů se má želva otočit vlevo. Například: d 100 p 90 do 100 l 90 do 100 p 90. Písmena příkazů (d, p a l) a čísla jsou od sebe navzájem odděleny mezerou nebo koncem řádku. Napište program, který přečte údaje v textovém souboru a pomocí objektu Želva vykreslí odpovídající obrázek. procedure TForm1.Button1Click(Sender: TObject); var z: Char; f: TextFile; z1: TZelva; cis: Integer; begin AssignFile(f, 'text.txt'); Reset(f); z1:=TZelva.Create; while not eof(f) do begin if eoln(f) then begin readln(f); z:=' ' end else read(f, z); if z<>' ' then read(f, cis); case z of 'd' : z1.vpred(cis); 'p' : z1.vpravo(cis); 'l' : z1.vlevo(cis); end end; CloseFile(f); end;
Úkol k textu 6.3.1: Vyzkoušejte program z příkladu 6.3.1 pro soubor s posloupností příkazů: d 100 p 90 d 100 p 180 d 200 l 180 d 100 l 90 d 100
Navrhněte další jednoduché obrázky a definujte potřený obsah souboru text.txt. Úkol k textu 6.3.2: Upravte program z příkladu 6.3.1 tak, že ve vstupním souboru text.txt nebudou příkazy vpřed, vpravo a vlevo zadány pouze jedním písmenem, ale dvojicí písmen – například do, vp, vl. Pokaždé prověřte, že příkaz byl opravdu zapsán oběma písmeny. Jak bychom museli program změnit, kdyby příkazy byly zadávány celými slovy? Příklad 6.3.2: Budeme sledovat pohyb želvy a do souboru zelva.txt zaznamenávat její momentální pozici tak, abychom dokázali pohyb želvy zpětně rekonstruovat. Je zřejmé, že pozici želvy stačí zaznamenávat pouze v těch případech,kdy se opravdu mění – po převedení příkazu vpřed. Formulář programu sestává z grafické plochy Image a dvou tlačítek: jednoho pro zápis aktuální pozice želvy do souboru a druhého pro rekonstrukci pohybu želvy podle údajů v textovém souboru.
procedure TForm1.Button1Click(Sender: TObject); var z: TZelva; f: TextFile; procedure poly(n, d, u: Integer); begin while n>0 do begin z.vpred(d); writeln(f, z.X:0:2, ' ', z.Y:0:2); z.vpravo(u); dec(n); end; end; var i: Integer; begin AssignFile(f, 'zelva.txt'); Rewrite(f); z:=TZelva.Create; z.Smaz; z.vpravo(10); // lupínky poly(9, 6, 10); z.vpravo(90); poly(9, 6, 10); z.vpravo(90); z.vlevo(100); poly(9, 6, 10); z.vpravo(90); poly(9, 6, 10); z.vpravo(90); z.vpravo(90); poly(1, 100, 0); // stonek for i:=1 to 7 do // květ begin poly(9, 5, 10); z.vpravo(90); poly(9, 5, 10); z.vpravo(90); z.vpravo(360/7); end; CloseFile(f); end;
Rekonstrukce pohybu želvy podle údajů v textovém souboru zelva.txt. procedure TForm1.Button2Click(Sender: TObject); var f: TextFile; z: TZelva; x, y: Real; begin AssignFile(f, 'zelva.txt'); Reset(f); z:=TZelva.Create; z.Smaz; while not eof(f) do begin readln(f, x, y); z.SetXY(x, y); end; CloseFile(f); end;
Úkol k textu 6.3.3: Vybarvěte květinu z příkladu 6.3.2 (viz obrázek). Upravte obě části programu: při vykreslování květiny se do souboru zelva.txt zaznamená aktuální barva pro vykreslování, při rekonstrukci pohybu želvy se právě tato barva pro vykreslení pouřije.
Cvičení: 1. Napište program, který ze vstupního souboru text.txt vyhodí všechny mezerové řádky (prázdné řádky nebo řádky obsahující pouze mezerové znaky). 2. Napište program, který v textovém souboru vstup.txt nahradí všechny skupiny mezer pouze jednou mezerou. 3. V textovém souboru slova.txt je na každém řádku pouze jedno slovo. Napište program, který vytvoří nový soubor nejdelsi.txt, který bude obsahovat všechna slova délky nejdelšího slova vstupného souboru. Vstupný soubor slova.txt můžete přečíst pouze jednou, tj. délku nejdelšího slova musíte hledat současně s kopírováním nejdelších slov do výstupného souboru. 4. V textovém souboru povidka.txt jsou uloženy věty ukončeny ‘.’, ‘?’ nebo ‘!’. Napište program, který přepíše vstupní soubor tak, aby každá věta byla napsána na samostatném řádku. Mezery na začátcích řádků vyhoďte. 5. Napište program, který upraví textový soubor povidka.txt tak, že každý řádek natáhne na 80 znaků, tj. stejnoměrně vloží mezi slova mezery tak, aby délka řádku byla 80 znaků. 6. Je daný soubor celých maximálně trojciferných čísel vzájemně oddělených mezerami nebo konci řádků. Napište program, který tato čísla seřadí po pěticích na jeden řádek. Výstup naformátujte tak, aby vzniklo pět sloupců čísel oddělených jednou mezerou (viz obrázek). 7. Je daný soubor celých čísel vzájemně oddělených mezerami nebo konci řádků. Napište program, který vypočte: a) počet všech čísel v souboru, b) součet a průměr čísel v souboru. c) maximální a minimální číslo souboru, d) druhé nejmenší číslo a druhé největší číslo souboru. 8. Napište proceduru pro náhodný pohyb želvy po obrazovce: v každém bodě své dráhy se želva rozhodne, kterým směrem, jakou barvou a tloušťkou pera a jak daleko se vydá. Želva by neměla opustit vymezený prostor (kruh nebo obdélník). Postpný pohyb želvy průběžně zaznamenávejte včetně všech důležitých údajů do souboru. Napište proceduru pro zpětnou ekonstrukci pohybu želvy.
7 Typ pole Pokud potřebujeme pro výpočet více proměnných stejného typu, musíme je všechny vhodně pojmenovat a deklarovat. Jejich množství a různá jména mohou někdy způsobit značné obtíže a prodloužit program. Zejména v případech, kdy proměnné používáme obdobným způsobem (provádíme s nimi obdobné výpočty), pociťujeme potřebu vhodného typu proměnných: vyhovovalo by nám, kdyby proměnné měly stejné jméno a lišily se pouze pořadovým číslem. Ukážeme si, jak tuto situaci řešit pomocí typu pole. Předtím však zavedeme typ interval, jenž nám poslouží k „očíslování“ prvků pole. 7.1
Typ interval
Typ interval je odvozený z nějakého ordinálního typu (Integer, Boolean, Char apod.). Definujeme ho tak, že určíme minimální a maximální hodnotu (konstantu) tohoto typu. Například zápisem 1..10
definuje podtyp ordinálního typu Integer (celá čísla). Takto definovaný typ obsahuje všechny celočíselné konstanty z intervalu <1, 10>, tj. čísla 1, 2, 3, 4, 5, 6, 7, 8, 9 a 10. Současně automaticky přebírá všechny vlastnosti a operace svého „nadřazeného“ ordinálního typu Integer, který proto nazýváme bázovým typem. Deklarace type CisloMysi = 1..10; var x: CisloMysi;
deklaruje proměnnou x typu CisloMysi. Tato proměnná může nabývat celočíselných hodnot z intervalu <1, 10>. Při pokusu o přiřazení hodnoty proměnné x mimo tento interval, ohlásí program chybu. Typ interval přebírá vlastnosti a operace svého bázového typu – je proto také ordinálním typem. Můžeme ho použít ve FOR-cyklu, v příkazu case apod. Jeho výhodou je, že v paměti může zabírat méně místa než původní bázový typ. V našem případě proměnná x zabírá v paměti pouze 1 B (= 8 bitů) na rozdíl od bázového typu Integer, který zabírá 4 B. Příklad 7.1.1: Další možné deklarace typu interval: type roky = 1900..2100; malacisla = -100..100; bajt = 0..255; cislice = ’0’..’9’; VelkaPismena = ’A’..’Z’;
Uvedené deklaraci typu interval říkáme přímá deklarace. Typ můžeme deklarovat i nepřímo při deklaraci proměnné: var pis:’a’..’z’;
Některé předdefinované typy, které můžeme přímo použít: type Integer = -2147483648 .. 2147483647; Smallint = -32768 .. 32767; Shortint = -128 .. 127; Byte = 0 .. 255; Word = 0 .. 65535; Cardinal = 0 .. 4294967295;
7.2
Strukturovaný typ pole
Typ pole je strukturovaný (složený) typ. Obsahuje více prvků (proměnných) stejného typu. Jednotlivé prvky pole jsou označeny indexem, který umožňuje přístup k danému prvku pole. Pole deklarujeme následovně: type pole = array [typ_intexu] of typ_prvku;
Typ_indexu je libovolný ordinální typ, typ_prvku je libovolný typ. Typ pole zabírá v paměti tolik místa, kolik zabírá jeden prvek krát počet prvků pole (počet různých indexů). Příklad 7.2.1: Deklarace type mojepole = array [1..100] of Integer; var p: moje pole;
deklaruje pole, které má sto celočíselných prvků. Typ Integer zabírá v paměti 4 B, proměnná typu mojepole zabírá tedy 4x100 = 400 B. Prvky pole p jsou očíslovány čísly 1 až 100. První prvek pole p je p[1], následují prvky p[2], p[3], p[4], ..., p[37], ..., p[100]. Indexy jednotlivých prvků pole píšeme do hranatých závorek. S prvky pole pracujeme stejně jako s proměnnými odpovídajícího typu. Prvky pole nemohou být proměnnou FOR-cyklu. Prvky pole jsou jednoznačně očíslovány a uspořádány pomocí jejich indexů, proto naopak pro práci s prvky pole používáme obvykle FOR-cyklus. Příklad 7.2.2: Je daný textový souboru text.txt, který obsahuje celá čísla vzájemně oddělena mezerou nebo koncem řádku. Z daného souboru načteme do pole 15 čísel a vypíšeme jejich součet a průměr. Zjistíme, kolik prvků pole je nadprůměrných a kolik podprůměrných. procedure TForm1.Button1Click(Sender: TObject); const max = 15; var p: array [1..max] of Integer; f: TextFile; prumer: Real; suma, poc1, poc2, i: Integer; begin AssignFile(f, 'text.txt'); Reset(f); for i:=1 to max do read(f, p[i]); CloseFile(f); suma:=0; for i:=1 to max do suma:=suma+p[i]; prumer:=suma/max; poc1:=0; poc2:=0; for i:=1 to max do if p[i]>prumer then Inc(poc1) else if p[i]<prumer then Inc(poc2); Memo1.Lines.Clear; Memo1.Lines.Add('Součet hodnot je '+IntToStr(suma)); Memo1.Lines.Add('Průměr hodnot je '+FLoatToStr(prumer)); Memo1.Lines.Add('Počet nadprůměrných hodnot je '+IntToStr(poc1)); Memo1.Lines.Add('Počet podprůměrných hodnot je '+IntToStr(poc2)); end;
Srovnejte uvedený program s programem z příkladu 6.2.8.
Pole jako parametry podprogramů Typy formálních parametrů definovaných procedur a funkcí musí být deklarovány přímo, tj. v deklaracích podprogramů použijeme pouze identifikátory již deklarovaných typů. Pole může být také výsledkem funkce. V tom případě musí být opět deklarováno přímo. Příklad 7.2.3: Chybné deklarace podprogramů: procedure pomoc(a: array [1..10] of Integer); procedure udelej(x: 1..20]; function prevod(x: Boolean): array [1..20] of Char;
Správné deklarace podprogramů: type interval: 1..20; pole1: array [1..10] of Integer; pole2: array [interval] of Char; procedure pomoc(a: pole1); procedure udelej(x: interval]; function prevod(x: Boolean): pole2;
Přímá deklarace typů umožňuje také přímé přiřazení hodnot proměnných daného typu. Uvažujeme-li proměnné a, b typu pole1 (viz příklad 7.2.3), máme následující možnosti přiřazení hodnot prvků pole b do pole a: for i:=1 to 10 do a[i]:=b[i]; – pomocí FOR-cyklu: a:=b; – přímo: – efektivnější, rychleší a přehlednější způsob, který je však možný pouze při přímé deklaraci typu. Příklad 7.2.4: Náhodně vygenerujeme prvky pole X a Y v rozmezí 0..n-1. Sečtením odpovídajících prvků pole X a Y vznikne pole Z. Hodnoty všech tří polí přehledně vypíšeme. type pole = array [1..100] of Integer; function generuj(n: Integer): pole; var i: Integer; begin for i:=1 to high(pole) do result[i]:=random(n) end; function soucet(a, b: pole): pole; var i: Integer; begin for i:=1 to high(pole) do result[i]:=a[i]+b[i] end; procedure vypis(a, b, c: pole); var i: Integer; begin for i:=1 to high(pole) do Memo1.Lines.Add(IntToStr(a[i])+’ + ’+IntToStr(b[i])+’ = ‘ +IntToStr(c[i])) end; var x, y, z: Pole;
begin x:=generuj(100); y:=generuj(150); z:=soucet(x, y); vypis(x, y, z); end;
V programu z příkladu 7.2.4 jsme použili funkci high. Při práci s poli můžeme využít následující funkce (předpokládejme type pp = array [-3..25] of Integer): – funkce low vrátí hodnotu minimálního indexu pole, (low(xp)=-3), – funkce high vrátí hodnotu maximálního indexu pole, (high(xp)=25), – funkce length vrátí počet prvků pole, (length(xp)=29). 7.3
Pole želv – animace pomocí želv, honičky želv
Tak, jak jsme pracovali s poli celých čísel nebo znaků, můžeme vytvářet a používat pole želv. S takovým polem budeme opět pracovat pomocí FOR-cyklu. Více želv vykonávajících obdobné činnosti vytvoří zajímavé animované obrázky. Příklad 7.3.1: Vygenerujeme v řadě za sebou 50 želv, každou z nich natočíme pod jiným úhlem a všechny želvy najednou uvedeme do pohybu. procedureTForm1.Button1Click(Sender: TObject); type poleZelv = array [1..50] of TZelva; var p: poleZelv; i, j: Integer; begin for i:=low(poleZelv) to high(poleZelv) do begin p[i]:=TZelva.Create; p[i].moveXY(10*i+100, 250); p[i].setUhel(360/50*i); p[i].PW:=5; end; for j:=1 to 180 do begin for i:=low(poleZelv) to high(poleZelv) do begin p[i].vpred(4); p[i].vpravo(2) end; cekej(10) end; end;
Úkol k textu 7.3.1: Ilustrační obrázek k příkladu 7.3.1 vznikne otočením skutečného obrazu namalovaného želvami o 90°. Upravte předchozí program tak, aby želvy vykreslili přímo znázorněný obrázek. Jak bychom museli program dále upravit, aby vzniknul obrázek otočený o dalších 90°? Zvládli by jste otočit obrázkem o různý zadaný úhel?
Příklad 7.3.2: (animace pomocí želv) Želvy z příkladu 7.3.1 necháme obíhat po kružnicích pořád dokola. Před pohybem želv smažeme obrazovku – na obrazovce zůstane zaznamenán jenom poslední krok pohybu želv. procedureTForm1.Button1Click(Sender: TObject); type poleZelv = array [1..50] of TZelva; var p: poleZelv; i: Integer; begin for i:=low(poleZelv) to high(poleZelv) do begin p[i]:=TZelva.Create; p[i].moveXY(10*i+100, 250); p[i].setUhel(360/50*i); p[i].PW:=5; end; while true do begin p[1].Smaz; for i:=low(poleZelv) to high(poleZelv) do begin p[i].vpred(4); p[i].vpravo(2) // vyzkoušejte různé úhly – 3, 5, 10, 20 // nebo různé úhly pro každou želvu – i, 10+i, 1+i/50 end; cekej(10) end; end;
Příkaz p[i].vpred(4) můžeme nahradit dvojicí příkazů p[i].vpred(100); p[i].vpred(-96);
V obou případech se želva posune o 4 kroky vpředu, ve druhém případě však nechá za sebou čáru, čímž vzniknou zajímavé efekty. Příklad 7.3.3: (animace pomocí želv) Budeme postupovat podobně, jako v příkladu 7.3.2. Želvy však vygenerujeme ve společném středu odkud je rozestavíme na počátečné pozice v kruhu. Každou želvu natočíme pod jiným úhlem. procedure TForm1.Button1Click(Sender: TObject); var p: array [1..50] of TZelva; i: Integer; begin for i:=1 to 50 do begin p[i]:=TZelva.Create; p[i].setuhel(360/50*i); // rozestavění po kružnici p[i].ph; p[i].vpred(100); p[i].pd; p[i].setuhel(p[i].H*2); // různý úhel natočení želv end; // vyzkoušejte i jiné násobky while true do // původního úhlu p[i].H, begin // například 3, 4, 9, 10, 15, 30, p[1].Smaz; // 45, 48, 49, 50, 51, 56, 60,… for i:=1 to 50 do begin p[i].vpred(100); p[i].vpred(-96); p[i].vpravo(2) end; cekej(1) end end;
Zajímavé obrázky a efekty získáváme pokud se několik želv navzájem honí. Základním principem každé honičky je, že ten, který honí, běží vždy směrem k honěnému. Abychom zjistili směr, kterým se má honící želva v daném okamžiku vydat, použijeme funkci smer. Ta na základě souřadnic bodu [zX, zY] aktuální pozice honící želvy a souřadnic bodu, ke kterému má tato želva směřovat (bod [x, y] = pozice honěné želvy), vrátí úhel (v stupních), pod kterým má honící želva vykročit směrem k honěné želvě. function Smer(zX, zY, x, y: Real): Real; begin x := x-zX; y := zY-Y; if (y=0) or (x=0) then if y=0 then if x<0 then result:=180 else result:=0 else if y<0 then result:=270 else result:=90 else if y>0 then if x>0 then result:=arctan(y/x)*180/pi else result:=180-arctan(-y/x)*180/pi else if x>0 then result:=360-arctan(-y/x)*180/pi else result:=180+arctan(y/x)*180/pi end;
Příklad 7.3.4: Definujeme pole želv, které rozmístíme náhodně po grafické ploše Image1. Pak budeme opakovat následující akci: každá želva se posune o jednu setinu vzdálenosti směrem ke svému předchůdci (první želva se posune směrem k poslední želvě). V programu využijeme funkci smer. procedure TForm1.Button1Click(Sender: TObject); const n=8; var z: array[1..n] of TZelva; i, j: Integer; d: Real; begin randomize; for i:=1 to n do begin z[i]:=TZelva.Create; z[i].moveXY(random(Image1.Width), random(Image1.Height)); z[i].PW:=5; z[i].PC:=random(16777216); end; z[1].Smaz; while true do begin for i:=1 to n do begin j:=i mod n+1; // následující želva z[j].setuhel(smer(z[j].X, z[j].Y, z[i].X, z[i].Y)); d:=z[j].vzdal(z[i].X, z[i].Y); z[j].vpred(d/100); end; cekej(100); end; end;
Úkol k textu 7.3.2: Program z příkladu 7.3.4 by bylo vhodné ukončit, pokud jsou želvy již dostatečně blízko u sebe. Upravte podmínku WHILEcyklu tak, aby vykonávání procedury skončilo, pokud jsou všechny želvy již dostatečně blízko vedle sebe (viz obrázek).
Úkol k textu 7.3.3: Zajímavé efekty vzniknou, pokud honící želva předtím, než udělá krok směrem k honěné želvě, nakreslí k ní spojnici. Obrázek znázorňuje situaci pro tři želvy. Upravte vhodně program z příkladu 7.3.4 tak, aby honící želva nakreslila nejprve spojnici k honěné želvě a teprve pak se k ní posunula o jednu desetinu vzdálenosti. Cvičení: 1. Napište funkci, která cyklicky posune prvky pole o jeden doprava, resp. doleva. 2. Je daný typ type pole=array [1..10] of 0..9; reprezentující maximálně deseticiferná přirozená čísla. a) Napište funkci PoleToInt pro převod těchto čísel do typu Integer. Napište funkci IntToPole pro převod kladných čísel typu Integer do definovaného pole. b) Napište funkci pro sčítání dvou čísel reprezentovaných definovaným polem – čísla sečtěte přímo v poli, nepřevádějte je do jiného typu. 3. Je daný textový soubor text.txt. Napište program, který sestaví frekvenční tabulku výskytů jednotlivých písmen anglické abecedy v souboru. Program bude číst textový soubor po znacích a v poli pocet=array [’A’..’Z’] of Integer; bude počítat počet výskytů jednotlivých znaků abecedy (bez diakritiky). Rozdíl mezi malými a velkými písmeny ignorujte. Výsledek vypište na textovou plochu. 4. Pomocí Erastotenova síta vypište na textovou plochu všechna prvočísla do čísla n. Při výpočtu použijte číselné pole. 5. Je dané pole kostka=array [1..6] of Integer; reprezentující, kolikrát padlo dané číslo (čísla 1 až 6) při hodu kostkou. Napište program, který bude simulovat náhodný hod kostkou a bude počítat, kolikrát padlo to-které číslo. Výsledky vypisujte průběžně po každých 100 hodech. 6. Je dané pole celých čísel. Napište funkci, která setřídí dané pole vzestupně nebo sestupně podle zadaného parametru. Při třídění není možno použít další pole, přípustná je pouze vzájemná výměna dvou prvků pole. 7. Napište proceduru PosunK, která cyklicky posune prvky pole o k prvků vlevo, resp. vpravo. Při přesunu není dovoleno použít další pole, ani v cyklu pole k-krát posouvat o jeden prvek.
8. Rozmístěte n želv rovnoměrně na kružnici s poloměrem d (želvy budou rozmístěny ve vrcholech pravidelného núhelníku). Každou s želv natočte k jejímu následovateli, přičemž konstanta p určuje, kdo tímto následovatelem je – například pro p=3 je želva 1 natočena k želvě 4. Na závěr projdou všechny želvy najednou vzdálenost ke svému následovateli krokem 1.
9. Pomocí více želv vykreslete najedou pravidelný n-úhelník se všemi jeho úhlopříčkami – každou úhlopříčku bude kreslit jedna želva. Promyslete, kolik úhlopříček má n-úhelník a kolik želv budete tedy pro kreslení potřebovat. Všechny želvy vykreslí svoji úhlopříčku bez ohledu na její délku ve stejném čase – rozdělte každou úhlopříčku na 100 dílů a v každém kroku vykreslí každá želva jednu setinu své dráhy. 10. Upravte program z příkladu 7.3.2 tak, že v každém kroku pohybu želv vykreslíte úsečky spojující vždy dvě sousední želvy. Jak se změní výsledná animace? 11. N želv je na začátku rovnoměrně rozestavených na vodorovné přímce ve středu obrazovky. Všechny želvy jsou natočené směrem nahoru a začínají se pohybovat přímo vzhůru. Pokud některá z želv překročí hranici 200 bodů od vodorovného středu obrazovky, otočí se a pokračuje opačným směrem. Želvám zvedněte pero. Na začátku nekonečného cyklu smažte obrazovku. V každém kroku projde první želva vzdálenost 1, druhá želva vzdálenost 2 (dva kroky délky 1), třetí želva vzdálenost 3 (tři kroky délky 1) atd., přičemž se v případě potřeby otočí a pokračuje opačným směrem. Po přejití své vzdálenosti se želva zviditelní malou tečkou. 12. N želv je za začátku rovnoměrně rozestavených na vodorovné přímce ve středu obrazovky. Želvy jsou rovnoměrně natočeny, úhel mezi želvami je 360/N. Želvy se budou pohybovat v nekonečném cyklu následujícím způsobem: - smaže se obrazovka, - každá želva nakreslí čáru s délkou 10 bodů a vrátí se zpět, každá želva se zvednutým perem přejde vzdálenost i a otočí se vpravo o úhel i° (první želva projde vzdálenost 1 a otočí se o 1°, druhá projde vzdálenost 2 a otočí se o 2° atd.). 13. N želv je za začátku rovnoměrně rozestavených na vodorovné přímce ve středu obrazovky. Všechny mají tloušťku pera 10 a pohybují se následovně: - smaže se obrazovka, - první želva se náhodně otočí o úhel v rozmezí –30° a 30° a posune se o 10 bodů vpřed, - ostatní želvy se posunou na místo svého předchůdce – od poslední želvy ke druhé. 14. N želv je na začátku náhodně rozestavených po grafické ploše (náhodná pozice, náhodný úhel natočení). První želva se pohybuje rovně s krokem 1, na okraji grafické plochy se odrazí jako kulečníková koule (podle zákona odrazu: úhel dopadu se rovná úhlu odrazu) a pokračuje dál. Ostatní želvy se navzájem honí – druhá za první, třetí za druhou atd.
8 Znakové řetězce, dvourozměrná pole V předchozí kapitole jsme definovali typ pole. Viděli jsme, že prvky pole musí být stejného typu. Nyní definujeme typ znakový řetězec a ukážeme si, jak souvisí znakové pole (pole s prvky typu Char) se znakovými řetězci. Definice pole nám umožní definovat pole, jehož prvky budou opět typu pole. Tím dostaneme dvourozměrné pole. Ukážeme si využití dvourozměrného pole při práci s obrázky. 8.1
Znakové řetězce
Typ znakový řetězec je typ, který obsahuje posloupnost znaků (typu Char). Znaky znakového řetězce jsou (na rozdíl od textového souboru) očíslovány od 1 po momentální délku řetězce – podobně jako prvky jednorozměrného pole. Délka znakového řetězce se uchovává ve 4 bajtech a mohla by teoreticky být až 4 gigabajty. Ve skutečnosti je délka znakového řetězce omezena možnostmi Windows, můžeme počítat s délkou znakového řetězce asi 1 gigabajt. Proměnnou s typu znakový řetězec (String) deklarujeme: var s: String;
Deklarovaná proměnná s má zatím nedefinovanou hodnotu. Můžeme jí přiřadit řetězcovou konstantu příkazem: s := ’řetězec’;
Délku znakové proměnné určíme příkazem Length(s). Proměnná s má teď délku 7 znaků, první znak je ‘ř’, druhý ‘e’, …, sedmý znak je ‘c’. Řetězcové konstanty stejně jako znakové konstanty uzavíráme v apostrofech. Řetězcové proměnné můžeme přiřadit prázdný řetězec, tj. prázdnou posloupnost znaků (řetězec délky 0): s := ’’;
Protože jsou znaky řetězce očíslovány čísly, můžeme pracovat přímo z jednotlivými znaky znakového řetězce: s := ’hodinky’;
Příkazem s[3] := ’l’ změníme třetí znak řetězce s (s má nyní hodnotu ‘holinky’). Nemůžeme se však odvolávat na znaky s číslem větším, než je délka řetězce – například příkaz s[10] := ’*’ způsobí chybu. Řetězcové proměnné můžeme použít pro zápis a čtení z textového souboru. Použití je obdobné jako u znakových proměnných: write(f, promenna_typu_string); writeln(f, promenna_typu_string); read(f, promenna_typu_string); readln(f, promenna_typu_string);
Operace se znakovými řetězci - Logické operátory: =, <>, <, >, <=, >= srovnávají dva řetězce. Výsledek operace je typu Boolean a je dán lexikografickým uspořádáním řetězců: ! Dva řetězce se srovnávají znak po znaku. Pokud jsou znaky v obou řetězcích shodné, pokračuje se ve srovnávání. Narazíme-li ve srovnávání na rozdílné znaky, výsledek srovnání řetězců je nastaven na výsledek porovnání prvních dvou rozdílných znaků. ! Porovnávání znaků se provádí podle pravidel typu Char, tj. menší je znak s menším ASCII kódem.
Úkol k textu 8.1.1: Napište program pro lexikografické porovnávání dvou řetězců zapsaných v textovém souboru. Odpovídá lexikografické uspořádání řetězců jejich abecednímu uspořádání? Dokážete definovat podmínku, při splnění které odpovídá lexikografické uspořádání abecednímu? Situaci zvažte s ohledem na velká a malá písmena a také s ohledem na používání českých znaků. -
Zřetězení řetězců: +. s:='abc'+'def'; // s='abcdef' s:=''; for i:=1 to 10 do s:=s+'*'; // s='**********'
Operátor + je tzv. polymorfní operátor – výsledek operace závisí od typu operandů (celé číslo, reálné číslo, řetězec, …). Úkol k textu 8.1.2: Vytvořte program, který bude donekonečna prodlužovat deklarovaný řetězec a vypisovat do textové plochy jeho aktuální délku. Jaká je maximální délka řetězce na Vašem počítači, se kterou ještě můžete pracovat? Krátké znakové řetězce Maximální délku řetězce můžeme určit při jeho deklaraci. Deklarace: var s : string[10];
deklaruje řetězec s s maximální délkou 10 znaků. Při pokusu o přiřazení delšího řetězce do proměnné s jsou zbývající znaky smazány. Například: s := ‘abc’ + ‘defgh’ + ‘ijklm’;
// s=’abcdefghij’
Krátké znakové řetězce šetří paměť a mohou urychlit výpočet. Podprogramy na řetězcích Length(řetězec) Momentální délka řetězce. SetLength(řetězcová_proměnná, délka)
Nastaví délku řetězce. Pokud je menší než momentální délka, znaky na konci se ztrácí, pokud je větší než momentální délka, znaky na konci mají nedefinovanou hodnotu.
Copy(řetězec, od, kolik)
Vybere podřetězec délky kolik původního řetězce od pozice od. s:=copy('fialová kráva', 9, 3) // s='krá' s:=copy('fialová kráva', 20, 3) // s='' s:=copy('fialová kráva', 9, 13) // s='kráva'
Potřebujeme-li vybrat podřetězec od nějakého indexu až do konce, nemusíme počítat přesnou velikost, můžeme použít konstantu maxInt. s:=copy('fialová kráva', 9, maxInt) // s='kráva' Pos(podřetězec, řetězec)
Zjistí počáteční index prvního výskytu podřetězce v daném řetězci. Pokud se hledaný podřetězec v řetězci nenachází, vrátí hodnotu 0. i:=pos('kráva', 'fialová kráva') // i=9 i:=pos('tráva', 'fialová kráva') // i=0 i:=pos('a', 'fialová kráva') // i=3
Příklad 8.1.1: Funkce Pocet vrátí počet výskytů podřetězce p v řetězci s. function Pocet(p, s: String): Integer; var i, j: Integer; begin result:=0; j:=0; repeat i := pos(p,copy(s,j+1,MaxInt)); if i>0 then begin inc(result); j:=j+i end; until i=0; end;
Při řešení tohoto příkladu jsme využili jiné konstrukce cyklu a to REPEAT-cyklus. Této cyklus opakovaně vykonává tělo cyklu mezi příkazy repeat a until. Po každém vykonání těchto příkazů otestuje podmínku cyklu – logický výraz za slovom until. Pokud je podmínka splněna (její hodnota je true), cyklus končí a vykonávání programu pokračuje příkazy za cyklem. V opačném případě (není-li podmínka splněna), se cyklus opakuje. Úkol k textu 8.1.3: Přepište funkci z příkladu 8.1.1 pomocí WHILE-cyklu. Jaký je rozdíl mezi WHILE-cyklem a REPEAT-cyklem? Kolikrát se vykoná tělo REPEAT-cyklu? Příklad 8.1.2: Vytvoříme vlastní funkci Pos podle její definice v Delphi. function Pos(p, s: String): Integer; var nasel: Boolean; begin nasel:=false; result:=0; while not nasel and (result<=Length(s)) do begin Inc(result); nasel:=p=copy(s, result, length(p)) end; if not nasel then result:=0 end;
Promyslete si tučně označený příkaz. Jak bychom mohli tento příkaz přepsat pomocí podmiňovacího příkazu? Uvedeme další Delete(řetězec, od, kolik)
Smaže z původního řetězce kolik znaků od pozice od.
s:=delete('motorkár', 4, 3) Insert(co, řetězec, od)
// s='motár'
Vloží do původního řetězce podřetězec co od pozice od. s:=insert('a a koč', 'motorkár', 7)
Str(číslo, řetězec)
Převede číslo na řetězec.
str(123, s); str(-1.23, s); str(123:5, s); str(-1.23:8:2, s); Val(řetězec, číslo, ok)
// // // //
s='123' s='-1.23' s=' 123' s=' -1.23'
Převede řetězec na číslo. Pokud převod proběhl v pořádku, má proměnná ok hodnotu 0. V opačném případě má hodnotu pozice znaku v řetězci, kde došlo k chybě. val('123', i, ok); // val('1,23', i, ok); // val('123 ', i, ok); // val('- 123', i, ok); //
i=123, ok=0 i=? ok=2 i=? ok=4 i=? ok=2
Úkol k textu 8.1.4: Projděte si ještě jednou podprogramy na řetězcích a zjistěte, které z nich jsou procedury a které funkce. Definujte hlavičky jednotlivých podprogramů včetně označení typů použitých proměnných. Pokuste se vytvořit vlastní varianty podprogramů pro práci s řetězci. Příklad 8.1.3: Vytvoříme funkci, která za každý znak vstupního řetězce vloží mezeru. function Vloz(s: String): String; var i: Integer; begin result:=’’; for i:=1 to Length(s) do result:=result+s[i]+’ ’ end;
Druhá funkce naopak odstraní všechny mezery z řetězce. function Odstran(s: String): String; var i: Integer; begin result:=s; repeat i:=Pos(’ ’, result); if i>0 then delete(result, i, 1) until i=0 end;
8.2
Dvourozměrné pole, matice
Jak již bylo uvedeno údajová struktura pole může mít prvky libovolného typu, tedy i prvky typu znakový řetězec. Ukážeme si příklad použití pole znakových řetězců. Příklad 8.2.1: Napíšeme program, který načte textový soubor text.txt do pole znakových řetězců – jednotlivé řádky textu budou jednotlivými položkami pole. Každý řádek textu upravíme tak, že vymažeme přebytečné mezery na začátku a na konci řádku a každému slovu změníme první písmeno na velké. Předpokládáme, že text je psán bez diakritiky. Výsledky úpravy vypíšeme na textovou plochu Memo1. procedure TForm1.Button1Click(Sender: TObject); var f: TextFile; p: array [1..1000] of String; n, i, j: Integer; begin AssignFile(f, ’text.txt’); Reset(f); n:=0; while not Eof(f) and (n
begin Inc(n); readln(f, p[n]) end; CloseFile(f); Memo1.Lines.Clear; for i:=1 to n do begin j:=1; // vymaže mezery na začátku řádku while (j<=Length(p[i])) and (p[i][j]=’ ’) do Inc(j); delete(p[i], 1, j-1); j:=Length(p[i]); // vymaže mezery na konci řádku while (j>=1) and (p[i][j]=’ ’) do Dec(j); delete(p[i], j+1, maxInt); // záměna počátečních písmen slov for j:=1 to Length(p[i]) do if (j=1) or (p[i, j-1]=’ ’) then p[i, j] := UpCase(p[i, j]); Memo1.Lines.Add(p[i]) end; end;
Zápis p[i][j] označuje j-té písmeno na řádku i. Můžeme jej psát zkráceně jako p[i, j]. Dalším zobecnění naznačených myšlenek můžeme uvažovat o poli, jehož jednotlivé prvky jsou opět pole. Takto dostáváme dvourozměrné pole, ve kterém například prvek a[3][5] označuje 5-tý prvek třetího pole (můžeme psát také a[3, 5]). Dvourozměrné pole si můžeme představit jako tabulku (matici), ve které a[3, 5] označuje prvek ve třetím řádku a pátém sloupci. Příklad 8.2.2: Je dané dvourozměrné pole znaků, ve kterém je zapsán nějaký text. Napište program, který posune druhý řádek pole na místo prvního, třetí řádek na místo druhého, atd. až první řádek pole přesune do uvolněného posledního řádku. var a: array [1..10] of array [1..10] of Char; r: array [1..10] of Char; // paměť prvního řádku i, j: Integer; begin // naplnění pole for j:=1 to 10 do r[j]:=a[1, j]; for i:=1 to 9 do for j:=1 to 10 do a[i, j]:=a[i+1, j]; for j:=1 to 10 do a[10, j]:=r[j]; … end;
Pokud použijeme přímou deklaraci pole, můžeme místo FOR-cyklu použít pro přiřazování prvků pole přímé přiřazení. type radek = array [1..10] of Char; matice = array [1..10] of radek; var a: matice; r: radek; // paměť prvního řádku i: Integer; begin // naplnění pole r:=a[1]; for i:=1 to 9 do a[i]:=a[i+1]; a[10]:=r; … end;
Příklad 8.2.3: Zjistíme, zda je daná matice symetrická – napíšeme potřebnou funkci. const n=10; type matice = array [1..n] of array [1..10] of Integer; function Symetricka(a: matice): Boolean; var i, j: Integer; begin result:=true; for i:=1 to n do for j:= 1 to i-1 do if a[i, j] <> a[j, i] then begin result:=false; Exit end; end;
Pro přechod všemi prvky dvourozměrného pole používáme vnořené cykly. Protože u matice je obvykle znám její rozměr, jejími prvky můžeme projít vnořenými FOR-cykly. Pokud při zjišťování symetričnosti matice narazíme na dva rozdílné prvky umístěné symetricky podél hlavní úhlopříčky (a[i, j] <> a[j, i]), matice není symetrická a její další prohledávání je tedy zbytečné. Proto z podprogramu vyskočíme příkazem Exit. Úkol k textu 8.2.1: Přepište program z příkladu 8.2.3 pomocí WHILE-cyklů tak, aby prohledávání matice bylo ukončeno ihned, pokud je zřejmé, že matice není symetrická. Nepoužívejte příkaz Exit. Pro výpis obsahu dvourozměrného pole nejlépe vyhovuje tabulka, kterou vložíme na plochu formuláře prostřednictvím komponenty StringGrid (záložka Additional v paletě komponent). Po vložení komponenty StringGrid na plochu formuláře upravíme celkový vzhled tabulky nastavením odpovídajících parametrů v objektovém inspektoru: FixedRows – počet řádků v záhlaví tabulky, obvykle nastavíme na 0, FixedCols – počet sloupců v záhlaví tabulky, nastavíme na 0, DefaultRowHeight – předdefinovaná výška řádku, DefaultColWidth – předdefinovaná šířka sloupce, RowCount – počet řádků tabulky, můžeme nastavit i při běhu programu: například StringGrid1.RowCount:=n;
ColCount – počet sloupců tabulky, můžeme nastavit i při běhu programu: například StringGrid1.ColCount:=m;
Šířku a výšku komponenty StringGrid (parametry Width a Height) přizpůsobíme velikosti vypisované matice. Pro zápis hodnot do jednotlivých buněk tabulky použijeme parametr Cells typu řetězec (String). Příklad 8.2.4: (výpis dvourozměrné matice do tabulky) Napíšeme program, který náhodně vygeneruje obsah jednotlivých prvků matice a pak tuto matici zobrazí prostřednictvím komponenty StringGrid. Po vložení komponenty StringGrid na plochu formuláře nastavíme této komponentě v objektovém inspektoru jednotlivé parametry: DefaultColWidth:=30; FixedRows:=0; FixedCols:=0. procedure TForm1.Button10Click(Sender: TObject); const m=10; n=8;
var p: array [1..m, 1..n] of Integer; i, j: Integer; begin for i:=1 to m do for j:=1 to n do p[i, j]:=random(1000); with StringGrid1 do begin ColCount:=m; RowCount:=n; Width:=DefaultColWidth*m+20; Height:=DefaultRowHeight*n+20; end; for i:=1 to m do for j:=1 to n do StringGrid1.Cells[i-1, j-1]:=IntToStr(p[i, j]) end;
Příkaz with názevdo, který jsme v příkladu použili, umožní zkrátit zápis příkazů v jeho těle. Pojmenování název v záhlaví příkazu je automaticky vkládáno před pojmenování parametrů a metod. Blíže se tímto příkazem budeme zabývat při popisu typu záznam. 8.3
Bitmapové obrázky, práce s myší, editovací řádek
Bitmapový obrázek je dvourozměrné pole (matice), jehož jednotlivé prvky (pixely Pixels) jsou barevné body (typu TColor - barva). Práci s bitmapovými obrázky si předvedeme a vysvětlíme přímo při řešení konkrétních příkladů. Příklad 8.3.1: (základy práce s bitmapovými obrázky) Připravíme si několik stejně velkých bitmapových obrázků (například 150x200 bodů) a uložíme je do nově vytvořené složky, Na plochu formuláře vložíme dvě grafické plochy Image a upravíme jejich rozměry tak, aby byly shodné s rozměry vytvořených obrázků (150x200 bodů), Na plochu formuláře umístíme také několik tlačítek, kterým budeme postupně přiřazovat potřebné funkce. Celý projekt uložíme do stejné složky, ve které jsou již umístěny připravené obrázky. Postupně klikneme na tlačítka Button1 a Button2 a přiřadíme jim akci pro načtení bitmapových obrázků:do grafické plochy Image1:
procedure TForm1.Button1Click(Sender: TObject); begin Image1.Picture.LoadFromFile('p_delfini.bmp'); end;
procedure TForm1.Button2Click(Sender: TObject); begin Image1.Picture.LoadFromFile('p_medved1.bmp'); end;
Tlačítku Button3 přiřadíme akci postupného kopírování (po řádcích) obrázku zobrazeného na grafické ploše Image1 do grafické plochy Image2. procedure TForm1.Button3Click(Sender: TObject); var i, j: Tnteger; begin for i := 0 to Image1.Height-1 do begin for j := 0 to Image1.Width-1 do Image2.Canvas.Pixels[j, i] := Image1.Canvas.Pixels[j, i]; Image2.Repaint; end; end;
Příkaz Image2.Repaint překreslí grafickou plochu Image2 po překopírování každého řádku, aby se tato změna stala viditelnou. V opačném případě by se zobrazila pouze konečná změna (celý překopírovaný obrázek). Pro vzájemné kopírování částí grafických ploch můžeme použít také metodu CopyRect. Tato metoda má tři parametry: – obdélník, do něhož má být část grafické plochy zkopírována, – plocha (Canvas), jehož část budeme kopírovat, – obdélník (zdrojová oblast plochy Canvas), z níž chceme kopírovat. Obdélníky definujeme obvykle pomocí konstrukce Rect(x1, y1, x2, y2), ve které zadáme souřadnice levého horního a pravého dolního rohu zvolené oblasti.. procedure TForm1.Button3Click(Sender: TObject); begin Image2.Canvas.CopyRect(Rect(10, 10, Image2.Width-10, Image2.Height-10), Image1.Canvas, Rect(0, 0, Image1.Width, Image1.Height)); end;
Uvedená procedura zkopíruje obsah grafické plochy Image1 na plochu Image2, přičemž zde vytvoří rámeček o tloušťce 10 bodů. Příklad 8.3.2: (barvy, barevné body) Jak již bylo uvedeno, můžeme si jednotlivé barvy na počítači namíchat ze tří základních barev – červené, zelené a modré (viz kapitola 2.3). V tomto příkladě se pokusíme o opačný proces: zobrazený obrázek rozložit na jednotlivé barevné složky – viz tři tlačítka v příkladu 8.3.1: procedure TForm1.Button4Click(Sender: TObject); var i, j: Integer; c: Byte; pom: TColor; begin // červená složka for i := 0 to Image1.Height-1 do for j := 0 to Image1.Width-1 do begin // červená složka c:=GetRValue(Image1.Canvas.Pixels[j, i]); Image2.Canvas.Pixels[j, i] := RGB(c, 0, 0); { další možnosti // pro zelenou složku c:=GetGValue(Image1.Canvas.Pixels[j, i]); Image2.Canvas.Pixels[j, i] := RGB(0, c, 0);
// pro modrou složku c:=GetBValue(Image1.Canvas.Pixels[j, i]); Image2.Canvas.Pixels[j, i] := RGB(0, 0, c); // pro černobílé zobrazení pom := Image1.Canvas.Pixels[j, i]; c:=(GetRValue(pom)+GetGValue(pom)+GetBValue(pom)) div 3; Image2.Canvas.Pixels[j, i] := RGB(c, c, c); } end; end;
Příkaz GetRValue vybere červenou složku ze zadané barvy, příkaz GetGValue zelenou a GetBValue modrou. S jednotlivými barevnými složkami můžeme experimentovat výše naznačeným způsobem. Promyslete a vyzkoušejte všechny uvedené možnosti. Pokud chceme v Delphi pracovat s myší, musíme se naučit obsluhovat události, jenž automaticky vznikají při každém pohybu nebo kliknutí myši. Při práci s myší se informace o jejím pohybu nebo kliknutí na její tlačítko dostávají ke komponentě, nad kterou se myš právě nachází (například grafická nebo textová plocha, tlačítko apod.). Daná komponenta událost „zachytí“ a vykoná akci (část programu), jenž je pro tuto událost určena. Události jsou speciální procedury, jejichž vykonání je vyvoláno vnějším podnětem. Následující příklad demonstruje obsluhu události, jenž nastane při pohybu myši. Příklad 8.3.3: (práce s myší) Napíšeme proceduru, která se vykoná pokaždé, když pohneme kurzorem myši nad grafickou plochu Image2 (viz příklad 8.3.1). Tato procedura překopíruje bod z Image1 a to právě bod, jenž se nachází na aktuální pozici myši. V režimu návrhu formuláře klikneme na komponentu Image2. V objektovém inspektoru klikneme na záložku Events (události) a dvojklikneme na pravou část řádku při události OnMouseMove (při pohybu myši) – viz obrázek. V editovacím okně se automaticky definuje záhlaví této události – procedura TForm1.Image2MouseMove. Tato procedura se automaticky zavolá při každém pohybu myší nad komponentou Image2. Z parametrů této procedury jsou zajímavé souřadnice [X, Y], jenž představují souřadnice aktuální pozice myši na grafické ploše Image2. Definujeme proceduru TForm1.Image2MouseMove: procedure TForm1.Image2MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin Image2.Canvas.Pixels[i, j] := Image1.Canvas.Pixels[i, j]; end;
Při pohybu myši nemusíme kopírovat pouze bod ale s daným bodem můžeme kopírovat i celé jeho okolí: procedure TForm1.Image2MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); const d = 3;
var i, j: integer; begin for i := X-d to X+d do for j := Y-d to Y+d do Image2.Canvas.Pixels[i, j] := Image1.Canvas.Pixels[i, j]; end;
Editovací řádek je komponenta sloužící k načtení textových (ale i číselných) dat při běhu programu. Nalezneme jí v paletě komponent na záložce Standard pod jménem Edit. Aktuální text editovacího řádku obsahuje proměnná Edit1.Text. Následující příklad demonstruje použití komponenty editovací řádek pro načtení potřebných číselných údajů. Příklad 8.3.3: (editovací řádek) Napíšeme program, který „rozmaže“ obrázek na grafické ploše Image2 (viz příklad 8.3.1). Podle čísla d, zadaného prostřednictvím editovacího řádku, spojí obrazové body (pixely) ve čtverci o rozměrech (2*d)x(2*d) do jednoho bodu tak, že všechny body čtverce vykreslí barvou prostředního bodu daného čtverce - viz obrázek. Na plochu formuláře umístíme pod grafickou plochu Image2 editovací řádek Edit1. Upravíme událost OnKeyPress editovacího řásku Edit1 (viz objektový inspektor, záložka Events), jenž je vyvolána při každém zmáčknutí klávesy v editovacím řásku. Nás bude zajímat pouze klávesa <Enter> (potvrzení) s kódem #13. procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); var i, j, x, y, x1, y1, d, Chyba: integer; c: TColor; begin if Key=#13 then // zmáčnuta klávesa <Enter> begin Val(Edit1.Text, d, Chyba); // převod obsahu Edit1.Text na číslo if Chyba>0 then Exit; for x:=0 to (Image2.Width-1) div (2*d) do for y:=0 to (Image2.Height-1) div (2*d) do begin x1:=x*2*d+d; y1:=y*2*d+d; c:=Image2.Canvas.Pixels[x1, y1]; for i:=x1-d to x1+d-1 do for j:=y1-d to y1+d-1 do Image2.Canvas.Pixels[i, j] := c; end end end;
8.4
Objekt Bitmapa
V předchozí kapitole jsme si ukázali základy práce s bitmapovými obrázky. Pro pokročilejší práci použijeme objekt Bitmapa (srovnejte s objektem Želva). Deklarace proměnné: var bmp: TBitmap;
Deklarací proměnné typu TBitmap objekt (instance objektu) ještě nevzniká. Objekt vytvoříme příkazem
bmp := TBitmap.Create;
Takto vznikne prázdný obrázek s nulovými rozměry. Do existujícího objektu Bitmapa můžeme načíst obrázek příkazem LoadFromFile. Tím se změní obsah obrázku i jeho velikost. Obrázek můžeme vykreslit na grafickou plochu Image příkazem Draw. Tomuto příkazu zadáme nejenom bitmapu, kterou chceme vykreslit, ale i souřadnice levého horního rohu vykreslovaného obrázku na grafické ploše Image. Objekt Bitmapa musíme při skončení práce uvolnit ze systému příkazem Free. Objekt Bitmapa je speciálním objektem, který odčerpává prostředky operačního systému MS Windows, co může mít neblahé následky na správnou funkci systému (systém se může i zhroutit). Příklad 8.4.1: Na plochu formuláře umístíme grafickou plochu Image1 a tlačítko. Po smáčknutí tlačítka načteme obrázek ze souboru a vykreslíme ho na plochu Image1. procedure TForm1.Button1Click(Sender: TObject); var bmp: TBitmap; begin bmp:=TBitmap.Create; bmp.LoadFromFile('p_delfini.bmp'); Image1.Canvas.Draw(40, 30, bmp); bmp.Free; end;
Speciálním případem metody Draw je metoda StretchDraw, která nám umožní obrázek při vykreslování libovolně zmenšit nebo zvětšit. Jejím parametrem je obdélník, do něhož má být obrázek umístěn (může to být i celá grafické plocha Image). Obdélník definujeme obvykle pomocí konstrukce Rect(x1, y1, x2, y2), ve které zadáme souřadnice levého horního a pravého dolního rohu oblasti, do níž se má obrázek vykreslit. procedure TForm1.Button1Click(Sender: TObject); var bmp: TBitmap; begin bmp:=TBitmap.Create; bmp.LoadFromFile('p_delfini.bmp'); Image1.Canvas.StretchDraw(Rect(40,30,Image1.Width,Image1.Height), bmp); bmp.Free; end;
Příklad 8.4.2: „Vytapetujeme“ grafickou plochu Image1 bitmapovým obrázkem. Vykreslíme obrázek víckrát vedle sebe tak, aby pokryl celou plochu Image1 – viz obrázek. procedure TForm1.Button1Click(Sender: TObject); var bmp: TBitmap; i, j: Integer; begin bmp:=TBitmap.Create; bmp.LoadFromFile('tripton.bmp'); i:=0; while i
Image1.Canvas.Draw(i, j, bmp); j:=j+bmp.Height end; i:=i+bmp.Width end; bmp.Free; end;
Na vytapetované pozadí vložíme přesně doprostřed grafické plochy další obrázek: procedure TForm1.Button2Click(Sender: TObject); var bmp: TBitmap; begin bmp:=TBitmap.Create; bmp.LoadFromFile('pozdrav.bmp'); Image1.Canvas.Draw((Image1.Width-bmp.Width) div 2, (Image1.Height-bmp.Height) div 2, bmp); bmp.Free; end;
Vidíme (viz obrázek vlevo), že vložený obrázek je nakreslený na bílém pozadí. Pokud chceme pozadí obrázku udělat průhledným, nastavíme jeho vlastnost Transparent. Průhlednými se stanou všechny barevné body (pixely) shodné s pixelem v levém horním rohu obrázku (viz obrázek uprostřed). bmp:=TBitmap.Create; bmp.LoadFromFile('pozdrav.bmp'); bmp.Transparent:=true; Image1.Canvas.Draw((Image1.Width-bmp.Width) div 2, (Image1.Height-bmp.Height) div 2, bmp); bmp.Free;
Za průhlednou můžeme určit kteroukoliv barvu (na obrázku vpravo je průhlednou červená barva nápisu): bmp:=TBitmap.Create; bmp.LoadFromFile('pozdrav.bmp'); bmp.TransparentColor:=clRed; bmp.Transparent:=true; Image1.Canvas.Draw((Image1.Width-bmp.Width) div 2, (Image1.Height-bmp.Height) div 2, bmp); bmp.Free;
Viděli jsme, že bitmapové obrázky můžeme přímo načíst ze souboru (pomocí LoadFromFile). Bitmapu si můžeme i smi nakreslit – bitmapa má Canvas jako grafická plocha Image, proto zde můžeme použít všechny metody plochy Canvas (Rectangle, Ellipse, MoveTo, LineTo, TextOut, CopyRect apod.). Před kreslením na bitmapu musíme určit její velikost (bmp.Width a bmp.Heght) a barevný formát bmp.PixelFormat (budeme používat formát pf24bit).
Příklad 8.4.3: (kreslení na bitmapu) Nakreslíme vlastní bitmapu – žluté kolečko. Na ní „otiskneme“ bitmapu s pozdravem (viz příklad 8.4.2). Takto vytvořenou bitmapu vložíme na vytapetované pozadí – viz obrázek. Mohli bychom jí také uložit do souboru příkazem SaveToFile. procedure TForm1.Button2Click(Sender: TObject); var bmp, bmp1: TBitmap; begin bmp:=TBitmap.Create; bmp.Width:=130; // nastavení bitmapy bmp.Height:=130; bmp.PixelFormat:=pf24bit; bmp.Transparent:=true; // žluté kolečko bmp.Canvas.Brush.Color:=clYellow; bmp.Canvas.Ellipse(0, 0, 130, 130); bmp1:=TBitmap.Create; // vložení další bitmapy na bitmapu bmp bmp1.LoadFromFile('pozdrav.bmp'); bmp1.Transparent:=true; bmp.Canvas.Draw(5, 35, bmp1); bmp1.Free; // SaveToFile('kolecko.bmp'); Image1.Canvas.Draw((Image1.Width-bmp.Width) div 2, (Image1.Height-bmp.Height) div 2, bmp); bmp.Free; end;
Upozornění 8.1.1: Bitmapy nelze vzájemně přiřazovat!!! procedure TForm1.Button1Click(Sender: TObject); var bmp, bmp1: TBitmap; begin bmp:=TBitmap.Create; bmp.LoadFromFile('pozdrav.bmp'); bmp1:=TBitmap.Create; bmp1:=bmp; // Chyba!!! Image1.Canvas.Draw(0, 0, bmp1); bmp1.Free; bmp.Free; end;
Později, když se naučíme pracovat se směrníky a objekty, pochopíme pozadí této chyby. Zatím si musíme pamatovat, že vzájemné přiřazení bitmap musíme provést buď přímo přes zkopírovaní obrázku (metoda Draw) nebo pomocí metody Assign. procedure TForm1.Button1Click(Sender: TObject); var bmp, bmp1: TBitmap; begin bmp:=TBitmap.Create; bmp.LoadFromFile('pozdrav.bmp'); bmp1:=TBitmap.Create; bmp1.Assign(bmp); Image1.Canvas.Draw(0, 0, bmp1); bmp1.Free; bmp.Free; end;
Úkol k textu 8.4.1: Vytvořte seznam všech metod objektu Bitmapa včetně stručného jejích popisu.
Cvičení: 1. Je dáno celočíselné dvourozměrné pole p: array [1..m, 1..n] of Integer; Čísla m, n jsou konstanty určující rozměry pole. a) Definujte proceduru pro naplnění této dvourozměrné matice – jednotlivé prvky matice jsou náhodná čísla v rozmezí 1 až 100. b) Napište funkci, která vypočítá součet všech prvků dané matice. c) Napište funkci, která vypočítá průměr všech prvků dané matice. d) Napište proceduru, která vrátí ve var parametrech pozici maximálního (minimálního) prvku v matici. 2. Napište funkce (6 funkcí), které sečtou prvky čtvercové matice na červeně vyznačených místech.
3. Na ploše formuláře jsou umístěny dvě stejně velké komponenty Image1 a Image2. Na grafické ploše Image1 je zobrazen obrázek. Překopírujte obrázek z Image1 do Image2 tak, že a) ho překlopíte vodorovně, b) ho překlopíte svisle, c) ho otočíte o 90°, 180° nebo 270°, d) přiblížíte jeho tak, že do Image2 překopírujete pouze jeho střední část a tuto část zvětšíte na celou plochu Image2, e) každý řádek posunete o náhodný počet bodů (v rozmezí například 0-10 bodů) – viz obrázek vpravo,
překopírujte pouze ty barevné body, které jsou od středu obrázku vzdáleny nejvíc 70 bodů, ostatní pixely zabarvěte na bílo – viz obrázek vlevo,
f)
g)
překopírujete obrázek tak, že budete postupně odebírat nebo přidávat barvu jedné barevné složky RGB palety – vznikne zajímavě tónovaný obrázek.
4. Napište program, který bude při tahání myši kreslit kruhy náhodné barvy. Kruhy budou mít stejný poloměr (například 10) a střed na pozici kurzoru myši. 5. Napište program, ve kterém bude při tahání myši kreslit želva tři různě barevná kolečka do trojúhelníku. Při každém vykreslení koleček se želva otočí o malý úhel (například 5°). Při pomalém táhnutí myší vznikne zajímavý obrázek točené šňůry – viz obrázek. 6. Nakreslete na grafickou plochu obdélník. Napište program, kterým budete při tahání myši za některou ze stran obdélníku měnit jeho velikost. 7. Napište program, ve kterém bude želva kreslit čáru při tahání myší. Vytvořte kopie želvy podle os x a y, tyto želvy budou kreslit zrcadlově podle příslušné osy – viz symetrický obrázek.
8. Kreslení naslepo: Napište program, který si při tahání myši bude pamatovat cestu, kterou jsme myš táhli (bez vykreslování této cesty). Myš táhneme při současném stlačení jejího levého tlačítka (platí výraz ssLeft in Shift). V okamžiku uvolnění levého tlačítka se „naslepo“ vykreslená a zapamatovaná cesta zobrazí náhodnou barvou a tloušťkou pera.