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 <a, b>.
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
Abc<Eoln><Eoln>de<Eoln>f<Eof>
nebo přesněji při čtení po znacích
de
Abc#13#10#13#10de#13#10f<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<high(p)) do
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.Width do
begin
j:=0;
while j<Image1.Height do
begin
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.
Download

Proč se budeme učit programovat právě v Delphi