Hlavní navigace

Programovací jazyk TCL (3)

2. 8. 2005
Doba čtení: 11 minut

Sdílet

V dnešním pokračování seriálu o programovacím jazyku Tcl bude popsána práce s podmíněnými příkazy (podmínkami), s iteračními příkazy určenými pro tvorbu cyklů a dále s příkazy a funkcemi umožňujícími práci se strukturovanými datovými typy, zejména s asociativními poli a seznamy.

Obsah

1. Podmíněné příkazy
2. Iterační příkazy – cykly
3. Strukturované datové typy
4. Asociativní pole
5. Seznamy
6. Obsah dalšího pokračování tohoto seriálu

1. Podmíněné příkazy

V prakticky každém imperativním programovacím jazyce (samozřejmě pokud nepoužívá speciální formy) jsou k dispozici příkazy, kterými je možné rozvětvit běh programu v závislosti na nějaké podmínce. Nejinak je tomu i ve zde popisovaném programovacím jazyku Tcl. Ten obsahuje hned dvě varianty větvení: strukturovaný příkaz typu if-then-else a příkaz typu switch. Nejprve si popíšeme první variantu, tj. příkaz typu if-then-else. Nejjednodušší forma tohoto příkazu vypadá následovně:

if podmínka tělo_podmínky 

Podmínka (která většinou musí být kvůli zákazu předčasného vyhodnocení umístěna do složených závorek) se vyhodnocuje stejným způsobem jako v případě minule popsaného příkazu expr, tj. je možné použít všechny dostupné matematické, logické i relační operátory, stejně jako jednoduché (skalární) proměnné. Výsledkem vyhodnocené podmínky je řetězec, který je interně převeden na booleovskou proměnnou. Pokud tento řetězec obsahuje nenulové číslo, hodnotu „true“ nebo hodnotu „yes“, je interně převeden na logickou hodnotu „true“ a následně se provede tělo podmínky umístěné za příkazem if (to je taktéž ve většině případů uzavřeno do složených závorek). Pokud řetězec obsahuje jinou hodnotu (včetně nuly), tělo podmínky se neprovede a program pokračuje ve svém běhu příkazem umístěným za podmíněným příkazem.

Prozatím jsme si uvedli tu nejjednodušší formu podmíněného příkazu. Jazyk Tcl je však v tomto případě poměrně variabilní a podporuje i další způsoby vytváření podmínek. První – ve své podstatě pouze podpůrnou – variantou je uvedení slova then přímo za výraz specifikující podmínku. Toto klíčové slovo je totiž použito v mnoha programovacích jazycích (z ne-Cčkovské rodiny, jedná se například o jazyky založené na Algolu), proto jsou pro snadný přechod z dalších jazyků na Tcl podporovány obě varianty. Podmíněný příkaz se slovem then lze zapsat následovně (připomínám, že v případě jazyka Tcl se nejedná o klíčové slovo – tento jazyk pojem klíčového slova vůbec nezná a ani nepotřebuje):

if podmínka then tělo_podmínky 

Další variantou je, jak jistě sami očekáváte, přidání druhé větve příkazů. Příkazy ve druhé větvi se vykonají v případě, že podmínka není splněna, tj. vyhodnotí se na logickou hodnotu false. I zde je možné (nikoli však nutné) použít slovo then, slovo else uvozující druhou větev také nemusí být použito:

if podmínka tělo_podmínky tělo_druhé_větve
if podmínka tělo_podmínky else tělo_druhé_větve
if podmínka then tělo_podmínky tělo_druhé_větve

if podmínka then tělo_druhé_větve else tělo_druhé_větve 

To však není zdaleka vše. Vzhledem k tomu, že se v reálných problémech nepoužívají pouze jednoduché podmíněné výrazy, ale složitější struktury, obsahuje Tcl rozšíření pro zápis více podmíněných výrazů a větví v jednom bloku if. Jedná se o vícenásobně použité slovo elseif, za nímž vždy následuje vyhodnocovaný výraz a tělo (blok příkazů), které se provede v případě, že je výraz vyhodnocen jako pravdivý. Vše je patrné z následujících schémat:

if podmínka tělo1 elseif podmínka2 tělo2 tělo_else
if podmínka then tělo1 elseif podmínka2 then tělo2 else tělo_else 

Na tomto místě musím upozornit na fakt, že slovo elseif je nutné uvádět, nelze s ním tedy zacházet tak liberálně jako se slovy then a else.

