Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 3 - Pokročilé cykly v jazyce C

V minulé lekci, Kompilace v jazyce C a C++ pokračování, jsme dokončili téma kompilace.

V lekci o cyklech z kurzu Základních konstrukcí jazyka C jsme si vysvětlili tři základní typy cyklů (while, do while, for) a řekli jsme si, jak fungují. V dnešním C tutoriálu se podíváme na další příkazy, kterými lze běh cyklu řídit. Nakonec ještě jednou rozebereme for cyklus a ukážeme si nějaké triky, ke kterým lze využít.

Continue

Prvním klíčovým slovem je continue. Ukončí právě prováděné tělo cyklus a skočí na další iteraci (další průběh cyklu). Ukážeme si to například při vypisování pole čísel. Úkolem bude vypsat celé pole kromě čísel, které jsou v intervalu od 5 do 10. Původním řešením by bylo přidání podmínky a při jejím splnění by se číslo nevypsalo. Řešení pomocí continue by vypadalo následovně:

int main(int argc, char** argv)
{
    int cisla[10] = {1, 3, 5, 7, 9, 11, 13, 15, 17, 19};
    int i;

    for (i = 0; i < 10; i++)
    {
        if (cisla[i] >= 5 && cisla[i] <= 10)
            continue;
        printf("%d\n", cisla[i]);
    }
    return (EXIT_SUCCESS);
}

Výsledek:

Continue
1
3
11
13
15
17
19

Všimněme si jedné důležité věci. Při použití continue se u cyklu for provedla operace, která se standardně provádí na konci cyklu (v cyklu for se jedná o třetí "parametr"). Toho lze velmi hezky využít pro různé cyklické operace. Více se tomu budu věnovat dále v článku v kapitole věnované právě cyklu for.

Break

Stejně jako continue, i break ukončí právě prováděné tělo cyklu. Na rozdíl od continue se ale ukončí celý cyklus a program pokračuje za cyklem. Předvedeme si to na algoritmu Eratosthenova síta. Jedná se o algoritmus pro hledání prvočísel. Prvočísla si budeme ukládat v poli. Postupně budeme procházet všechny čísla a narazíme-li na číslo, které nedělí žádné jiné prvočíslo, poté jsme narazili na nové prvočíslo a můžeme jej vložit mezi ostatní. Nejdříve uvedu kód a poté uvedu vysvětlení.

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

#define POCET_HLEDAYCH_PRVOCISEL 10

int main(int argc, char** argv)
{
    int prvocisla[POCET_HLEDAYCH_PRVOCISEL] = {2};  //pole pro uložení provočísel
    int pocet_nalezenych_prvocisel = 1;             //počet již nalezených prvočísel
    int i = 2;  //aktuálně zpracovávané číslo
    int j = 0;  //dočasný index

    while (1)   //nekonečný cyklus
    {
        //zjistíme, jestli nějaké z prvočísel dělí aktuální číslo
        for (j = 0; j < pocet_nalezenych_prvocisel; j++)
            if (i % prvocisla[j] == 0)  //našli jsme prvočíslo, které dělí aktuální číslo
                break;  //cyklus for můžeme ukončit - to znamená že podmínka cyklu je stále platní

        //pokud cyklus doběhl, jedná se o prvočíslo
        if (j == pocet_nalezenych_prvocisel)
        {
            prvocisla[j] = i;
            pocet_nalezenych_prvocisel++;
        }

        //podmínka konce cyklu
        if (pocet_nalezenych_prvocisel == POCET_HLEDAYCH_PRVOCISEL)
            break;
        i++;
    }

    //vypíšeme prvočísla
    for (i = 0; i < pocet_nalezenych_prvocisel; i++)
        printf("Prvocislo: %d\n", prvocisla[i]);

    return (EXIT_SUCCESS);
}

Hlavní částí programu je první for cyklus. Pokud nalezneme již existující prvočíslo, které dělí aktuální číslo, tak cyklus ukončíme. Na rozdíl od continue, při break neproběhne akce na konci cyklu. Tedy pouze v případě, kdy se nezavolá žádný break, bude hodnota j rovna počtu nalezených prvočísel (cyklus ukončí jeho podmínka). Toho využíváme v další podmínce. Celý nekonečný cyklus ukončíme tehdy, máme-li již v poli dostatečný počet prvočísel. Poté pole pouze vypíšeme.

EratesthenovoSito
Prvocislo: 2
Prvocislo: 3
Prvocislo: 5
Prvocislo: 7
Prvocislo: 11
Prvocislo: 13
Prvocislo: 17
Prvocislo: 19
Prvocislo: 23
Prvocislo: 29

Goto

S příkazem goto se setkáte pouze velmi zřídka. Obecně se jedná o špatný přístup k návrhu programu a je to spíše pozůstatek z nízkoúrovňových jazyků jako je jazyk symbolických adres. Příkazem goto můžeme skočit na libovolní místo v programu. Nejprve toto místo označíme (tzv. návěstí). Poté můžeme zavolat goto na název návěstí. Ukážeme si na úpravě předchozího příkladu, kdy místo vypisování prvočísel v cyklu použijeme příkaz goto.

//vypíšeme prvočísla
i = 0;                                  //počíteční inicializace
zacatek_cyklu:                          //návěstí
    if( i < pocet_nalezenych_prvocisel)    //podmínka cyklu
    {
        printf("Prvocislo: %d\n", prvocisla[i]);
        i++;
        goto zacatek_cyklu;             //skok na začátek cyklu
    }

