Programovanie (1) v C/C++
1-INF-127, ZS 2017/18

Úvod · Pravidlá · Prednášky · Netbeans · SVGdraw · Testovač
· Vyučujúcich môžete kontaktovať pomocou e-mailovej adresy E-prg.png (bude odpovedať ten z nás, kto má príslušnú otázku na starosti alebo kto má práve čas).
· Tretia písomka bude v stredu 29.11. o 18:15. Bude pokrývať rekurziu, znaky, reťazce, smerníky (vrátane smerníkov na struct), vektor, matice
· DÚ3 je zverejnená, odovzdávajte do pondelka 20.11. 22:00.
· Prosíme študentov, aby si pravidelne čítali e-maily na @uniba.sk adrese alebo aby si tieto emaily presmerovali na adresu, ktorú pravidelne čítajú, viď návod tu: [1]


Prednáška 10

Z Programovanie
Prejsť na: navigácia, hľadanie

Oznamy

  • Budúci týždeň programovanie nebude - rektorské a dekanské voľno a sviatok
  • Pondelok 6.11. opäť normálny rozvrh (vrátane doplnkových cvičení k tomuto týždňu)
  • DÚ2 odovzdávajte do pondelka 6.11. 22:00.
    • V prípade otázok nás kontaktujte emailom aj budúci týždeň
  • Druhá písomka bude v stredu 8.11. o 18:10. Miestnosť oznámime neskôr. Bude pokrývať učivo po prednášku 9 (funkcie, polia, rekurzia).

Zopár komentárov k DÚ a cvičeniam

  • Odporúčam správne programy odsadzovať
    • je to prehľadnejšie, ľahšie sa hľadajú chyby
    • na DÚ za škaredé programy môžete aj stratiť body
    • v Netbeans môžete vysvietiť program, kliknúť pravým tlačidlom myši a zvoliť Format, preformátuje vám automaticky; v Linuxe odporúčam program astyle
  • Bodujeme len odovzdania spravené načas
    • ak niečo odovzdáte o pár minút neskoro alebo ak máte na neskoré odovzdanie závažný dôvod, môžete nás požiadať o predĺženie termínu, ale iniciatíva musí prísť od vás
  • Ospravedlňujem sa za medzeru na konci riadku v príklade o BubbleSorte (cvičenia 4, príklad 5 bonus). Ak vidíte v zadaní alebo na testovači niečo divné, kontaktujte nás, chybu opravíme.

Rozdeľuj a panuj, rýchle triedenia

Videli sme tri algoritmy na triedenie: bubblesort, insertsort, maxsort

  • Jednoduché, ale pomalé, kvadratická zložitosť O(n^{2})

Dnes si ukážeme dve rýchlejšie triedenia, mergesort a quicksort.

  • obe používajú metódu rozdeľuj a panuj

Rozdeľuj a panuj je všeobecný rekurzívny postup, ktorým sa dá riešiť viacero problémov. Má nasledujúce fázy:

  • Rozdeľuj: rozdelíme problém na nejaké menšie časti (podproblémy), ktoré sa dajú riešiť ďalej samostatne.
  • Vyriešime podproblémy: rekurzívne vyriešime úlohu pre každý podproblém
  • Panuj: spojíme výsledky pre podproblémy do výsledku celkového problému.

Triedenie zlučovaním, MergeSort

V triedení zlučovaním sa pole veľkosti N rozdelí na polovicu najjednoduchším spôsobom - na prvú a druhú polovicu. Tieto rekurzívne utriedime, čím dostávame 2 utriedené postupnosti. Preto vo fáze panuj potrebujeme tieto dve polia zlúčiť (merge) do výsledného poľa.

void mergesort(int a[], int low, int high) {
/* utriedi prvky poľa a od indexu low po index high (vrátane) */

    // triviálny prípad: ak máme 1 alebo 0 prvkov
    if (low >= high) return;

    // rozdeľuj: spočíta stred triedeného úseku
    int mid = (low+high) / 2;

    //rekurzívne volanie na dva podproblémy
    mergesort(a, low, mid);       // triedi prvky od low po mid
    mergesort(a, mid+1, high);    // triedi prvky od mid+1 po high 

    // panuj: zlúči dve utriedené postupnosti do jednej 
    merge(a, low, mid, high);
}

