Vítejte v sedmém dílu seriálu o jazyku Object Pascal pro začátečníky. V tomto dílu si odpočineme od nových pojmů z OOP (ale to, co už jsme se naučili, budeme používat). Vrátíme k základům programování, tentokrát k cyklu for. Přeji vám pohodu při čtení!
Úvod
Sedmý díl seriálu o jazyku Object Pascal je rozdělen na čtyři kapitoly:
- Podíváme se na řešení úloh z minulého dílu.
- Stručně si uvedeme cykly v Pascalu.
- Důkladněji se budeme zabývat zatím cyklem for.
- Nakonec vám nabídnu některé úkoly.
ad 1) Řešení úloh z minulého dílu
Úkol č. 1
Zadání: Zkuste změnit konstruktor ve třídě TKvadrRovnice tak, aby vytvořil rovnici přímo s koeficienty zadanými uživatelem. Provedeme to asi tak, že z hodnot koeficientů uděláme parametry konstruktoru, např. constructor vytvorRovnici(a, b, c: real), a v těle konstruktoru se hodnoty parametrů přiřadí atributům (koeficientům) rovnice, např. self.a := a;. V hlavním programu pak přemístíme příkazy pro zadání hodnot koeficientů uživatelem ještě před vytvoření třídy. Vyvolání konstruktoru by pak mohlo mít tvar vytvorRovnici(a, b, c) - při volání metody se už typ metody (constructor) ani typ parametrů (real) nepíše. Protože o práci s procedurami jsme si zatím řekli velmi málo, bude to pro vás možná těžší úkol.
Řešení: Připojil jsem ještě jeden konstruktor, se třemi parametry a, b, c (vytvorRovnici(a, b, c: real)). Přitom jsem ale ponechal i původní konstruktor bez parametrů se stejným (a původním) názvem (vytvorRovnici). Delphi nabízí jako jiné současné jazyky použití více metod se stejným názvem, lišících se počtem nebo typem parametrů. Říká se tomu přetěžování metod, a protože je zde pro přetížení dobrá příležitost, už jsem ho použil. Všimněte si, že přetížení musíme překladači oznámit, a to slovem overload. Uvede se ale jen v hlavičce obou (či více) přetížených metod, ne už u jejich implementace.
Výpis části kódu jednotky s třídou by mohl být takový (většinu už známe z minulého dílu):
unit U_TKvadrRovnice; interface // sem by prisla cast uses type //**** definice tridy pro resení kvadratické rovnice TKvadrRovnice = class private a: real; b: real; c: real; x1: real; x2: real; pocetKorenu: integer; procedure najdiKoreny(); public constructor vytvorRovnici(); overload; constructor vytvorRovnici(a, b, c: real); overload; procedure zadejKoeficienty(a, b, c: real); function vratKoefA(): real; function vratKoefB(): real; function vratKoefC(): real; function vratPocetKorenu(): integer; function vratKorenX1(): real; function vratKorenX2(): real; end; //**** konec definice tridy, musí následovat jeste definice metod, // zde ale az v casti implementation implementation //**** definice metod ke tride (instancím tridy) TKvadrRovnice //** soukromá metoda najdiKoreny, urci z koeficientu a, b, c koreny rovnice, // pokud existují, a urci také pocet korenu a vlozi ho do atributu // pocetKorenu dané instance procedure TKvadrRovnice.najdiKoreny(); // lokální promenne této metody var D, odmD: real; x1, x2: real; begin // diskriminant D := self.b * self.b - 4 * self.a * self.c; // slovo "self" je odkazem // na instanci, která práve metodu pouzivá. // Není zde nutné, ale doporucuji ho pouzivat // urcení poctu korenu a jejich výpocet if D > 0 then begin // budou dva koreny self.pocetKorenu := 2; // pripravíme si odmocninu z D, at se nemusí opakovane pocitat odmD := sqrt(D); self.x1 := (-self.b - odmD) / (2 * self.a); self.x2 := (-self.b + odmD) / (2 * self.a); end // zde jeste nekonci podmínený príkaz, nepíseme tedy stredník else if D = 0 then begin // bude jeden koren self.pocetKorenu := 1; self.x1 := -self.b / (2 * self.a); end else self.pocetKorenu := 0; // nebude zádný koren end; //** verejný konstruktor, který vytvorí instanci (kdvadratickou rovnici) // s prednastavenými hodnotami koeficientu napr. a = 1, b = 2, c = 1. // Tato rovnice by mela mít jediný koren x = -1 a muze poslouzit jako // rychlý test programu // Konstruktor také hned rovnici vyresí - urcí pocet korenu a koreny // privátní metodou najdiKoreny() constructor TKvadrRovnice.vytvorRovnici(); // zde uz se overload begin // nepise self.a := 1; self.b := 2; self.c := 1; self.najdiKoreny; end; // druha verze konstruktoru (pretizena metoda) - s parametry constructor TKvadrRovnice.vytvorRovnici(a, b, c: real); // zde uz se overload begin // nepise self.a := a; // do atributu objektu a, b, c - oznacenych slovem self - self.b := b; // priradi hodnoty zadanych parametru a, b, c self.c := c; self.najdiKoreny; end;
Nový konstruktor s parametry bychom hned měli vyzkoušet v hlavním programu. Jednu možnost uvádím, popř. zkopírujte do Delphi a vyzkoušejte.
program P_kvadrRovnice; {$APPTYPE CONSOLE} uses SysUtils, U_TKvadrRovnice in 'U_TKvadrRovnice.pas'; //**** deklarace promenných programu var instanceKvadrRov, rov2, rov3: TKvadrRovnice; // promenná pro odkaz na instanci (objekt) a, b, c: real; // pomocné promenné pro moznost zadání koeficientu //**** vlastní program begin writeLn('Program na reseni kvadraticke rovnice, zkouska prace s objekty.'); writeLn; // vynechání rádku ve výpisu //** První rovnice // vytvorení odkazu na novou instanci - objekt - kvadratické rovnice: instanceKvadrRov := TKvadrRovnice.vytvorRovnici; // kontrolní výpis koeficientu, zadaných nasím základním konstruktorem write('Koeficienty prvni rovnice jsou a = ', instanceKvadrRov.vratKoefA:3:3); write(', b = ', instanceKvadrRov.vratKoefB:3:3); writeLn(', c = ', instanceKvadrRov.vratKoefC:3:3); writeLn(' Byly zadany v implicintnim konstruktoru.'); // resení rovnice uz probehlo behem volání konstruktoru // dle hodnoty atributu PocetKorenu se vypísí koreny if instanceKvadrRov.vratPocetKorenu = 2 then begin write(' Rovnice ma dva koreny x1 = ', instanceKvadrRov.vratKorenX1:3:3); writeLn(', x2 = ', instanceKvadrRov.vratKorenX2:3:3); end else if instanceKvadrRov.vratPocetKorenu = 1 then writeLn(' Rovnice ma jeden koren x = ', instanceKvadrRov.vratKorenX1:3:3) else writeLn(' Rovnice nema zadny koren'); instanceKvadrRov.Free; // zruseni objektu (instance) - uvolneni pameti RAM //** První rovnice - konec reseni //** Druha rovnice writeLn; // vynechání rádku ve výpisu // vytvorení odkazu na novou instanci - objekt - kvadratické rovnice: rov2 := TKvadrRovnice.vytvorRovnici(2,4,-3); // konstruktor // s parametry, zde "natvrdo" zadanymi bez prispeni uzivatele // kontrolní výpis koeficientu, zadaných nasím základním konstruktorem write('Koeficienty druhe rovnice jsou a = ', rov2.vratKoefA:3:3); write(', b = ', rov2.vratKoefB:3:3); writeLn(', c = ', rov2.vratKoefC:3:3); writeLn(' Byly zadany "natvrdo" v konstruktoru s parametry.'); // resení rovnice uz probehlo behem volání konstruktoru // dle hodnoty atributu PocetKorenu se vypísí koreny if rov2.vratPocetKorenu = 2 then begin write(' Rovnice ma dva koreny x1 = ', rov2.vratKorenX1:3:3); writeLn(', x2 = ', rov2.vratKorenX2:3:3); end else if instanceKvadrRov.vratPocetKorenu = 1 then writeLn(' Rovnice ma jeden koren x = ', rov2.vratKorenX1:3:3) else writeLn(' Rovnice nema zadny koren'); rov2.Free; // zruseni objektu (instance) - uvolneni pameti RAM //** Druha rovnice - konec reseni //** Treti rovnice, tentokrat pomoci noveho objektu tridy TKvadrRovnice. writeLn; // vynechání rádku ve výpisu // Vytvoreni noveho objektu konstruktorem // rov3.vytvorRovnici; POZOR: Casta chyba zacatecnika, chybny zapis konstruktoru // protoze objekt rov3 jeste nebyl vytvoren, nemuze jeste volat nejakou metodu // toto uz by nebylo vhodne: rov3 := TKvadrRovnice.vytvorRovnici; // pouzijeme druhy konstruktor s parametry // Zadání a resení nové rovnice writeLn('Zadejte koeficienty a, b, c nove rovnice:'); writeLn(' Koeficienty se pouziji jako parametry konstruktoru s parametry.'); write(' a = '); readLn(a); write(' b = '); readLn(b); write(' c = '); readLn(c); rov3 := TKvadrRovnice.vytvorRovnici(a, b, c); // zde uz byly parametry zadane rov3.zadejKoeficienty(a, b, c); // uzivatelem // resení rovnice uz probehlo behem volání metody zadejKoeficienty(a, b, c) // dle hodnoty atributu PocetKorenu se vypísí koreny if rov3.vratPocetKorenu = 2 then begin write(' Rovnice ma dva koreny x1 = ', rov3.vratKorenX1:3:3); writeLn(', x2 = ', rov3.vratKorenX2:3:3); end else if rov3.vratPocetKorenu = 1 then writeLn(' Rovnice ma jeden koren x = ', rov3.vratKorenX1:3:3) else writeLn(' Rovnice nema zadny koren'); // sem by mel prijit jeste jeden prikaz - jaky? rov3.Free; //** Treti rovnice - konec reseni readLn; // pozastaveni vypisu pred zavrenim konzoly end.
Jak je vidět, část kódu se nám zde třikrát stejně opakuje. To není dobré, měli bychom to odstranit použitím procedury. O tom ale až jindy.
Úkol č. 2
Zadání: Doplňte na vhodné místo programu i druhé použití destruktoru free.
Řešení: šlo o příkazy
rov2.Free;popř.
rov3.Free;které jsou už v předchozí ukázce uvedeny.
Úkol č. 3
Zadání: Zkuste si rychle naprogramovat nový projekt na řešení lineární rovnice tak, že k němu vytvoříte jednotku pro řešení lineární rovnice (např. s názvem U_TLinearRovnice, v jednotce bude třída třeba s názvem TLinearRovnice). Opět bychom mohli využít už starší program, ale zkuste to spíš sami.
Řešení: Zde nechám řešení už na vás. Pokud by někdo řešení poslal, rád ho zveřejním.
ad 2) Základní cykly v Pascalu
Úvod
Cykly umožňují opakování jisté části programu: jednoduchého (jednoho) příkazu nebo složeného příkazu (bloku více příkazů). Prakticky žádný program se bez cyklu neobejde (pro opakování se používá také označení iterace).
V Pascalu jsou pro cyklus tři příkazy: cyklus for, cyklus while a cyklus repeat. Cyklus for využijeme, pokud předem známe počet opakování (průchodů cyklem). Zbylé dva cykly použijeme, když předem počet opakování cyklu neznáme.
- Známe předem počet opakování (cyklus for).
- Počet opakování předem neznáme, je řízen podmínkou (cykly while, repeat-until). O těch si řekneme až příště.
ad 3) Cyklus for
Je nejjednodušší, ale abychom jej mohli použít, musíme znát předem požadovaný počet průchodů cyklem. Průchody počítá (řídí) k tomu určená proměnná, tzv. řídicí proměnná cyklu. Obvykle ji značíme malými písmeny i, j, k. Této proměnné přiřadíme na začátku počáteční hodnotu a zadáme také hodnotu koncovou. Při každém průchodu cyklem se její hodnota automaticky zvýší o jedničku. To je důležité a příjemné: u ostatních cyklů se o změnu řídící proměnné musíme postarat sami.
Syntaxe (způsob zápisu) cyklu for
Konkrétní příklady:
1. příklad:
for i := 1 to 10 do // Cyklus obsahuje jediný jednoduchý příkaz writeLn writeLn(i); // Co cyklus provede?
2. příklad:
soucet := 0; for i := 1 to 10 do begin // Cyklus obsahuje víc příkazů, soucet := soucet + i; // které spojíme do 1 složeného příkazu writeLn(‘Pricitam cislo ‘, i); end; writeLn(‘Součet od 1 do 10 je: ‘‚ soucet);
Otázka 1: Promyslete, co každý z těchto cyklů provede. (Odpověď je na konci dílu.)
Obecný zápis cyklu for
Cyklus s jednoduchým (jediným) příkazem:
for ridici_promenna_cyklu := dolni_mez to horni_mez do jednoduchy_prikaz;
Cyklus se složeným příkazem (skupinou více příkazů):
for ridici_promenna_cyklu := dolni_mez to horni_mez do begin prikaz_1; prikaz_2; ... prikaz_n; // za posledním příkazem nemusí být středník, ale pišme jej. end;
Jiná varianta cyklu for: hodnota řídicí proměnné může klesat, pak nejdřív napíšeme horní mez, teprve po ní dolní mez a místo klíčového slova to musíme použít downto (na to se snadno zapomene a program pak hlásí chybu). Příklad:
soucet := 0; for i := 10 downto 1 do begin // Cyklus obsahuje složený příkaz soucet := soucet + i; writeLn(‘Pricitam cislo ‘, i); end; writeLn(‘ Součet od 10 do 1 je: ‘‚ soucet);
Znázornění cyklu for vývojovým diagramem
Tento grafický způsob zobrazení algoritmů je pro pochopení základních principů jistě užitečný. O vývojových diagramech se píše např. v malém seriálu Jiřího Chytila na Programujte.com, ve 2. dílu se uvádí cykly, ale ne cyklus for.
Možné znázornění cyklu for jsem nakreslil v programu Draw z OpenOffice.
Víme už, že po průchodu cyklem se hodnota řídící proměnné zvýší (popř. u varianty downto sníží) automaticky o jedničku. To jsem v diagramu zdůraznil kroužkem s řídící proměnnou na konci cyklu.
Doporučuji nakreslit si vývojový diagram aspoň k některým z dalších programů (pro znázornění algoritmu, čili myšlenky postupu řešení). Protože jde o grafický způsob zápisu algoritmu, můžeme jistě opustit příkazy Pascalu a psát do značek vývojového diagramu klidně česky.
Úkoly k cyklu for
Úkol 1: napište program, který spočte součet čísel od 1 do 100. (Úloha zadaná kdysi malému Gaussovi ve škole. Ten si s ní ovšem poradil rychle a bez počítače.)
Úkol 2: napište program, který spočte součet čísel od zadané dolní meze do zadané horní meze. Vstupními hodnotami tedy budou dvě celá čísla, výstupní hodnotou jedno číslo - součet.
Úkol 3: napište program, který spočte součin čísel od zadané dolní meze do zadané horní meze. Vstupními hodnotami tedy budou dvě celá čísla, výstupní hodnotou jedno číslo - součin.
Úkol 4: napište program, který spočte součet n čísel posloupnosti, jejíž členy jsou čísla 1, ½, ¼, 1/8, 1/16 atd. Každý další člen posloupnosti je tedy polovinou předchozího. Vstupní hodnotou bude zadané číslo n (počet sčítaných čísel), výstupní hodnotou bude součet prvních n členů této posloupnosti. Jakému číslu se součet pro rostoucí n stále více blíží? Otázka 2: Může být součet nekonečného počtu čísel konečné číslo? (Odpověď je na konci článku).
Úkol 5: napište program, který spočte součet n čísel posloupnosti, jejíž členy jsou čísla 1, -1/3, 1/5, -1/7, 1/9, -1/11 atd. Nakonec tento součet program vynásobí čtyřmi. Vstupní hodnotou bude tedy zadané číslo n, výstupní hodnotou bude čtyřnásobek součtu prvních n členů této posloupnosti. Jakému číslu se výsledek pro rostoucí n stále více blíží?
Úkol 6: napište program, který spočte součet n čísel posloupnosti, jejíž členy jsou čísla 1, -½, 1/3, -¼, 1/5, -1/6 atd. Vstupní hodnotou bude zadané číslo n, výstupní hodnotou bude součet prvních n členů této posloupnosti. Porovnejte s číslem ln(2).
Úkol 7: napište program pro výpočet faktoriálu z čísla n (tedy součinu 1*2*3*...*n).
Je rozumné i v těchto prográmcích na zkoušení cyklu for používat OOP? Zde by asi stačilo psát krátké programy bez tříd a objektů, ideální by ale bylo části kódu, řešící samotný úkol, vkládat do samotné jednotky třeba s názvem U_Soucty.pas, a to jako funkce (function), aby se daly použít opakovaně i v jiných programech (projektech). V části interface by pak byla jen hlavička funkce, jak už to z povídání o jednotkách umíme, jen tentokrát bez definice třídy a tedy také bez specifikátorů přístupu (jako public nebo private). V části implementation pak funkci naprogramujeme - naimplementujeme.
Závěr
Odpovědi na otázky
Otázka 1: Promyslete, co každý z těchto cyklů provede.
Odpověď: První cyklus vypíše čísla od 1 do 10, druhý napíše totéž a přitom spočte jejich součet, který po provedení cyklu vypíše.
Otázka 2: Může být součet nekonečného počtu čísel konečné číslo?
Odpověď: Ač je to překvapivé, sami asi z matematiky víte, že to možné je. Ukazuje se to i v našich programech z tohoto dílu: ať budete zadávat sebevětší počet sčítanců, u příkladů 4, 5 i 6 součet nepřekročí nikdy jisté číslo, ke kterému se stále víc blíží. To je právě součet nekonečného počtu čísel. Vysvětlení je v tom, že čísla ve sčítané posloupnosti se musí dostatečně rychle zmenšovat.
Cyklus for budete jistě často používat. Čím víc programů zkusíte vytvořit, tím líp. Příště si uvedeme řešení některých úloh a budeme pokračovat cyklem while. Přeji vám vše dobré.