Hledáme nového kolegu do redakce - 100% home office, 100% flexibilní pracovní doba. Více informací.
Využij akce až 80 % zdarma při nákupu e-learningu - více informací. Zároveň pouze tento týden sleva až 80 % na e-learning týkající se Swift

3. diel - Pokročilé cykly v jazyku C

V minulej lekcii, Kompilácie v jazyku C a C ++ pokračovanie , sme dokončili téma kompilácie. V lekcii o cykloch z kurzu Základných konštrukcií jazyka C sme si vysvetlili tri základné typy cyklov (while, do while, for) a povedali sme si, ako fungujú. V dnešnom C tutoriálu sa pozrieme na ďalšie príkazy, ktorými možno beh cyklu riadiť. Nakoniec ešte raz rozoberieme for cyklus a ukážeme si nejaké triky, ku ktorým možno využiť.

Continue

Prvým kľúčovým slovom je continue. Ukončí práve vykonávané telo cyklus a skočí na ďalšie iteráciu (ďalší priebeh cyklu). Ukážeme si to napríklad pri vypisovaní pole čísel. Úlohou bude vypísať celé pole okrem položiek, ktoré sú v intervale od 5 do 10. Pôvodným riešením by bolo pridanie podmienky a pri jej splnení by sa číslo nevypísala. Riešenie pomocou continue by vyzeralo nasledovne:

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ýsledok:

Continue
1
3
11
13
15
17
19

Všimnime si jednej dôležitej veci. Pri použití continue sa u cykle for vykonala operácia, ktorá sa štandardne vykonáva na konci cyklu (v cykle for sa jedná o tretiu "parameter"). To možno veľmi pekne využiť pre rôzne cyklické operácie. Viac sa tomu budem venovať ďalej v článku v kapitole venovanej práve cyklu for.

Break

Rovnako ako continue, aj break ukončí práve vykonávané telo cyklu. Na rozdiel od continue sa ale ukončí celý cyklus a program pokračuje za cyklom. Predvedieme si to na algoritme Eratosthenova sita. Jedná sa o algoritmus pre hľadanie prvočísel. Prvočísla si budeme ukladať v poli. Postupne budeme prechádzať všetky čísla a narazíme ak na číslo, ktoré nedelí žiadne iné prvočíslo, potom sme narazili na nové prvočíslo a môžeme ho vložiť medzi ostatné. Najskôr uvediem kód a potom uvediem vysvetlenie.

#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);
}

Hlavnou časťou programu je prvý for cyklus. Ak nájdeme už existujúce prvočíslo, ktoré delia aktuálne číslo, tak cyklus ukončíme. Na rozdiel od continue, pri break neprebehne akcie na konci cyklu. Teda iba v prípade, keď sa nezavolá žiadny break, bude hodnota j rovná počtu nájdených prvočísel (cyklus ukončí jeho podmienka). Toho využívame v ďalšej podmienke. Celý nekonečný cyklus ukončíme vtedy, ak máme už v poli dostatočný počet prvočísel. Potom pole len vypíšeme.

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

Goto

Tento výukový obsah pomáhajú rozvíjať nasledujúce firmy, ktoré možno hľadajú práve teba!

S príkazom goto sa stretnete len veľmi zriedka. Všeobecne sa jedná o zlý prístup k návrhu programu a je to skôr pozostatok z nízkoúrovňových jazykov ako je jazyk symbolických adries. Príkazom goto môžeme skočiť na ľubovoľné miesto v programe. Najprv toto miesto označíme (tzv. Návestí). Potom môžeme zavolať goto na názov návestie. Ukážeme si na úprave predchádzajúceho príkladu, kedy miesto vypisovania prvočísel v cykle použijeme prí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čite sa zhodneme na tom, že použitie for cyklu bolo oveľa elegantnejšie. Čo viac, dokázali by ste rozlúštiť, čo vypíše nasledujúci 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čite na prvý pohľad nie. Práve preto sa od goto opúšťa, pretože vždy sa dá tú rovnakú úlohu vyriešiť inak a prehľadnejšie.

Ternárne operátor

Teraz sa len veľmi rýchlo pozrieme na ternárne operátor. Je to jediný operátor v C, ktorý prijíma tri operandy. Syntax je nasledovná:

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

Ak je podmienka splnená, bude vrátený prvý výraz, inak bude vrátený výraz druhý. Pre predstavu možno program prepísať pomocou podmienok:

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

Avšak sa nejedná o plnú rovnocennosti! Ternárne operátor môže byť použitý aj k priradenie.

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

Oba výrazy vráti väčšie z hodnôt (ak sú hodnoty ekvivalentnej, je jedno ktorú vrátime). O ternárním operátora môžete skutočne uvažovať tým spôsobom, že niečo vracia. Nič nám ale nebráni zavolať vnútri výrazov funkciu:

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

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

Ternárne operátor možno aj ponoriť, avšak opäť za cenu zníženia čitateľnosti kódu. Výber najväčšieho z troch čísel by vyzeral nejako takto:

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

Cyklus for

Teraz sa ešte naposledy vrátim k cyklu for. Ako vieme, skladá sa z troch zložiek. Prvou zložkou je počiatočná inicializácia, druhou zložkou je podmienka a posledná zložkou je akcia po dokončení cyklu. Tieto zložky sa nemusia týkať len samotného cyklu, ale môžete o nich uvažovať v širšom meradle. Prepíšeme si algoritmus Eratosthenova sita do kratšie 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šimnime si skrátenie samotných cyklov. Namiesto aby sme použili break na ukončenie cyklu, pridáme samotnú podmienku do vyhodnotenia pokračovania cyklu. Ďalej sa zmenil aj cyklus, ktorý mal na starosti vypisovanie. V operácii "na konci cyklu" sme pridali výpis prvočísla. Ak by sme chceli vykonať operácií niekoľko, jednoducho ich oddelíme čiarkou. Dôležité sú tiež bodkočiarkami za cyklom. Tie hovoria, že cyklus nemá žiadne telo.

V našom prípade to program nezjednodušil. Dokonca sa vo výsledku program číta horšie. Osobne sa mi tento postup osvedčil u štruktúr ako sú napríklad spojové zoznamy. Pre príklad si predstavme jednosmerne reťazcový spojový zoznam. Ako sa dostaneme na koniec zoznamu? Pomocou cyklu for veľmi rýchlo a elegantne.

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 predovšetkým) teda nemusí pracovať len s indexmi - súčasťou cyklu môže byť ľubovoľná operácia. Tiež môžu niektoré časti úplne chýbať. Je praktické uvažovať nad jednotlivými časťami v cykle oveľa všeobecnejšie. V budúcej lekcii, Makrá v programovacom jazyku C , na nás čakajú makrá.


 

Mal si s čímkoľvek problém? Stiahni si vzorovú aplikáciu nižšie a porovnaj ju so svojím projektom, chybu tak ľahko nájdeš.

Stiahnuť

Stiahnutím nasledujúceho súboru súhlasíš s licenčnými podmienkami

Stiahnuté 12x (206.39 kB)
Aplikácia je vrátane zdrojových kódov v jazyku C

 

Predchádzajúci článok
Kompilácie v jazyku C a C ++ pokračovanie
Všetky články v sekcii
Pokročilé konštrukcia jazyka C
Článok pre vás napísal Patrik Valkovič
Avatar
Ako sa ti páči článok?
Ešte nikto nehodnotil, buď prvý!
Věnuji se programování v C++ a C#. Kromě toho také programuji v PHP (Nette) a JavaScriptu (NodeJS).
Aktivity

 

 

Komentáre

Avatar
Patrik Valkovič
Člen IT Redactor Gang
Avatar
Patrik Valkovič:4.9.2019 14:33

Libovolnou se složek můžeš vynechat. Pokud vynecháš podmínku, bude cyklus nekonečný. Pokud vynecháš první složku, před začátkem cyklu se nic neprovede. Když vynecháš poslední složku, na konci cyklu se nic nebude provádět. Stejně tak to můžeš kombinovat. Například while(i > 0) a for(; i > 0;) jsou ekvivalentní zápisy.

Odpovedať
4.9.2019 14:33
Nikdy neumíme dost na to, abychom se nemohli něco nového naučit.
Avatar
DarkCoder
Člen
Avatar
DarkCoder:4.9.2019 15:21

Takováto podoba for cyklu je nebezpečná. For cyklus bude nekonečný pokud ho sám explicitně neukončíš. To nebezpečí spočívá v neustálé inkrementaci proměnné i. Pokud ji nebudeš uvnitř cyklu konktrolovat, přeteče její povolený rozsah a funkce programu pak bude minimálně chybná.

Pokud chceš vytvořit nekonečný cyklus for, používej následující podobu:

for(;;)

Tato podoba je ekvivalent while(1).

Inicializace nemusí být nutně uvnitř cyklu, může být vně. Cyklus tak bude normálně fungovat.

i = 100;
for( ; i > 0; i--) ...

Stejně tak inkrementovat můžeš uvnitř cyklu a tedy část inkrementace ve fory cyklu vynechat. Program bude provádět činnost jako obvykle.

Odpovedať
4.9.2019 15:21
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovedá na DarkCoder
Martin Russin:30. júla 14:54

Ahoj, chcel by som Ťa poprosiť o vysvetlenie príkladu z tejto lekcie.

#include <stdio.h>
#include <stdlib.h>
#define POCET_HLADANYCH_PRVOCISEL 10

int main(int argc, char** argv) {
    int prvocisla[POCET_HLADANYCH_PRVOCISEL] = {2};
    int pocet_najdenych_prvocisel = 1;
    int i = 2;
    int j = 0;

    while(1) {
        for(j=0; j<pocet_najdenych_prvocisel; j++)
            if(i%prvocisla[j] == 0)
                break;
// dosadím si hodnoty premenných a konštánt pre lepšie pochopenie
// for(j=0; j<1; j++)
//    if(2%2 == 0) -> platí táto podmienka
//    break; ->nasleduje príkaz break, ktorý ukončí cyklus for a program
// pokračuje ďalšími riadkami za cyklom for
        if(j == pocet_najdenych_prvocisel) {
// if(0 == 1) { -> neplatí podmienka, príkazy sa neuskutočnia
            prvocisla[j] = i;
            pocet_najdenych_prvocisel++;
        }
        if(pocet_najdenych_prvocisel == POCET_HLADANYCH_PRVOCISEL) {
// if(1 == 10) { -> neplatí podmienka, príkazy sa neuskutočnia
            break;
        }
        i++; // zvýši sa hodnota 2 na 3
    }
//
// cyklus while sa spustí od začiatku
//    for(j=0; j<pocet_najdenych_prvocisel(opäť 1); j++) {
//        if(i(3)%prvocisla[j](2) == 0) -> neplatí podmienka,
//            break; -> nenasleduje príkaz break, avšak cyklus for sa už nemôže zopakovať
// a ja proste neviem nájsť v programe, kde sa zvýši hodnota pocet_najdenych_prvocisel
//    }

    for(i=0; i<pocet_najdenych_prvocisel; i++) {
        printf("Prvočíslo: %d\n", prvocisla[i]);
    }
    return (EXIT_SUCCESS);
}

Vopred Ti ďakujem za odpoveď.

 
Odpovedať
30. júla 14:54
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:30. júla 16:50

Ke změně proměnné pocet_najdenych_prvo­cisel dochází v následujícím bloku

if (j == pocet_najdenych_prvocisel) {
        prvocisla[j] = i;
        pocet_najdenych_prvocisel++;
}

a to tehdy, když test na zbytek po dělení pro všechny aktuální prvky v poli, neprošel. To se pak hodnota j stává rovna hodnotě pocet_najdenych_prvo­cisel a dojde tak k přidání dalšího prvočísla do pole a inkrementace hodnoty pocet_najdenych_prvo­cisel.

Pro názornost doporučuji si pustit program v režimu ladění - krokovat s vnořením (F11). Je zde krásně vidět kde se v programu aktuálně nacházíš a také hodnoty jednotlivým proměnných a to jak se mění.

Odpovedať
30. júla 16:50
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovedá na DarkCoder
Martin Russin:30. júla 17:07

To se pak hodnota j stává rovna hodnotě pocet_najdenych_prvo­cisel

Na základe čoho?

 
Odpovedať
30. júla 17:07
Tento výukový obsah pomáhajú rozvíjať nasledujúce firmy, ktoré možno hľadajú práve teba!
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:30. júla 17:14

Na základě řídící proměnné for cyklu, který není ukončen příkazem break a dojede až nakonec. Ukončení for cyklu nastane, když bude platit podmínka j == pocet_najdenych_prvo­cisel.

Odpovedať
30. júla 17:14
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovedá na DarkCoder
Martin Russin:30. júla 17:27

Pomocou cyklu for sa prepísal kód

for(j=0; j<pocet_najdenych_prvocisel; j++)
            if(i%prvocisla[j] == 0)
                break;

na

for (j = 0; j < pocet_nalezenych_prvocisel && i % prvocisla[j] != 0; j++);

lenže cyklus for nemá v tele žiaden príkaz, ako si mám teda predstaviť jeho funkciu? Ako dôjde k ukončeniu cyklu bez príkazu break?
.
Rovnako sa tento kód

for(i=0; i<pocet_najdenych_prvocisel; i++) {
        printf("Prvočíslo: %d\n", prvocisla[i]);
    }

prepísal na

for (i = 0; i < pocet_nalezenych_prvocisel; printf("Prvocislo: %d\n", prvocisla[i++]));

Ako prvá hodnota sa vypíše hodnota na indexe 0? Pretože ak i=0 a príkaz je napísaný ako prvocisla[i++], tak príkaz i++ zinkrementuje premennú i na hodnotu 1 a teda sa vypíše hodnota na indexe 1?

 
Odpovedať
30. júla 17:27
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:30. júla 17:53

Každý z cyklů (for, do-while, while) se vyhodnocuje v závislosti na vyhodnocení podmínky. Tedy na výsledku výrazu. Cyklus se opakuje dokud podmínka platí. U for a while cyklus je podmínka na začátku, u cyklu do-while je podmínka na konci.

V uvedeném for cyklu by rozhodně bylo dobré používat závorky pro větší přehlednost. Princip je stejný jako u běžného for cyklu - začne se inicializační částí, pak se otestuje podmínka, je-li pravdivá, neproběhne nic. Pak se provede inkrementační část, otestuje se podmínka, atd.. K ukončení cyklu dojde tehdy, stane-li se podmínka nepravdivá.. Což se stane předčasně u vyhodnocení logického součinu.

K druhému přepisu for cyklu řeknu napíšu akorát toto - Na takovéto zápisy uvnitř hlavičky cyklů úplně zapomeň! Takovýto zápis je velmi nepřehledný a narušuje chápaní činnosti daného bloku programu.

Zápisy ala

prvocisla[i++]

je dobré zpočátku nepoužívat, dokud se to nestane přirozené. Není ostudou si to rozepisovat na

prvocisla[i]
i++;

Je to mnohem čitelnější a méně často může dojít k chybě..

Odpovedať
30. júla 17:53
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Avatar
Odpovedá na DarkCoder
Martin Russin:30. júla 18:19

V lekcii je spomenutá nasledovná štruktúra

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

// ...

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

nemalo by deklarovanie funkcie byť prepísane takto?

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

.
Bola by chyba, ak by som prepísal for cyklus nasledovne?

NODE* posledni_node = NULL;
for(posledni_node = prvni_node; posledni_node->dalsi != NULL; posledni_node = posledni_node->dalsi);
 
Odpovedať
30. júla 18:19
Avatar
DarkCoder
Člen
Avatar
Odpovedá na Martin Russin
DarkCoder:30. júla 18:43

Ano, definování nového typu NODE je chybné.

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

Výše uvedený kód nelze takto napsat, neboť typ NODE není ještě v čase překladu řádky
NODE* dalsi; znám.

Správně by mělo být

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

Inicializace řídící proměnné může být uvnitř ale i vně hlavičky cyklu. Lze to tedy i takto zapsat.

Odpovedať
30. júla 18:43
"„Učíš-li se proto, aby sis zapamatoval, zapomeneš. Učíš-li se proto, abys porozuměl, zapamatuješ si."
Robíme čo je v našich silách, aby bola tunajšia diskusia čo najkvalitnejšia. Preto do nej tiež môžu prispievať len registrovaní členovia. Pre zapojenie sa do diskusie sa zaloguj. Ak ešte nemáš účet, zaregistruj sa, je to zadarmo.

Zatiaľ nikto nevložil komentár - buď prvý!