Zlučovanie utriedených postupností

Zostáva nám naprogramovať zlúčenie dvoch utriedených postupností. V prvej verzii predpokladajme, že máme dve utriedené postupnosti A[0..N-1] a B[0..M-1] a chceme ich zlúčiť do utriedeného poľa C dĺžky N+M.

  • Prvým prvkom výslednej postupnosti bude menší z prvkov A[0] a B[0]. Potom ostávajú postupnosti A[1..N-1] a B[0..M-1] alebo A[0..N-1] a B[1..M-1].
  • Vo všeobecnosti máme postupnosti A[i..N-1] a B[j..M-1]. Ďalším prvkom postupnosti bude buď A[i] alebo B[j] a ostanú nám postupnosti A[i+1..N-1] a B[j..M-1] alebo A[i..N-1] a B[j+1..M-1].
  • Toto robíme dovtedy, kým s jedným poľom neskončíme. Vtedy na koniec dokopírujeme zvyšok druhého poľa.

Z toho dostávame nasledovnú funkciu.

void merge(int A[], int N, int B[], int M, int C[]) {
    /* zlúči utriedené pole A dĺžky N a utriedené pole B dĺžky M 
     * do poľa C, ktoré musí mať dĺžku aspoň N+M */

    int i = 0;
    int j = 0;
    while (i < N && j < M) { // kym v oboch poliach zostavaju prvky
        if (A[i] <= B[j]) {
            C[i + j] = A[i]; // vysledok ukladame na (i+j)-te miesto
            i++;
        } else {
            C[i + j] = B[j];
            j++;
        }
    }

    while (i < N) { // ak skoncilo pole B, prekopirujeme zvysok pola A
        C[i + j] = A[i];
        i++;
    }

    while (j < M) {  // ak skoncilo pole A, prekopirujeme zvysok pola B
        C[i + j] = B[j];
        j++;
    }
}

Výsledný MergeSort

Ostáva už iba drobnosť: nezlučujeme dve nezávislé polia, ale prvky jedného poľa a navyše by sme radi, aby tie prvky v tomto poli aj skončili.

  • Funkcia namiesto dvoch vstupných polí dostane iba pole A a pozície začiatkov a koncov úsekov.
    • Keďže zlučované úseky idú za sebou, stačia nám tri indexy: low, mid a high
    • Hodnotu mid by sme si mohli aj dopočítať z low a high.
  • Druhý problém vyriešime pomocným poľom C, ktoré na konci nakopírujeme naspäť do A.
    • Keďže merge už nevolá ďalšie funkcie, v zásobníku bude vždy len jedno pole C, po skončení zlučovania sa vždy vymaže.
#include <iostream>
using namespace std;

const int MAX = 100;

void merge(int A[], int low, int mid, int high) {
    /* V poli A su utriedene useky A[low..mid] a A[mid+1..high].
     * Zluci ich do utriedeneho useku A[low..high]. */

    int C[MAX];       // pomocne pole
    int i = low;      // poloha v prvej catsi pola
    int j = mid + 1;  // poloha v druhej casti pola
    int k = 0;        // poloha v zlucenom poli
    while (i <= mid && j <= high) { // kym v oboch poliach zostavaju prvky
        if (A[i] <= A[j]) {  // pouzi prvok z lavej casti
            C[k] = A[i];
            i++;
            k++;
        } else {            // pouzi prvok z pravej casti
            C[k] = A[j];
            j++;
            k++;
        }
    }

    while (i <= mid) { // ak skoncila prava cast, prekopirujeme zvysok lavej
        C[k] = A[i];
        i++;
        k++;
    }

    while (j <= high) { // ak skoncila lava cast, prekoprujeme zvysok pravej
        C[k] = A[j];
        j++;
        k++;
    }

    for (int k = low; k <= high; k++) {
        A[k] = C[k - low];
    }
}

