Anti-Santa banner
Pozor! Veškerý obsah se přesouvá na www.klusik.cz

Řídící struktury


Možná už jste si někdy zkoušeli, např. v dávkovém souboru systému MS-DOS nebo Windows, takzvané výpisy - prostá zobrazení textu, který jaksi plyne po obrazovce. Tyto texty jsou zajisté "fajn", ale přeci jen tomu něco chybí. Mám na mysli jakousi interaktivitu, prostě jakákoli komunikace počítače a uživatele je dosti problematická (a spíše nemožná).

Proto je zde něco "lepšího" - ze začátku zajisté nebudete spokojeni - bude toho najednou moc, mnoho nových (a občas nepochopitelných pojmů, jejichž význam většinou vyplyne až po několikerém použití a jejich zautomatizování. Hlavním rozdílem opravdového programu a dávkové úlohy je to, že program není jen jakýmsi výpisem, ale opravdovým univerzálním pomocníkem. Slovo univerzálním zvláště vyzdvihuji, protože je to jeden z hlavním základních faktů o programování. Například vaše dávková úloha (prostě BAT) je jakýmsi jednoúčelovým stvořením, které např. umí vypsat nějaký text, doplnit to nějakou jednodušší grafikou a možná se i umí zeptat na možnost ukončení úlohy. Při programování je to jiné. Dobře naprogramovaný program je vlastně celý řízen uživatelem a jemu se jeví, jako by ho právě jen on sám ovládal. Možná v pozadí dělá milióny operací, ale uživatel musí mít pocit splynutí s programem (např. časté v počítačových hrách).

K tomu, aby program nebyl jen prostým výpisem textu a znaků, musí mít nějaké prostředky ke komunikaci s uživatelem a rozhodování o uživatelských vstupech. Těmto základním prostředkům se říká právě řídící struktury. Možná jste si všimli, že v některých kapitolách píši o vkládání knihoven. Jazyk C sám skoro vlastně nic neumí a všechno se mu musí vkládat pomocí různých knihoven. Ale pár věcí umí a to jsou právě naše zmiňované řídící struktury.

IF:

Jednou z hlavních takových je if, neboli jestliže. Její funkční předpis je jednoduchý:

      if (výraz)  pravda; else nepravda;
   

nebo také:

      if (výraz)
      {
         blok příkazů pro pravdivý výraz;
      }
      else
      {
         blok příkazů pro nepravdivý výraz;
      }
   

Možná právě na příkladu to bude lépe pochopitelné. Mějme proměnnou x a y. Jak se proměnné deklarují a informace o proměnných jsou v sekci začátky s C a v podsekci proměnné a popřípadně v konstanty. Sledujme program:

#include <stdio.h>
#include <stdlib.h>

int main()
{
   int x;
   int y;
   
   x=10;
   y=0;
   
   if (x==y)
   {
      printf("X se rovna Y\n");
   }
   else
   {
      printf("X se nerovna Y\n");
   }
   return;
}

A teď postupně probereme jednotlivé řádky kódu

#include <stdio.h>
#include <stdlib.h>

Dva známé příkazy - pomocí příkazu include vkládáme do našeho programu buďto knihovny, nebo další svoje soubory. Nyní jsme tedy zavedli knihovny stdio.h a stdlib.h.

int main()
{
}

Nyní jsme definovali funkci main(). Jak již bylo řečeno, tato funkce se v každém programu musí alespoň jedenkráte vyskytovat.

int x;
int y;

Tímhle jsme si deklarovali proměnné x a y.

x=10;
y=0;

Naplnili jsme proměnné x a y hodnotami 10 a 0.

if (x==y)
{
   printf("X se rovna Y\n");
}
else
{
   printf("X se nerovna Y\n");
}

Provedli jsme porovnání proměnných x a y. V tomto případě se provede druhá varianta (varianta else), protože výraz 10==0 je logická nula, takže se bude provádět blok příkazů pro nepravdu. Pokud bychom zadání opravili např. na x=10 a y=10, logická hodnota výrazu by byla jedna, takže by se prováděl blok příkazů pro pravdivý výraz.

Cyklus FOR:

Vysvětlili jsme si, jak funguje příkaz if. Jak však uděláme program, kde bychom chtěli vidět jednotlivé mezivýsledky testování? Myslím tím příklad z lekce o if, kdy jsme mohli v programu změnit hodnoty proměnných x a y na jakákoli dvě čísla (v rozsahu INT) a ta následně porovnat. Ale co když nechceme měnit kód programu a chceme rovnou vidět, které kombinace čísel jsou platné a které ne? Zde se velice hodí cykly. Příklad, který jsem výše uvedl je krásným příkladem toho, k čemu se využívá. Je to řízený cyklus, to znamená, že se ne-nekontrolovatelně provádí do té doby, dokaď chceme.

Zápis syntaxe příkazu cyklu FOR je:

for (inicializace; podminka; prikaz1) prikaz2;

Nebo tím používanějším způsobem:

for (inicializace; podminka; prikaz)
{
   blok prikazu;
}

Prakticky skoro vždy budeme využívat (pro názornost) tohoto druhého případu, takže doporučuji se naučit zápis toho druhého. Ten první je prakticky to samé, akorát lze zapsat pouze jeden příkaz (prikaz2). A jak to tedy funguje? Nejlépe to asi opět pochopíme z jednoduchého příkladu:

int i;

for (i=0;i<10;i++)
{
   printf("%d\n",i);
}

Následující část programu nám vypíše na obrazovku:


   0
   1
   2
   3
   4
   5
   6
   7
   8
   9

Takže čísla od 0 do 9. Proč tomu tak je? Podívejme se na zápis funkce for v našem případě se provede inicializace - takže do i se uloží 0. Proběhne první průběh, vypíše se 0 a odřádkuje se. Dále pak (u ukončovací závorky) se provede příkaz cyklu, který je v našem případě zvětši i o jedna - i++ a porovná se platnost podmínky. 1 < 10, takže vše je v pořádku. Vypíše se 1 a přičte se zase jedna. Takhle to běží až do 9, kdy se vypíše 9 a po přičtení se zjistí, že 10 není menší, než 10, tak se průběh cyklu ukončí. V proměnné i zůstane číslo 9.

Nyní se pokusme vyřešit úkol dělitelnosti řady čísel postupně všemi dalšími. Navrhuji následující program:
#include <stdio.h>
#include <stdlib.h>

#define MAX 10
int main()
{
   int i, j;

   i=0;

   for (i=0;i<(MAX+1);i++)
   {
      for (j=1;j<(MAX+1);j++)
      {
         printf("Cisla %d a %d je ",i,j);
         if ((i%j)==0)
         {
            printf("celociselny.  %d/%d=%d\n",i,j,i/j);
         }
         else
         {
            printf("necelociselny. %d/%d=%d ",i,j,i/j);
            printf("se zbytkem %d\n",i%j);
         }
      }
   }
}

Výsledek na obrazovce bude vypadat nějak takto:

Cisla 0 a 1 je celociselny.  0/1=0
Cisla 0 a 2 je celociselny.  0/2=0
Cisla 0 a 3 je celociselny.  0/3=0
Cisla 0 a 4 je celociselny.  0/4=0
Cisla 0 a 5 je celociselny.  0/5=0
Cisla 0 a 6 je celociselny.  0/6=0
Cisla 0 a 7 je celociselny.  0/7=0
Cisla 0 a 8 je celociselny.  0/8=0
Cisla 0 a 9 je celociselny.  0/9=0
Cisla 0 a 10 je celociselny.  0/10=0
Cisla 1 a 1 je celociselny.  1/1=1
Cisla 1 a 2 je necelociselny. 1/2=0 se zbytkem 1
Cisla 1 a 3 je necelociselny. 1/3=0 se zbytkem 1
Cisla 1 a 4 je necelociselny. 1/4=0 se zbytkem 1
Cisla 1 a 5 je necelociselny. 1/5=0 se zbytkem 1
Cisla 1 a 6 je necelociselny. 1/6=0 se zbytkem 1
Cisla 1 a 7 je necelociselny. 1/7=0 se zbytkem 1
Cisla 1 a 8 je necelociselny. 1/8=0 se zbytkem 1
Cisla 1 a 9 je necelociselny. 1/9=0 se zbytkem 1
Cisla 1 a 10 je necelociselny. 1/10=0 se zbytkem 1
Cisla 2 a 1 je celociselny.  2/1=2
Cisla 2 a 2 je celociselny.  2/2=1
Cisla 2 a 3 je necelociselny. 2/3=0 se zbytkem 2
Cisla 2 a 4 je necelociselny. 2/4=0 se zbytkem 2
Cisla 2 a 5 je necelociselny. 2/5=0 se zbytkem 2
Cisla 2 a 6 je necelociselny. 2/6=0 se zbytkem 2
Cisla 2 a 7 je necelociselny. 2/7=0 se zbytkem 2
Cisla 2 a 8 je necelociselny. 2/8=0 se zbytkem 2
Cisla 2 a 9 je necelociselny. 2/9=0 se zbytkem 2
Cisla 2 a 10 je necelociselny. 2/10=0 se zbytkem 2
Cisla 3 a 1 je celociselny.  3/1=3
Cisla 3 a 2 je necelociselny. 3/2=1 se zbytkem 1
Cisla 3 a 3 je celociselny.  3/3=1
Cisla 3 a 4 je necelociselny. 3/4=0 se zbytkem 3
Cisla 3 a 5 je necelociselny. 3/5=0 se zbytkem 3
Cisla 3 a 6 je necelociselny. 3/6=0 se zbytkem 3
Cisla 3 a 7 je necelociselny. 3/7=0 se zbytkem 3
Cisla 3 a 8 je necelociselny. 3/8=0 se zbytkem 3
Cisla 3 a 9 je necelociselny. 3/9=0 se zbytkem 3
Cisla 3 a 10 je necelociselny. 3/10=0 se zbytkem 3
Cisla 4 a 1 je celociselny.  4/1=4
Cisla 4 a 2 je celociselny.  4/2=2
Cisla 4 a 3 je necelociselny. 4/3=1 se zbytkem 1
Cisla 4 a 4 je celociselny.  4/4=1
Cisla 4 a 5 je necelociselny. 4/5=0 se zbytkem 4
Cisla 4 a 6 je necelociselny. 4/6=0 se zbytkem 4
Cisla 4 a 7 je necelociselny. 4/7=0 se zbytkem 4
Cisla 4 a 8 je necelociselny. 4/8=0 se zbytkem 4
Cisla 4 a 9 je necelociselny. 4/9=0 se zbytkem 4
Cisla 4 a 10 je necelociselny. 4/10=0 se zbytkem 4
Cisla 5 a 1 je celociselny.  5/1=5
Cisla 5 a 2 je necelociselny. 5/2=2 se zbytkem 1
Cisla 5 a 3 je necelociselny. 5/3=1 se zbytkem 2
Cisla 5 a 4 je necelociselny. 5/4=1 se zbytkem 1
Cisla 5 a 5 je celociselny.  5/5=1
Cisla 5 a 6 je necelociselny. 5/6=0 se zbytkem 5
Cisla 5 a 7 je necelociselny. 5/7=0 se zbytkem 5
Cisla 5 a 8 je necelociselny. 5/8=0 se zbytkem 5
Cisla 5 a 9 je necelociselny. 5/9=0 se zbytkem 5
Cisla 5 a 10 je necelociselny. 5/10=0 se zbytkem 5
Cisla 6 a 1 je celociselny.  6/1=6
Cisla 6 a 2 je celociselny.  6/2=3
Cisla 6 a 3 je celociselny.  6/3=2
Cisla 6 a 4 je necelociselny. 6/4=1 se zbytkem 2
Cisla 6 a 5 je necelociselny. 6/5=1 se zbytkem 1
Cisla 6 a 6 je celociselny.  6/6=1
Cisla 6 a 7 je necelociselny. 6/7=0 se zbytkem 6
Cisla 6 a 8 je necelociselny. 6/8=0 se zbytkem 6
Cisla 6 a 9 je necelociselny. 6/9=0 se zbytkem 6
Cisla 6 a 10 je necelociselny. 6/10=0 se zbytkem 6
Cisla 7 a 1 je celociselny.  7/1=7
Cisla 7 a 2 je necelociselny. 7/2=3 se zbytkem 1
Cisla 7 a 3 je necelociselny. 7/3=2 se zbytkem 1
Cisla 7 a 4 je necelociselny. 7/4=1 se zbytkem 3
Cisla 7 a 5 je necelociselny. 7/5=1 se zbytkem 2
Cisla 7 a 6 je necelociselny. 7/6=1 se zbytkem 1
Cisla 7 a 7 je celociselny.  7/7=1
Cisla 7 a 8 je necelociselny. 7/8=0 se zbytkem 7
Cisla 7 a 9 je necelociselny. 7/9=0 se zbytkem 7
Cisla 7 a 10 je necelociselny. 7/10=0 se zbytkem 7
Cisla 8 a 1 je celociselny.  8/1=8
Cisla 8 a 2 je celociselny.  8/2=4
Cisla 8 a 3 je necelociselny. 8/3=2 se zbytkem 2
Cisla 8 a 4 je celociselny.  8/4=2
Cisla 8 a 5 je necelociselny. 8/5=1 se zbytkem 3
Cisla 8 a 6 je necelociselny. 8/6=1 se zbytkem 2
Cisla 8 a 7 je necelociselny. 8/7=1 se zbytkem 1
Cisla 8 a 8 je celociselny.  8/8=1
Cisla 8 a 9 je necelociselny. 8/9=0 se zbytkem 8
Cisla 8 a 10 je necelociselny. 8/10=0 se zbytkem 8
Cisla 9 a 1 je celociselny.  9/1=9
Cisla 9 a 2 je necelociselny. 9/2=4 se zbytkem 1
Cisla 9 a 3 je celociselny.  9/3=3
Cisla 9 a 4 je necelociselny. 9/4=2 se zbytkem 1
Cisla 9 a 5 je necelociselny. 9/5=1 se zbytkem 4
Cisla 9 a 6 je necelociselny. 9/6=1 se zbytkem 3
Cisla 9 a 7 je necelociselny. 9/7=1 se zbytkem 2
Cisla 9 a 8 je necelociselny. 9/8=1 se zbytkem 1
Cisla 9 a 9 je celociselny.  9/9=1
Cisla 9 a 10 je necelociselny. 9/10=0 se zbytkem 9
Cisla 10 a 1 je celociselny.  10/1=10
Cisla 10 a 2 je celociselny.  10/2=5
Cisla 10 a 3 je necelociselny. 10/3=3 se zbytkem 1
Cisla 10 a 4 je necelociselny. 10/4=2 se zbytkem 2
Cisla 10 a 5 je celociselny.  10/5=2
Cisla 10 a 6 je necelociselny. 10/6=1 se zbytkem 4
Cisla 10 a 7 je necelociselny. 10/7=1 se zbytkem 3
Cisla 10 a 8 je necelociselny. 10/8=1 se zbytkem 2
Cisla 10 a 9 je necelociselny. 10/9=1 se zbytkem 1
Cisla 10 a 10 je celociselny.  10/10=1

Vysvětlení programu:

První dvě řádky - ony includy - nám vloží do programu knihovny (viz. výše). Následující řádka

#define MAX 10

nám vyrábí konstantu MAX, kterou inicializujeme hodnotou 10 v našem případě. Hodnota MAX bude využívána dále.

int i, j;

i=0;

jsou deklarace proměnných a jejich pozdější inicializace. Hodnotu i vlastně ani nemusíme inicializovat, protože se inicializuje hned poté v bloku for, ale je dobrým programátorským zvykem inicializovat veškeré deklarované proměnné. Nikdy totiž nevíte, kterou část paměti dostanete přidělenou (pokaždé hlavně jinou), takže nevíte, jestli náhodou nějaký jiný program před vámi na stejné místo neuložil a "neuklidil". Ale teď přijde to hlavní...

for (i=0;i<(MAX+1);i++)
{
   for (j=1;j<(MAX+1);j++)
   {
      printf("Cisla %d a %d je ",i,j);
      if ((i%j)==0)
      {
         printf("celociselny.  %d/%d=%d\n",i,j,i/j);
      }
      else
      {
         printf("necelociselny. %d/%d=%d ",i,j,i/j);
         printf("se zbytkem %d\n",i%j);
      }
   }
}

To je vlastně část programu, která dělá "tu chytrou věc" - abychom nemuseli (jako v dávkové úloze) vypisovat všechny kombinace, necháme to jednoduše na programu, ať se stará sám. Vidíme dva vložené for do sebe. Bude se tedy provádět (v našem případě) posloupnost [i,j]: [0,1] [0,2] [0,3] .... [0,9] [0,10] [1,1] [1,2] a tak dále. Proč tomu tak je? Jak víme, cyklus for probíhá tak dlouho, dokud je podmínka v něm uvedená platná. Tuto podmínku nám pomáhá naplnit inicializační příkaz, který se provede pouze jednou a to při vstupu do smyčky. No a naše i nám bude měnit příkaz uvedený v závorce jako poslední, takže i++. Pokud se podíváme do základů o C, uvidíme, co postfix ++ znamená. Takže první blok foru nám počítá a přičítá i od 0 do 10. Druhý for nám vlastně dělá totéž, akorát s proměnnou j. Všimněte si, že jsem j inicializoval na 1, nikoli na 0. Je to laciné ošetření vstupu, abych nemusel později kontrolovat, jestli náhodou nedělím nulou :-). Pak následuje příkaz

