MFF UK / Ústav teoretické fyziky / Tomáš Ledvinka
Přednášky
. . . . . . . . . . . . . . . . . . . . . . . . . .
Programování pro fyziky (1.r)
  Úvod
  Programy a algoritmy
  Píšeme program
  Píšeme program II
  Procedury a funkce
  Malujeme funkce
  Chyby. Typy I.
  Typy II. Pole a Záznamy
  Pole II.Řetězce.Soubory.
  Gnuplot.Interpolace...
  Matice. Velké O...
  Fronta,Zásobník. Postscript
  Bin. soubory, ...
  Ukazatele,Objekty, ...
    Ukazatele
    Přetypování
    Num. kvadratura
    Objekty
    Přehled probraných témat
Maple pro fyziky (3.r)
Klasická elektrodynamika (2.r)
Vybrané partie OTR

Cvičení
. . . . . . . . . . . . . . . . . . . . . . . . . .
Programování pro fyziky (1.r)
Teoretická mechanika (2.r)
Klasická elektrodynamika (2.r)


Věda
. . . . . . . . . . . . . . . . . . . . . . . . . .
Diskové zdroje v OTR
Hyperbolické systémy v OTR


Kontakt
. . . . . . . . . . . . . . . . . . . . . . . . . .
Email
Konzultační hodiny


Ostatní
. . . . . . . . . . . . . . . . . . . . . . . . .
Mallorca
Ze společnosti

Objektů se nelekejte (na tečky nehleďte)

Tendence a paradigmata v programování jsou v mnohém podobná evoluci biologických druhů. Ve velkých množstvích je to jednoduché, přežije the fittest. Pokud ovšem mluvíme o dostatečně malém rybníku, hraje velkou roli i náhoda.
Nelze dokázat, že dnes používané metody programování jsou ty nejlepší, prostě se vyvinuly z těch předchozích a kdejaký jazyk má své příznivce a odpůrce. Kromě jednotliých jazyků se vyvíjí i způsoby jak psát programy (jakési vzory správně psaných programů, tzv. paradigmata). Některé koncepce vyhynou (třeba dnes by žádného tvůrce nového počítačového jazyka nenapadlo označovat povinně řádky rostoucí posloupností čísel - BASIC) jiné jsou do té míry úspěšné, že se s nimi musejí v konkurečním boji o přežití vyrovnat všichni. Dnes je takovým vítězným paradigmatem objektově orientované programování (OOP) a v posledním desetiletí OOP dozrálo pro nasazení v rozličných oblastech programování. Výjimkou je bohužel právě oblast, kterou se snaží pokrývat tento úvodní kurs programování, a kde jakkoli žádná z inkarnací OOP nebyla, myslím, vhodná. Jenže jste si určitě všimli, že v Delphi (tedy sytému s překladačem, vývojovým prostředíma, knihovnami, dokumentací ....) je použitá verse Pascalu nazývána ObjectPascal. Objekty nezahrnuli tvůrci do jazyka z idealistických důvodů, nýbrž proto, že právě OOP umožnilo běžnému programátoru zvládnout tvorbu Opravdu Užitečných Programů v prostředí pokročilých grafických, komunikačních a databázových API dnešní doby (definice příkladem: Assign, Reset, Rewrite, Read, Write, Close... tvoří v Pascalu API pro práci se soubory).

