× Aktuálně z oboru

SHIELD Experience Upgrade 7 – méně hledání a více zábavy [ clanek/2018052902-shield-experience-upgrade-7-mene-hledani-a-vice-zabavy/ ]
Celá zprávička [ clanek/2018052902-shield-experience-upgrade-7-mene-hledani-a-vice-zabavy/ ]

Object Pascal - 04: Pokračování v OOP

[ http://programujte.com/profil/13963-josef-svoboda/ ]Google [ ?rel=author ]       [ http://programujte.com/profil/14523-martin-simecek/ ]Google [ ?rel=author ]       12. 4. 2009       18 703×

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 [ http://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:

  1. velikost proměnné (počet bytů), aby si program mohl pro proměnnou na začátku rezervovat vhodné místo v paměti,
  2. přípustné hodnoty (např. celá čísla, desetinná čísla, znaky, řetězce znaků atd.),
  3. 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ž:

  1. 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.
  2. 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í.


Článek stažen z webu Programujte.com [ http://programujte.com/clanek/2008120200-object-pascal-04-pokracovani-v-oop/ ].