Programovanie (1) v C/C++
1-INF-127, ZS 2018/19

Úvod · Pravidlá · Prednášky · Netbeans a Kate · 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).
· 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]
· Prvý test bude v stredu 17.10. o 18:10 v posluchárni B. Pokrýva učivo prednášok 1-4, resp. cvičení 1 až 3 (cin, cout, premenné, výrazy, podmienky, cykly, funkcie, struct). Prineste si perá a ťahák v rozsahu jedného listu (dvoch strán) A4.
· DÚ1 je zverejnená, odovzdávajte do piatku 26.10. 22:00


Prednáška 2

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

Opakovanie

Doteraz sme videli:

  • Načítavanie pomocou cin, výpis pomocou cout.
  • Celočíslené premenné typu int.
  • Podmienky (if).
#include <iostream>
using namespace std;

int main(void) {
    int x;
    cout << "Zadajte cislo: ";
    cin >> x;

    if (x < 0) {
        cout << "Cislo " << x << " je zaporne." << endl;
    } else {
        cout << "Cislo " << x << " je nezaporne." << endl;
    }
    return 0;
}

Komentáre

Do zdrojových kódov programov v jazykoch C a C++ je možné pridávať komentáre, čo sú časti kódu ignorované kompilátorom. Možno tak urobiť dvoma spôsobmi:

  • Za komentár sa považuje akákoľvek časť programu začínajúca /* a končiaca */. Táto konštrukcia môže byť aj na viac riadkov.
  • Čokoľvek za // až po koniec riadku sa tiež považuje za komentár. To je užitočné na písanie krátkych komentárov na jeden riadok.

Poznámka: Staršie štandardy jazyka C (bez ++) nepodporujú notáciu // pre jednoriadkové komentáre.

#include <iostream>
using namespace std;

int main() {
    cout << "Som program s komentarmi." << endl; // Som komentar na jeden riadok.
    /* Som komentar
       na
       velmi
       vela
       riadkov
    */
    return 0;
}

Dátové typy int, double a bool

Na začiatok budeme pracovať s troma dátovými typmi:

  • S typom int pre celé čísla – príkladmi konštánt typu int1, 42, -2, alebo 0.
  • S typom double pre reálne čísla (s pohyblivou rádovou čiarkou a dvojnásobnou presnosťou) – príkladmi konštánt typu double4.2, -3.0, 3.14159, alebo 1.5e3 (v poslednom prípade ide o tzv. semilogaritmický zápis v tomto prípade znamenajúci 1,5\cdot 10^{3}, t.j. 1500).
  • S typom bool pre logické hodnoty – jedinými konštantami sú true (ekvivalentná číselnej hodnote 1) a false (ekvivalentná číselnej hodnote 0).

Poznámka: V jazyku C++ ide vo všetkých troch prípadoch o primitívne (zabudované) typy. V jazyku C je potrebné na prácu s typom bool použiť knižnicu stdbool.h a staršie štandardy ho nepodporujú vôbec. Typ bool sa však vždy dá nahradiť napríklad typom int.

Rozsahy (čiže „množiny všetkých reprezentovateľných čísel”) premenných typov int a double sú obmedzené. Tieto obmedzenia sa môžu líšiť v závislosti od konkrétnej architektúry. V súčasnosti však:

  • Typ int zvyčajne zaberá 4 bajty (32 bitov) pamäte a dajú sa ním reprezentovať celé čísla z intervalu <-2 147 483 648, +2 147 483 647>. (Na 64-bitových architektúrach môže niekedy ísť až o 8 bajtov.)
  • Typ double zvyčajne zaberá 8 bajtov. Ním reprezentované reálne čísla sú v pamäti uložené vo forme z\cdot a\cdot 2^{b}, kde z je znamienko, a je reálne číslo z intervalu <1,2) (mantisa) a b je celé číslo (exponent). Na uloženie mantisy sa používa 52 bitov a na uloženie exponentu 11 bitov. Typ double tak možno použiť na prácu s reálnymi číslami približne v rozsahu od 10^{{-300}} po 10^{{300}} s presnosťou na 15 až 16 platných číslic. Pri takejto reprezentácii sa zjavne nevyhradzujú pevné počty bitov na reprezentáciu celej resp. destatinnej časti; počet cifier pred a za rádovou čiarkou je naopak určený exponentom. Hovoríme preto o pohyblivej rádovej čiarke.