void mergesort(int A[], int low, int high) {
    /* utriedi prvky pola a od indexu low po index high (vratane) */

    // trivialny pripad: ak mame 1 alebo 0 prvkov
    if (low >= high) return;

    // rozdeluj: spocita stred triedeneho useku
    int mid = (low + high) / 2;

    //rekurzivne volanie na dva podproblemy
    mergesort(A, low, mid);
    mergesort(A, mid + 1, high);

    // panuj: zluci dve utriedene postupnosti do jednej
    merge(A, low, mid, high);
}

int main(void) {
    const int N = 10;
    int A[N] = {13, 1, 5, 7, 2, 4, 8, 10, 3, 24};
    mergesort(A, 0, N - 1);
    for (int i = 0; i < N; i++) cout << A[i] << " ";
}

Ukážka na príklade

Pole A[]= {6, 1, 5, 7, 2, 4, 8, 9, 3, 0};

Všetky volania:

sort(0,9) sort(0,4) sort(0,2) sort(0,1) sort(0,0) 
          .         .         .         sort(1,1)
          .         .         .         merge(0,0,1)
          .         .         sort(2,2)
          .         .         merge(0,1,2)
          .         sort(3,4) sort(3,3)
          .         .         sort(4,4)
          .         .         merge(3,3,4)
          .         merge(0,2,4)
          sort(5,9) sort(5,7) sort(5,6) sort(5,5)
          .         .         .         sort(6,6)
          .         .         .         merge(5,5,6)
          .         .         sort(7,7)
          .         .         merge(5,6,7)
          .         sort(8,9) sort(8,8)
          .         .         sort(9,9)
          .         .         merge(8,8,9)
          .         merge(5,7,9)
          merge(0,4,9)

Čo sa deje v poli:

merge(A,0,0,1): |6|1|5 7 2 4 8 9 3 0 -> |1 6|5 7 2 4 8 9 3 0
merge(A,0,1,2): |1 6|5|7 2 4 8 9 3 0 -> |1 5 6|7 2 4 8 9 3 0
merge(A,3,3,4):  1 5 6|7|2|4 8 9 3 0 ->  1 5 6|2 7|4 8 9 3 0
merge(A,0,2,4): |1 5 6|2 7|4 8 9 3 0 -> |1 2 5 6 7|4 8 9 3 0
merge(A,5,5,6):  1 2 5 6 7|4|8|9 3 0 ->  1 2 5 6 7|4 8|9 3 0
merge(A,5,6,7):  1 2 5 6 7|4 8|9|3 0 ->  1 2 5 6 7|4 8 9|3 0
merge(A,8,8,9):  1 2 5 6 7 4 8 9|3|0|->  1 2 5 6 7 4 8 9|0 3|
merge(A,5,7,9):  1 2 5 6 7|4 8 9|0 3|->  1 2 5 6 7|0 3 4 8 9|
merge(A,0,4,9): |1 2 5 6 7|0 3 4 8 9|-> |0 1 2 3 4 5 6 7 8 9|

Odhad zložitosti

Zlučovanie dvoch postupností, ktoré spolu obsahujú N prvkov, trvá čas O(N). Prečo?

V algoritme máme \log _{2}N úrovní rekurzie, lebo na prvej spracovávame úseky dĺžky N, na druhej N/2 na tretej N/4 atď, až pol \log _{2}N úrovniach dostaneme úseky dĺžky 1. Na každej úrovni je každý prvok najviac v jednom zlučovaní, teda celkový čas zlučovaní na každej úrovni je O(N). Celkový čas výpočtu je O(N log N).

Quicksort

Quicksort je tiež založený na metóde rozdeľuj a panuj. Postupuje nasledovne:

  • Rozdeľuj: prvky poľa rozdelí na dve skupiny: prvky menšie ako nejaké x a prvky väčšie alebo rovné x
    • hodnotu x nazývame pivot
  • Rekurzívne utriedi obe skupiny prvkov a dostáva dve utriedené postupnosti - utriedenú postupnosť menších prvkov a utriedenú postupnosť väčších prvkov.
  • Panuj: stačí dať tieto utriedené postupnosti za seba (najskôr menšie, potom väčšie)