Při zápisu podmíněného příkazu na více řádků je zapotřebí zaručit, aby se začátek případné větve else nacházel na stejném řádku, kde je uzavírací závorka větve then. Je to z toho důvodu, že Tcl celý skript zpracovává po jednotlivých řádcích a v případě, že by řádek začínal přímo slovem else, považoval by ho interpreter za začátek dalšího příkazu. Následující příklad je tedy korektní:

if {výraz} {
    tělo_podmínky
} else {
    tělo_druhé_větve
} 

Stejné pravidlo platí pro zápis dvou větví bez slova else:

if {výraz} {
    tělo_podmínky
} {
    tělo_druhé_větve
} 

Zatímco podmíněný příkaz typu if-then-else je vcelku běžný, příkaz typu switch už tak obyčejný není, jak by se mohlo podle jeho podobnosti s dalšími programovacími jazyky znát. Vzhledem k tomu, že všechny hodnoty jsou v Tcl reprezentovány pomocí řetězců, porovnávají se v příkazu switch právě řetězce, přičemž je možné specifikovat porovnávání na základě regulárních výrazů, což celý příkaz switch posouvá na mnohem vyšší úroveň použitelnosti než běžné příkazy switch a case známé z jiných programovacích jazyků. Obecný zápis rozvětvení pomocí příkazu switch vypadá takto:

switch řetězec vzor1 větev1 vzor2 větev2 ... default větev_n 

Při praktickém použití jsou jednotlivé větve (tj. bloky příkazů) uzavřeny do složených závorek, stejně jako tomu bylo u podmíněného příkazu if-then-else. Podrobnější informace budou uvedeny v další části tohoto seriálu.

2. Iterační příkazy – cykly

Kromě podmíněných příkazů je možné řídit průběh programu pomocí iteračních příkazů, tj. příkazů, kterými lze programovat cykly. Základní a zcela univerzální smyčkou je v mnoha jazycích smyčka while. Nejinak je tomu v Tcl, kde lze smyčku typu while zapsat podle následujícího schématu:

while podmínka telo_smyčky 

Tato smyčka pracuje podobným způsobem jako stejně pojmenovaná smyčka v jazycích, jako je C, C++, Java, Pascal nebo Visual Basic (v posledním případě se jedná o smyčku do while). Před prvním provedením smyčky se vyhodnocuje podmínka stejným způsobem jako u podmíněného příkazu if. Pokud je výsledkem vyhodnocení podmínky logická hodnota „true“, vykoná se jedna iterace a řízení programu se vrátí na začátek smyčky, tj. na test podmínky. Pokud se je výsledkem vyhodnocení podmínky logická hodnota „false“, pokračuje běh programu příkazem uvedeným za smyčkou. Následuje příklad jednoduché smyčky, jejíž běh je řízen proměnnou i. Smyčka se provede celkem devětkrát:

set i 1
while {$i<10} {
    puts $i
    set i [expr $i+1]
} 

Jak je z předchozího příkladu patrné, není smyčka moc přehledná, zejména vinou složitého příkazu pro zvýšení hodnoty proměnné i o jedničku. Kvůli zjednodušení zápisu smyček (ale i v jiných případech) proto v Tcl existuje příkaz incr, který zvýší či sníží hodnotu proměnné o zadané číslo. Pokud není žádné číslo v příkazu incr zadáno, zvýší se implicitně hodnota proměnné o jedničku. Předchozí příklad je tedy možné přehledněji zapsat následovně:

set i 1
while {$i<10} {
    puts $i
    incr i
} 

Můžeme si vyzkoušet, co se stane, pokud specifikujeme číslo, o které se hodnota proměnné bude zvyšovat. Po spuštění dalšího příkladu se na konzoli vypíšou všechna sudá čísla od dvou do dvaceti:

set i 2
while {$i<=20} {
    puts $i
    incr i 2
} 

Zpětné počítání je také jednoduché, postačí specifikovat záporný krok:

set i 10
while {$i} {
    puts $i
    incr i -1
} 

Použití smyčky while si můžeme ukázat na jednoduchém příkladu, ve kterém je vytvořena funkce, pomocí níž je možné spočítat celočíselnou mocninu libovolného základu.

proc power {base p} {
    set result 1
    while {$p} {
        set result [expr $result * $base]
        incr p -1
    }
    return $result
} 