printf("Cisla %d a %d je ",i,j);

o kterém zatím stačí vědět to, že vypíše "Cisla i a j je " a misto i a j dosadí hodnoty i a j. Pokud bychom napsali např

printf("Cisla %d a %d je ",j,i);

vypsal by nám program prakticky totéž, akorát by vypsal na pozice %d hodnoty opačně, než v našem předchozím případě.

if ((i%j)==0)

zde jsme použili specifickou programátorskou funkci, není to překlep, opravdu tam nepatří lomeno /, ale procento %. Říkáme tomu dělení modulo a znamená to, že uděláme podíl (v našem případě) i/j, avšak výstupní hodnotou našeho výpočtu nebude podíl těchto dvou čísel, ale zbytek po dělení. Dále pak běžné / pro hodnoty int je celočíselné dělení. Takže např. 5/3=1 a 5%3=2. Takže můžeme říci, že celočíselný podíl 5 ku 3 je 1 se zbytkem 2.

printf("celociselny.  %d/%d=%d\n",i,j,i/j);

nám tedy vypíše "celociselny i/j=(i/j)" a odřádkuje (ono opačné lomítko s "n" - viz. escape sekvence).Za hodnoty do fukce printf můžeme dávat cokoli - konstanty, vypočtené hodnoty nebo i rovnou výpočty.Je to jedno. Zbytek programu by měl být pochopitelný.

