Vítejte ve čtvrtém dílu malého seriálu o jazyku Object Pascal určeném pro začátečníky. V tomto dílu budeme pokračovat v úvodu k objektově orientovanému programování (OOP), protože OOP by dnes do základů programování jistě patřit mělo. Po úvodní teorii z minulého dílu konečně dojde i na program v Pascalu. Nejdřív ale přijde opět malé opakování pojmů z minulého dílu. Další, už třetí část povídání o datových typech v Pascalu, tedy "Typy 3. část", nechám ale až na jindy. Přeji vám pohodu při čtení a práci!
Čtvrtý díl seriálu je rozdělen do dvou kapitol: v první připomenu úlohy z minula a ve druhé kapitole budeme pokračovat v úvodu do pojmů OOP. Pokud tyto řádky čtete, minulé povídání o OOP vás asi neodradilo. Časem zjistíte, že je to jiný a hlavně příjemnější styl práce. Půjde asi zase o delší povídání, tak si udělejte dobrý čaj, kafe (v angličtině hovorově javu, ale my zde programujeme v Pascalu) apod. a snažte se vydržet. Nedávno jsem v rádiu (Vltava, ČR 3) slyšel o pití čaje a kávy (javy) krásnou písničku skupiny Ink spots s názvem Java jive (1940), mohla by vás při práci potěšit. Najděte si ji na netu, popř. stáhněte na svo.xf.cz.
1. kapitola: opakování, řešení úloh z 3. dílu
Datové typy
Z 2. a 3. dílu víme, že každá proměnná v Pascalu musí mít určeny tři vlastnosti:
- velikost proměnné (počet bytů), aby si program mohl pro proměnnou na začátku rezervovat vhodné místo v paměti,
- přípustné hodnoty (např. celá čísla, desetinná čísla, znaky, řetězce znaků atd.),
- přípustné operace, které lze s danou proměnnou provádět (např. násobení, dělení, nalezení bezprostředního následníka, nalezení bezprostředního předchůdce, výpočet odmocniny…).
V minulém dílu jsme se zabývali třetí vlastností: přípustnými operacemi, a to u typů integer, char a real.
K operacím s typem integer byly zadány 4 úkoly:
- Úkol 1: Napište program, který po zadání čísla vypíše, jestli je zadané číslo liché, nebo sudé.
- Úkol 2: Napište program, který po zadání čísla vypíše, jestli je zadané číslo dělitelné třemi.
- Úkol 3: Napište program, který po zadání čísla vypíše, jestli je zadané číslo dělitelné třemi, a pokud není dělitelné třemi, vypíše zbytek při tomto dělení.
- Úkol 4: Napište program, který požádá uživatele o zadání dvou čísel - první bude dělitel a druhé dělenec. Program poté vypíše, jestli je druhé zadané číslo dělitelné prvním (dělitelem), popř. vypíše i zbytek při dělení, pokud číslo dělitelem dělitelné není.
Podívejme se na možné řešení posledního z nich, který je nejobecnější. Vložím do něj ale i řešení úkolu 1. Všimněte si zde možnosti zkráceného, ale správného zápisu podmínky (lze vynechat dokončení " = true ", 18. řádek).
program deleniInteger;
{$APPTYPE CONSOLE}
uses
SysUtils;
var delenec, delitel, vysledek, zbytek: integer;
// vysledek bychom mohli nazvat take podíl
begin
// zadani dvou cisel:
write('Zadej prvni cele cislo (delenec): ');
readLn(delenec);
write('Zadej druhe cele cislo (delitel): ');
readLn(delitel);
// jsou cisla suda/licha? (ukol 1)
writeLn; // vynechame jeden radek
if odd(delenec) = true // uplny zapis podminky
then writeLn('1. cislo je liche.')
else writeLn('1. cislo je sude.');
if odd(delitel) // !!! zkraceny, ale take spravny zapis podminky!
then writeLn('2. cislo je liche.')
else writeLn('2. cislo je sude.');
// zkouska delitelnosti: delenec / delitel
writeLn; // vynechame jeden radek
writeLn('Zkouska delitelnosti cisel.');
writeLn;
vysledek := delenec div delitel;
writeLn('Druhe cislo je obsazeno v prvnim ',
vysledek, ' krat');
if (delenec mod delitel = 0)
then writeLn('Prvni cislo je delitelne druhym.')
// pozor, za radkem then neni strednik,
// jeste zde nekonci cely podmineny prikaz
else
begin
writeLn('Prvni cislo neni delitelne druhym.');
zbytek := delenec mod delitel;
writeLn('Zbytek pri deleni je ', zbytek);
end;
readLn; // pozastaveni programu kvuli vypisu
end.
K operacím s typem real byly zadány 3 úkoly:
- Úkol 5: Sestavte program, který po zadání dvou celých čísel (typu integer) přiřadí jejich součet, součin a podíl do nových proměnných s názvy soucet, soucin, podil (promyslete jejich typy) a vypíše je.
- Úkol 6: Upravte program tak, aby u podílu čísel spočetl také jeho celočíselnou část a zaokrouhlenou hodnotu, přiřadil je do proměnných celaCast, zaokrouhleno a opět vypsal.
- Úkol 7: Napište program, který vypíše hodnoty funkce sinus, kosinus a tangens v intervalu 0° až 90° po 1°. Zjistíte jistě brzy, že funkce sin a cos počítají pouze s úhly v jednotce ... (dokončení v odpovědi č. 2 na konci lekce).
V úkolu 5 by proměnné soucet a soucin mohly být typu integer, ale podil jistě typu real.
Uvedu možné řešení úkolu 7, kde bude použit cyklus for, se kterým jsme se setkali už v minulých příkladech. Úhly ve stupních bude nutné před použitím goniometrických funkcí převést na radiány, buď to zařídíme sami takto:
uhelRad := uhelDeg * pi / 180;
Nebo pohledáme v nápovědě Delphi a najdeme k tomu určenou funkci DegToRad. V nápovědě se o ní píše, že je uložena v jednotce Math, takže abychom tuto funkci Delphi mohli v našem programu použít, musíme k němu jednotku Math přiložit. To se zařídí v přípravné části programu v příkazu uses, do kterého novou jednotku připíšeme ke standardně přiložené jednotce SysUtils, takže tato část zdrojového kódu bude vypadat takto:
uses
SysUtils, Math;
Samotná funkce je v nápovědě uvedená pro nás zatím složitějším zápisem:
function DegToRad(const Degrees: Extended): Extended;
Ten říká, že se jedná o funkci s názvem DegToRad, s povinným parametrem typu Extended (varianta real) a její výstupní hodnota, kterou vrátí funkce do programu, bude téhož typu. Parametr je zde nazván Degrees, ale my si ho v programu můžeme nazvat jinak. Při použití v našem programu také uvedené typy už nepíšeme, ty jsou zde uvedeny, protože se jedná o zápis definice funkce. V programu by se tedy mohl vyskytnout při použití této funkce reálně takovýto zápis:
uhelRad := DegToRad(uhelDeg);
Z nápovědy Delphi také zjistíte, že třeba funkce sinus se značí sin a že se vyskytuje v jednotce System. Tuto jednotku nemusíme k programu připojovat, to dělá Delphi automaticky ke každému programu za nás, protože jednotka System obsahuje skutečně základní funkce a procedury, například naše známé writeLn a readLn!
Naproti tomu funkce tangens (tan) je už trochu něco navíc a je tedy uložena v jednotce Math. No a teď už samotný program:
program goniomFunkce07;
{$APPTYPE CONSOLE}
uses
SysUtils, Math;
var uhelDeg, cisloTabulky, pocetSloupcu, dolniMezCyklu, horniMezCyklu: integer;
uhelRad: real;
begin
writeLn('Program pro vypis hodnot goniometrickych funkci.');
// cyklus, ktery zajisti postupny vypis kratsich radku, at se hodnoty
// vypisuji pekne pod sebe
pocetSloupcu := 7; // 7 sloupcu v tabulce na jeden radek
for cisloTabulky := 1 to 13 do // 13 krat 7 = 91, dostaneme se do 90 stupnu
begin
dolniMezCyklu := (cisloTabulky - 1) * pocetSloupcu;
horniMezCyklu := cisloTabulky * pocetSloupcu;
// cyklus pro vypis hlavicky: hodnot uhlu. Tabulator pro oddeleni
// se zapise znakem 9 z ASCII kodu
write('Uhel:', #9); // zacatek radku
for uhelDeg := dolniMezCyklu to horniMezCyklu do
begin
write(uhelDeg, #9);
end;
writeLn; // odradkovani
// vypis radku s hodnotymi sinu
write('Sin:', #9); // zacatek radku
for uhelDeg := dolniMezCyklu to horniMezCyklu do
begin
uhelRad := DegToRad(uhelDeg);
write(sin(uhelRad):1:3, #9);
end;
writeLn; // odradkovani
// vypis radku s hodnotymi cosinu
write('Cos:', #9); // zacatek radku
for uhelDeg := dolniMezCyklu to horniMezCyklu do
begin
uhelRad := DegToRad(uhelDeg);
write(cos(uhelRad):1:3, #9);
end;
writeLn; // odradkovani
// vypis radku s hodnotymi tangenty
write('Tan:', #9); // zacatek radku
for uhelDeg := dolniMezCyklu to horniMezCyklu do
begin
uhelRad := DegToRad(uhelDeg);
write(tan(uhelRad):1:3, #9);
end;
writeLn; // odradkovani
writeLn; // odradkovani
end;
readLn;
end.
Tento program není moc zajímavý: prostředí konzoly je zastaralé a hodnoty funkcí v tabulce nás asi moc neoslní.
Konzolu ovšem časem vyměníme za formuláře, které nám Delphi nabízí především a velmi pěkně (začátečníka by ale práce s formuláři asi jen pletla, takto se můžeme soustředit na samotné programování v Pascalu. Přitom přechod na konzoli nebude časem žádným problémem.), proto také myslím nemá smysl moc se zabývat různým "vylepšováním" výpisů, já jsem použil tabelátor (#9), ale nijak víc bych se tím nezabýval a ani vám to nedoporučuji.
A nakonec i z hodnot funkcí se přece jen dá něco zajímavého vyčíst jak z pohledu matematiky, tak programování. Zkuste to (nápověda je na konci tototo dílu).
Další úkoly z minula už nechám bez řešení, pokud někdo chcete, spíš mi pošlete řešení vaše, aspoň uvidím, že kurz někdo čte a i sám programuje :-).
2. kapitola: pokračování v OOP
Opakování úvodu z minulého dílu
- Snažil jsem se vysvětlit, proč používat OOP místo už zastaralého procedurálního programování.
- Uvedli jsme si základní vlastnosti OOP (většinou se uvádí: zapouzdření - encapsulation, dědičnost - inheritance, mnohotvarost - polymorfismus).
- Podrobněji jsme rozebírali zapouzdření (data či vlastnosti nebo atributy společně s metodami neboli schopnostmi, přístupová práva public a private).
- Dalším základním pojmem je dvojice třída (class, jakási šablona) a instance třídy (často je také nazývána objekt). Názvy tříd v Delphi začínáme písmenem T.
- Tečková notace při výběru metod či atributů třídy nebo objektu (název_třídy.název_metody, název_třídy.název_atributu, totéž pro objekty).
Zadaný úkol
- Začali jsme chystat prográmek, který by řešil lineární rovnici a*x + b = 0 (čili při vstupních hodnotách - číslech a, b našel výstupní hodnotu - číslo x).
- Máme za sebou úvodní, ale velmi důležitou, fázi řešení (je myslím dobré si na nutnost této fáze zvyknout, pokud se chceme vyhnout problémům s pracnými opravami během programování a hlavně nebezpečí toho, že vytvoříme program, který zadavateli nebude vyhovovat), a to návrh třídy, vycházející z analýzy problému.
- Výsledkem byl následující obrázek (schéma, vycházející z jazyka UML):
TLinRovnice - a: real
- b: real
- x: real- function najdiKoren(): real
+ constructor vytvorRovnici()
+ procedure zadejKoeficienty()
+ procedure vypisReseni() - A čeká nás konečně programování, tedy: fáze implementace.
Navrhuji následující řešení úlohy. Pročtěte si je, snažte se spojit si je s předcházející teorií a sami také spustit v Delphi.
Užitečnou metodou, kterou jsem zde použil, je tzv. konstruktor. Je vyvolána při vytvoření objektu ve vlastním programu příkazem
rovnice := TLinRovnice.vytvorRovnici;
a její úlohou je nejen vytvořit objekt, ale i nastavit potřebné hodnoty jeho
atributů (proměnných). Zde to není nutné, ale chtěl jsem na to upozornit.Tak se pusťte do studia:
program ProjectOOP1;
{$APPTYPE CONSOLE}
uses
SysUtils;
type
// zde zacina priprava na vlastni program: definice tridy TLinRovnice,
// a potom podrobne jejich metod, neboli funkci, procedur a popr. konstruktoru
TLinRovnice = class // definice tridy
private
a, b: real;
x: real;
function najdiKoren: real;
public
constructor vytvorRovnici();
procedure zadejKoeficienty();
procedure vypisReseni();
end;
var rovnice: TLinRovnice; // deklarace instance - objektu tridy TLinRovnice
// deklarace jednotlivych metod tridy;
// volim poradi nejdriv soukrome metody - zde funkce, pak konstruktor
// a dalsi verejne metody - zde procedury
function TLinRovnice.najdiKoren: real;
var x: real; // lokalni promenna funkce
begin
// vypocte koren, pokud existuje; pokud koren neexistuje,
// vrati pomocne cislo 0.112358132134
// rovnice ax+b=0 muze mit jen koren x=-b/a
if self.a <> 0
then
x := -b / a
else
x := 0.112358132134;
result := x; // navratova hodnota z funkce
end;
constructor TLinRovnice.vytvorRovnici();
begin // priradi koeficientum jednoduche pocatecni hodnoty
self.a := 1;
self.b := 0;
end;
procedure TLinRovnice.zadejKoeficienty();
begin
write('Zadej koeficient a rovnice ax + b = 0: ');
readLn(self.a);
write('Zadej koeficient b rovnice ax + b = 0: ');
readLn(self.b);
self.x := self.najdiKoren(); // rovnou se vyresi rovnice
end;
procedure TLinRovnice.vypisReseni();
begin
writeLn; // vynecha jeden radek
if self.x = 0.112358132134
then
writeLn('Rovnice nema reseni!')
else
begin
write('Rovnice ', self.a:3:3, '*x + ', self.b:3:3);
write(' = 0 ma reseni ', x:3:3);
end;
end;
// Uf! Pripravna faze byla narocna.
begin // tady teprve zacina vlastni program, ale jak je krasne jednoduchy!
rovnice := TLinRovnice.vytvorRovnici;
rovnice.zadejKoeficienty;
rovnice.vypisReseni;
readLn; // pozastaveni programu kvuli vypisum
end.
Pozor: u takové malé úlohy je použití třídy, metod a objektu ve vlastním programu trochu jako pověstné střílení kanónem na vrabce! Vždyť stejnou úlohu jsme řešili na začátku tohoto kurzu Object Pascalu, a to daleko jednodušším programem (toto řešení bylo v druhém dílu):
program linearniRovnice;
{$APPTYPE CONSOLE} // direktiva Delphi pro konzolu
uses SysUtils; // přiložená knihovna Delphi
var a,b,x: real; // deklarace proměnných
begin
writeLn('Zadejte hodnoty koeficientu rovnice ax+b=0:');
write(' a: '); readLn(a);
write(' b: '); readLn(b);
if a=0
then
begin
if b=0
then writeLn('Tato rovnice ma nekonecne mnoho korenu')
else writeLn('Tato rovnice nema zadny koren');
end
else
begin
x:=-b/a;
writeLn('Rovnice ma koren' ,x:2:3);
end;
readLn;
end.
Ale proč nezačít s co nejjednodušším problémem? Možná i tak někomu chvíli potrvá, než se v programu pořádně zorientuje. A co je hlavní: u složitých problémů je objektově orientované programování rozhodně efektivnější, jednodušší a hlavně z hlediska možnosti dalších úprav či znovupoužití kódu výhodnější!
Úkol na příště
Zkuste napsat objektově program pro řešení kvadratické rovnice. Doporučuji ale nezačít hned programovat, nýbrž:
- Provést podobnou analýzu a návrh třídy (atributů a metod) jako v minulém dílu, tentokrát pro úlohu řešení kvadratické rovnice.
- Teprve pak úkol naprogramovat (implementovat).
Tento úkol je myslím pro začátečníka dost těžký. Za jeho i nedokonalé řešení by můj student dostal dvě velké jedničky (analýza + návrh, program). Držím vám palce a těším se na případné řešení či ohlas.
Řešení otázky z tohoto dílu:
- Z pohledu matematiky ve výstupech programu s hodnotami goniometrických funkcí pěkně vidíme, že funkce sinus a tangens mají pro malé úhly prakticky stejné hodnoty. Při přesnosti na 3 desetinná místa to platí až do 6°. Tato vlastnost se např. ve fyzice dá mnohde využít.
- Z pohledu programování si všimněte, že i když tg(90°) není definován, náš program ho vypíše (mně vypsal číslo se 17 místy). To je dáno zřejmě tím, že při převodu na radiány z 90° nevypočtou Delphi číslo pí/2 zcela přesně, jinak by u tohoto úhlu nemohl být tangens určen, dokonce by program měl skončit chybou (dělit nulou není možné).
To už je dnes vše. Přeji vám vše dobré, ať se vám daří nejen v programování.