Pretypovanie

Hodnotu niektorého z typov bool, int, double je možné skonvertovať na „zodpovedajúcu” hodnotu ľubovoľného ďalšieho z týchto typov. V takom prípade hovoríme o pretypovaní. Platí pritom nasledujúce:

  • Konverzia z „menej všeobecného” typu na „všeobecnejší” sa správa očakávateľným spôsobom. Booleovské hodnoty 0 resp. 1 sa teda konvertujú na celé čísla 0 resp. 1, prípadne na reálne čísla 0.0 resp, 1.0; celé číslo sa tiež konvertuje na reálne číslo, ktoré je mu rovné.
  • Konverzia zo „všeobecnejšieho” typu na „menej všeobecný” dodržiava určité vopred stanovené pravidlá. Napríklad pri konverzii z int alebo double na bool sa ľubovoľná nenulová hodnota skonvertuje na true a nula sa skonvertuje na false. Pri konverzii z double na int dôjde k zaokrúhleniu smerom k nule (čiže nadol pri kladných číslach, nahor pri záporných).

Pretypovanie je možné realizovať dvoma spôsobmi:

  • Implicitne, napríklad priradením premennej jedného typu do premennej iného typu, alebo vo všeobecnosti použitím premennej jedného typu v kontexte, kde sa očakáva premenná druhého typu.
  • Explicitne, použitím pretypovacieho operátora: (nazov_noveho_typu) vyraz_stareho_typu.

Možnosti pretypovania sú ilustrované nasledujúcim ukážkovým programom. Čitateľ si môže sám skúsiť vymyslieť preň niekoľko vstupov, odhadnúť výstupy programu na týchto vstupoch a následne svoj odhad overiť spustením programu.

#include <iostream>
using namespace std;

int main() {
  bool b1 = true;
  int n1 = 4;
  double x1 = 1.234;

  bool b2;
  int n2;
  
  b2 = n1; // b2 = 1
  n2 = x1; // n2 = 1 
  cout << b2 << " " << n2 << endl; // Vypise 1 1
  
  x1 = n2; // x1 = 1.0
  cout << x1 << endl; // Vypise 1
  
  cout << (bool) 7 << " " << (bool) 0 << endl; // Vypise 1 0 
  cout << (int) 4.2 << " " << (int) -4.2 << endl; // Vypise 4 -4
  
  return 0;
}

Aritmetické operátory a výrazy

Základnými štyrmi aritmetickými operátormi na celých číslach (napríklad typu int) aj reálnych číslach (napríklad typu double) sú:

  • + pre sčítanie;
  • - pre odčítanie;
  • * pre násobenie;
  • / pre delenie.

Operátor / sa pritom na argumentoch typu int správa ako celočíselné delenie – hodnota podielu sa zaokrúhli smerom k nule. Napríklad výraz 5/3 teda má hodnotu 1. Akonáhle je však aspoň jeden operand typu double, interpretuje sa operátor / ako delenie reálnych čísel. Výrazy 5.0/3.0, 5.0/3, 5/3.0 a 5/(double)3 teda majú všetky hodnotu 1.66667. Výstup aritmetickej operácie má navyše rovnakú hodnotu ako „všeobecnejší” z operandov. Napríklad 2+3.0 je teda typu double a výraz (2+3.0)/3 tak má hodnotu 1.66667; naopak výraz (2+3)/3 má hodnotu 1.

#include <iostream>
using namespace std;

