Else
End If <podmínka> zde představuje tzv. logický výraz, tj. takový, který po vyhodnocení obsahuje hodnotu pravda/nepravda (true/false). Takovou hodnotu lze kromě podmíněného příkazu také uloţit do proměnné typu Boolean (viz výše). Logika příkazu je z výše uvedeného zápisu zcela zřejmá (určitě není třeba připomínat překlad anglických slov if – pokud, then – pak, else – jinak, resp. v opačném případě). Část else lze vynechat – pak se v případě nesplněné podmínky z podmíněného příkazu nevykoná nic. Příklad: If x > 0 Then x = x + 1 Else x = 1 End If Příkaz If y > max Then max = y End If je ekvivalentní se zkráceným zápisem If y > max Then max = y Zkrácený zápis lze pouţít v případě, ţe je podmíněně vykonáván právě jeden příkaz. Ukončení příkazu End If pak není pouţito. Příklad programu, řešícího kvadratickou rovnici: 1 2 3 4 5 6 7 8
Sub kvadRovnice() ' -- Deklarace proměnných Dim a As Double, b As Double, c As Double, d As Double Dim x1 As Double, x2 As Double, real As Double, imag As Double Dim vystupRovnice As String ' -- Načtení proměnných z listu
- 23 -
Informatika • Modul 01 • Algoritmizace a programování 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
a = InputBox("Zadejte koeficient a") b = InputBox("Zadejte koeficient b") c = InputBox("Zadejte koeficient c") ' -- Vytvoření řetězce se zadáním rovnice vystupRovnice = "Kořeny rovnice " & a & "x2 + " & b & "x + " & c & " = 0" ' -- Výpočet potřebných výrazů d = b * b - 4 * a * c ' diskriminant real = -b / (2 * a) ' reálný člen ' -- Výpočet a zobrazení kořenů podle znaménka diskriminantu If d >= 0 Then x1 = real + Math.Sqr(d) / (2 * a) x2 = real - Math.Sqr(d) / (2 * a) MsgBox (vystupRovnice & vbCrLf & "x1: " & x1 & vbCrLf & "x2: " & x2) Else imag = Math.Sqr(-d) / (2 * a) ' imaginární člen MsgBox (vystupRovnice & vbCrLf & "x1: " & real & "+i." & imag & _ vbCrLf & "x2: " & real & "-i." & imag) End If End Sub
Komentář Do proměnných a, b, c načteme koeficienty rovnice ax2+bx+c = 0 (řádky 9 – 11). Do řetězcové proměnné vystupRovnice zkonstruujeme zápis rovnice pomocí zadaných koeficientů (řádek 14). Pouţijeme ho při výstupu, aby uţivatel viděl, jaká rovnice se vlastně řeší. V proměnné d uloţíme diskriminant d = b2 – 4ac (17). Podle jeho hodnoty rozhodujeme (řádek 21), zda vypočteme nebo zobrazíme reálné kořeny
nebo zobrazíme komplexní řešení
- 24 -
Studijní prameny
Pro výpočet odmocniny vyuţijeme vestavěnou matematickou funkci VB s názvem Sqr (square root). Všechny matematické funkce jsou soustředěny v objektu Math, proto musíme při jejich volání pouţít konstrukci s tečkou tak, jak je uvedeno v programu na řádcích 22, 23 a 26. Význam tohoto zápisu bude objasněn v kapitole o objektech. Pro zobrazení výstupního řetězce musíme vhodně spojit nejen obsahy proměnných, ale i speciální znak pro nový řádek vbCrLf, coţ je vestavěná pojmenovaná konstanta VB.
3.4
Cyklus
Příkaz cyklu je algoritmická konstrukce pro opakované vykonávání příkazů, dokud je splněna určitá podmínka. Pouţívá se v iteračních výpočtech, při práci s poli (viz dále) apod. VB umoţňuje pouţít cyklus v několika syntaktických variantách, z nichţ nejběţnější je: Do While <podmínka>
Loop Syntaxe je zřejmá z anglického překladu pouţitých slov (do while – vykonávej pokud, loop – smyčka). Podmínka cyklu má stejný význam jako v podmíněném příkazu If. Příkazy v těle cyklu jsou opakovaně vykonávány, přičemţ je před jejich vykonáním vţdy znovu vyhodnocena podmínka. Jakmile není podmínka splněna, příkaz končí a program pokračuje příkazy za ukončovacím slovem Loop. Příkazy v těle cyklu by tedy měly mít na podmínku vliv – v opačném případě hrozí nebezpečí nekonečného cyklu. Výjimkou je případ, kdy některý příkaz v těle je vytvořen tak, ţe cyklus násilně ukončí. Častější je pak případ, kdy podmínka souvisí s čekáním na vnější událost (podmínku lze např. vytvořit tak, ţe cyklus probíhá, dokud neuplyne určitý časový interval, nebo dokud uţivatel nestiskne klávesu apod.). Nekonečný cyklus často vede k tomu, ţe program je zapotřebí násilně zastavit pomocí prostředků operačního systému (obvykle také pomůţe kombinace kláves Ctrl–Break). Pouţití cyklu demonstrujeme na příkladu programu, který sečítá "nekonečnou" řadu: Budeme na chvíli předpokládat, ţe VB neumí vyčíslit exponenciální funkci a vytvoříme program, který vypočítá pro zadané x hodnotu ex se zadanou přesností: Vyuţijeme známý rozvoj exponenciální funkce do řady
Snadno nahlédneme, ţe pro kaţdé dva po sobě následující členy platí:
- 25 -
Informatika • Modul 01 • Algoritmizace a programování
Máme tedy předpis, jak snadno z předcházejícího členu vyjádříme další člen řady a implementace programu je jiţ přímočará: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
Sub expX() ' -- deklarace proměnných Dim rada As Double, x As Double, eps As Double, a As Double Dim i As Integer ' -- vstup dat x = InputBox("Zadejte argument exponenciální funkce") eps = InputBox("Zadejte přesnost výpočtu (jako malé číslo)") ' -- inicializace cyklu a = 1 rada = a i = 0 ' -- vlastní cyklus přidávající další členy Do While a > eps a = a * x / (i + 1) rada = rada + a i = i + 1 Loop ' -- výstup výsledku MsgBox ("Zadaná přesnost: " & eps & vbCrLf & _ "Moje funkce exp(" & x & "): " & rada & vbCrLf & _ "Math.exp(" & x & "): " & Math.Exp(x)) End Sub
Komentář Do proměnné x načteme hodnotu argumentu funkce a do proměnné eps načteme poţadovanou přesnost výpočtu (řádky 8, 9):
Do proměnné a ukládáme jednotlivé členy počítané řady. 0-tý člen řady má podle uvedeného rozvoje hodnotu 1. Tuto hodnotu vloţíme také do proměnné rada, kam budeme kumulovat členy řady. Proměnná i obsahuje index členů řady a zpočátku má hodnotu 0. V rámci cyklu počítáme nové členy z předcházejících členů a přičítáme je do proměnné rada. Cyklus ukončíme, jakmile velikost dalšího vypočteného členu klesne pod zadanou přesnost. Do výstupu (viz obrázek) zahrneme potřebné údaje sloučením řetězců s konstantními částmi a obsahem proměnných (všimneme si, ţe vypočítaná a přesná hodnota se liší na desetinném místě podle zadané přesnosti):
- 26 -
Studijní prameny
V cyklu jsou pouţity standardní konstrukce:
řádek 18 – vynásobení obsahu proměnné, výsledek uloţen do stejné proměnné řádek 19 – přičítání hodnoty k obsahu proměnné řádek 20 – přičtení jedničky k obsahu proměnné
Uvedené řádky mají jednu společnou vlastnost: do proměnné je vloţen výsledek výrazu, v němţ se objevuje tatáţ proměnná. Začátečníky tato konstrukce někdy mate – je třeba si uvědomit časovou posloupnost vykonávání přiřazovacího příkazu: nejprve je vyhodnocen výraz, teprve potom je výsledek vloţen do proměnné. Při vyhodnocení výrazu se tedy pouţije původní hodnota proměnné. Konstrukce výstupního řetězce v příkazu rozděleném na řádky 24 – 26 vypadá poněkud nepřehledně. Osvojit si způsob spojování konstantních a proměnných částí řetězců je však bezpodmínečně nutné, pouţívá se velmi často. Nicméně později budeme pro vstupy a výstupy pouţívat jiné konstrukce, vyuţívající aplikace, v nichţ naše programy poběţí. Příklad výpočtu goniometrické funkce obdobným způsobem, který není úplně analogický: Rozvoj funkce sinus do nekonečné řady je následující
Opět vyuţijeme iterační předpis pro výpočet dalšího členu řady z předcházejícího členu:
V implementaci programu ukáţeme, ţe alternující znaménko řady má několik důsledků: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Sub sinX() ' -- deklarace proměnných Dim rada As Double, x As Double, eps As Double, a As Double Dim i As Integer ' -- vstup dat x = InputBox("Zadejte argument funkce sinus") eps = InputBox("Zadejte přesnost výpočtu (jako malé číslo)") ' -- inicializace cyklu a = x rada = a i = 1
- 27 -
Informatika • Modul 01 • Algoritmizace a programování 16 17 18 19 20 21 22 23 24 25 26 27
' -- vlastní cyklus přidávající další členy Do While Math.Abs(a) > eps a = -a * x * x / (2*i) / (2*i + 1) rada = rada + a i = i + 1 Loop ' -- výstup výsledku MsgBox ("Zadaná přesnost: " & eps & vbCrLf & _ "Moje funkce sin(" & x & "): " & rada & vbCrLf & _ "Math.sin(" & x & "): " & Math.Exp(x)) End Sub
Komentář Na řádku 17 musíme v podmínce pouţít absolutní hodnotu členu. Pouţijeme k tomu funkci Abs z modulu Math. Pokud bychom to neučinili, cyklus by skončil hned při druhém členu, neboť ten je záporný, a tudíţ menší neţ zadaná přesnost. Pro relativně malé úhly (zadané samozřejmě v radiánech) program funguje správně:
Pro větší úhly je však výsledek tristní:
Abychom zjistili, proč je výsledek našeho výpočtu nepřesný, zobrazíme si velikosti členů sečítané řady:
Vidíme, ţe kolem 20. členu jejich velikosti divoce kmitají aţ k hodnotám 1016, avšak výsledná velikost součtu členů musí být samozřejmě v intervalu <-1, 1>. Odečítat obrovská čísla, která se jen nepatrně liší, je v počítači vţdy riskantní
- 28 -
Studijní prameny
(numericky nestabilní) – vlivem i relativně velmi malého zaokrouhlení velkých čísel můţe být jejich rozdíl fatálně nepřesný. Tento problém lze snadno vyřešit, pokud si uvědomíme, ţe uvedená funkce je periodická – její hodnota pro velké úhly je stejná jako pro vhodně vybrané úhly z intervalu <0, 2π>. K dispozici máme aritmetickou operaci "modulo", která vypočítá zbytek po celočíselném dělení dvou čísel: x = 7 Mod 3 x = 5.2 Mod 3.6
' x obsahuje hodnotu 1 ' x obsahuje hodnotu 1.6
Program lze upravit vloţením řádku za řádek 8 (načtení argumentu): x = x Mod (2 * 3.1415926535898) Pak jiţ bude výstup programu správný pro kaţdý vloţený úhel. Poznámka Ludolfovo číslo π objekt Math neobsahuje. Pokud ho v programu často pouţíváme, měli bychom v deklarační části uvést definici konstanty např. takto: Const PI = 3.1415926535898 Vyuţít lze také vestavěnou funkci arkustangens Math.Atn: pi = Math.Atn(1) * 4 Platí totiţ tg(π/4) = 1
3.5
Podprogramy
Pokud bychom chtěli výše uvedený příklad pro výpočet goniometrické funkce pouţívat opakovaně, museli bychom mít moţnost: 1. spustit ho z jiných programů 2. nastavit pro něj z jiných programů výchozí úhel, resp. přesnost – předat mu výchozí parametry Bod číslo 1 je splněn automaticky – ve VB pouţívaném v jiném prostředí (MS Excel) vytváříme pouze podprogramy (klíčové slovo sub je zkratka ze subroutine – podprogram). "Volání podprogramu" provádíme pomocí jeho názvu. Z tradičních důvodů můţeme před identifikátor přidat nepovinné klíčové slovo Call (zavolat):
Poznámka:
- 29 -
Informatika • Modul 01 • Algoritmizace a programování
Příkaz volání podprogramu je přeloţen do instrukce skoku – příští instrukce, kterou procesor vykoná, bude na stanovené adrese určené identifikátorem (názvem) podprogramu. Tak jako je název proměnné spojen s adresou dat, tak je název (pod)programu spojen s adresou sekvence instrukcí v paměti. Součástí instrukce skoku je v tomto případě uloţení stávajícího stavu procesoru do tzv. zásobníku (stack – speciální místo v paměti). Kaţdý podprogram má stanoven konec příkazem End Sub. V tomto místě se obnoví původní stav procesoru ze zásobníku včetně původní adresy instrukce, takţe procesor pokračuje ve vykonávání instrukcí v místě, které při volání podprogramu opustil. Bod číslo 2 lze splnit více způsoby:
3.5.1
Předávání informací mezi programem a podprogramem
Musíme si uvědomit základní fakt – v paměti počítače je současně uloţeno mnoţství programů (a podprogramů). Aby si vzájemně neškodily a nedošlo ke zmatkům, operační systém zabezpečuje adresový prostor kaţdého programu tak, ţe instrukce programu nemohou pracovat s adresami mimo tento prostor. Zjednodušeně řečeno, kdyţ jsou vykonávány instrukce podprogramu, mohou pracovat pouze s identifikátory proměnných, deklarovaných v rámci tohoto podprogramu. Proměnné z jiného (pod)programu k dispozici nemají. Ve většině případů však při volání musíme podprogramu nějaké výchozí informace poskytnout – jednoduchou moţnost poskytují tzv. globální proměnné. 3.5.1.1 Globální proměnné Globální proměnné jsou deklarované mimo tělo jakéhokoli programu. VB takovou deklaraci umoţňuje – takové proměnné jsou pak přístupné z libovolného podprogramu. Volající program tak můţe před voláním podprogramu nastavit obsah globální proměnné a běţící podprogram si tento obsah můţe přečíst:
Proměnné deklarované v podprogramech, které jsme dosud pouţívali, se pak samozřejmě nazývají lokální. Uvedený postup předávání informací se pouţívá omezeně a spíše začátečníky. Jeho nevýhodou je nutnost hlídání názvů globálních proměnných, abychom je nepouţili také v lokálních deklaracích – takový střet deklarací vede k těţko odhalitelným chybám. Největší problém však tkví v obtíţné údrţbě obsahu globálních proměnných. V prostředí, kde pouţíváme desítky podprogramů
- 30 -
Studijní prameny
(např. od různých autorů), si nemůţeme být jisti tím, zda obsah globální proměnné nebyl některým podprogramem změněn, nebo zda nebude některým podprogramem globální proměnná pouţita v jiném významu apod. 3.5.1.2 Vstupní parametry (předávané hodnotou) Lepší moţnosti poskytuje mechanizmus předávání parametrů. Za identifikátorem v hlavičce kaţdého podprogramu vyţaduje VB závorky, kam můţeme vloţit deklaraci tzv. parametrů podprogramu. Parametr si můţeme představit jako speciální proměnnou podprogramu, jejíţ obsah nastavuje volající program. Parametr je tak součástí adresního prostoru podprogramu jako kaţdá jiná lokální proměnná. Vše je nejlépe vidět na příkladu definice a pouţití parametru:
Proměnná r podprogramu je definovaná v hlavičce v podobě deklarace. Pouze místo klíčového slova Dim je pouţito byVal (zkratka by value). Touto zkratkou vyjadřujeme skutečnost, ţe předtím, neţ bude v podprogramu proměnná r pouţita, bude její hodnota (obsah) naplněna volajícím programem. V podprogramu lze pak nakládat s takovým parametrem tak, jako s kaţdou jinou lokální proměnnou. Poznámka Podprogram v tomto případě nemůţe ovlivnit data volajícího programu. V uvedeném podprogramu plochaKruhu lze měnit libovolně obsah parametru r, na obsah proměnné polomer v programu mujProgram to ţádný vliv nemá. Je to ostatně zřejmé i z toho, ţe v druhém případě voláme podprogram a na místě parametru stojí literál (není obsahem ţádné proměnné).
Na místě parametru lze pouţít také výraz (např. Call plochaKruhu(polomer/4). Výraz se nejprve vyhodnotí a jeho výsledek je vloţen do parametru podprogramu. Parametrů můţe být více – pak je uvádíme v hlavičce podprogramu v podobě seznamu deklarací oddělených čárkou:
- 31 -
Informatika • Modul 01 • Algoritmizace a programování
Pořadí hodnot parametrů ve volajícím programu musí souhlasit s pořadím v definici záhlaví podprogramu. Navíc musí být datový typ uvedený v záhlaví podprogramu kompatibilní s hodnotou vloţenou při volání. Vyţadovaná kompatibilita vkládané hodnoty a datového typu parametru je pochopitelně stejná jako v případě přiřazovacího příkazu (ve skutečnosti se o nic jiného nejedná). 3.5.1.3 Výstupní parametry (předávané odkazem) Aţ dosud naše podprogramy nepředávaly zpět do volajícího programu ţádnou informaci. Často však potřebujeme volající program informovat o výsledku provádění podprogramu. Jinými slovy, potřebujeme pro podprogram nějaký způsob výstupu poskytnutý zpět do programu, který podprogram vyvolal. VB k tomu poskytuje (kromě globálních proměnných) mechanizmus sdílení proměnné volajícího programu s volaným podprogramem. Pokud v záhlaví podprogramu vyměníme u deklarace parametru klíčové slovo byVal za byRef (z anglického by reference – odkazem), bude předání parametru do podprogramu poněkud odlišné: parametr se bude v podprogramu chovat sice stejně jako předtím, bude však umístěn v adresním prostoru volajícího programu. Aby měl uvedený mechanizmus smysl, musíme v programu (při volání podprogramu) na místě takového typu parametru pouţít název lokální proměnné. Nelze pouţít výraz ani literál – ve skutečnosti hraje z hlediska volajícího programu tento parametr roli levé strany přiřazovacího příkazu: přiřazení hodnoty do proměnné pak provede podprogram. Jednoduše pochopíme celý mechanizmus v následujícím příkladu:
- 32 -
Studijní prameny
Proměnná volume ve volajícím programu se při volání podprogramu ztotoţní s parametrem vysledek podprogramu. To, co podprogram do takové proměnné uloţí, se ve volajícím programu projeví jako hodnota proměnné volume. Výstupní parametr se tedy chová tak, jakoby program a podprogram pouţívaly stejnou globální proměnnou, ve volajícím programu však můţe vystupovat pod jiným názvem neţ v podprogramu. Je však pochopitelné, ţe deklarovaný typ takové proměnné by měl být v programu i podprogramu stejný (jedná se o totoţný prostor v paměti). Čtenáře jistě napadne, ţe tak jako počet vstupních, tak i počet výstupních parametrů můţe být libovolný. Poznámka Z uvedeného je pochopitelný název "parametr předávaný odkazem": do podprogramu je předávána adresa proměnné z volajícího programu (odkaz na paměťový prostor, který si podprogram ztotoţní se svou proměnnou – parametrem).
Samozřejmě lze pouţít výstupní parametr také v roli vstupního. Obsah, kterým program předávanou proměnnou naplní před voláním, je k dispozici i v podprogramu. To je patrně také důvod, proč je tento typ parametru implicitní – pokud vynecháme při deklaraci parametru v podprogramu klíčové slovo by… , bude automaticky pouţito byRef. Parametr předávaný hodnotou lze pouţít pouze pro případ skalárních dat (nedělitelných na další části). Pokud chceme do podprogramu předat pole nebo objekt (viz dále), musíme pouţít předání odkazem i v případě, ţe se jedná o vstupní parametr (podprogram ho nemění).
3.5.2
Typy podprogramů, funkce
VB jako většina jazyků rozeznává dva typy podprogramů: proceduru (subroutine) a funkci (function). Zásadní rozdíl mezi oběma typy podprogramů je v tom, ţe funkce obsahuje mechanizmus předání výstupního parametru hodnotou, který u podprogramu typu procedura absentuje. Funkci lze pouţít ve výrazu přirozeným způsobem: na místo, kde je funkce uvedena, je vloţena výsledná hodnota (výstup) funkce. Teprve pak proběhne vyhodnocení ostatních částí výrazu.
- 33 -
Informatika • Modul 01 • Algoritmizace a programování
Příklad definice a pouţití funkce:
Funkce sinc(x) = sin x / x (často pouţívána v teorii zpracování signálu) je definována všude kromě hodnoty x = 0. V tomto bodě má však funkce limitní hodnotu 1. Definujeme tedy vlastní funkci, která testuje hodnotu parametru a v případě potřeby vrátí sinc(0)=1 (jinak by došlo k chybě dělení nulou). Oproti definici procedury je v hlavičce funkce rozdíl – obsahuje deklaraci výstupní (návratové) hodnoty. K identifikátoru funkce zde přistupujeme, jako by to byl název proměnné, u níţ musíme definovat datový typ. Tuto analogii podporuje i nastavení návratové hodnoty: prostým přiřazovací příkazem vloţíme v podprogramu do "proměnné", kterou představuje název funkce, hodnotu. Ta pak bude figurovat v místě volání funkce. Výhody pouţití jsou nesporné – funkci lze pouţít v jakémkoli výrazu prakticky tak, jak jsme zvyklí z běţných algebraických zápisů. Kromě jedné návratové hodnoty můţe mít funkce i libovolné mnoţství parametrů předaných hodnotou nebo odkazem se stejným významem a syntaxí, jako jsme uvedli u procedur. Nyní můţeme upravit náš program pro výpočet goniometrické funkce do podoby funkce (pro zajímavost upravíme výpočet tak, aby funkce počítala s úhlem zadaným ve stupních):
- 34 -
Studijní prameny
Takto definovaná funkce je pak přímo pouţitelná v listu Excelu jako běţná funkce. Můţeme ji ihned pouţít ve všech formulích:
- 35 -
Informatika • Modul 01 • Algoritmizace a programování
3.6
Indexovaná proměnná – pole
Často musíme řešit úlohu vyţadující velký počet proměnných stejného datového typu. Navíc tvoří tyto proměnné často logicky kompaktní celek (např. mnoţinu naměřených hodnot, poloţky n-rozměrných vektorů, uzly diferenční sítě apod.). Není právě elegantní (a často ani moţné) deklarovat velká mnoţství proměnných, v nichţ jsou uloţeny všechny prvky takových celků. Kaţdý moderní programovací jazyk tedy zavádí proměnné typu pole, kde jednotlivé prvky odlišujeme celočíselným indexem. Všechny prvky přitom musí být stejného datového typu. Ve VB provádíme deklaraci takového typu proměnné např. takto: Dim mereni(100) As Double Za názvem pole je v závorkách uvedeno nejvyšší číslo (index) prvku v poli. V případě uvedené deklarace je nejniţší index implicitně roven nule. Obsahem kaţdého prvku bude reálné číslo v dvojnásobné přesnosti (Double). Naše pole s názvem mereni tedy bude obsahovat 101 prvků s indexem 0 – 100 (v paměti zabere 808 bajtů). Pokud nám nevyhovuje nulová implicitní hodnota nejniţšího indexu, můţeme pouţít deklaraci typu: Dim hodnoty(1 To 10) As Integer
- 36 -
Studijní prameny
V této deklaraci jsme explicitně stanovili rozsah indexu za pomoci klíčového slova To. Ve všech případech si musíme osvojit základní pravidlo: při manipulaci s obsahem pole lze pracovat pouze s jednotlivými prvky pole. S polem jako celkem (aţ na několik světlých výjimek, na které vţdy upozorníme) ve VB pracovat nelze. Nelze tedy jediným příkazem vytvořit z pole kopii nebo sečíst dva vektory apod. Vţdy je třeba provést potřebné operace po jednotlivých prvcích. Ve většině případů pouţijeme cyklus typu For … Next (tzv. počítaný cyklus). Příklad Vytvoříme pole o 10000 prvcích a naplníme ho náhodnými celými čísly od jedné do šesti: Dim hodKostkou(1 To 10000) As Integer Dim i As Integer For i = 1 To 10000 hodKostkou(i) = Fix(6 * Rnd() + 1) Next i Poznámka Je zřejmé, ţe deklarovat samostatně 10000 proměnných by nebylo moţné. Tímto způsobem jsme vytvořili v paměti velké mnoţství dat, které můţeme např. statisticky analyzovat.
Funkce Rnd() vrací náhodné reálné číslo s rovnoměrným rozdělením z polouzavřeného intervalu <0,1). Uvedená konstrukce transformuje tento interval na celá čísla 1 – 6, přičemţ funkce Fix vrací celočíselnou část desetinného čísla. Proměnná hodKostkou má vnitřní strukturu; skládá se z prvků (v tomto případě celých čísel). Proměnné typu pole patří do třídy strukturovaných proměnných. Proměnné, které vnitřní strukturu nemají, se nazývají skalární.
3.6.1
Vícedimenzionální pole
Jazyk VB umoţňuje pracovat i s poli, která mají více neţ jeden index. Syntaxe podléhá přirozeným zvyklostem. Např. matici 3 x 3 můţeme deklarovat takto: Dim A(1 To 3, 1 To 3) As Double Matice A má devět prvků, s nimiţ pracujeme pomocí dvojice indexů: A(3,1) = 7.2 A(1,3) = -A(3,1) V paměti jsou samozřejmě prvky uloţené za sebou, např. po řádcích, organizace data nás však v tomto případě nemusí zajímat. Kaţdopádně obsadí matice v paměti 9*8 = 72 bajtů. Při manipulaci se strukturami typu matice pouţíváme rovněţ počítané cykly, které však musíme vnořit do sebe. Např. příkazy pro transpozici matice mohou vypadat takto: Dim A(1 To 3, 1 To 3) As Double Dim B(1 To 3, 1 To 3) As Double Dim i As Integer, j As Integer
- 37 -
Informatika • Modul 01 • Algoritmizace a programování
… naplnění matice A ' B bude obsahovat transponovanou matici A For i = 1 to 3 For j = 1 to 3 B(i,j) = A(j,i) Next j Next i Poznámka VB umoţňuje pouţít aţ 60 indexů, coţ je značně nadbytečné mnoţství. V běţných algoritmech se obvykle nesetkáme s vyšším počtem indexů neţ tři (např. transformace tenzorů apod.).
V následujícím příkladu uloţíme do matice C výsledek maticového součinu A x B: Dim Dim Dim Dim Dim
A(1 To 3, 1 To 3) As Double B(1 To 3, 1 To 3) As Double C(1 To 3, 1 To 3) As Double i As Integer, j As Integer, k As Integer suma As Double
… naplnění matice A a B ' C bude obsahovat matici A x B For i = 1 to 3 For j = 1 to 3 suma = 0 For k = 1 to 3 suma = suma + A(i,k) * B(k,j) next k Next j Next i Pomocná proměnná suma nám v tomto případě slouţí jako akumulátor mezivýsledků pro skalární součin i-tého řádku matice A a j-tého sloupce matice B. Poznámka Vloţené cykly často představují pro začátečníky značnou bariéru, je proto zapotřebí věnovat uvedeným konstrukcím dostatečnou pozornost.
3.6.2
Dynamická pole
Podobně jako jiné programovací jazyky ani VB neumoţňuje přímo deklarovat pole, jehoţ rozsah indexů není před spuštěním programu znám. Nelze tedy pouţít konstrukci: Dim n As Integer n = 100 Dim pole(1 To n) As Double VB potřebuje v době překladu zkrátka vědět, kde v paměti se proměnná vytvoří a kolik místa zabere. Ţe bude při běhu programu do proměnné n přiřazena nějaká hodnota, je pro proces překladu do instrukcí procesoru irelevantní.
- 38 -
Studijní prameny
Přesto je nastavení velikosti pole aţ v době běhu programu velmi uţitečné a můţe program zjednodušit a zpřehlednit. Ve VB lze naštěstí pouţít následující posloupnost příkazů: Dim n as Integer, poleA() As Double n = InputBox("Zadej počet prvků:") ReDim poleA(1 To n) Deklarace pole s prázdnými závorkami připraví VB na pouţití proměnné typu pole. Příkaz ReDim pak za běhu programu předimenzuje pole na nový rozsah. Pole, jehoţ rozsah (počet prvků) se stanoví aţ za běhu programu, se nazývá dynamické. Příklad pouţití dynamického pole:
Program vygeneruje pole o zadaném počtu prvků, do kaţdého prvku vloţí náhodné reálné číslo z intervalu 0 – 1. Potom sečte hodnotu všech prvků a zároveň nalezne maximální a minimální hodnotu v poli. Dalším cyklem spočítáme střední kvadratickou odchylku a rozptyl.
- 39 -
Informatika • Modul 01 • Algoritmizace a programování
Pozorný čtenář si určitě všiml, ţe druhý cyklus je zbytečný, součet prvků pole a testování minimální a maximální hodnoty lze provádět v těle cyklu pro generování prvků. Na druhé straně pro výpočet směrodatné odchylky potřebujeme další cyklus, protoţe pracujeme s průměrem, který v předcházejícím cyklu ještě neznáme. Poznámka Všimněme si, ţe programátoři jsou při pouţívání proměnných přirozeně skoupí: pro všechny cykly jsme v našem programu pouţili tutéţ proměnnou (čítač) i, proměnnou suma jsme pouţili jak pro střádání prvků pole, tak kvadratických odchylek prvků od průměru. Šetříme tím rozsah pouţité paměti (coţ je v dnešní rozmařilé době poněkud absurdní), zlepšujeme však i přehlednost programu.
3.7
Vývojové diagramy
Pro obecné znázornění algoritmu pouţíváme vývojový diagram. Jedná se o mnoţinu symbolů spojených orientovanými úsečkami, které vyjadřují směr postupu při řešení dílčích kroků. Výhoda vývojového diagramu tkví zejména v přehlednosti a univerzálnosti, není závislý na konkrétním programovacím jazyce. Vyjadřuje obecným způsobem to, co posléze implementujeme v programu. V našich úlohách bude vývojový diagram většinou zbytečný, jeho výhody se naplno projeví teprve u sloţitých a rozsáhlých algoritmů. Vývojový diagram je vyţadován ve fázi analýzy profesionálních programových produktů. Klasický vývojový diagram se skládá z následujících druhů symbolů: 1. Startovací a ukončovací symboly Jsou znázorněny pomocí kruhů, oválů či zaoblených obdélníků, obvykle obsahují nápis „Start“ nebo „Konec“, či podobnou frázi určující začátek a konec procesu. 2. Spojovací úsečky a šipky Šipka směřující z jednoho symbolu a končící u druhého vyjadřuje časovou posloupnost vykonávání algoritmu (řídící tok). 3. Dílčí krok algoritmu Je znázorněn pomocí obdélníku. Obsahuje popis činnosti, kterou je třeba v daném kroku vykonat. Jeho sloţitost můţe být variabilní, obvykle začínáme s komplexním krokem, který se snaţíme postupně dekomponovat aţ na elementární kroky (viz dále). Příklad: „načti matici“ nebo „vypočítej síly“ apod. 4. Podmíněný příkaz Je reprezentován kosočtvercem, pouţívá se tam, kde je zapotřebí rozhodnutí. Má většinou podobu otázky a její odpovědi ve tvaru Ano/Ne nebo Pravda/Nepravda. Ze symbolu podmíněného příkazu vychází dvě a více šipek, kaţdá z šipek obsahuje moţný druh odpovědi na danou otázku. V tomto prvku se diagram větví. 5. Cyklus Je znázorněn pomocí šestiúhelníku. Cyklus probíhá, dokud je jeho podmínka splněna, poté se přejde k dalšímu kroku algoritmu. Cyklus lze dekomponovat - 40 -
Studijní prameny
na podmíněný příkaz a dílčí kroky, někdy se do základních symbolů nezařazuje. 6. Spojovací značka Pro její značení se obvykle pouţívá kruh. Pouţívá se tam, kde je třeba spojit více toků procesu do jednoho společného pokračování. V případě, ţe má vývojový diagram vyjádřit následující programovou implementaci algoritmu, pouţíváme také symboly pro: 7. Podprogram Je znázorněn obdélníkem se svislými čarami po stranách. Jiţ víme, ţe podprogramy jsou takové části algoritmu, které se mohou opakovat a mohou být znázorněny samostatným vývojovým diagramem. 8. Vstup/Výstup Pro vyjádření místa vstupu a výstupu dat do/z algoritmu se pouţívá rovnoběţník, v případě uţivatelského vstupu se někdy pouţívá lichoběţník. Příklad: „Získej proměnnou X od uţivatele; zobraz proměnnou X“. Postupem času byly zavedeny některá další označení a různé modifikace uvedených symbolů; pro pochopení významu vývojových diagramů však nejsou důleţité.
3.8
Strukturované programování
Strukturované programování je metodika návrhu algoritmu, která je zaloţena na dvou principech: 1. Sloţitou úlohu rozdělíme na dílčí úkoly, které řešíme samostatně. Tato metoda postupné dekompozice můţe mít iterační charakter. 2. Při návrhu řešení dílčích úloh pouţijeme pouze povolené řídící struktury s jedním vstupem a jedním výstupem. Právě jeden vstup a jeden výstup řídících struktur je velmi důleţitý pro postup podle bodu 1. Z hlediska VB lze pouţívané příkazy pro řízení běhu programu vyjádřit vývojovým diagramem takto:
- 41 -
Informatika • Modul 01 • Algoritmizace a programování
1. …příkazy…
2. if <podmínka> then … příkazy 1 … else … příkazy 2 … end if
Příkazový blok
?
Ne
Ano
3.
Příkazový blok 1
do while <podmínka> … příkazy … loop
?
Příkazový blok 2
Ne
Ano Příkazový blok
Protoţe má kaţdá algoritmická struktura pouze jeden vstup a jeden výstup, lze ji vţdy vloţit na místo příkazového bloku a tím rozvíjet původně hrubý algoritmus do podrobnějších detailů, aţ skončíme u verze, která je jiţ snadno implementovatelná v programovacím jazyce. Výhodou uvedeného postupu je přehlednost a odstranění příkazů explicitních skoků (příkaz GoTo <číslo řadku>), které byly zdrojem chyb a obtíţné orientace v programu.
3.9
Práce s řetězci
Řetězcová proměnná je sice povaţována ve VB za skalární, ve skutečnosti má však mnoho speciálních vlastností. Jiţ bylo uvedeno, ţe posloupnost znaků uloţená v řetězcové proměnné nezačíná ve skutečnosti na adrese symbolicky určené názvem proměnné – zde nalezneme pouze další adresu fyzického umístění řetězce. Tímto způsobem je moţné pracovat s řetězci přirozeným způsobem, slučovat je, zkracovat a rozšiřovat, aniţ bychom se starali o počet znaků, které obsahují. Mnoho jiných programovacích jazyků tuto vlastnost nemá a práce s řetězci pak vyţaduje speciální techniky. Komplexnější příklad práce s řetězci naleznete v modulu CU01_M03 v části zpracování dat z druţicového systému GPS.
- 42 -
Studijní prameny
Navzdory tomu, ţe s řetězcem lze ve VB pracovat jako s kompaktním celkem, potřebujeme často pracovat s vnitřní strukturou řetězce – jednotlivými znaky. K tomu nám slouţí sada vestavěných funkcí. Pouţití těch nejdůleţitějších zde stručně shrneme za pouţití následujícího vzoru: Dim pozdrav As String Dim n As Integer pozdrav = "Dobré ráno!"
3.9.1
Určení délky řetězce – funkce Len
n = Len(pozdrav) Proměnná n bude obsahovat číslo 11 (vykřičník i mezera jsou také znaky).
3.9.2
Vytvoření podřetězce – funkce Left
Funkce vrátí z řetězce prvních n znaků. retez = Left(pozdrav, 5) Proměnná retez bude obsahovat "Dobré".
3.9.3
Vytvoření podřetězce – funkce Right
Funkce vrátí z řetězce posledních n znaků. retez = Right(pozdrav, 5) Proměnná retez bude obsahovat "ráno!".
3.9.4
Obecné určení podřetězce – funkce Mid
Funkce Mid(<řetězec>,
3.9.5
Vyhledání pozice podřetězce – funkce InStr
Funkce InStr(<řetězec>,<podřetězec>) má obvykle dva parametry a vrací pozici podřetězce v řetězci (číslo prvního znaku nalezeného podřetězce). V případě nenalezení podřetězce funkce vrátí nulu. n = InStr(pozdrav,"no") Proměnná n bude v tomto případě obsahovat číslo 9.
3.9.6
Převod řetězce na velká písmena – funkce UCase
retez = UCase(pozdrav)
- 43 -
Informatika • Modul 01 • Algoritmizace a programování
Proměnná retez bude obsahovat "DOBRÉ RÁNO!". Všimněte si, ţe nealfabetické znaky zůstávají při převodu nedotčeny.
3.9.7
Převod řetězce na malá písmena – funkce LCase
retez = LCase(pozdrav) Proměnná retez bude obsahovat "dobré ráno!".
3.9.8
Nahrazení podřetězce – funkce Replace
Replace(<řetězec>,
3.9.9
Konverze znaku na jeho kód – funkce Asc
n = Asc("A") Proměnná n bude obsahovat číslo 65 (kód znaku "A"). Pozor, vrácené kódy znaků z národních abeced (s kódem > 127) závisí na pouţitém kódování (ASCII, Unicode, …). Explicitně můţeme pro Unicode pouţít funkci AscW (vrátí číslo v rozsahu typu Integer), pro ASCII lze pouţít explicitně funkci AscB (vrátí číslo v rozsahu jednoho bajtu 0 – 255). Pokud parametr obsahuje více znaků, vrátí funkce kód prvního z nich.
3.9.10 Konverze kódu na znak – funkce Chr retez = Chr(97) Proměnná retez bude obsahovat znak "a". Jedná se o inverzi k předcházející funkci. Lze ji rovněţ pouţít ve verzích ChrW a ChrB. Funkci pouţijeme zejména v případě, kdy potřebujeme pracovat s řídícími nebo jinými znaky, které nelze jednoduše v řetězci pouţít.
3.9.11 Položky řetězce – funkce Split Dim pole() As String, radek As String pole=Split(<řetězec>,
- 44 -
Studijní prameny
3.10 Datový typ Date pro datum a čas V moderních programovacích jazycích je k dispozici datový typ umoţňující práci s datem a časem. Ve VB máme pro tyto účely k dispozici datový typ Date, který obsahuje v podstatě časový okamţik. Takto uloţená data můţeme porovnávat, třídit, hledat rozdíl dvou časových okamţiků v různých jednotkách apod. Zdálo by se, ţe uvedený datový typ je strukturovaný (musí obsahovat kalendářní datum + údaj o denním čase), je to však jinak: proměnná typu Date obsahuje ve skutečnosti reálné číslo (de facto typ Double) udávající počet sekund od půlnoci dne 30. 12. 1899. Celá část čísla udává den (např. 1. 1. 1900 má hodnotu 2), desetinná část obsahuje čas ve zlomcích jednoho dne – hodina = 1/24, minuta 1/1440 a sekunda 1/86400. Poledne je tedy vyjádřeno hodnotou 0,5. Tímto způsobem můţe VB pracovat s časem pomocí algebraických operací, strukturovanost údajů (měsíce, roky, hodiny, sekundy) řeší VB ve fázi vstupních a výstupních operací. Z rozsahu typu Double lze snadno odvodit, ţe proměnná typu Date můţe obsahovat datum a čas v rozmezí 1. 1. 100 – 31. 12. 9999 11:59:59 Poznámka Aţ naši potomci naleznou programy s tímto datovým typem, budou předpokládat, ţe jsme stanovili na den 31. 12. 9999 konec světa.
3.10.1 Vložení hodnoty typu Date Obsah proměnné typu Date lze nastavit přímo pomocí speciálního zápisu takto: Dim okamzik As Date okamzik = #1/13/2012 1:30:00 PM# Pravá strana přiřazení je speciální literál, který není řetězcem, a je bohuţel závislý na jazykovém prostředí (zde je pouţita nejobvyklejší anglosaská konvence zápisu data – měsíc/den/rok). Vynecháme-li nastavení času, bude automaticky pouţita hodnota 00:00:00. Pokud vynecháme datumovou část, bude analogicky pouţita nulová hodnota odpovídající datu 30. 12. 1899. Abychom se vyhnuli problémům s regionálním zápisem, pouţíváme pro vloţení data vestavěnou funkci DateSerial: datum = DateSerial(2012,1,31) Parametrem funkce jsou celá čísla obsahující rok, měsíc, den v tomto pořadí. Výhodou funkce je kontrola platnosti vkládaného data podle pravidel našeho kalendáře. Pokud datum není platné (např. DateSerial(2011,2,29)), funkce vloţí hodnotu odpovídající správnému datu (1. 3. 2011). Poznámka V mnoha zemích světa je pouţíván jiný neţ Gregoriánský kalendář – čtenář si dozajista dovede představit problémy, které tím v programech operujících s datem vznikají.
Stejně tak existuje funkce TimeSerial: cas = TimeSerial(15,25,40)
- 45 -
Informatika • Modul 01 • Algoritmizace a programování
která vytvoří časovou část proměnné typu datum. Parametry funkce tvoří opět celá čísla obsahující hodinu, minutu a sekundu v tomto pořadí. Tato funkce není pouţívána tak často jako DateSerial, neboť konvence zápisu času jsou univerzálnější.
3.10.2 Extrakce částí hodnot typu Date Z proměnné obsahující datum a čas lze extrahovat potřebnou část pomocí vestavěných funkcí Year, Month, Day, Hour, Minute, Second. Parametrem je vţdy hodnota typu Date, všechny funkce vrací celé číslo obsahující patřičnou část časového určení. Kromě toho máme k dispozici funkci Weekday, která vrací z hodnoty typu Date pořadové číslo dne v týdnu, např. Weekday(#2012/2/1#) vrátí hodnotu 4 (středa – anglosaský týden začíná v neděli).
3.10.3 Časové intervaly Pokud potřebujeme zjistit interval mezi dvěma časovými okamţiky, máme k dispozici funkci DateDif(<jednotka>, <čas1>, <čas2>) Funkce vrací celé číslo obsahující počet celých časových jednotek mezi okamţiky <čas1> a <čas2>. Jednotku určujeme pomocí řetězce, který můţe nabývat následujících hodnot:
"s" – počet sekund "n" – počet minut "h" – počet hodin "d" – počet dní "ww" – počet týdnů "m" – počet měsíců "q" – počet čtvrtletí "yyyy" – počet roků
Např. DateDiff("m", #08/02/2011#, #08/02/2012#) vrátí hodnotu 12 (měsíců). Ke zvolenému okamţiku můţeme přičíst (odečíst) zvolený časový interval pomocí funkce DateAdd(<jednotka>, <počet>, <čas>). Časovou jednotku stanovíme stejně jako u funkce DateDiff, <počet> přičítaných jednotek můţe být kladný i záporný, parametr <čas> musí obsahovat výraz kompatibilní s typem Date. Např. DateAdd("m", 1, #8/2/2011#) vrátí datum 2. 9, 2011.
3.10.4 Systémový čas a datum Ke zjištění času můţeme pouţít funkci Time(). Vrací hodnotu typu Date obsahující systémový čas. Funkce Date() vrací systémové datum. Funkce Now() vrací aktuální datum i čas.
- 46 -
Studijní prameny
3.10.5 Výstup hodnoty typu Date Implicitní zobrazení obsahu proměnné opět závisí na nastaveném jazykovém prostředí:
Ve VB je k dispozici vestavěná funkce Format; její pouţití je však značně komplikované. Mnohem jednodušší a univerzálnější je pouţít vlastní funkci pro výstup např. takto:
3.11 Další vestavěné funkce – třída Math Jiţ jsme viděli, ţe matematické funkce nalezneme ve třídě Math (viz modul CU01_M02). Jejich význam a pouţití je triviální:
3.11.1 Goniometrické funkce Cos, Sin, Tan Parametr i návratová hodnota jsou typu Double.
3.11.2 Inverzní goniometrické funkce Jediným představitelem je arkustangens Atn. Pro přesný výpočet čísla π lze pouţít: pi = 4 * Atn(1) Pomocí této funkce lze vyčíslit další inverzní goniometrické funkce, např. Function ArcSin(x As Double) As Double ArcSin = Atn(x / Sqr(1 - x * x)) End Function
- 47 -
Informatika • Modul 01 • Algoritmizace a programování
3.11.3 Exponenciálníní funkce Exp, Log Z přirozeného logaritmu získáme logaritmus s jiným základem takto: Function Log10(X) Log10 = Log(X) / Log(10) End Function
3.11.4 Druhá odmocnina Sqr (zkratka ze square root – kořen čtverce). Parametr musí být samozřejmě nezáporný, jinak dojde k chybě za běhu programu.
3.11.5 Absolutní hodnota Abs, Sgn Funkce Abs vrací absolutní hodnotu ve stejném datovém typu, jako je parametr. Funkce Sgn (signum – znaménko) vrací celé číslo určující znaménko parametru: 1 (parametr kladný), –1 (parametr záporný), 0 (parametr roven nule).
3.11.6 Náhodná čísla Funkce Rnd() vrací náhodné číslo typu Single s rovnoměrným rozdělením z polouzavřeného intervalu <0,1). Pokud potřebujeme náhodné celé číslo z intervalu
3.12 Konverzní funkce Jejich účelem je převádět hodnoty z jednoho datového typu na jiný. Parametrem konverzních funkcí je obvykle řetězec nebo číselný datový typ. Název funkcí je tvořen písmenem "C" (z angl. Convert) + zkratka datového typu. K dispozici tak máme zejména konverzní funkce: CBool – z řetězce nebo čísla vrátí logickou hodnotu True/False CSng – z řetězce nebo čísla vrátí obsah v podobě datového typu Single CDbl – z řetězce nebo čísla vrátí obsah v podobě datového typu Double
- 48 -
Studijní prameny
CInt – z řetězce nebo čísla vrátí obsah v podobě datového typu Integer. Případnou desetinnou část zaokrouhlí. Pokud potřebujeme extrahovat pouze celočíselnou část reálného čísla, musíme pouţít funkci Int. CLng – z řetězce nebo čísla vrátí obsah v podobě datového typu Long CStr – parametr převede na odpovídající znakový řetězec (připomínáme, ţe literál 3.45 je z programátorského hlediska zásadně odlišný údaj od řetězce "3.45") CDate – převádí řetězec obsahující datum a čas na typ Date, např. Dim datum As Date, strDatum As String strDatum = "12/31/2011" datum = CDate(strDatum) Pokud parametr na výsledný datový typ převést nelze (např. CInt("abc")), dojde k typové chybě za běhu programu:
Abychom se uvedenému typu chyb vyhnuli (zejména v případě uţivatelských vstupů), mám k dispozici testovací funkce IsNumeric a IsDate, které analyzují parametr z hlediska převoditelnosti na odpovídající datový typ. Jejich pouţití je zřejmé z následujícího příkladu:
Tímto ošetřením vstupu pak k chybě za běhu programu nemůţe dojít. VB provádí řadu typových konverzí automaticky, aniţ bychom pouţili odpovídající konverzní funkci. Zejména při výstupu spoléháme na automatickou konverzi jiných datových typů na řetězce, převody celých a reálných čísel také ošetřujeme programově jen zřídka. Měli bychom si však být vědomi rizik, které uvedené postupy přinášejí.
- 49 -