Funkce a subroutiny
Kromě vnitřních funkcí můžeme definovat své vlastní. Navíc můžeme vytvářet tzv. subroutiny. Funkcím a subroutinám souhrně říkáme procedury.
Procedury slouží k zapouzdření malých kusů kódu. Toto zapouzdření je užitečné pro rozdělení kódu na menší snadněji testovatelné kusy. Také nám to umožňuje napsat kus kódu, který se nám několikrát na různých místech opakuje pouze jednou. To vede ke znovupoužitelnosti, přehlednosti a celkově k větší jednoduchosti kódu.
Definice interních procedur se provádí za příkazem contains
.
program priklad_21
implicit none
! sem budu psat svuj kod
contains
! zde budu definovat interní funkce a subroutiny
end program
funkce
Zápis funkce vypadá takto (v [] jsou volitelné parametry):
[type] function jmenoFunkce(...argumenty funkce...)
... deklarace proměnných ...
... kod funkce ...
end function
Typ funkce je volitelný parametr a buď jej uvedeme nebo definujeme typ výstupní proměnné s deklarací proměnných. Výstupní proměnná má v tomto případě stejné jméno, jako je jméno funkce. Pokud je typ výstupní hodnoty složitější, např. pole se musí definovat až při deklaraci proměnných, proto doporučuji to tak dělat pokaždé. Funkci používáme stejně jako vnitřní funkce.
volba result
Za název funkce a výčet argumentů můžeme přidat specifikaci result _jmenoVýstupníProměnné_
. Zápis funkce můžeme potom zapsat také tako:
[type] function jmenoFunkce(...argumenty...) result jmenoVysupníProměnné
... deklarace proměnných ...
... kod funkce ...
end function
Potom při deklaraci nedefinujeme výstupní proměnnou s názvem funkce, ale s názvem který jsme zvolili.
program priklad_22
implicit none
real :: u = 1.0
real :: v = 2.0
write(*,*) "secti_1(u, v): ",secti_1(u, v)
write(*,*) "secti_2(u, v): ",secti_2(u, v)
write(*,*) "secti_3(u, v): ",secti_3(u, v)
write(*,*) "secti_4(u, v): ",secti_4(u, v)
contains
!------------------------------------------
function secti_1(a,b)
real :: a, b, secti_1! deklarujeme promennou secti_1 do ktere ukladame vysledek funkce
secti_1 = a + b
end function
!------------------------------------------
real function secti_2(a,b)
real :: a, b
! secti_2 nemusime deklarovat protoze jsem jiz urcili typ vystupni promenne pred prikazem function
secti_2 = a + b
end function
!------------------------------------------
function secti_3(a,b) result(c)
real :: a, b
real :: c! deklarujeme c jako vystupni prommenout
c = a + b
end function
!------------------------------------------
real function secti_4(a,b) result(c)
real :: a, b
c = a + b
end function
end program
subroutiny
Hlavní rozdíl funkcí a subroutin je v tom, že funkce má jen jednu výstupní proměnnou popř. pole (aspoň by měla mít) a subroutina jich má kolik chceme.
subroutine jmenoSubroutiny (...argumenty...)
... deklarace proměnných ...
... kod subroutiny ...
end subroutine
Subroutinu voláme pomocí call
.
call jmenoSubroutiny (...argumenty...)
program priklad_23
implicit none
real :: u = 1.0
real :: v = 2.0
real :: soucet, rozdil
call spocti(u, v, soucet, rozdil)
write(*,*) "soucet u a v = ",soucet
write(*,*) "rozdil u a v = ",rozdil
contains
!------------------------------------------
subroutine spocti(a, b, c, d)! subroutine do c ulozi soucet a do d rozdil
real :: a, b, c, d
c = a + b
d = a - b
end subroutine
end program
Příkaz return
Příkazem return
ukončíme subroutinu a kód pokračuje za jejím zavoláním.
Pole jako argument
Při předávání pole jako argumentu máme dvě možnosti jak demostrují následující příklady:
subroutine nazevSubroutiny(n,pole)
integer :: n
real :: pole(n)
nebo
subroutine nazevSubroutiny(pole)
real :: pole(:)
V prvním případě předáváme velikost pole jako argument. V druhém případě se vytvoří pole správné velikosti automaticky.
program priklad_24
implicit none
real :: u(5) = [ 1.2, 5.6, 8.5, -2.3, 6.1 ]
write(*,*) "Prumer hodnot v u = ",prumer(u)
contains
!------------------------------------------
real function prumer(a)
real :: a(:)
prumer = sum(a)/size(a)
end function
end program
Pro definici dalších polí v proceduře můžeme použít funkci size()
.
function nazevFunkce(a)
real :: a(:)
real :: b(size(a))
real :: nazevFunkce(size(a))
atd. ...
end function
character
jako argument
Postup je analogický jako v předchozí části s poli.
subroutine nazevSubroutiny(n,text)
integer :: n
character(n) :: text
nebo
subroutine nazevSubroutiny(pole)
character(*) :: text
Specifikace intent
Vzhledem k tomu, že v subroutině můžeme mít několik vstupních a několik výstupních proměnných může se hodit specifikovat, které jsou vstupní a které výstupní. To nám může pomoct nejen s tím, že si uděláme pořádek, ale také to může ochránit některé proměnné před nechtěným přepsáním.
Pro takové určení proměnných používáme specifikaci ,intent(_in nebo out nebo inout_) ::
. Pokud proměnnou označíme jako in
říkáme, že slouží jen pro vstup. out
označí proměnnou jako výstup, její hodnota na začátku není známá a je určena až za běhu. inout
je výchozí nastavení, její hodnota se nastaví na vstupu a můžeme ji libovolně měnit.
intent
můžeme použít také ve funkci abychom ochránili vstupní hodnoty před přepsáním.
Subroutinu z příkladu 23 bychom nyní mohli psát spíše:
subroutine spocti(a, b, c, d)
real,intent(in) :: a, b
real,intent(out) :: c, d
c = a + b
d = a - b
end subroutine
Specifikace save
Pokročlé - nepovinné
V některých případech se nám může hodit nechat si uloženou hodnotu lokální proměnné v proceduře. K tomu můžeme použít specifikaci ,save ::
. Pokaždé když použijeme proceduru, bude hodnota takto označené proměnné tak jak jsme ji nechali.
program priklad_25
implicit none
call pocitej()
call pocitej()
call pocitej()
call pocitej()
contains
!------------------------------------------
subroutine pocitej()
integer,save :: pocet = 0! pri prvnim pouziti funkce deklarujeme promennou pocet a nastavime ji na 0
pocet = pocet + 1! pri kazdem pouziti subroutiny pricteme k promenne pocet 1
write(*,*) "pocitam",pocet
end subroutine
end program
volitelné parametry Pokročlé - nepovinné
Fortran umožňuje nastavit některé argumenty jako volitelné. Tyto proměnné při definici procedury zadáváme za povinné argumenty. Při deklaraci proměnných volitelné parametry specifikujeme pomocí ,optional ::
.
Pokud chceme vynechat volitelnou proměnnou a zadat volitelnou proměnnou, která se nachází za ní, musíme při jejím volání napsat její jméno, _jmenoProcedury_(_povinné parametry..._, _volitelnáProměnna_=_hodnota_, _..._
)
Testovat zda byla volitelná proměnná zadána můžeme pomocí příkazu present(_nazevProměnné_)
program priklad_26
implicit none
call sub(1,2)! nastavujeme jeno a a b - jedine povinne parametry
call sub(1,2,3)! nastavujeme a, b a c
call sub(1,2,d=4)! nastavujeme a, b a d
contains
!------------------------------------------
subroutine sub(a,b,c,d)
integer :: a,b
integer, optional :: c,d
write(*,*) a
write(*,*) b
if (present(c)) write(*,*) c! pokud jsme zadali c vypise ho
if (present(d)) write(*,*) d! pokud jsem zadali d vypiseho
write(*,*)
end subroutine
end program
externí procedury
Procedury můžeme definovat také externě. V takovém případě definici píšeme mimo hlavní program, tj. mimo příkazy program
a end program
. Potom procedura nemá přístup ke globálním proměnným (proměnným v hlavním programu). V hlavním programu bychom měli definotavat interface. Ten nám zajišťuje správné používání procedury v hlavním programu.
Interface definujeme před nebo za deklarací proměnných mezi příkazy interface
a end interface
. Mezi ně zadáváme zkrácenou definici procedur ve které vynácháme vše kromě deklarace proměnných, které jsou na vstupu nebo výstupu, příkaz implicit none
a use
(viz. moduly, dále). Příkaz implicit none
musíme v případě externích procedur zadávat při každé definici procedury.!
Interface je jakýsi vzor který program očekává, že procedura bude implementovat. Všimněte si v příkladu, že jména proměnných v interface
jsou jiná než pri definici procedury.
program priklad_27
implicit none
integer :: c = 1, d = 2
interface
subroutine sub(e,f)
implicit none
integer :: e,f
end subroutine
end interface/)
call sub(c,d)
end program
!------------------------------------------
subroutine sub(a,b)
implicit none! musime psat neboj jiz nejsme v hlavnim programu
integer :: a,b
write(*,*) a
write(*,*) b
write(*,*)
end subroutine
Globální a lokální proměnná
Každá proměnná má své pole působnosti, které je závislé na tom, kde jsme ji definovali. Například proměnná definovaná v hlavním programu může být použita také v interní proceduře (není to vhodné, pokud není argumentem procedury). Pokud ovšem v této proceduře deklarujeme proměnnou se stejným jménem bude působnost této nové proměnné lokální (bude existovat jen vrámci procedury) a s vnější proměnnou (tzv. globální) téhož názvu již nemůžeme uvnitř procedury pracovat.
Ve fortranu nám nové záběry (z ang. “scope”) tvoří
program
-end program
subroutine
-end subroutine
function
-end function
interface
-end interface
type
-end type
program priklad_28
implicit none
real :: a = 5.0
write(*,*) "Hlavni program - a = ",a
call subroutinaSLokalniPromennou()!subroutina vypise svou vlastni promennou a
write(*,*) "Hlavni program - a = ",a
call subroutinaPouzivajiciGlobalniPromennou()! subroutina zmeni hodnotu promenne a v hlavnim programu a vypise ji
write(*,*) "Hlavni program - a = ",a! promenna a byla zmenena subroutinou
contains
!------------------------------------------
subroutine subroutinaSLokalniPromennou()
real :: a
a = 1.0
write(*,*) "subroutina S Lokalni Promennou - a = ",a
end subroutine
!------------------------------------------
subroutine subroutinaPouzivajiciGlobalniPromennou()
a = 2.0
write(*,*) "subroutina Pouzivajici Globalni Promennou - a = ",a
end subroutine
end program
V příkladě 27 si můžme všimnout, že jména proměnných, které nám z hlavního programu vstupují do subroutiny, v definici subroutiny a v interfacu jsou rozdílná. Příklad 27 bychom mohli napsat také tak, že všechny proměnné pojmenujeme stejně, ale půjde vždy o jiné proměnné se stejným jménem.
program priklad_27
implicit none
integer :: a = 1, b = 2
interface
subroutine sub(a,b)
implicit none
integer :: a,b
end subroutine
end interface
call sub(a,b)
end program
!------------------------------------------
subroutine sub(a,b)
implicit none! musime psat nebot jiz nejsme v hlavnim programu
integer :: a,b
write(*,*) a
write(*,*) b
write(*,*)
end subroutine
funkce jako argument Pokročlé - nepovinné
Funkci můžeme použít jako argument procedury. V těchto procedurách bychom měli definovat interface
.
program priklad_29
implicit none
call vykresliBodyFunkce(0.0, 10.0, 1.0, fce)
contains
!------------------------------------------
subroutine vykresliBodyFunkce(fod, fdo, fkrok, fx)
real :: fod, fdo, fkrok, x
interface! definujeme jaky interface musi vstupni funkce implementovat
function fx(x)
real :: fx, x
end function
end interface
x = fod
do while(x <= fdo)! x jde od fod do fdo
write(*,*) x,fx(x)! vypiseme x a hodnotu funkce fx v bode x
x = x + fkrok
end do
end subroutine
!------------------------------------------
function fce(x)
real :: fce, x
fce = x ** 2 - 10.0
end function
end program
rekurze
Abychom mohli použít funkce rekurzivně, tj. aby funkce mohla v sobě volat samu sebe, musíme jí označit typem recursive
a specifikovat result()
.
Subroutiny mohou být také rekurzivní, potom ovšem nezadáváme result()
.
program priklad_30
implicit none
write(*,*) faktorial(5)
contains
!------------------------------------------
recursive function faktorial(x) result(y)
integer :: x
integer :: y
if(x == 1) then
y = 1
else
y = x * faktorial(x - 1)
end if
end function
end program
přetížené funkce Pokročlé - nepovinné
Přetěžování funkcí nám umožňuje volat více funkcí s různými vtupními argumenty pod jedním názvem. Např. můžeme napsat funkci, která jako argument příjímá integer
a druhou která dělá totéž, ale jako argument příjímá real
. Tyto dvě funkce můžeme sloučit do tzv. přetížené funkce a podle toho, zda jsme použili jako argument integer
nebo real
, se zavolá jedna nebo druhá funkce.
Přetížení vytváříme pomocí příkazu interface
za které přidáme jméno přetížené funkce a dovnitř píšeme zkrácené definice jednotlivých funkcí.
Fortran nám také umožňuje přetížit operátor (+, -, \*, ...
) nebo také přiřazení (=, <=, ==, ...
)
program priklad_31
implicit none
interface kvadrat
function kvadrat_integer(x)
implicit none
integer :: x
real :: kvadrat_integer
end function
function kvadrat_real(x)
implicit none
real :: x, kvadrat_real
end function
end interface
write(*,*) kvadrat(5)
write(*,*) kvadrat(5.0)
end program
!------------------------------------------
function kvadrat_integer(x)
implicit none
integer :: x
real :: kvadrat_integer
kvadrat_integer = real(x)**2
end function
!------------------------------------------
function kvadrat_real(x)
implicit none
real :: x, kvadrat_real
kvadrat_real = x**2
end function
pure procedury Expertní - nepovinné
Pure procedury zabraňiují tzv. vedlejším efektům. Vzhledem k tomu, že procedury mají přístup ke globálním proměnným, tak je také mohou měnit. Pure procedury nemohou globální parametry mění. Dokonce nemohou používat ani vstupní či výstupní příkazy. Uvnitř těchto funkcí lze používat jen pure procedury.
pure function jmenoPureFunkce(...argumenty...) [result jmenoVysupníProměnné]
... deklarace proměnných ...
... kod funkce ...
end function
elemental procedury Expertní - nepovinné
Funkce tohoto typu nám umožňují definovat procedury operující s poli, které se aplikují prvek po prvku. V případě, že je polí v argumentu více musí mít stejný tvar.
elemental function jmenoPureFunkce(...argumenty...) [result jmenoVysupníProměnné]
... deklarace proměnných ...
... kod funkce ...
end function