Určitě se shodneme na tom, že použití for cyklu bylo mnohem elegantnější. Co víc, dokázali byste rozluštit, co vypíše následující program?

goto navesti_x;
navesti_b:
printf(" va");
goto navesti_h;
navesti_u:
printf(" cha");
goto navesti_z;
navesti_h:
printf("s z");
goto navesti_u;
navesti_d:
printf(" programu");
goto navesti_r;
navesti_x:
printf("Zdravim");
goto navesti_b;
navesti_r:
return (EXIT_SUCCESS);
navesti_z:
printf("otickeho");
goto navesti_d;

Určitě na první pohled ne. Právě proto se od goto opouští, protože vždy lze tu stejnou úlohu vyřešit jinak a přehledněji.

Ternární operátor

Nyní se jenom velice rychle podíváme na ternární operátor. Je to jediný operátor v C, který přijímá tři operandy. Syntaxe je následující:

podmínka ? výraz1 : výraz2;

Pokud je podmínka splněna, bude vrácen první výraz, jinak bude vrácen výraz druhý. Pro představu lze program přepsat pomocí podmínek:

if(podmínka)
    výraz1;
else
    výraz2;

Nicméně se nejedná o plnou ekvivalenci! Ternární operátor může být použit i k přiřazení.

int maximum = a > b ? a : b;
int maximum_func(int a, int b)
{
    return a > b ? a : b;
}

Oba výrazy vrátí větší z hodnot (jsou-li hodnoty ekvivalentní, je jedno kterou vrátíme). O ternárním operátoru můžete skutečně uvažovat tím způsobem, že něco vrací. Nic nám ale nebrání zavolat uvnitř výrazů funkci:

void print_max(int a)
{
     printf("Maximum je %d",a);
}

int main()
{
     int a, b;
     // .....
     a > b ? print_max(a) : print_max(b);
}

Ternární operátor lze i zanořit, ovšem opět za cenu snížení čitelnosti kódu. Výběr největšího ze tří čísel by vypadal nějak takto:

int max = a > b ? a > c ? a : c : b > c ? b : c;

Cyklus for

Nyní se ještě naposledy vrátím k cyklu for. Jak víme, skládá se ze tří složek. První složkou je počáteční inicializace, druhou složkou je podmínka a poslední složkou je akce po dokončení cyklu. Tyto složky se nemusí týkat jen samotného cyklu, ale můžete o nich uvažovat v obecnějším měřítku. Přepíšeme si algoritmus Eratosthenova síta do kratší podoby:

int main(int argc, char** argv)
{
    int prvocisla[POCET_HLEDAYCH_PRVOCISEL] = {2}; //pole pro uložení provočísel
    int pocet_nalezenych_prvocisel = 1; //počet již nalezených prvočísel
    int i = 2; //aktuálně zpracovávané číslo
    int j = 0; //dočasný index

    while (1) //nekonečný cyklus
    {
        //zjistíme, jestli nějaké z prvočísel dělí aktuální číslo
        for (j = 0; j < pocet_nalezenych_prvocisel && i % prvocisla[j] != 0; j++);

        //pokud ne tak aktuální číslo přidáme mezi prvočísla
        if (j == pocet_nalezenych_prvocisel)
        {
            prvocisla[j] = i;
            pocet_nalezenych_prvocisel++;
        }

        //podmínka konce cyklu
        if (pocet_nalezenych_prvocisel == POCET_HLEDAYCH_PRVOCISEL)
            break;
        i++;
    }

    //vypíšeme prvočísla
    for (i = 0; i < pocet_nalezenych_prvocisel; printf("Prvocislo: %d\n", prvocisla[i++]));

    return (EXIT_SUCCESS);
}

Všimněme si zkrácení samotných cyklů. Místo abychom použili break na ukončení cyklu, přidáme samotnou podmínku do vyhodnocení pokračování cyklu. Dále se změnil i cyklus, který měl na starosti vypisování. V operaci "na konci cyklu" jsme přidali výpis prvočísla. Pokud bychom chtěli provést operací několik, jednoduše je oddělíme čárkou. Důležité jsou také středníky za cyklem. Ty říkají, že cyklus nemá žádné tělo.

V našem případě to program nezjednodušilo. Dokonce se ve výsledku program čte hůře. Osobně se mi tento postup osvědčil u struktur jako jsou například spojové seznamy. Pro příklad si představme jednosměrně zřetězený spojový seznam. Jak se dostaneme na konec seznamu? Pomocí cyklu for velice rychle a elegantně.

typedef struct {
    int val;
    NODE* dalsi;
} NODE;

// ...

NODE* posledni_node = prvni_node;
for(;posledni_node->dalsi != NULL; posledni_node = posledni_node->dalsi);

Cykly (a cyklus for především) tedy nemusí pracovat jen s indexy - součástí cyklu může být libovolná operace. Také mohou některé části úplně chybět. Je praktické uvažovat nad jednotlivými částmi v cyklu mnohem obecněji.

V příští lekci, Makra v jazyce C, na nás čekají makra.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 17x (206.39 kB)
Aplikace je včetně zdrojových kódů v jazyce c

 

Předchozí článek
Kompilace v jazyce C a C++ pokračování
Všechny články v sekci
Pokročilé konstrukce jazyka C
Přeskočit článek
(nedoporučujeme)
Makra v jazyce C
Článek pro vás napsal Patrik Valkovič
Avatar
Uživatelské hodnocení:
5 hlasů
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity