3.10.2002
|
Parametre procedúr
Začneme takouto úlohou: napíšeme program,
v ktorom sa 2 korytnačky budú stále pohybovať
po svojich kruhoch. Súčasne s nimi sa tretia
korytnačka bude stále snažiť byť presne v strede
medzi nimi (ako keby bola v strede gumenej nite, ktorá
je pripevnená na týchto dvoch korytnačkách).
- Vytvoríme si pomocnú procedúru
pocitaj_stred, ktorá z dvoch korytnačiek k1 a
k2 vypočíta súradnice stredu (x,y) pre
tretiu korytnačku:
tretia korytnačka je vždy medzi
ďalšími dvoma:
|
procedure TForm1.Button1Click(Sender: TObject);
var
k1,k2,k3:Tkor;
x,y:real;
procedure pocitaj_stred;
begin
x:=(k1.X+k2.X)/2;
y:=(k1.Y+k2.Y)/2;
end;
begin
k1:=TKor.Create(300,250,27);
k2:=TKor.Create(200,200,0);
pocitaj_stred;
k3:=TKor.Create(x,y,0); k3.FP:=clRed;
while true do begin
k1.dopredu(4); k1.vpravo(3);
k2.dopredu(3); k2.vlavo(2);
pocitaj_stred;
k3.ZmenXY(x,y);
cakaj(10);
end;
end;
|
V tomto príklade sme vytvorili procedúru
pocitaj_stred bez formálnych parametrov, lebo
táto "vidí" aj na korytnačky
k1, k2 aj na premenné x a y. Tieto premenné
sú pre ňu globálne (nie sú to jej
lokálne premenné). Tento spôsob,
keď procedúra používa globálne
premenné, nie je vždy použiteľný, lebo
niekedy môžeme potrebovať vypočítať stred
iných dvoch korytnačiek a výsledok dať
do iných dvoch premenných, alebo samotnú
procedúru chceme mať "globálnu"
a táto nebude vidieť na potrebné premenné
(lokálne v našej procedúre Button1Click).
Úlohu preprogramujeme tak, aby sa nepoužili
"globálne" premenné, ale formálne
parametre:
|
procedure TForm1.Button1Click(Sender: TObject);
procedure pocitaj_stred(k1,k2:TKor; xx,yy:real);
begin
xx:=(k1.X+k2.X)/2;
yy:=(k1.Y+k2.Y)/2;
end;
var
k1,k2,k3:TKor;
x,y:real;
begin
k1:=TKor.Create(300,250,27);
k2:=TKor.Create(200,200,0);
pocitaj_stred(k1,k2,x,y);
k3:=TKor.Create(x,y,0); k3.FP:=clRed;
while true do begin
k1.dopredu(4); k1.vpravo(3);
k2.dopredu(3); k2.vlavo(2);
pocitaj_stred(k1,k2,x,y);
k3.ZmenXY(x,y);
cakaj(10);
end;
end;
|
Takto zapísaný program nebude fungovať,
lebo formálne parametre xx a yy sú "lokálne"
premenné, ktoré sú inicializované
hodnotami skutočných parametrov a po skončení
procedúry sa ich hodnoty "zabúdajú".
Doteraz známy mechanizmus formálnych parametrov
nám v tomto prípade nepomôže - potrebujeme
niečo nové.
Formálne parametre v pascale
Existujú rôzne prístupy k informáciám
- predstavme si napr. diár, pre ktorý
majú rôzne osoby rôzne prístupové
práva:
- vlastník - zápis, čítanie
- 1. sekretárka - prístup k duplikátu
- čítanie, modifikácia kópie
- 2. sekretárka - len čítanie
- ostatní - žiadny prístup
Typy prístupov k informáciám:
- úplný - čítanie, zápis
- len čítanie
- len zápis
- úplný prístup k duplikátu
- žiaden
Poznáme štandardné procedúry
na prácu so súbormi:
- procedúra read(t,x)
- prístup k súboru t na čítanie
- k premennej x na zápis (pôvodný
obsah premennej nás nezaujíma)
- procedúra write(t,x)
- prístup k súboru t na zápis
- k premennej x na čítanie
3 typy parametrov v Pascale
- prístup k duplikátu - hodnotové
parametre, volanie hodnotou (mali sme doteraz)
- úplný prístup - premenné
parametre, volanie adresou (var parametre)
- prístup len na čítanie - konštantné
parametre (hodnota formálneho parametra sa nezmení)
(const parametre)
V predchádzajúcom príklade sme
procedúre pocitaj_stred poslali všetky parametre
ako hodnotové, t.j. použili volanie hodnotou.
Čo sa teda stane:
- vypočítajú sa hodnoty skutočných
parametrov, s ktorými je procedúra volaná
(v tomto prípade k1, k2 a momentálne hodnoty
x a y)
- odovzdá sa riadenie procedúre, pričom
sa pre ňu vytvoria lokálne premenné k1,
k2, xx, yy a do týchto sa priradia hodnoty, ktoré
sa pri volaní vypočítali
- s korytnačkami (grafickými perami) nebude
problém, lebo nepotrebujeme meniť hodnoty korytnačiek,
problém bude s premennými x a y:
- tu by sa nám viac hodil úplný
prístup k týmto premenným, t.j.
potrebovali by sme volanie adresou
- v Pascale sa to zapíše rezervovaným
slovom var pred príslušným parametrom
korektné riešenie predchádzajúcej
úlohy:
|
procedure TForm1.Button1Click(Sender: TObject);
procedure pocitaj_stred(k1,k2:TKor; var xx,yy:real);
begin
xx:=(k1.X+k2.X)/2;
yy:=(k1.Y+k2.Y)/2;
end;
...
|
Teraz zhrňme všetky doterajšie poznatky o tom ako pracuje počítač pri volaní procedúry:
- keď sa objaví v programe meno procedúry
(musela byť definovaná v deklaráciách),
ide o volanie tejto procedúry, t.j.
- zapamätá sa návratová
adresa (kam sa bude treba vrátiť)
- vytvoria sa lokálne premenné procedúry
- naozajstné lokálne premenné
dostávajú nedefinovanú hodnotu
- aj hodnotové formálne
parametre sú lokálne premenné
- len sú inicializované hodnotami
skutočných parametrov (duplikát)
- premenné parametre sa
nikde nevytvárajú - sú
to len dočasné nové mená
premenných - skutočných parametrov
- prenesie sa riadenie programu do tela podprogramu
- vykonajú sa všetky príkazy podprogramu
- zrušia sa lokálne premenné - teda
aj nové hodnoty hodnotových formálnych
parametrov
- riadenie sa vráti za miesto v programe, odkiaľ
bol podprogram volaný
Ukážme si teraz inú verziu tejto úlohy,
kde jedna z korytnačiek chodí po obvode štvorca
a druhá po kružnici. Všimnite si, že procedúru
pocitaj_stred sme vytiahli pred Button1Click,
takže sa stala globálnou.
keď jedna z korytnačiek chodí
po obvode štvorca:
|
procedure pocitaj_stred(k1,k2:TKor; var xx,yy:real);
begin
xx:=(k1.X+k2.X)/2;
yy:=(k1.Y+k2.Y)/2;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
k1,k2,k3:TKor;
x,y:real;
i:integer;
begin
k1:=TKor.Create(300,250,27);
k2:=TKor.Create(200,200,0);
pocitaj_stred(k1,k2,x,y);
k3:=TKor.Create(x,y,0); k3.FP:=clRed;
while true do begin
for i:=1 to 30 do begin // skúste iný počet opakovaní, napr. 20
k1.dopredu(4);
k2.dopredu(3); k2.vlavo(2);
pocitaj_stred(k1,k2,x,y);
k3.ZmenXY(x,y);
cakaj(10);
end;
k1.vpravo(90); // skúste iné uhly: 120,144 a pod.
end;
end;
|
Funkcie
máme procedúru, ktorá vráti pomocou
parametra minimum z dvoch čísel:
|
procedure min(a,b:integer; var m:integer);
begin
if a<b then m:=a else m:=b;
end;
|
- podprogram môžeme definovať v tvare funkcie:
- v hlavičke podprogramu musíme okrem
formálnych parametrov zadať aj typ funkcie
- v tele funkcie sa musí do špeciálnej
lokálnej premennej Result priradiť
nejaká hodnota - táto bude výsledkom
funkcie
- lokálna premenná Result
je automaticky už zadeklarovaná (my ju
nesmieme deklarovať) a je rovnakého typu
ako je typ funkcie
- POZOR! táto premenná má
na začiatku nedefinovanú hodnotu,
t.j. ak do nej nič nepriradíme, výsledkom
bude nezmysel...
a teraz min ako funkcia:
|
function min(a,b:integer):integer;
begin
if a<b then Result:=a else Result:=b;
// v starom pascale bolo min:=a ... my toto používať nebudeme!
end;
|
Príklady:
Logická funkcia jecifra, ktorá vracia
true, ak je vstupný parameter cifrou:
|
function jecifra(z:char):boolean;
begin
if (z>='0') and (z<='9') then Result:=true
else Result:=false;
end;
|
túto funkciu zapíšeme elegantnejšie:
|
function jecifra(z:char):boolean;
begin
Result:=(z>='0') and (z<='9');
end;
|
Celočíselná funkcia, ktorá vracia
hodnotu načítanej cifry (inak -1):
|
function cifra(z:char):integer;
begin
if jecifra(z) then
Result:=ord(z)-ord('0') // 48
else
Result:=-1;
end;
|
Logická funkcia, ktorá vracia true,
ak je vstupný parameter písmenom:
|
function pismeno(z:char):boolean;
begin
Result:=(z>='a') and (z<='z') or (z>='A') and (z<='Z');
end;
|
- Logická funkcia odd, ktorá vracia true,
ak je celé číslo, ktoré je jej
parametrom, nepárne (pozn.: odd je už preddefinovaná ako štandardná
funkcia):
funkcia zistí, či je číslo
nepárne:
|
function odd(x:integer):boolean;
begin
Result:= x mod 2 = 1;
end;
|
celočíselná funkcia, ktorá vracia
súčet prvých N celých čísel:
|
function suma(N:integer):integer;
begin
Result:=0; // zrejme by fungovalo aj Result:=n*(n+1) div 2;
while N>0 do begin
Result:=Result+N;
dec(N);
end;
end;
|
- Funkcia, ktorá vracia znak zo vstupu, pričom
vracia ' ' ak bol eoln, '#' ak bol eof (znaková
premenná z je globálna):
funkcia spracuje jeden znak zo
vstupu:
|
var
z:char;
function znak(var t:TextFile):char;
begin
if eof(t) then z:='#'
else if eoln(t) then begin z:=' '; readln(t) end
else read(t,z);
Result:=z;
end;
|
Poznámka:
všimnite si použitie textového súboru
ako formálneho parametra - zapamätajte si,
že textový súbor ako parameter musí
byť vždy typu var-parameter!
- Zaujímavá je potom časť programu, ktorá
preskakuje všetky medzery a konce riadkov:
Príklad s počítaním mocniny
čísla:
najprv chybné riešenie:
|
function mocnina(x,K:integer):integer;
begin
Result:=1;
while K>0 do begin
Result:=Result*x;
dec(K);
end;
end;
|
- Táto funkcia občas vráti nesprávny
výsledok, pričom my nevieme, či momentálny
výsledok je dobrý - preto vo var-parametri
budeme vrátime informáciu, či nepretiekol výsledok:
funkcia počíta mocninu čísla:
|
function mocnina(x,K:integer; var ok:boolean):integer;
var
hranica:integer;
begin
Result:=1; hranica:=maxint div x;
while (K>0) and (Result<=hranica) do begin
Result:=Result*x;
dec(K);
end;
ok:=K=0;
end;
|
- maxint je preddefinovaná konštanta,
ktorá obsahuje maximálne celé
číslo, t.j. 2147483647
Funkcia random2 – náhodné číslo
zo zadaného itervalu:
|
function random2(a,b:integer):integer;
begin
Result:=random(b-a+1)+a;
end;
|
Funkcia vzd2 – vzdialenosť dvoch korytnačiek:
|
function vzd2(k1,k2:TKor):real;
begin
Result:=sqrt(sqr(k1.X-k2.X) + sqr(k1.Y-k2.Y));
// alebo Result:=k1.vzd(k2.X,k2.Y);
end;
|
NDÚ:
- celočíselná funkcia, ktorá
počíta NSD dvoch prirodzených čísel
Euklidovým algoritmom
- funkcia zistí, či dané číslo
je prvočíslo
- procedúra SKRAT(a,b,p,q) s celočíselnými
parametrami (b<>0), ktorá skráti
zlomok a/b na základný tvar p/q
- dve prirodzené čísla sa nazývajú
"priateľské", ak je každé
z nich rovné súčtu všetkých
deliteľov druhého bez neho samého
(také sú napr. čísla 220 a
284); vypíšte všetky páry "priateľských"
čísel, ktoré nepresahujú zadané
prirodzené číslo
Znakové reťazce
- údajový typ, ktorý obsahuje
postupnosť znakov (char)
- znaky v postupnosti sú očíslované
od 1 až po momentálnu dĺžku reťazca
- funkcia Length vráti momentálnu
dĺžku
- Delphi si uchováva dĺžku v 4 bajtoch,
t.j. teoreticky by maximálna dĺžka mohla
byť 4 gigabajtov - v skutočnosti je obmedzená
možnosťami Windows (možno len 1 gigabajt - otestujte
na vašom počítači)
- deklarujeme:
var
s:string;
- premenná s môže mať
zatiaľ nedefinovanú hodnotu - musíme
niečo priradiť - reťazcovú konštantu:
s:='reťazec';
- premenná s obsahuje postupnosť
znakov dĺžky 7: prvý znak je 'r',
druhý 'e',
atď.
- Length(s) je teraz 7
- reťazcové konštanty sú uzavreté
rovnako ako znakové konštanty v apostrofoch
- prázdny reťazec:
s:='';
- premenná s obsahuje prázdnu
postupnosť znakov - dĺžka je 0
- môžeme pracovať s jednotlivými
prvkami postupnosti, t.j. so jednotlivými
znakmi v reťazci:
s:='abcdef'; s[3]:='*';
- najprv sme do s priradili 5-znakový
reťazec a potom sme v ňom zamenili 3-tí
znak (znak 'c')
za hviezdičku
- nesmieme sa odvolávať na znaky
mimo rozsahu 1 a momentálna dĺžka, napr.
s[10]:='+';
// reťazec má zatiaľ
dĺžku len 6
- spôsobí chybovú správu
- okrem priradení môžeme reťazce zapisovať
do textového súboru a tiež ich môžeme
zo súboru čítať:
- write(t, premenná_typu_string
); //
výpis hodnoty premennej
- read(t, premenná_typu_string
); //
načítanie riadka zo vstupu
- operácie so znakovými reťazcami:
- logické operátory: =, <>,
<, >, <=,
>=
- napr. 'abc'>'ABC', 'jana'<'jano' -
hovoríme tomu lexikografické
usporiadanie
- postupne sa porovnáva znak za znakom,
kým sú rovnaké v oboch
reťazcoch, tak sa pokračuje v porovnávaní,
keď sa narazí na rozdielne znaky,
tak tieto sa porovnajú a nastavia
výsledok porovnania
- porovnávanie samotných
znakov sa robí podľa pravidiel char:
t.j. menší je znak s menším
ascii-kódom
- zreťazenie reťazcov '+' (+ je polymorfný operátor
- závisí od typu operandov), napr.
s:='abc'+'def'; // s='abcdef' s:=''; for i:=1 to 10 do s:=s+'*'; //
s='**********'
Krátke znakové reťazce
- v Turbo pascale ste mohli poznať typ string,
pri ktorom sme do hranatých zátvoriek
zapisovali nejaké číslo od 1 do 255
- maximálnu dĺžku reťazca, napr.
var
s:string[20];
- označuje, že premenná s bude môže
mať maximálnu dĺžku 20 znakov - priradenie
dlhšieho reťazca spôsobí odhodenie
všetkých znakov za 20. znakom
- aj v Delphi fungujú takéto reťazce
- my ich budeme používať veľmi zriedka
- zápis
var
s:string;
- označoval reťazec maximálnej dĺžky 255
znakov - toto je zrejme už v Delphi inak
- pri priraďovaní :=, ak je ľavá strana
kratšia ako pravá, tak sa reťazec oreže
- napr. s:='abcd'+'efgh'+'ijkl'; pre s:string[10])
je výsledné s='abcdefghij'
- tento typ reťazcov budeme používať veľmi
zriedka - len v špeciálnych prípadoch
Preddefinované (štandardné) podprogramy
s reťazcami
- Length( reťazec
)
- táto funkcia vráti momentálnu dĺžku
reťazca
- SetLength( reťazcová_premenná
, dĺžka )
- táto procedúra nastaví dĺžku
reťazca
- pre krátke reťazce nesmie presiahnuť deklarované maximum
- ak je menšia ako momentálna dĺžka, znaky na
konci sa strácajú
- ak je väčšia ako momentálna, tak pridané
znaky (od konca) majú nedefinovanú hodnotu
- Copy( reťazec
, od , koľko )
- táto funkcia vráti nový reťazec - podreťazec pôvodného
- ak je od mimo rozsahu, vráti prázdny
reťazec, t.j. '', napr.
s:=copy('abcde',2,3) => s='bcd' s:=copy('abcde',7,3) => s='' s:=copy('abcde',3,5) => s='cde'
- ak potrebujeme podreťazec od nejakého indexu
až do konca, nemusíme vypočítať presný
počet, ale môžeme použiť konštantu maxInt, napr.
s:=copy('abcde',3,maxInt)
- Pos( podreťazec
, reťazec )
- táto funkcia vráti začiatočný index prvého výskytu
podreťazca v reťazci alebo 0, ak sa v reťazci podreťazec
nenachádza, napr.
i:=pos('d','abcde'); =>
i=4 i:=pos('x','abcde'); =>
i=0 i:=pos('ba','abababab'); => i=2
Podprogramy na reťazcoch
program zistí počet všetkých výskytov
podreťazca v reťazci:
|
var
s,p:string;
i,j,poc:integer;
begin
...
poc:=0; j:=0;
repeat
i:=pos(p,copy(s,j+1,MaxInt));
if i>0 then begin inc(poc); j:=j+i end;
until i=0;
...
end;
|
Poznámka:
- všimnite si použitie inej konštrukcie cyklu
a to cyklu repeat:
- tento cyklus opakovane vykonáva telo
cyklu (príkazy medzi slovami repeat
a until) a po každom takomto vykonaní
týchto príkazov, otestuje podmienku
cyklu (logický výraz za slovom
until) - keď je podmienka splnená
(hodnota je true), cyklus končí
a pokračuje sa príkazmi za cyklom; keď je
podmienka nepravdivá (hodnota je false),
cyklus pokračuje - hovoríme tomu podmienka
ukončenia cyklu
- tento cyklus sa teda správa "opačne"
ako cyklus while
- uvedomte si, že telo cyklu sa vykoná
vždy aspoň raz, aj keď je podmienka hneď na začiatku
splnená
príklad: všetky výskyty znaku ' ' nahraď
'***':
|
repeat
i:=pos(' ',s);
if i>0 then s:=copy(s,1,i-1)+'***'+copy(s,i+1,MaxInt);
until i=0;
|
NDÚ:
- vyriešte aj pre prípad, že všetky výskyty
' ' sa nahradia '* *' - vsunutú medzeru už nenahrádza
Ďalšie preddefinované (štandardné)
procedúry/funkcie s reťazcami:
- ukážeme, ako by sme mohli sami naprogramovať
napr. funkciu pos
ukážka funkcie pos:
|
function pos(const p,s:string):integer;
var
nasiel:boolean;
begin
nasiel:=false; Result:=0;
while (Result<length(s)) and not nasiel do begin
inc(Result);
nasiel:=p=copy(s,Result,length(p));
end;
if not nasiel then Result:=0;
end;
|
Poznámka:
- vo while podmienke začiatočníci ľúbia
zapisovať nasiel=false, je to škaredé - radšej
zapisujeme not nasiel
- priradenie nasiel:=p=copy(s,Result,length(p)); začiatočníci
sú niekedy schopní zapísať:
if p=copy(s,i,length(p)) then nasiel:=true else nasiel:=false;
- vo while-podmienke by sa dalo skončiť aj skôr:
(Result<=length(s)-length(p))
Funkcia delete(s,od,kolko)
- zmení reťazec s,
tak, že vypustí
kolko znakov z pôvodného s od pozície
od:
ukážka funkcie delete:
|
procedure delete(var s:string; od,kolko:integer);
begin
s:=copy(s,1,od-1)+copy(s,od+kolko,MaxInt);
end;
|
Procedúra insert(co,s,od);
- vloží podreťazec co
do reťazca s od zadanej pozície od:
ukážka procedúry insert:
|
procedure insert(const co:string; var s:string; od:integer);
begin
s:=copy(s,1,od-1)+co+copy(s,od,MaxInt);
end;
|
Procedúra str(číslo,s);
- prevod čísla na
reťazec
- napr.
str(123,s); -> s='123' str(-5.72,s); -> s='-5.72'; str(123:5,s); -> s=' 123' str(-5.72:7:3,s); -> s=' -5.720';
naša zjednodušená verzia:
|
procedure str(c:integer; var s:string);
var
znam:boolean;
begin
znam:=c<0; if znam then c:=-c;
s:='';
repeat
s:=chr(c mod 10+ord('0'))+s;
c:=c div 10;
until c=0;
if znam then s:='-'+s;
end;
|
Procedúra val(s,cislo,ok);
- prevod reťazca na číslo
- napr.
val('123',i,ok); -> i=123, ok=0 - prevod bol v
poriadku val('123,124',i,ok); -> i=?, ok=4 - pozícia
v reťazci, kde bola chyba pri prevode val('- 123',i,ok); -> i=?, ok=2 val('123 ',i,ok); -> i=?, ok=4
naša zjednodušená verzia:
|
procedure val(const s:string; var i,ok:integer);
var
p:integer;
znam:boolean;
begin
p:=1; i:=0; znam:=s[1]='-'; if znam then p:=2;
while (p<=length(s)) and (s[p]>='0') and (s[p]<='9') do begin
i:=i*10+ord(s[p])-ord('0');
inc(p);
end;
if p>length(s) then begin ok:=0; if znam then i:=-i end
else ok:=p;
end;
|
NDÚ:
- preprogramujte str aj val, aby pracovali pre reálne
čísla
Príklad: Za každý znak v reťazci pridaj
' '
najprv chybné riešenie:
|
function pridaj(s:string):string;
var
i:integer;
begin
for i:=1 to length(s) do insert(' ',s,i);
Result:=s;
end;
|
iné riešenie:
|
function pridaj(s:string):string;
var
i:integer;
begin
for i:=1 to length(s) do insert(' ',s,2*i);
Result:=s;
end;
|
iné riešenie:
|
function pridaj(s:string):string;
var
i:integer;
begin
Result:='';
for i:=1 to length(s) do Result:=Result+s[i]+' ';
end;
|
NDÚ:
- Napíšte funkciu UpCaseStr, ktorá prerobí
v reťazci malé písmená na veľké.
- Napíšte procedúru Center, ktorá
centruje reťazec na udanú šírku:
procedure Center(var s:string; sirka:byte);
- Napíšte funkciu, ktorá pre vstupný
reťazec s dvoma slovami oddelenými medzerou vytvorí
reťazec, ktorý má tieto slová v
opačnom poradí (napr. krstné meno a priezvisko)
Delphi má preddefinované tieto užitočné
funkcie (v programe musí byť uses SysUtils;)
- function UpperCase(const S: string): string;
- function LowerCase(const S: string): string;
- function Trim(const S: string): string;
- function TrimLeft(const S: string): string;
- function TrimRight(const S: string): string;
- function AdjustLineBreaks(const S: string): string;
- function IntToStr(Value: Integer): string;
- function IntToHex(Value: Integer; Digits: Integer):
string;
- function FloatToStr(Value: Extended): string;
- function StrToInt(const S: string): Integer;
- function StrToIntDef(const S: string; Default: Integer):
Integer;
- function StrToFloat(const S: string): Extended;
oplatí sa s nimi zoznámiť a používať
ich - môžete si ich cvične naprogramovať aj sami
ako NDÚ
Tiež Delphi ponúkajú tieto funkcie na prácu
so súbormi:
- function FileExists(const FileName: string): Boolean;
- function DeleteFile(const FileName: string): Boolean;
- function RenameFile(const OldName, NewName: string):
Boolean;
- function CreateDir(const Dir: string): Boolean;
- function RemoveDir(const Dir: string): Boolean;
tieto funkcie vrátia true, ak prebehli bez
chyby - ak nás chybný výsledok
nezaujíma, môžeme tieto funkcie volať ako
keby
to boli procedúry
|