void quicksort(int A[], int low, int high) {
    /* utriedi prvky v A medzi low a high (vratane) */
    
    if (low >= high) return;  // trivialny pripad: 0 alebo 1 prvok

    // rozdelenie: mid je posledny index medzi mensimi prvkami
    int mid = partition(A, low, high);

    quicksort(A, low, mid);     // rekurzivne utried mensie prvky
    quicksort(A, mid + 1, high); // rekurzivne utried vacsie prvky

    //zlucenie nebude treba - su spravne za sebou
}

Čo sa stane, ak funkcia partition zvolí taký pivot, že jedna zo skupín je prázdna?

Aby sme sa takýmto problémom vyhli, upresníme trochu úlohu funkcie partition:

  • ako pivot x si zvolí jeden z prvkov A[low..high]
  • preusporiada túto časť poľa tak, že
    • A[low..mid-1] obsahuje prvky < x
    • A[mid+1..high] onsahuje prvky >= x
    • A[mid] obsahuje x, ktorý je teda už na svojom finálnom mieste
  • vráti index mid

Potom stačí rekurziu volať na A[low..mid-1] a A[mid+1..high]

Ak sme teda začali s N prvkami, ľavá aj pravá časť poľa bude mať najviac N-1 prvkov.

Rozdelenie prvkov, funkcia partition

Jednoduchá implementácia funkcie partition by mohla používať dve pomocné polia: do jedného si dá menšie prvky, do druhého väčšie.

Dá sa však ľahko napísať priamo v poli A, bez pomocných polí. Počas algoritmu budeme udržiavať nasledujúci invariant:

  • na indexe low je pivot x
  • na indexoch low+1..lastLeft sú prvky menšie ako x
  • na indexoch lastLeft+1..unknown-1 sú prvky >= x
  • na indexoch unknown..high sú prvky, ktoré sme ešte s x neporovnali

V každom kroku porovnáme A[unknown] s x

  • ak je menší, potrebujeme ho dať do ľavej časti, vymeníme ho teda s A[lastLeft+1] a rozšírime ľavú časť
  • ak je >= x, jednoducho rozšírime pravú časť

Nakoniec ešte vymeníme A[low] a A[lastLeft], čím sa pivot dostane na svoje miesto

int partition(int A[], int low, int high) {
    /* vrati index mid (low<=mid<=high)
     * a preusporiada prvky v A[low..high] tak, ze plati
     * A[low..mid-1] obsahuje prvky < A[mid]
     * A[mid+1..high] obsahuje prvky >= A[mid] */

    // ak chceme pouzit iny prvok ako pivot, vymenime ho s A[low]
    int pivot = A[low];
    int lastLeft = low;
    for (int unknown = low + 1; unknown <= high; unknown++) {
        if (A[unknown] < pivot) {
            lastLeft++;
            swap(A[unknown], A[lastLeft]);
        }
    }
    swap(A[low], A[lastLeft]);
    return lastLeft;
}

Výsledný Quicksort

#include <iostream>
using namespace std;

void swap(int &x, int &y) {
    /* Vymeň hodnoty premenných x a y. */
    int tmp = x;
    x = y;
    y = tmp;
}

int partition(int A[], int low, int high) {
    /* vrati index mid (low<=mid<=high)
     * a preusporiada prvky v A[low..high] tak, ze plati
     * A[low..mid-1] obsahuje prvky < A[mid]
     * A[mid+1..high] obsahuje prvky >= A[mid] */

    // ak chceme pouzit iny prvok ako pivot, vymenime ho s A[low]
    int pivot = A[low];
    int lastLeft = low;
    for (int unknown = low + 1; unknown <= high; unknown++) {
        if (A[unknown] < pivot) {
            lastLeft++;
            swap(A[unknown], A[lastLeft]);
        }
    }
    swap(A[low], A[lastLeft]);
    return lastLeft;
}

void quicksort(int A[], int low, int high) {
    /* utriedi prvky v A medzi low a high (vratane) */
    
    if (low >= high) return;  // trivialny pripad: 0 alebo 1 prvok

    // rozdelenie: mid je posledny index medzi mensimi prvkami
    int mid = partition(A, low, high);

    quicksort(A, low, mid - 1);     // rekurzivne utried mensie prvky
    quicksort(A, mid + 1, high); // rekurzivne utried vacsie prvky
}