int main() {
    int a = 4;
    int b = 3;
    double d = 3; // Automaticky pretypuje cele cislo 3 na realne cislo 3.0

    cout << a / b << endl; // Celociselne delenie: 4 / 3 = 1
    cout << a / d << endl; // Necelociselne delenie: 4 / 3.0 = 1.33333
    cout << (1.0 * a) / b << endl; // Necelociselne delenie: (1.0 * 4) / 3 = 4.0 / 3 = 1.33333
    cout << ((double)a) / b << endl; // Necelociselne delenie: 3.0 / 4 = 1.33333 
    
    double e = a / b; // Do e je priradeny vysledok celociselneho delenia 4 / 3 = 1; po pretypovani je to rovne 1.0
    cout << e << endl; // Vypise 1
    cout << e / 2 << endl; // Vypise 0.5, lebo 1.0 / 2 je necelociselne delenie  
    
    return 0;
}
  • Na celých číslach je ďalej definovaný operátor %, ktorého výstupom je zvyšok po celočíselnom delení jeho operandov. Napríklad výraz 5%3 tak má hodnotu 2.
  • Ďalšie matematické operácie a funkcie vyžadujú #include <cmath> (pre jazyk C++) resp. #include <math.h> (pre jazyk C, ale funguje aj v C++) v hlavičke programu:
    • Napríklad cos(x), sin(x), tan(x) (tangens), acos(x) (arkus kosínus), exp(x) (e^{x}), log(x) (prirodzený logaritmus), pow(x,y) (x^{y}), sqrt(x) (odmocnina), abs(x) (absolútna hodnota), floor(x) (dolná celá časť), ...
    • Viac detailov možno nájsť v dokumentácii.

Relačné operátory

Hodnoty typov int, double a bool možno porovnávať nasledujúcimi relačnými operátormi:

  • == pre rovnosť;
  • != pre nerovnosť;
  • < pre reláciu „menší ako”;
  • > pre reláciu „väčší ako”;
  • <= pre reláciu „menší alebo rovný ako”;
  • >= pre reláciu „väčší alebo ako”.

Výstupom relačného operátora je potom booleovská hodnota 1 (true), ak je daná relácia splnená a 0 (false) v opačnom prípade.

Poznámka: V jazyku C narozdiel od C++ nemusí pri výstupoch relačných operácií vždy ísť o booleovskú hodnotu, ale výstupom môže byť aj celé číslo, ktoré je nenulové práve vtedy, keď je daná relácia splnená. Pri väčšine kompilátorov je však týmto nenulovým číslom aj tak číslo 1.

Logické operátory a výrazy

Na výrazoch typu bool sú v jazykoch C a C++ definované logické operátory, ktoré sa správajú rovnako ako logické spojky známe z výrokovej logiky:

  • || pre disjunkciu;
  • && pre konjunkciu;
  • ! pre negáciu – tu ide o unárny operátor, dobre utvoreným logickým výrazom je napríklad !((1 >= 2) && (3 >= 4)) alebo !true.

Operátory priradenia, inkrementu a dekrementu

Operátor priradenia sme už využívali, má však jednu (občas jemne zákernú) črtu, o ktorej sme zatiaľ nehovorili. Napríklad výraz a = b priradí premennú b do premennej a; okrem toho však má aj sám o sebe hodnotu, ktorou je nová hodnota premennej a. To umožňuje písať napríklad cout << (a = b) << endl;.

Často realizovanou operáciou na číslach je zvýšenie hodnoty o 1. To možno urobiť napríklad nasledujúcimi spôsobmi:

  • x=x+1;
  • x+=1;
  • x++;
  • ++x.

Analogicky sú definované operátory ako --, -=, *=, atď.

Viac o podmienkach

Logické výrazy môžu byť efektívnym nástrojom na elimináciu množstva vnorených podmienok: napríklad konštrukcia typu

if (a == 0) {
    if (b == 0) {
        čokoľvek
    }
}

je ekvivalentná konštrukcii

if (a == 0 && b == 0) {
    čokoľvek
}

a podobne.

Každé nenulové prirodzené číslo sa pri pretypovaní na bool zmení na 1 (čiže true). V dôsledku toho je korektná napríklad aj podmienka

if (42) {
    čokoľvek
}

a čitateľ sa môže sám presvedčiť o tom, že telo takejto podmienky sa vždy vykoná. Táto črta, spoločne s vlastnosťami operátora priradenia, je príčinou častej programátorskej chyby demonštrovanej nasledujúcim ukážkovým programom.

#include <iostream>
using namespace std;

int main() {
    int a = 1;
    int b = 2;
    if (a == b) {
        cout << "Prva podmienka splnena." << endl; // Nevypise sa, lebo 1 sa nerovna 2
    }
    if (a = b) {
        cout << "Druha podmienka splnena." << endl; // Vypise sa, lebo a = b ma hodnotu 2, co je nenulove cislo 
    } 
    return 0;
}

Priorita a asociativita operátorov