Vzhledem ke způsobu převodu řetězců na čísla při matematických výpočtech si musíme dát pozor na rozdíl mezi zavoláním funkce „power 2 100“ a „power 2.0 100“. V prvním případě nebude výsledek správný, neboť se bude počítat s čísly typu integer, ve druhém případě se výpočet provede s čísly typu float (je škoda, že Tcl nepoužívá knihovnu pro práci s velkými čísly tak, jako to dělá například Lisp, bc nebo dc – byla by tak zaručena větší přenositelnost skriptů mezi různými platformami).

Malou úpravou výše uvedeného příkladu je výpočet faktoriálu, který lze zapsat následovně:

proc fact {n} {
    set result 1.0
    if {$n<0} {return 1}
    while {$n} {
        set result [expr $result*$n]
        incr n -1
    }
    return $result
} 

Všimněte si způsobu, jakým si vynucujeme výpočty v pohyblivé řádové čárce – do proměnné result je vložena hodnota 1.0, která způsobí to, že dostáváme korektní výsledky pro všechna n menší než 171 (platí pro platformu i386).

Kromě smyčky while je možné použít i „počítanou“ smyčku for, která zde má stejný význam jako v programovacím jazyku C. Tuto smyčku je možné zapsat podle následujícího schématu:

for start test iterační_příkaz tělo_smyčky 

V reálných programech bývají všechny čtyři části smyčky for zapsány ve složených závorkách, aby se zamezilo předčasnému vyhodnocení příkazů. Výpočet faktoriálu je možné pomocí smyčky for zapsat takto:

proc fact2 {n} {
    set result 1.0
    for {set i $n} {$i} {incr i -1} {
        set result [expr $result*$i]
    }
} 

Vidíme, že zápis je nejen kratší, ale i přehlednější, protože všechny příkazy, které smyčku „opečovávají“, jsou umístěny u sebe na jednom řádku. Podobně jako v jazyku C lze i zde využít toho, že nulová hodnota (resp. řetězec obsahující nulu) se považuje za logickou hodnotu „false“ a nenulová hodnota za logickou hodnotu „true“.

V obou uvedených smyčkách je možné použít příkazy break a continue. Příkazem break se může smyčka v libovolném místě předčasně ukončit. Příkaz continue způsobí skok na začátek smyčky.

Posledním popisovaným typem smyčky je smyčka foreach, kterou lze použít při průchodu seznamem. Bližší informace o této smyčce budou uvedeny v sedmé kapitole.

3. Strukturované datové typy

Programovací jazyk Tcl umožňuje základní práci pouze se skalárními hodnotami. Vzhledem ke značné rozšiřitelnosti jazyka však není problémem pracovat i se strukturovanými datovými typy. Mezi tyto datové typy patří zejména asociativní pole a seznamy. Práce s asociativními poli je popsána ve čtvrté kapitole, práce se seznamy v kapitole páté. Zajímavé je, že rozšíření jazyka o tyto datové typy se obešlo bez zásahu do interpreteru, pouze se zavedlo několik nových funkcí/příkazů.

4. Asociativní pole

Jazyk Tcl, podobně jako další imperativní programovací jazyky, umožňuje práci s poli. Nejedná se však o pole indexovaná číselnými hodnotami, ale o asociativní pole, kde je klíčem obecně libovolný řetězec (asociativní pole byla po dlouhou dobu známá jako hashe – viz například programovací jazyk Perl či JavaScript). Nejprve si ukážeme použití polí indexovaných čísly (tak to známe z nižších programovacích jazyků); musíme však mít na paměti, že číselné hodnoty se interně převádějí na řetězce. Pole jsou jako celek uložena také ve formě řetězce, interní formát nás však většinou nemusí zajímat:

set pole(0) 100
set pole(1) 150
set pole(9) "bla bla"

puts $pole(0)
puts $pole(1)
puts $pole(9) 

Jak jsme si již řekli v předchozím odstavci, lze jako klíč použít libovolný řetězec, což je ukázáno na dalším příkladu s typickým využitím asociativních polí pro tvorbu velmi jednoduchého slovníku:

set slovnik(pocitac) computer
set slovnik(mys) mouse
set slovnik(skok) jump 

Pole lze samozřejmě plnit i pomocí smyčky:

while {$i<10} {set a($i) $i; incr i} 

Pro práci s poli je k dispozici několik funkcí, z nichž ty nejvýznamnější jsou uvedeny v následující tabulce. Všechny funkce začínají příkazem array, který jako svůj první parametr vyžaduje název operace, jež se má s polem provést:

Funkce pro práci s poli
Název funkce Význam funkce
array get pole vrací všechny hodnoty klíčů i prvků pole
array get pole vzor vrací všechny hodnoty klíčů i prvků pole, kde klíče odpovídají zadanému vzoru
array names pole vrací všechny klíče (indexy) pole
array names pole vzor vrací všechny klíče pole, které odpovídají zadanému vzoru
array set pole seznam vytváří pole ze seznamu (indexuje se automaticky)
array exists pole provede ověření (predikát), zda existuje pole o zadaném názvu – vrací řetězec 0 nebo 1
array size pole vrátí počet prvků v poli – vhodné pro počítané smyčky

Za povšimnutí stojí příkaz array get pole, kterým je možné převádět pole na seznam. Kromě výše zmíněných příkazů lze vyhledávat prvky v poli pomocí voleb startsearch, donesearch a nextelement. V některých případech je však vhodnější převést pole na seznam a procházet seznamem pomocí smyčky foreach.

5. Seznamy

Kromě polí je v Tcl k dispozici i datový typ seznam. Seznam se od polí liší zejména v tom, že jeho prvky mohou být libovolného typu, například další seznamy. Rekurzivním vkládáním seznamů do sebe lze z původně lineární datové struktury vytvořit například binární či n-ární strom. Seznam se vytvoří velmi jednoduše pomocí příkazu set:

set seznam1 { 1 2 3 4 5 6 }
set seznam2 { jedna dve tri ctyri pet sest }
set seznam3 { 1 {2 3} {4 5} {6 7} {8} } 

Všimněte si zde faktu, že pro vytvoření seznamu není zapotřebí žádná další jazyková konstrukce – vystačíme si s konstrukcemi uvedenými v předchozí části tohoto seriálu. I to ukazuje, jak je na první pohled jednoduchý (až triviální) jazyk při praktickém používání flexibilní (seznamy už vlastně známe, neboť se používaly při zápisu bloků kódu ve smyčkách a podmínkách). Pro práci se seznamy je k dispozici mnoho funkcí, z nichž některé jsou uvedeny v následující tabulce (způsob zápisu funkcí je podobný funkcím pro práci s poli):

Funkce pro práci se seznamy
Název funkce Význam funkce
list vytvoření seznamu z argumentů, které jsou tomuto příkazu zadány
concat spojení dvou a více seznamů
llength získání počtu prvků v seznamu
split rozložení řetězce na seznam buď podle bílých znaků nebo podle specifikovaného oddělovače
join vytvoření řetězce spojením prvků seznamu, mezi něž se může volitelně vložit oddělovač
lappend přidání jednoho či více prvků do seznamu
linsert vložení jednoho či více prvků na danou pozici (index)
lindex získání prvku ze seznamu na dané pozici (indexu)
lreplace nahrazení prvků v seznamu
lrange vyjmutí více prvků ze seznamu (souvislá oblast)
lsearch hledání prvků v seznamu (možné i podle regulárních výrazů!)
lsort setřídění prvků v seznamu podle zadaných kritérií

Pro průchod seznamem je samozřejmě možné použít smyčky while či for – stačí využít funkce llength a lindex. To však není příliš efektivní ani přehledné, proto byla do Tcl přidána speciální forma smyčky, která se používá právě při práci se seznamy. Tato smyčka se jmenuje foreach a její použití se v nejjednodušší podobě řídí podle schématu:

foreach proměnná seznam tělo_smyčky 

Podobně jako u smyčky for se i zde používá řídicí proměnná. Tato proměnná však nenabývá číselných hodnot, ale jsou do ní postupně dosazovány jednotlivé prvky seznamu. Příklad použití této smyčky:

foreach i {jedna dve tri ctyri} {
    puts $i
} 

Ve složitějších případech je možné použít i seznam názvů proměnných, do kterých se při běhu smyčky budou postupně dosazovat sousední prvky:

foreach {i j} {1 2 3 4 5 6} {
    puts "i= $i"
    puts "j= $j"
} 

Poslední modifikací smyčky foreach je specifikace jedné či více proměnných, kde každá proměnná bude použita pro svůj vlastní seznam:

foreach i {1 2 3} j {4 5 6}
    puts "i= $i"
    puts "j= $j"
} 

6. Obsah dalšího pokračování tohoto seriálu

V dalším pokračování tohoto seriálu dokončíme část věnovanou programovacímu jazyku Tcl (dále již bude popisována knihovna Tk). Budeme se věnovat práci s řetězci, regulárním výrazům atd.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.