int main(void) {
    const int N = 10;

    int A[N] = {6, 1, 5, 7, 2, 4, 8, 9, 3, 0};
    quicksort(A, 0, N - 1);
    for (int i = 0; i < N; i++) cout << A[i] << " ";
}

Príklad

Volania funkcií

sort(0,9) sort(0,5) sort(0,-1)
          .         sort(1,5) sort(1,0)
          .         .         sort(2,5) sort(2,4) sort(2,2)
          .         .         .         .         sort(4,4)
          .         .         .         sort(6,5)
          sort(7,9) sort(7,8) sort(7,7)
                    .         sort(9,8)
                    sort(10,9)

Čo robí partition:

partition(0,9): |6 1 5 7 2 4 8 9 3 0| -> |0 1 5 2 4 3|6|9 7 8|
partition(0,5): |0 1 5 2 4 3|6 9 7 8  -> |0|1 5 2 4 3|6 9 7 8
partition(1,5):  0|1 5 2 4 3|6 9 7 8  ->  0|1|5 2 4 3|6 9 7 8
partition(2,5):  0 1|5 2 4 3|6 9 7 8  ->  0 1|3 2 4|5|6 9 7 8
partition(2,4):  0 1|3 2 4|5 6 9 7 8  ->  0 1|2|3|4|5 6 9 7 8
partition(7,9):  0 1 2 3 4 5 6|9 7 8| ->  0 1 2 3 4 5 6|8 7|9|
partition(7,8):  0 1 2 3 4 5 6|8 7|9  ->  0 1 2 3 4 5 6|7|8|9

Cvičenie:

  • Ako sa bude Quicksort správať, keď mu dáme utriedené pole?
  • Ako sa bude správať, ak budú prvky v poli od najväčšieho po najmenšie?

Odhad zložitosti

V ideálnom prípade pivot rozdelí pole na dve rovnako veľké časti. Vtedy je čas výpočtu O(N log N), podobne ako pre Mergesort.

Nepríjemné je, keď pivot je vždy najmenší alebo najväčší prvk v danom úseku. Vtedy je čas O(N^{2}). Dobrá správa je, že takýchto prípadov nie je príliš veľa.

  • Aby sme sa vyhli problémom, ak je vstupné pole (takmer) utriedené, môžeme ho pred triedením náhodne premiešať alebo vyberať ako pivot náhodný prvok z intervalu

Iná implementácia

Občas sa môžete stretnúť aj s takto napísaným quicksortom. Skúste si premyslieť, prečo funguje a prečo si môžeme dovoliť niektoré prvky vynechať.

void quickSort(int A[], int left, int right) {
    if (left>=right) return;
    int i = left, j = right;
    int pivot = A[(left + right) / 2];

    /* partition */
    while (i <= j) {
        while (A[i] < pivot) i++;
        while (A[j] > pivot) j--;
        if (i <= j) {
            int tmp = A[i]; A[i] = A[j]; A[j] = tmp;
            i++; j--;
        }
    };

    /* rekurzia */
    quickSort(A, left, j);
    quickSort(A, i, right);
}

Zhrnutie: triedenia

Jednoduché triedenia: bubblesort, insertsort, maxsort

  • Jednoduché, ale pomalé, zložitosť O(n^{2})

Rekurzívne triedenia, rozdeľuj a panuj

  • Rýchlejšie, zložitejšie
  • Mergesort, zložitosť O(n\log n)
  • Quicksort, zložitosť O(n^{2}) v najhoršom prípade, pre väčšinu stupov O(n\log n), väčšinou rýchlejší ako Mergesort

Príklad: na mojom počítači som triedila pol milióna náhodne permutovaných čísel programami z prednášok

  • quicksort: 0.25s
  • mergesort: 0.27s
  • insertionsort: 58s

Znaky

Doteraz sme pracovali iba s číselnými dátami, ale pri programovaní často pracujeme z reťazcami (textami).

  • Reťazce budú na ďalšej prednáške, dnes si ukážeme, ako pracovať s ich jednotlivými súčasťami, znakmi (písmená, čísla, medzery,...)
  • Znakové konštanty sa zapisujú v apostrofoch, napr. 'A', '1', ' ' a pod.
  • Znakové premenné sú typu char, z anglického character. Ich veľkosť je spravidla 1 bajt, t.j. 8 bitov.