Cyklus WHILE

Pokud bychom náhodou chtěli provádět cyklus, u kterého předem nevíme kolikrát se provede, je velkou výhodou použít cyklu while. Narozdíl od for je tento cyklus řízen jedinou podmínkou v parametru. Stejně, jako u for se cyklus provádí tak dlouho, dokaď je podmínka platná (nejsem si jist, ale mám za to, že v Pascalu je to naopak - dokud není platná). Syntaxe while vypadá následovně:

   while(podminka)
   {
      provadej podminku;
   }

Možná by bylo dobré si zavést dva další pojmy - break a continue. Oba tak nějak s cykly (všeobecně) souvisejí, tak by bylo chybou je neuvést. Nejprve tedy jen stručné definice příkazů. Příkaz break ukončí právě probíhající cyklus (resp. ten, ve kterém je použit) a program bude pokračovat následujícím řádkem po ukončovací složené závorce cyklu while. Zato příkaz continue - ač to vypadá podobně - dělá něco trochu jiného. Příkazem continue se ukončí právě probíhající průchod smyčkou (tedy stejně, jako v případě break), avšak ukazatel nevyskočí ze smyčky ven a pokračuje se prvním řádkem v bloku příkazů smyčky, ve které bylo continue použito. Tyto zkomolené věty budou možná pochopitelnější z příkladu:

   int a, b, c;
   
   a=b=c=0;
   
   while(1)
   {
      c=a+b;
      if ((c%2)==0)
      {
         a=a+2;
         b=b+1;
         continue;
      }
      else
      {
         printf("%d\n",c);
         a=a+2;
         b=b+1;
      }
      if (a>100) break;
   }