Výrazy sa vyhodnocujú v nasledujúcom poradí preferencie jednotlivých operátorov. Operátory v jednom riadku majú rovnakú prioritu a operátory vo vyššom riadku majú vyššiu prioritu, než operátory v nižších riadkoch.

  • ++ (inkrement), -- (dekrement), ! (logická negácia)
  • *, /, %
  • +, -
  • <, >, <=, >=
  • ==, !=
  • && (logická konjunkcia)
  • || (logická disjunkcia)
  • = (priradenie)

Poradie vyhodnocovania je možné meniť zátvorkami, ako napríklad vo výraze 4*(5-3).

Uvedené operátory sa väčšinou vyhodnocujú zľava doprava (hovoríme, že sú zľava asociatívne) – napríklad 1 - 2 - 3 sa teda vyhodnotí ako (1 - 2) - 3, t.j. -4 a nie ako 1 - (2 - 3), t.j. 2. Výnimkou sú operátory !, ++, -- a =, ktoré sú sprava asociatívne. To umožňuje napríklad viacnásobné priradenie a = b = c, ktoré najprv priradí hodnotu c do b a následne hodnotu výrazu b = c – tou je nová hodnota premennej b, čiže hodnota premennej c –, do a.

Viac sa o operátoroch v C++ možno dočítať napríklad tu.

Cyklus for

Doposiaľ sme písali programy, ktoré na každom vstupe vykonajú nejaký konečný (a hlavne od vstupu nezávislý) počet krokov a zastavia sa. Je zrejmé, že možnosti takýchto programov sú veľmi obmedzené. Stačí napríklad uvažovať program, ktorý má postupne vypisovať prirodzené čísla od 1 po nejaké n:

  • Ak n = 10, môžeme sa pokúsiť všetky čísla vypísať ručne.
  • Pre n = 100 je to už pomerne prácne.
  • Pre n = 1000 by sa o to už málokto pokúšal.
  • Pre n dané na vstupe to z pohľadu teoretika uvažujúceho n ako ľubovoľne veľké ani možné nie je (a nie je to možné ani z pohľadu praktika, akurát z trochu odlišných dôvodov).

V nasledujúcom sa zoznámime s prvou z niekoľkých konštrukcií umožňujúcich opakovanie nejakej skupiny príkazov. Pôjde o takzvaný cyklus for.

Príklad č. 1: vypisovanie čísel od 1 po n

Nasledujúci program načíta zo vstupu číslo n a postupne vypíše prirodzené čísla od 1 po n oddelené medzerami.

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    for (int i = 1; i <= n; i++) {
        cout << " " << i;
    }
    cout << endl;
    return 0;
}

Tu je výstup programu pre n = 9:

 1 2 3 4 5 6 7 8 9

Základné použitie cyklu for

Novou črtou programu z predchádzajúceho príkladu je konštrukcia pre cyklus for:

for (int i = 1; i <= n; i++) {
    telo cyklu
}
  • Táto konštrukcia pozostáva z kľúčového slova for nasledovaným zátvorkou s troma „časťami” oddelenými bodkočiarkami:
    • Príkaz int i = 1 vytvorí novú celočíselnú premennú i a priradí jej hodnotu 1.
    • Podmienka i <= n určuje dokedy sa má cyklus opakovať. V tomto prípade to má byť kým je hodnota premennej i menšia alebo rovná n.
    • Príkaz i++ hovorí, že po každom „zopakovaní” cyklu (t.j. po každej jeho iterácii) sa má hodnota premennej i zvýšiť o jedna.
  • Medzi zloženými zátvorkami { a } je potom tzv. telo cyklu – čiže jeden alebo viac príkazov, ktoré sa budú opakovať postupne pre rôzne hodnoty premennej i.

V príklade č. 1 je telom cyklu iba príkaz cout << " " << i;, ktorý vypíše medzeru a hodnotu premennej i.

Nateraz ostaňme pri tomto intuitívnom chápaní cyklu for. Detaily jeho funkcionality budú jednou z tém nasledujúcej prednášky.

Poznámka: Staršie štandardy jazyka C (bez ++) nepodporujú definíciu premennej priamo v cykle for (ako napríklad int i = 1 v našom príklade). Premennú je potom nutné zadefinovať samostatne pred začatím cyklu.

Príklad č. 2: vypisovanie čísel od 0 po n-1

Drobnou zmenou predchádzajúceho programu môžeme napríklad vypísať všetky čísla od 0 po n-1:

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; i++) {
        cout << " " << i;
    }
    cout << endl;
    return 0;
}

