Přeskočit na obsah

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