Existuje několik pokusů o Objektově Orientované Vědecké Výpoočty, ale bohužel současné počítačové jazyky neposkytují zdaleka vše, co by bylo potřeba. (Není divu, počítačové jazyky jsou méně a méně vyvíjeny s ohledem na naše potřeby, typický zákazník není student ani učitel fyziky. Evoluční boj se dnes odehrává v pro nás vzdálených oblastech C#, Javy a servletů - abych vás praštil žargonem).

Připoměňme si základní události v dávné historii počítačových jazyků (zamlčuji COBOL, PL1, ...)

1945 - stroj. kód
1957 - překlad výrazů -- (FORTRAN nebo později BASIC)
1961 - strukturované příkazy (třeba ALGOL 60)
1971 - strukturovaná data (typ record v Pascalu, struct v C)

Jenže to jsme zhruba u roku 1971 a zřejmě se ješte něco převratného muselo na poli poč. jazyků urodit ...

Především, jak že se má pracovat s proměnnou typu záznam?

Aby mělo smysl pakovat víc různých věcí dohromady musí se to taky dohromady používat. A to, jak jsme viděli, jde kromě přiřazovacího příkazu (nuda) jen předáváním záznamu jako paramteru proceduře či funkci. Jako příklad vezměma následující program:

program Maticka;

type tVektor = array of real;
     tMatice = record
                  M , N : integer;
                  a : array of tVektor;
               end;

procedure VytvorJednotkovou(var A : tMatice; N : integer);
var i,j : integer;
begin
  SetLength(A.a,N,N);
  A.M := N;
  A.N := N;
  for i := 0 to N-1 do for j := 0 to N-1 do
    if i=j then A.a[i,j]:=1 else A.a[i,j]:=0;
end;

function JeCtvercova(var A : tMatice): boolean;
begin
 JeCtvercova := A.M = A.N;
end;

function Stopa(var A : tMatice): real;
begin
 ...
end;

procedure UvolniPamet(var A : tMatice);
begin
  SetLength(A.a,0,0);
  A.M := 0;
  A.N := 0;
end;

var S : tMatice;

begin
  VytvorJednotkovou(S,3);
  Writeln(JeCtvercova(S));
  Readln;
end.

V programu je záměrně použito předání proměnné typu tMatice odkazem a vždy je to první parametr, takže všechny procedury a funkce se volají:

UdelejNeco(PromTypuMatice, ostatní parametry);

tedy vlastně (vzpomeneme-li si, že příkazy jazyka Pascal se skoro mohly/měly číst jako věty)

UdělejNeco s Čím Tak a Tak.

První změna, se kterou přichází OOP je obrácení slovosledu na

S Tímhle UdělejNeco Tak a Tak.

což psáno v Pascalu bude vypadat

PromTypuMatice.UdelejNeco( pripadne parametry )

Proto se výše uvedený program změní takto:

program MatickO;

type tVektor = array of real;
     tMatice = object
                  M , N : integer;
                  a : array of tVektor;
                  constructor VytvorJednotkovou(k : integer);
                  function JeCtvercova: boolean;
                  function Stopa: real;
               end;


constructor tMatice.VytvorJednotkovou(k : integer);
var i,j : integer;
begin
  SetLength(a,k,k);
  M := k;
  N := k;
  for i := 0 to N-1 do for j := 0 to N-1 do
    if i=j then a[i,j]:=1 else a[i,j]:=0
end;

function tMatice.JeCtvercova: boolean;
begin
 JeCtvercova := M = N;
end;

function tMatice.Stopa: real;
begin
 ...
end;

var S:tMatice;

begin
  S.VytvorJednotkovou(3);
  Writeln(S.JeCtvercova);
  Readln;
end.

 

V deklarační části oznámíme, že procedura VytvorJednotkovou a dvě funkce JeCtvercova a Stopa jsou součástí součástí záznamu, který se teď jmenuje objekt. K prvkům záznamu tak přibyly tzv.metody, které s prvky záznamu pracují. Jejich vlastní deklarace vypadá jako běžná deklarace funkce, s tím, že prvky záznamu/objektu jsou přístupné, jako by to byly lokální proměnné a identifikátor metody předchází určení pro který typ objektu danou metodu vlastně deklarujeme, protože nic nebrání tomu aby dva objekty mohly mít stejně se jmenující metodu. Proto také typ objekt nemůže být beze jména:

var x,y:object // nelze!!  
           ...
           Procedure MetodaX;  // pod jakým jménem bych asi pak MetoduX deklaroval
        end;

Navíc musí být typy objekt deklarovány jako globální ( tady si tvůrci jen ulehčili práci, když to stejně nikdo nechce).

Důležité jsou pro nás objekty hlavně proto, že i když sami nebudme chtít vlastní objekty tvořit, může nějaký užitečný modul exportovat (místo typu a sady procedur pro práci s ním) právě objekt. Proto musíme vědět, že pri použití tohoto modulu budeme muset proměnnou daného typu deklarovat a používat právě způsobem, jímž se objekty používají, tedy PromennaTypuObjekt.NejakaMetoda(parametry) .

Cvičení: Doplňte v obou příkladech výše tělo procedury/metody stopa....

Druhou podstatnou vlastností objektů je dědičnost a opět si ji ukážeme na příkladě, který by měl připomínat situaci ze života. Řekněme, že máme (z webu) k dispozici následující modul pro quicksort třídění:

unit ObjTridic;

interface

type tTridic = object
                 function  Porovnej( j,k : integer ) : integer; virtual; abstract;
                 procedure Prehod( j,k : integer ); virtual; abstract;

                 procedure Setrid(l,r:integer);
               end;

implementation

procedure tTridic.Setrid(l,r:integer);
var  i, j, k_rozhod : Integer;
begin
  k_rozhod := (l + r) div 2;

  i := l; j := r;
  while i<j do begin
    while Porovnej(i,k_rozhod)<0  do i:=i+1;
    while Porovnej(k_rozhod,j)<0  do j:=j-1;
    if i <= j then begin
      Prehod(i,j);
      if i=k_rozhod then k_rozhod:=j
      else if j=k_rozhod then k_rozhod:=i;

      i:=i+1; j:=j-1;
    end;
  end;
  if l < j then Setrid(l, j);
  if i < r then Setrid(i, r);
end;

end.

Procedury pro porovnání a přehozní z minulé verse s procedurálními parametry byly tentokrát nahrazeny metodami. Tento objekt je ale nehotový, umí sice třídit ale přitom nemá co a tady ani tedy neví jak to nic porovnávat a přehazovat. Metody Porovnej a Prehdo jsou sice tedy deklarovány, aby mohly být použity v metodě Setrid, ale jejich kód se odkládá do budoucna. Tentokrát ale nejde jako u předběžné (forward) deklarace jen o odložení na pozdější místo v daném modulu, fukce jsou označeny jako abstraktní a pokud je použít skončí behovou chybou. Deklarce funkcí je odložena až do doby, kdy bude co třídit.

Vezmeme tedy data (pole reálných čísel) a přidáme k nim metody pro porovnácní dvou prvků a jejich přehození a vytvoříme z nich potomka objektu typu tTridic jak je tomu v následujícím programu. Navíc přidáme inicializační metodu, která tam musí být z technických důvodů (a ještě navíc má místo slova procedure psáno constructor) a využijeme ji k obsazení pole náhodnými čísly. Navíc i z kontroly správnosti setřídění učiníme metodu v souladu s principy OOP. Tak dostaneme:

program ObjTridTest;
uses ObjTridic;

type tSeznamRCisel = object(tTridic) // je to potomek tTridic
                        Data : array [0..220000] of real;

                        constructor Init; // naprosto nezbytný kvůli virtuálním metodám

                        function  Porovnej( j,k : integer ) : integer; virtual;
                        procedure Prehod( j,k : integer ); virtual;

                        function  Zkontroluj : boolean;
                     end;

function tSeznamRCisel.Porovnej( j,k : integer ) : integer;
begin
  if Data[j]<Data[k]      then Porovnej := -1
  else if Data[j]=Data[k] then Porovnej := 0
                          else Porovnej := +1;
end;

procedure tSeznamRCisel.Prehod( j,k : integer );
var s : real;
begin
  s       := Data[k];
  Data[k] := Data[j];
  Data[j] := s;
end;

constructor tSeznamRCisel.Init;
var i : integer;
begin
  for i := Low(Data) to High(Data) do Data[i]:=random;
end;

function  tSeznamRCisel.Zkontroluj : boolean;
var i : integer;
begin
  Zkontroluj := false;
  for i :=1 to High(Data) do if Data[i-1]>Data[i] then exit;
  Zkontroluj := true;
end;

var  Seznam:tSeznamRCisel;

begin
  Seznam.Init;
  Seznam.Setrid(0 , High(Seznam.data) );
  if Seznam.Zkontroluj then Writeln('OK') else Writeln('Prusvih');
  readln;
end.

Výklad (3 minuty): Dědičnost, virtuální metody, kostruktor.

V případě náhrady var parametrů tečkou šlo jen o jakýsi přepis, který sice obrážel změnu pohledu na operace s daty (procedury --> metody), který ale např. ve výsledném strojovém kódu nemusí být vůbec vidět. (Nevypadá ale kód X.Init; X.Setrid; X.Zkontroluj nějak hezčeji...) Výše uvedený kód ale využívá také druhé klíčové vlastnosti zvané dědičnost. Ta představuje opravdovou změnu na všech úrovních.

Tak jako v minulém příkladě bylo možno jednou provždy vyřešit quicksort a už jen kostruovat seznamy různých druhů, našly dnes objekty (hlavně kvůli dědičnosti) svoje velké využití v oblasti grafického rozhraní programů a toto hlavní použití ovlivnilo zpětně jazyk.(Na přednášce za 10 sekund říct proč...) Pro užití objektů ve vědeckých výpočtech ale chybí v ObjectPascalu některé důležité možnosti a tak s objekty skončíme výše uvedeným ilsutračním příkladem na třídění, který už tak používá dost prvků OOP aby k jejich úplnému vyložení bylo potřeba několik přednášek.

Cvičení: Opět uvažujte seznam náhodných komplexních čísel, modifikujte výše uvedený typ tSeznamRCisel na tSeznamCCisel a pridejte do nej metody SetridPodleRealCasti, SetridPodleImagCasti a SetridPodleAbsHodnoty a asi uvazujte i tri testy ZkontrolujPodleRealCasti atd... Zkompilujte. Vyzkoušejte.

.