Tu je výstup programu pre n = 9:

 0 1 2 3 4 5 6 7 8

Príklad č. 3: simulovanie hodov kocky

Nasledujúci program od používateľa načíta číslo n a vypíše n simulovaných hodov kocky (každý na samostatný riadok).

#include <iostream>
#include <cstdlib>
#include <ctime>

using namespace std;

int main() {
    srand(time(NULL)); // Inicializacia generatora pseudonahodnych cisel
 
    int n;
    cout << "Zadajte pocet hodov: ";
    cin >> n;
    
    for (int i = 1; i <= n; i++) {
        cout << rand() % 6 + 1 << endl; // Vygenerovanie a vypisanie hodu kockou
    }
  
    return 0;
}

Príklad behu programu:

Zadajte pocet hodov: 6
2
6
1
2
1
4
  • Program využíva funkciu rand(), ktorá generuje pseudonáhodné celé čísla. (Nie sú v skutočnosti náhodné, lebo ide o pevne definovanú matematickú postupnosť, ktorá však má mnohé vlastnosti náhodných čísel). Aby bolo možné použiť túto funkciu, treba do hlavičky pridať #include <cstdlib>.
    • Výstupom funkcie rand() je nezáporné celé číslo medzi nulou a nejakou veľkou konštantou.
    • Zvyšok po delení tohto čísla šiestimi, t.j. rand() % 6, je potom číslo medzi 0 a 5. Ak k tomu pripočítame 1, dostaneme číslo od 1 po 6.
  • Funkcia srand inicializuje generátor pseudonáhodných čísel na základe parametra „určujúceho bod pseudonáhodnej postupnosti”, počnúc ktorým sa budú jej hodnoty generovať. My ako tento parameter používame aktuálny čas (v sekundách od začiatku roku 1970), čo zaručuje dostatočný efekt náhodnosti. Aby bolo možné použiť funkciu time, je treba do hlavičky pridať #include <ctime>.

Príklad č. 4: výpočet faktoriálu

Nasledujúci program si od používateľa vypýta číslo n a vypočíta n!, t.j. súčin celých čísel od 1 po n.

#include <iostream>
using namespace std;

int main() {
    int n;
    
    cout << "Zadajte n: ";
    cin >> n;
    
    int vysledok = 1;
    for (int i = 1; i <= n; i++) {
       vysledok = vysledok * i;
    }
    
    cout << n << "! = " << vysledok << endl;
    return 0;
}

Príklad behu programu pre n=4 (1*2*3*4=24):

Zadajte n: 4
4! = 24
  • Program používa premennú vysledok, ktorú na začiatku inicializuje hodnotou 1 a postupne ju násobí číslami 1, 2, ..., n.
  • Riadok vysledok = vysledok * i; zoberie pôvodnú hodnotu premennej vysledok, vynásobí ju hodnotou premennej i (t.j. jedným z čísel 1, 2, ..., n) a výsledok uloží naspäť do premennej vysledok (prepíše pôvodnú hodnotu). To isté sa dá napísať ako vysledok *= i;

Funkcia n! však veľmi rýchlo rastie a už pre n=13 sa výsledok nezmestí do premennej typu int. Dostávame nezmyselné hodnoty:

12! = 479001600
13! = 1932053504
14! = 1278945280
15! = 2004310016
16! = 2004189184
17! = -288522240

Správne hodnoty (ktoré možno získať zmenou typu premennej vysledok na long long int) sú:

12! = 479001600
13! = 6227020800
14! = 87178291200
15! = 1307674368000
16! = 20922789888000
17! = 355687428096000

Príklad č. 5: vypisovanie deliteľov (podmienka v cykle)

Nasledujúci program načíta od používateľa prirodzené číslo a vypíše zoznam jeho deliteľov.

#include <iostream>
using namespace std;

int main() {
    int n;
    cout << "Zadajte cislo: ";
    cin >> n;
    
    cout << "Delitele cisla " << n << ":";
    for (int i = 1; i <= n; i++) {
        if (n % i == 0) {
            cout << " " << i;
        }
    }    
    cout << endl;
    return 0;
}

Beh programu:

Zadajte cislo: 12
Delitele cisla 12: 1 2 3 4 6 12