Zatím jsme se zabývali jen jednoduchými typy proměnných. Nyní se podíváme na to, jak dát proměnným logickou strukturu.
Jelikož programování není suchá teorie, ale spíš praktická činnost, bude důležitá látka vysvětlována na příkladech.
Přehled:
Začněme trochu historií (dnešní lekce tak bude srozumitelnější)
Nejprve se objevilo tzv. lineární programování (příkazy následovaly
za sebou a program nešlo řídit.)
Poté přišlo programování skokové (program šlo k rozhodování
donutit velice těžce).
Pak se objevily proměnné (sice byly už předtím, ale v poněkud jiné,
hůře ovladatelné formě)
Někdo také vymyslel lepší způsob, jak ovládat (řídit) program - řízené
programy.
Standardní typy proměnných brzy nestačily, a tak se vymyslelo programování
strukturované.
Ovšem ani struktury nestačily a objevily se objekty.
Ty umožnily vizualizaci tvorby programu a tzv. programování událostí.
V poslední době se pak objevila spousta nových metod, jak napsat program (extrémní programování a jiné).
Občas potřebujeme nadefinovat několik různých proměnných, které však spolu souvisí. Pro lepší orientaci pak tento souhrn označíme jako jedinou proměnnou a té dáme vlastní název. Ona složená proměnné se pak nazývá struktura. (Správně se mluví o proměnné strukturovaného typu).
Zopakujme si některé poznatky:
Proměnnou definujeme takto:
var Název : TypProměnné; (takto je to nejjednodušší)
var Název1, Název2 : TypProměnné; (pochopitelně lze udat více názvů,
oddělených čárkou)
Pokud jenom definujeme proměnné, stačí vypsat var jen jednou. Nejčastěji se tedy setkáte s něčím takovýmto:
type TOsoba = Word; {Třeba výše příjmu - maximum 65 000 snad bude stačit} var I,J : integer; RZ,IZ : Real; Jmeno,Prijmeni : string[25]; Znak1, Znak2 : char; K : extended; Veta : string; Lubos : TOsoba;
Bývá dobrým zvykem, že definice typů se uvedou před definice proměnných.
Zopakujme si ještě, jak takový typ vlastně definovat:
type NázevTypu = PopisTypu;
My jsme se zatím setkali pouze s několika jednoduchými způsoby, jak typ
popsat - pomocí jiného, již známého typu, pomocí výčtu a jako interval z
již známého typu. Je nám tedy jasný následující zápis:
type CeleCislo = Integer; PrirozeneCislo = Word; Dny = (pondeli,utery,streda,ctvrtek,patek,sobota,nedele); PracovniDen=pondeli..patek; Vikend = sobota..nedele; MalaPismena = 'a'..'z';
Nejdříve probereme ukazatele, ty stojí na hranici mezi jednoduchými a strukturovanými typy.
Jak je proměnná uložena v paměti počítače? Hodnota proměnné spí někde v paměti a
je vzbuzena, provádí-li se něco s proměnnou.
Občas se nám ale hodí vědět, kde proměnná sídlí, mít její adresu,
ukazatel na cestu k jejímu skrovnému domečku. A naopak, máme-li ukazatel,
chtěli bychom občas také vědět, jaká hodnota na dané adrese přebývá.
Každá proměnná je jenom shlukem jedniček a nul.
Chceme-li z nich vytěžit nějakou informaci, musíme vědět, co vlastně
znamenají, proto Pascal zavádí typy proměnných.
(Například : Proměnná typu byte o hodnotě 159 a proměnná typu shortint o
hodnotě -97 vytvoří v paměti úplně shodný shluk jedniček a nul... - typy
tedy musíme rozlišovat)
Rozlišujeme-li typy u proměnných, měli bychom rozlišovat
typy také u ukazatelů. Ukazatel tedy definujeme na určitý datový
typ. Tato definice se provádí takto:
type Nazev = ^DatovyTyp;
Příklady:
type Ukazatel = ^integer; {A máme ukazatel na integer}
Uk1 = ^Ukazatel; {I ukazatel je proměnná a lze na něj ukazovat}
var A:Ukazatel;
B:^Uk1; {Ukazatel lze definovat i přímo ve var, ale není to vhodné.
Ovšem i teď se definuje pomocí ^typ.}
Je zvykem, že názvy ukazatelů začínají písmenem P (z
anglického pointer). Můžeme se tedy setkat s tímto:
type Zlodej = string;
PZlodej = ^Zlodej;
var Pachatel : PZlodej; {Kupodivu, zde to P ve slově Pachatel ukazatel
neznačí}
Už umíme ukazatel nadefinovat a víme, co to je. Jak s ním
zacházet? Pochopitelně je možné přiřazení.
Ukazatel1 := Ukazatel2;
Také můžeme změnit hodnotu proměnné, na kterou ukazatel ukazuje. To se provádí
takto :
Pachatel^ := 'zahradnik'; (pomocí znaku ^ tzv. zpřístupníme
proměnnou).
Pochopitelně je možné nastavit ukazatel na danou proměnnou:
Ukazatel := @Proměnná;
Existuje také ukazatel, který nikam neukazuje. Říká se mu nil.
Ukazatel :=nil; (nikam neukazuj)
Pro pochopení si vše projděte na tomto jednoduchém příkladu.
program Demo; type PInt = ^Integer; var Ukaz : PInt; I : Integer; begin I:=15; Ukaz:=@I; {Ukazatel ted bude ukazovat na I} Writeln(Ukaz^); Ukaz^ := -10; Writeln(Ukaz^); Readln; end.
Pozorně si pročtěte text o ukazatelích a důkladně si tento příklad
projděte.
(Pozn: V Pascalu existují také ukazatele bez udaného typu - tzv. pointry,
které se definují takto:
var X : pointer; nebo také type PX = pointer; S těmito
ukazateli se hůře pracuje, takže o nich až někdy příště.)
Pascal rozeznává hned několik druhů struktur: množinu, pole, záznam a soubor.
Množina je určitý soubor prvků stejného typu.
Bázový typ = základní nestrukturovaný typ, jehož hodnot mohou prvky množiny
nabývat. Nesmí obsahovat více jak 256 přípustných hodnot a nesmí
obsahovat hodnoty záporné.
Typ Množina se definuje takto Název = set of
BázovýTyp;
Raději na příkladu:
type Cisla = set of byte;
TDen = (pondeli,utery,streda,ctvrtek,patek,sobota,nedele);
TDny = set of TDen;
var Dny = TDny;
JineDny = TDny;
Nyní se podívejme, jak se s množinou pracuje:
Prázdná množina se značí [ ] (levá hranatá závorka, pravá hranatá
závorka - bez mezery!!!). Prázdnou množinu lze přiřadit jakékoliv jiné
množině.
Množiny stejného bázového typu lze vzájemně přiřazovat.( Dny := [ ],
JineDny :=Dny )
Množinu lze také označit výčtem prvků, které do ní patří (pomocí
hranatých závorek ) :
Dny := [pondeli,utery,streda,ctvrtek,patek];
JineDny := [ctvrtek,sobota,nedele];
Máme-li dvě množiny stejného typu, lze s nimi dále provádět tyto
operace:
Operace | Operátor | Výsledek |
---|---|---|
sjednocení | + | množina |
průnik | * | množina |
rozdíl | - | množina |
srovnání (jsou shodné?) | = | boolean |
srovnání (jsou rozdílné?) | <> | boolean |
je A podmnožinou B? | <= | boolean |
je B podmnožinou A? | >= | boolean |
Příklad:
Dny := [pondeli];
JineDny := [utery];
JineDny := Dny + JineDny; {Poté bude JineDny = [pondeli,utery] }
Dny := Dny + Dny; {Teď máme Dny = [pondeli] }
Dny := Dny - JineDny; {Dny = [], prvky, které se nemají od čeho odečíst se
neodečtou}
if Dny = JineDny then writeln('Tyto množiny jsou shodné, což je
blbost');
if Dny <> JineDny then writeln('Množiny Dny a JineDny jsou
dvě rozdílné množiny);
if Dny <= JineDny then writeln('Prázdná množina Dny je podmnožinou
jednoprvkové množiny JineDny');
if JineDny >= JineDny then writeln('Každá množina je svou
vlastní podmnožinou');
Také můžeme pomocí in zjistit, zda prvek patří do dané množiny
:
if pondeli in JineDny then writeln('Pondělí je jiný
den');
Jednotlivý prvek lze přidat či odebrat pomocí + a - (Dny=Dny+[pondeli], nezapomeňte na hranaté závorky!). Mnohem efektivnější je však použít
těchto procedur:
Include(S:Množina, I : PrvekMnožiny); Přidá prvek do množiny (stejně
jako S:=S+[I] )
Exclude(S:Množina, I : PrvekMnožiny); Odebere prvek z množiny (stejně
jako S:=S-[I] )
Rád bych připomněl, že u množin nezáleží na pořadí prvků
Tím bychom množiny skončili. Pročtěte si je ještě jednou a procvičte na
příkladech. Pak čtěte dál. Nejčastější použití množin je při ověřování, zda
nějaký prvek splňuje určitou podmínku (je velké písmeno,...)
Množina je typ neuspořádaný. Pole se snaží vnésti do toho trochu pořádku.
Definuje se takto:
type Název = array[DolníMez .. HorníMez] of LibovolnýTyp;
DolniMez a HorniMez musí být stejného ordinálního typu.
Pole je jakýsi soubor prvků jednotného typu, kde jsou jednotlivé prvky řazeny
za sebou. Hodnoty se mohou opakovat. Zjednodušeně lze říct, že pole obsahuje
několik prvků s různými indexy (x[1], x[2],...) Raději na příkladu. Snažte
se z něj pochopit i způsob zacházení s polem:
program Pole; type TPoleS=array[1..8] of Integer; TPoleR=array['a'..'h'] of Integer; TPoleF=array[1..64] of char; var Sloupec,S: TPoleS; Radka : TPoleR; Figura : TPoleF; I : integer; C : char; begin Sloupec[1]:= 15; Sloupec[2]:= -7; Sloupec[3]:= 15; Radka['a']:=Sloupec[3]; Radka['h']:=Sloupec[2]; Figura[64]:='a'; Figura[38]:='g'; Writeln(Figura[64],Radka[3]); I:=8; C:='a'; Sloupec[I]:=11; Radka[C]:=Sloupec[I-2]; end.
Tento příklad si důkladně prostudujte. Nyní byste již měli vědět,
jak se s takovým polem zachází.
Jelikož pole může být definováno z jakéhokoliv typu, existuje i pole polí
(a pole polí polí...) V takovýchto případech Pascal nabízí jednodušší způsob zápisu:
array[a..b] of array of [c..d] of Něco se zkracuje jako array[a..b,c..d]
of Něco.
Zcela analogicky můžeme psát array[a..b,c..d,e..f,g..h] of string;
S rozměry pole rostou nároky na paměť. Velice rychle se může
stát, že nebude stačit. (Turbo Pascal využívá jen 65kB paměti, jiné verze trošku víc)
Pole stejných parametrů jdou sobě přiřazovat. Jinak se musí s poli
pracovat prvek po prvku. K tomu se často využívá cyklu for.
Nyní již jste schopni vyřešit následující příklad:
Příklad : Napište program, který seřadí vzestupně 25 uživatelem zadaných hodnot typu integer.
Pokud máte řešení, vyzkoušejte, jestli program funguje a poté ho
srovnejte se zde uvedeným řešením. Jestliže jste úkol ani po několika dnech nevyřešili, čtěte dále:
program InsertSort; const rozsah = 100; type Tpole = array[1..rozsah] of Integer; var A:Tpole; I,J:integer; min:integer; kde:integer; procedure Prohod(var A:integer;var B:integer); {Procedura k prohozeni dvou promennych} var C:integer; begin C:=A; A:=B; B:=C; end; procedure Vypis(pole:Tpole); var I:integer; begin for I:=1 to rozsah do write(Pole[I]:8); writeln; end; begin Randomize; for I:=1 to rozsah do A[I]:=Random(Maxint); Vypis(A); for I:=1 to rozsah do begin min:=Maxint; J:=I; for J:=I to rozsah do {Hledame nejmensi prvek a ten dame na prvni misto} if min>A[j] then begin min:=A[j]; kde:=j; end; Prohod(A[I],A[kde]); end; {pak dame druhy nejmensi na druhe, pole prohledavame az od druheho mista, nebot na prvnim jiz je nejmensi prvek, a tak pokracujeme dale} Vypis(A); readln; end.
Tento postup si zapamatujte, bude se vám později hodit. (Ale existují i lepší.) Program nechávám bez nadbytečných komentářů, abyste si mohli každý řádek
samostatně zdůvodnit. (Např. rozsah je řešen jako konstanta, aby mohl být
kdykoliv snadno změněn...). Až vše zdůvodníte, bude pro vás hračka
napsat program, který vybere ze 100 celých čísel to nejmenší.
(Tip: Vytvořte proměnnou Min = MaxInt a porovnejte ji s každým prvkem pole.)
Pokuste se ještě napsat program, který vytvoří šachovnici (array['a'..'h',1..8]
of char) a rozmístí na ní šachové figurky jako při zahájení. Využijte
přitom běžné označení šachových figurek (P = pěšec, J=jezdec,...) Barvy
rozlišovat nemusíte (ale pokud chcete, můžete pro černé figurky zvolit malá
písmena)... Dobrý programátor ví vždy všechno, nebo alespoň ví, kde to najít. Součástí programování je i vyhledávání
informací, takže pokud nevíte, jak se značí šachové figurky či kde stojí
při zahájení, využijte přátel či odborné literatury...
Tím máme pole za sebou, i když se k nim určitě vrátíme při opakování.
Záznam spojuje několik proměnných, které mají logickou souvislost. Tyto proměnné nemusí být stejného typu. Záznam se definuje takto:
type Název = record Proměnná1 : TypProměnné1; Proměnná2 : TypProměnné2; ... end;
Konkrétní případ:
type TPohlavi = (muz,zena); TPracovnik = record Jmeno : string; Pohlavi: TPohlavi; Vek : Byte; {Vek pracujiciho urcite lezi mezi 0 a 128roky) Zenat : Boolean; Prijem : Word; {Zaporne platy nedavame, ale obcas jsme nad 32 000} end;
Používání tohoto typu poněkud zvyšuje přehlednost programu. (ale nezapomínejte na základní pravidla...). Podívejme se nyní na práci se záznamy. Použijme k tomu Frantu, našeho pracovníka:
var Franta1,Franta2 : TPracovnik;
begin
Franta1.Jmeno := 'Franta';
Franta1.Pohlavi := muz;
Franta1.Vek:=18;
Franta1.Zenat:=True;
Franta1.Prijem:=150;
Franta2:=Franta1;
writeln(Franta2.Prijem);
readln;
end.
Proměnné typu záznam lze tedy navzájem přiřazovat (jedná-li se o stejný typ záznamu). Ukázali jsme si také přístup k jednotlivým položkám záznamu. Neustále vypisování Franta1. je ovšem poněkud těžkopádné. Proto byl zaveden příkaz with, s jehož využitím náš program vypadá takto:
... begin with Franta1 do begin Jmeno:='Franta'; Pohlavi:=muz; Vek:=18; Zenat:=True; Prijem:=200; end; Franta2:=Franta1; ... end.
Použití příkazu with by mělo být z uvedené ukázky zřejmé.
Pokud chcete, podívejte se do tabulky v první lekci, kolik již znáte klíčových slov.
Tento typ se poněkud vymyká z ostatních struktur. Nesmí být
součástí žádné jiné struktury.
Nesmí obsahovat ukazatele. (Ukazatel ukazuje na určité místo v paměti
a není zaručeno, že při dalším spuštění bude proměnná na tom samém místě).
Pascal rozlišuje tři typy souborů: textový soubor (data ukládána jako text), soubor s udaným typem (data ukládána binárně, nejde se v nich vyznat a nejdou přenést na jiný počítač), soubor bez udaného typy (do souboru zapsán obsah paměti).
Nejjednodušším a nejpřenositelnějším typem je textový soubor Text;
Podívejme se na to, jak se se souborem zachází.
Nejdříve musíme oznámit, s jaké je skutečné jméno souboru, se kterým
pracujeme. To provedeme pomocí Assign(soubor, jméno_v_Dosu);
Teď se musíme rozhodnout, co vlastně se souborem chceme dělat. Chceme-li
do něj zapisovat, použijeme proceduru
Rewrite(soubor); - původní soubor se vymaže a je založen nový; anebo
Append(soubor); - data se připojí na konec souboru
Chceme-li ze souboru data získat, použijeme Reset(Soubor);
Až přestaneme se souborem pracovat, nesmíme zapomenout soubor uzavřít pomocí Close(Soubor);
Raději si to ukažme na příkladu :
program Telefonni_seznam; var fsoubor : Text; {Jména proměnných typu soubor obvykle začínají na f.} begin Assign(fsoubor,'c:\seznam.dat'); Rewrite(fsoubor); writeln('Teď můžeme do souboru zapisovat'); Close(fsoubor); writeln('Ted jsem soubor zavřeli a nic nemůžeme'); Reset(fsoubor);{Ted muzeme ze souboru cist} Close(fsoubor); Readln; {A končíme} end.
Jak se takový soubor používá? K tomu slouží procedury, které už známe
- write, writeln, read, readln.
write(soubor, proměnná) - zapíše do souboru proměnnou (ta musí mít
správný typ)
writeln(soubor, proměnná) - totéž, ale přidá znak konce řádky
read(soubor, proměnná) - přečte ze souboru hodnotu a uloží ji do
proměnné
readln(soubor, proměnná) - přečte hodnotu a přejde na další řádek
souboru
Otevřeme-li soubor pro čtení, nastaví se na
první znak v souboru pomyslný ukazatel.
Pokud přečteme proměnnou typu string, načte se celá řádka a ukazatel skočí na druhou.
Pokud načteme číslo, program vynechá všechny mezery a pokusí se načíst číslo (pokud následuje znak, skončí chybou),
pokud přečteme char program přečte jeden znak a posune ukazatel na další (pozor i znak konce řádky je znak).
Načítáme-li data ze souboru, potřebujeme zjistit, zda náhodou nejsme na konci. K tomu slouží následující funkce:
Eoln(Soubor):Boolean - Jsme v Souboru na konci řádky?
Eof:Boolean; - Je konec souboru?
Eof(Soubor):Boolean; - Je v souboru Soubor konec souboru?
Raději si to vyzkoušejme na příkladu. Tentokrát bude poněkud delší a úplný. Tak nějak by programy měly vypadat.
Program Data; var Soubor : Text; Z : char; Jmeno : string; Telefon: LongInt; procedure Zapis; begin Append(Soubor); repeat Write('Jmeno : '); Readln(Jmeno); if Jmeno<>'' then begin Write('Telefon : '); Readln(Telefon); Writeln(Soubor, Jmeno); Writeln(Soubor, Telefon); Writeln(Soubor); {Do souboru zapise prazdnou radku, je zde kvuli prehlednosti ukladanych dat} end; until Jmeno=''; Close(Soubor); end; procedure Prepis; begin Rewrite(Soubor); repeat Write('Jmeno : '); Readln(Jmeno); if Jmeno<>'' then begin Write('Telefon : '); Readln(Telefon); Writeln(Soubor,Jmeno); Writeln(Soubor,Telefon); Writeln(Soubor); {Do souboru zapise prazdnou radku kvuli prehlednosti} end; until Jmeno=''; Close(Soubor); end; procedure Cteni; begin Reset(Soubor); repeat Readln(Soubor,Jmeno); Readln(Soubor,Telefon); Readln(Soubor); {Preskoci prazdnou radku} Writeln(Telefon:9,' ',Jmeno); until Eof(Soubor); Close(Soubor); Readln; end; begin Assign(Soubor,'Cisla.txt'); Writeln('Co chcete dělat P-přepis, Z-zápis či T-čtení'); repeat Readln(Z); Z:=UpCase(Z); if Z = 'Z' then Zapis; if Z = 'P' then Prepis; if Z = 'T' then Cteni; until ((Z='Z')or (Z<>'T') or (Z<>'P')); end.
Program přepište a několikrát spusťte, pokaždé vyzkoušejte jiný způsob
přístupu k souboru (čtení, zápis, čtení, zápis, čtení, přepis, čtení...)
Podívejte se, jak se mění soubor "Cisla.txt".
Pozorně si příklad projděte a snažte se každý příkaz odůvodnit či
vylepšit. (Je tam write, protože vypadá lépe, zadáváme-li data na stejný
řádek. Program je členěn do procedur kvůli přehlednosti, zde by bylo lepší
udělat samostatnou proceduru na zápis - a měnit jen styl otevření
souboru...).
To by bylo pro dnešek vše.
DCV: Důkladně si zopakujte ukazatele a záznamy. Napište několik
programů na využití struktur. Zkoušejte i různé kombinace struktur (záznam
obsahující pole, záznam obsahující dvourozměrné pole a množinu, soubor
obsahující tento záznam...) Sledujte velikost proměnných v paměti (využijte SizeOf();).
Zopakujte si metodu návrhu programu shora dolů a první lekce pro pokročilé.