V jubiljním desátém dílu tohoto seriálu se naučíme používat funkce a procedury. Vlastně to již částečně umíme, ale nevíme o tom. Pokud to zjednoduším, procedura je vlastně speciální druh funkce, podobně jako čtverec je jen speciální druh obdélníka. Funkce je blok kódu, kterému předáme nějaké vstupní hodnoty pomocí proměnných (říká se jim argumenty, někdy také parametry), kód se provede a vrátí nějaký výsledek. Procedura žádný výsledek nevrací, ale jinak je stejná.
Funkce i procedura je něco jako taková „černá skříňka“ – něco jí předáme, skříňka něco udělá a pokud je to funkce, vrátí nám výsledek.
Zápisy funkce a procedury vypadají takto:
Function <NázevFunkce>(<Argument1> As <Typ1>, <Argument2> As <Typ2> ...) As <DatovýTypVýsledku>
<Kód funkce>
Return <Výsledek>
End Function
Sub <NázevProcedury>(<Argument1> As <Typ1>, <Argument2> As <Typ2> ...)
<Kód procedury>
End Sub
Počet argumentů v závorkách záleží na nás - může jich být 5, ale nemusí být ani jeden (v takovém případě se napíše jen otevřená a zavřená závorka). Jakmile proceduru nebo funkci zapíšeme, Visual Basic nám sám před každý argument doplní klíčové slovo ByVal. Má tam svůj význam, zatím si jej ale všímat nebudeme.
Pokud chceme funkci nebo proceduru spustit (zažitý termín je zavolat), musíme jí předat její argumenty. Pokud je to funkce, její výsledek přiřadíme do proměnné:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim soucet As Integer = Suma4(6, 7, -5, 4)
End Sub
Public Function Suma4(ByVal c1 As Integer, ByVal c2 As Integer, ByVal c3 As Integer, ByVal c4 As Integer) As Integer
Return c1 + c2 + c3 + c4
End Function
V této ukázce jsem tedy vytvořil jednoduchou funkci Suma4, které předáme 4 čísla a tato funkce vrátí jejich součet. Je to velmi jednoduchá funkce, kterou se možná ani nevyplatí vytvářet, protože můžeme čísla sečíst rovnou. Ve skutečnosti se funkce používají na poněkud složitější výpočty. Hodnota, která je za klíčovým slovem Return se vrátí jako výsledek funkce. Pokud by byl za řádkem s Return ještě nějaký další kód, už se neprovede. Return totiž vrátí hodnotu a vyskočí z funkce.
Důležité je ale to, že pokud chceme funkci zavolat, zapíšeme její název a do závorky popořadě hodnoty jejích argumentů (to je to Suma4(6, 7, -5, 4)). Čísla 6, 7, -5 a 4 se dosadí do proměnných c1 až c4 a provede se kód uvnitř funkce s těmito proměnnými, v tomto případě se jen vrátí jejich součet. Pokud se podíváme na řádek Dim soucet As Integer = Suma4(6, 7, -5, 4), tak výsledek volání funkce se přiřadí do proměnné soucet. Celý výraz Suma4(6, 7, -5, 4) může být součástí jiného výrazu, krásně mužeme např. napsat Dim a As Integer = 3 * (Suma4(6, 7, -5, 4) + 2) nebo MsgBox(Suma4(1, 2, 3, 4)). Než se začne vypočítávat výsledek celého výrazu, zjistí se nejdřív výsledek funkce a pak se pracuje s ním.
Procedura žádný výsledek nedává, volá se stejně, akorát musí stát na řádku samotná. Může to vypadat například takto:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
UkazSumu(6, 7, -5, 4)
End Sub
Public Sub UkazSumu(ByVal c1 As Integer, ByVal c2 As Integer, ByVal c3 As Integer, ByVal c4 As Integer)
MsgBox(c1 + c2 + c3 + c4)
End Sub
Procedura zobrazí hlášku se součtem předaných čtyř čísel. Volání této procedury je jen řádek UkazSumu(6, 7, -5, 4).
Aby toho nebylo málo, tak i při volání funkce či procedury můžeme používat výrazy. Přípustné je tedy i například toto (pro přehlednost jsem obarvil každý argument jinou barvou):
UkazSumu(6, Suma4(1, 5, 78, -54), -5 + x, 4 * (7 - t / 39))
Tento zápis je ovšem již na hranici přehlednosti - asi by bylo lepší alespoň výsledek funkce Suma4 nejprve přiřadit do nějaké proměnné a volat proceduru UkazSumu s použitím předvypočítané hodnoty. Nicméně i tento zápis funguje.
Procedury již známe - pracujeme s událostmi komponent
Jak jsem již na začátku předeslal, procedury již trochu známe. Pokud se podíváte na zápis procedury události kliknutí na tlačítko, najdete tam také Sub, End Sub a argumenty. První argument je sender a je to vždy objekt, který událost vyvolal. Druhý argument je e, což jsou doplňující informace k události. Tyto dva argumenty má každá událost jakékoliv komponenty. Dále následuje klíčové slovo Handles, které říká, na kterou komponentu a událost se má procedura napojit.
Takže to, co jsme psali mezi dva řádky, které se objevily při komponentu na tlačítko na formuláři, to byl vnitřek procedury. Tato procedura se zavolala vždy, když nastala událost dané komponenty.
Hledáme prvočísla
Teorii už máme za sebou, nyní nás čeká praktický úkol. Napíšeme aplikaci, která vypíše všechna prvočísla nižší než 1000. Prvočíslo je číslo, které je dělitelné jen jedničkou a samo sebou, ničím jiným. Jednička samotná se mezi prvočísla nepočítá.
Dříve než začneme psát, rozmyslíme si, jak bude program fungovat. A pokusíme se využít co nejvíc věcí, které již umíme. Umíme cyklus For, který provádí určitou operaci pro všechna celá čísla z nějakého intervalu. To se nám velice dobře hodí. Jednoduše uděláme cyklus od 1 do 1000 a vyzkoušíme, jestli je aktuálně procházené číslo prvočíslem. A pokud je, vypíšeme jej do seznamu. Pro správný návrh programu je dobré zamyslet se, která činnost se bude opakovat nejčastěji. A bude to právě zjišťování, jestli je číslo prvočíslo. A pokud se něco často opakuje, i když třeba pro jiné hodnoty, vyplatí se na to udělat funkci. Pak již máme jen skříňku, které se zeptáme "Je trojka prvočíslo?", a dostaneme z ní odpověď ano či ne.
Došli jsme tedy k tomu, že musíme napsat funkci JePrvocislo, které předáme jeden argument i, což bude číslo, na které se ptáme. Funkce bude typu Boolean, protože vrátí True, pokud i je prvočíslo, a False, pokud není. O tom, jestli je nějaké číslo prvočíslo, rozhodneme jednoduše - zkusíme ho vydělit vším možným a pokud najdeme nějaký dělitel kromě jedničky nebo testovaného čísla, prvočíslo to nebude. Zkusíme tedy vydělit číslo i všemi čísly od 2 do i-1 a pokud nějakého dělitele najdeme, prvočíslo to nebude. A jak zjistit dělitelnost? Známe operátor Mod, který vrátí zbytek po vydělení. A pokud je zbytek nula, číslo dělitelné je. Zkoušíme vždy vydělit číslo i číslem, které je čítač cyklu (protože to právě zkoušíme). Funkce tedy bude vypadat takto:
Function JePrvocislo(ByVal i As Integer) As Boolean
For a As Integer = 2 To i - 1
If i Mod a = 0 Then Return False
Next
Return True
End Function
A hlavní program již nebude těžký. Přidejte na formulář tlačítko a seznam (ListBox). Dvakrát klikněte na tlačítko, aby se vygenerovala procedura. Do ní napíšeme zkoušení a vypisování prvočísel. Zkusíme všechna čísla od 2 do 1000 a zavoláme na ně funkci. Pokud vrátí True, přidáme dané číslo do seznamu.
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
For i As Integer = 2 To 1000
If JePrvocislo(i) Then ListBox1.Items.Add(i)
Next
End Sub
A to je celé. Na první pohled složitá úloha. A bez funkce by byl kód poněkud nepřehledný a složitý. Pokud strukturu dobře navrhneme, usnadníme si tím mnoho práce. Vždy se snažte úkony, které se často opakují, izolovat do funkce. Nevadí, že jsou po každé pro jiné hodnoty, ty jim lze předat jako argument.
Proměnné a jejich platnost
Možná jste si všimli, že ve funkci i v proceduře máme proměnné i. Přesto se tyto proměnné navzájem neovlivňují. Je načase poodhalit roušku tajemství - záleží, kde proměnnou deklarujeme.
Pokud proměnnou nadeklarujeme uvnitř procedury (příkazem Dim nebo jako argument), platí nám jen v této proceduře. Označujeme ji jako lokální proměnnou, kdekoliv jinde může být proměnná se stejným názvem, ale je to jiná proměnná. Má jiné hodnoty a je v paměti uložena úplně jinde. Tyto dvě proměnné se navzájem neovlivňují. Pokud proměnnou nadeklarujeme mimo proceduru (ale mezi řádky Class a End Class, tedy uvnitř třídy - v našem případě formuláře), platí tato proměnná ve všech procedurách, které jsou v dané třídě (formuláři). Pokud by se ale v některé proceduře objevila deklarace lokální proměnné stejného názvu, tak v té konkrétní proceduře se bude používat proměnná lokální. Pokud proměnnou nadeklarujeme v cyklu, platí jen v tomto cyklu, za ním již ne. Pokud ji nadeklarujeme v podmínce, platí opět jen v té větvi podmínky.
Podobná věc funguje i u procedur a funkcí - pokud použijeme před slovem Sub nebo Function slovo Private, platí daná procedura jen v rámci dané třídy (formuláře). Pokud uvedeme Public, můžeme ji zavolat i z formulářů ostatních. Pokud nadeklarujeme proměnnou mimo proceduru, jen ve třídě, a použijeme místo Dim slovo Public, bude k ní také možno přistupovat z jiných formulářů.
Kouzelné slovíčko ByVal
Jak jsem již dříve upozornil, před každý argument nám Visual Basic automaticky přidá slovíčko ByVal. Znamená to, že pokud předáváme jako argument pouze proměnnou, vytvoří se její kopie a v proceduře či funkci se pracuje jen s touto kopií. Pokud ji tedy uvnitř procedury nebo funkce změníme, n původní předávanou proměnnou to nemá vliv. Pokud bychom slovíčko změnili na ByRef, pracovalo by se uvnitř funkce či procedury s předávanou proměnnou. Pokud ji změníme, změní se i proměnná, kterou jsme předávali při volání. Příklad je zde:
Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
Dim b As Integer = 10
Nastav1(b)
MsgBox(b)
Nastav2(b)
MsgBox(b)
End Sub
Sub Nastav1(ByVal a As Integer)
a = 5
End Sub
Sub Nastav2(ByRef a As Integer)
a = 16
End Sub
I když v první proceduře argument změníme, do proměnné b v proceduře události tlačítka se tato změna nepřenese. Argument je totiž ByVal. Druhá procedura ale proměnnou opravdu změní. To můžeme využít, pokud chceme, aby např. funkce vracela více než jeden výsledek - použitím ByRef argumentů nastavíme hodnoty proměnnách, které do funkce předáváme.
ByVal a ByRef má však smysl pouze u proměnných běžných (tzv. primitivních) datových typů (String, Integer, Double, Date, Boolean atd.). Visual Basic nám tyto datové typy zvýrazňuje modře. Pokud ale předáváme pole nebo objekty, vždy se to chová jako ByRef. Vytváření kopií objektů a polí by zabralo mnoho procesorového času a ani toto chování není ve většině případů žádoucí.
To je protentokrát vše. Než se dohrabu k napsání dalšího dílu, můžete si zkusit napsat program, který vypíše prvních 5000 prvočísel (ne od jedné do 5000). A zkuste si také rozmyslet, proč při zjišťování prvočíselnosti stačí procházet čísla pouze od 2 do odmocniny z i a ne až do i - 1.