Znaky majú svoje kódy uvedené v tabuľke ASCII. Najbežnejšie sa budeme stretávať s týmito:

  • 48...57: '0'...'9'
  • 65...90: 'A'...'Z'
  • 97...122: 'a'...'z'
  • 32: medzera ' '
  • 9: tabulátor '\t'
  • 10: koniec riadku '\n'
  • 0: špeciálny nulový znak (uvidíme nabudúce) '\0'

Poznámky

  • bežné znaky z US klávesnice sú v rozsahu 0..127 (7 bitov)
  • nakoľko char je 8-bitový, môže ešte nadobúdať hodnoty -1 ... -128 alebo 128..255 podľa kompilátora
  • moderný softvér väčšinou namiesto klasických 8-bitových znakov používa Unicode, aby sa dali reprezentovať aj rôzne špeciálne symboly, znaky s diakritikou, jazyky nepoužívajúce latinku a pod.
  • na tomto predmete si vystačíme s klasickými znakmi v rozsahu 0..127
  • znaky sa dajú zapísať aj pomocou ich kódu v osmičkovej alebo šestnástkovej sústave: '\101' a '\x41' reprezentujú znak s kódom 65, t.j. 'A'.

Do premennej typu char môžeme priraďovať, jej obsah zapísať alebo prečítať:

  char c='A';
  char z;
  z=c;
  cout << c;
  cin >> z;  // prečíta jeden znak (pozor, preskakujú sa biele znaky)

Znaky môžeme porovnávať. Na konci programu vyššie platí nasledovné:

  • c=='A' ... je pravda,
  • c=='a' ... nie je pravda – rozlišujú sa malé a veľké písmená,
  • c<='Z' ... je pravda – písmená sú usporiadané: A<B< ... <Z, a<b< ... <z, aj cifry sú usporiadané: 0<1< ... <9.

Pri čítaní zo vstupu pomocou cin do premennej typu char sa preskakujú tzv. biele znaky (napr. medzera, tabulátor, koniec riadku).

  • Toto nie je vždy žiadúce a preto môžeme použiť modifikátor noskipws, ktorý zruší preskakovanie takýchto znakov. Do premennej teda budeme vedieť prečítať aj medzeru.
#include <iostream>
using namespace std;

int main(void) {
  char a,b,c; 
  cin >> noskipws >> a >> b >> c;
  cout << a << b << c;
}

Hodnota jednoduchého výrazu

Nasledujúci program spočíta hodnotu jednoduchého výrazu, ktorý pozostáva z dvoch čísel spojených znamienkom +, -, * alebo /. Okolo sú medzery (kvôli jednoduchšiemu načítaniu).

Napr. pre vstup 1 / 2 program vypíše 0.5.

#include <iostream>
#include <cstdlib>
using namespace std;

int main(void) {
    double a, b;
    char znamienko;
    cin >> a >> znamienko >> b;
    double vysledok;
    if (znamienko == '+') {
        vysledok = a + b;
    }
    else if (znamienko == '-') {
        vysledok = a - b;
    }
    else if (znamienko == '*') {
        vysledok = a * b;
    }
    else if (znamienko == '/') {
        vysledok = a / b;
    } else {
        cout << "zle znamienko " << znamienko << endl;
        exit(1);
    }
    cout << vysledok << endl;
}

Switch

  • V predchádzajúcom programe bola pomerne dlhá a komplikovaná séria príkazov if, else
  • Namiesto toho sa dá použiť príkaz switch, ktorý podľa hodnoty výrazu pokračuje jednou z viacerých vetiev.

V našom jednoduchom príklade by mohol switch vyzerať nasledovne:

    switch (znamienko) {
    case '+' :
        vysledok = a + b;
        break;
    case '-' :
        vysledok = a - b;
        break;
    case '*' :
        vysledok = a * b;
        break;
    case '/' :
        vysledok = a / b;
        break;
    default:
        cout << "zle znamienko " << znamienko << endl;
        exit(1);
    }

Vo všeobecnosti obsahuje príkaz switch viacero rôznych prípadov vyhodnotenia výrazu v podmienke.

switch (výraz)
{
case k1: príkazy1
case k2: príkazy2
default: príkazyd
}

Takýto príkaz funguje nasledovne:

  • Vyhodnotí výraz.
    • Ak sa hodnota zhoduje s konštantným výrazom ki v niektorom z prípadov, pokračuje časťou príkazyi
    • Ak sa nezhoduje a máme vetvu default, pokračuje sa časťou prikazyd
    • Ak nie je vetva default, pokračuje sa za koncom switch bloku.
  • Pozor: Na rozdiel od pascalovského case, vykonávanie nekončí vykonaním posledného príkazu v prikazyi, ale pokračuje ďalej, kým nie je prerušené príkazom break.
    • Toto je častá chyba pri použití príkazu switch
#include <iostream.h>

void main () {
  int n;
  cout << "\n" << "Zadaj n (1,2,3,4): ";
  cin >> n;
  switch (n) {
    case 1: cout << "Jeden\n";
    case 2: cout << "Dva\n";
    case 3:
    case 4: cout << "Tri alebo styri\n";
    default: cout << "Chyba!\n";
  }
  cout << "Koniec.\n";
}

Pre n=2 sa začnú vykonávať príkazy uvedené za vetvou case 2:. Vypíše sa:

    Dva
    Tri alebo styri
    Chyba!
    Koniec.

Výhodou je, že môžeme zlúčiť viacero prípadov do jednej vetvy tým, ze príkazy napíšeme až za posledný prípad (tu vidíme napr. v situácii n=3 a n=4).

Dôležité upozornenie: break switch while

Predstavme si, že v programe sa pýtame užívateľa, či chce pokračovať s ďalším vstupom, pričom odpoveď má byť znak 'A' alebo 'N'. Ak by sme program napísali takto, nefungoval by:

    while (true) {
        // nejaky vypocet
        cout << "Chcete pokracovat? (Zadajte odpoved A alebo N)" << endl;
        char odpoved;
        cin >> odpoved;
        switch (odpoved) {
        case 'N':
            break;
        // spracovanie inych pripadov...
        }
    }
  • Príkaz break nevyskočí zo všetkých cyklov, ale iba z najvnútornejšieho - a tým je v tomto prípade switch. Program teda pokračuje aj keď užívateľ zadá 'N'.
  • Ak by break bol použitý s podmienkou, všetko by fungovalo: while (true) { ... if (odpoved=='N') { break; } }


Ešte znaky

Pretypovanie

Znakové premenné teda ukladajú kódy jednotlivých znakov, čo sú celé čísla. Preto medzi znakmi a celými číslami môžeme prechádzať úplne jednoducho.

#include<iostream>
using namespace std;

int main(void) {
  int N;
  char c;

  cout << "Napiste cislo: ";
  cin >> N;                     // prečíta číslo 
  c = N;                        // do znakovej premennej môžeme číslo priradiť bez problémov
  cout << c << endl;            // vieme vypísať znak
  cout << (c+1) << endl;        // ale keď už použijeme aritm. operáciu, je to ako číslo

  cout << "Napiste znak: ";     
  cin >> c;                     // prečíta znak
  N = c;                        // do celočíselnej premennej ho priamo vieme priradiť
  cout << N << endl;            // vypíšeme ho ako číslo
}

Okrem toho však vieme urobiť aj pretypovanie, keď chceme aby výsledok bol konkrétneho typu. Napríklad, aby nám v prvej časti vypísalo nie ďalší kód ale ďalší znak, mohli sme výsledok pretypovať.

  cout << (char)(c+1) << endl;          // vďaka pretypovaniu dostávame na výpise znak

Pretypovanie sme už používali, keď sme chceli dva inty vydeliť bez zaokrúhlenia na celé číslo:

  int a = 5;
  int b = 2;
  cout << a/b << " " << (double)(a/b) << " " << ((double)a)/b << endl;

Tento program vypíše nasledovný výstup:

2 2 2.5