Tento úryvek programu má za následek, že se budou vypisovat hodnoty proměné c jen v případě, že jsou lichá (pokud modulo dělení dvěma nebude nulové). Samozřejmě by tento příklad šel řešit mnohem jednodušeji, avšak tohle bylo nutné pro ilustraci problému s break a continue. Proberme si nyní program řádek po řádku:

   int a,b,c;
   a=b=c=0;

TTo je snad jasné - všechny proměnné se nastaví na hodnotu 0.

   while(1)
   {
   }

Tohle je ona smyčka while. Řekli jsme si, že cyklus probíhá tak dlouho, dokud je logická hodnota podmínky pravdivá - čili 1. A v tomto případě to bude vždy (prostě je tam natvrdo ta jednička daná).

   c=a+b;

Do proměnné c se uloží součet hodnot a+b.

      if ((c%2)==0)
      {
         a=a+2;
         b=b+1;
         continue;
      }
      else
      {
         printf("%d\n",c);
         a=a+2;
         b=b+1;
      }

Zde se program rozhoduje, zda-li je číslo c sudé, či liché (resp. jestli je jeho modulo podíl roven nule). V případě sudého čísla (varianta první) se k a přičtou 2 a k b se přičte 1. A protože nemá smysl, aby program prováděl cokoli dále, protože číslo holt sudé nebylo, použili jsme příkazu continue. V případě, že číslo je liché, provede se výpis na obrazovku a k proměnné se změní stejně, jako v případě sudého čísla c. A na konci programu..

   if (a>100) break;

kontroluje, zda-li číslo a není větší, než-li 100. V případě, že to tak doopravdy je, provede se příkaz break a vyskočí se ze smyčky pryč. Tento program obsahuje ale malou "logickou" chybku. Jde totiž o to, že předvídáme, že podíl a a b se neustále střídá ve své lichosti a sudosti. Takže nepředpokládáme, že by náhodou všechna čísla byla sudá. V takovém případě by se vlastně program nikdy neukončil, protože by se nikdy neprovedla právě ona poslední řádka s příkazem break.