Oznamy

Papierové rozcvičky

  • Od budúceho týždňa budú na niektorých cvičeniach papierové rozcvičky.
  • Je to krátky test z učiva, na ktoré už boli príklady na testovači.
  • Bude povolené používať pero a ťahák vo forme jedného obojstranne popísaného listu A4.
  • Zakázané sú akékoľvek elektronické zariadenia a komunikácia s inými ľuďmi okrem vyučujúcich.

Prečo robíme takéto rozcvičky a písomné testy na papieri?

  • Dobré na precvičenie porozumenia hotovým programom (treba na ďalších predmetoch aj pri štúdiu odbornej literatúry).
  • Lepšie sústredenie, keď vás nevyrušuje počítač a internet.
  • Núti vás dobre si premyslieť každý detail, nie len náhodne skúšať meniť rôzne časti programu, kým nezačne fungovať.
  • Pri zložitejších programoch sa už nedá postupovať náhodne.
  • Na papieri miernejšie hodnotíme drobné chyby ako chýbajúca bodkočiarka, to vám na počítači pomôže nájsť kompilátor. Dôležitejšie je sa rozhodnúť, či má byť niekde napríklad i < n alebo i <= n.

Budúci týždeň

  • Pondelok na cvičeniach papierová rozcvička, vzorové riešenia rozcvičky, možnosť spýtať sa k úlohám, ktoré majú termín do pondelka večera
    • Papierová rozcvička sa bude týkať prednášok 1-3.
  • Utorok na cvičeniach programátorská rozcvička a ďalšie programátorské úlohy s termínom ďalší pondelok večer
  • Prednášky pondelok a stredu ako obvykle

Body

  • Na testovači v záložke Body postupne budú pribúdať body z cvičení.
  • Už tam sú body z minulého týždňa.

Knižnica math

  • Obsahuje matematické funkcie, ktoré sa dajú použiť v C.
  • Na používanie týchto funkcií je potrebné do hlavičky programu pridať riadok #include <math.h>
  • Príklady funkcií z tejto knižnice: cos(x), sin(x), tan(x) (tangens), acos(x) (arkus kosínus), exp(x) (ex), log(x) (prirodzený logaritmus), pow(x,y) (xy), sqrt(x) (odmocnina), abs(x) (absolútna hodnota), floor(x) (dolná celá časť), …
  • Pri použití funkcie treba do príkazu na kompilovanie pridať prepínač -lm, napríklad: gcc -o program program.c -lm
  • Viac detailov o knižnici možno nájsť v dokumentácii.

Dĺžka úsečky

Nasledujúci program použije funkciu sqrt z knižnice math na výpočet dĺžky úsečky spájajúcej dva body zadané súradnicami [Ax,Ay] a [Bx,By]:

#include <stdio.h>
#include <math.h>
int main() {
    double Ax, Ay, Bx, By;
    
    printf("Zadaj suradnice bodu A: ");
    scanf("%lf %lf", &Ax, &Ay);
    printf("Zadaj suradnice bodu B: ");
    scanf("%lf %lf", &Bx, &By);
    
    double dlzka = sqrt((Ax - Bx) * (Ax - Bx) + (Ay - By) * (Ay - By));
    
    printf("Dlzka usecky AB je: %f\n", dlzka);
}

Príklad behu

Zadaj suradnice bodu A: 0 0
Zadaj suradnice bodu B: 3 4
Dlzka usecky AB je: 5.000000

Funkcie

Funkcia je samostatný kus kódu (postupnosť príkazov) s určitým menom. Po zavolaní funkcie jej menom sa daná postupnosť príkazov vykoná.

  • V iných programovacích jazykoch sa používajú aj podobné termíny ako procedúra, metóda, či podprogram.
  • Funkcia vo všeobecnosti dostane niekoľko (aj nula) vstupných argumentov, ktoré môže pri svojom behu používať.
  • Funkcia tiež môže vrátiť výstupnú hodnotu.
  • Ide teda o veľmi podobný koncept ako funkcie v matematike (ako napríklad sin(x)).
    • Oboje si možno predstaviť ako „krabičku”, ktorá na základe niekoľkých vstupných hodnôt vráti nejakú výstupnú hodnotu.
    • Funkcie v C ale môžu okrem vracania výstupných hodnôt vykonávať ľubovoľný kód, teda napríklad aj niečo vypisovať na konzolu a podobne.

Obvod trojuholníka bez použitia funkcií

Užitočnosť funkcií ilustrujeme na príklade. Chceme napísať program, ktorý od používateľa načíta súradnice vrcholov trojuholníka ABC a na výstup vypíše obvod tohto trojuholníka. Beh takéhoto programu teda môže vyzerať napríklad takto:

Zadaj suradnice vrcholu A: 0 0
Zadaj suradnice vrcholu B: 3 0
Zadaj suradnice vrcholu C: 0 4
Obvod trojuholnika ABC: 12

Obvod spočítame ako súčet dĺžok jednotlivých strán, v programe teda trikrát opakujeme výpočet dĺžky strany:

#include <stdio.h>
#include <math.h>

int main() {
    double Ax, Ay, Bx, By, Cx, Cy;
    
    printf("Zadaj suradnice vrcholu A: "); 
    scanf("%lf %lf", &Ax, &Ay);
    printf("Zadaj suradnice vrcholu B: ");
    scanf("%lf %lf", &Bx, &By);
    printf("Zadaj suradnice vrcholu C: ");
    scanf("%lf %lf", &Cx, &Cy);
    
    /* Spocitaj dlzky jednotlivych stran: */
    double bc = sqrt((Bx - Cx) * (Bx - Cx) 
                    + (By - Cy) * (By - Cy));
    double ac = sqrt((Ax - Cx) * (Ax - Cx) 
                    + (Ay - Cy) * (Ay - Cy));
    double ab = sqrt((Ax - Bx) * (Ax - Bx) 
                    + (Ay - By) * (Ay - By));
    
    printf("Obvod trojuholnika ABC: %f\n", ab + ac + bc);
}

Opakované písanie toho istého vzorca na výpočet dĺžky strany je prácne; navyše pri ňom ľahko spravíme chybu. V nasledujúcom programe ho teda nahradíme funkciou.

Obvod trojuholníka s použitím funkcie

Videli sme teda, že na výpočet obvodu trojuholníka sa nám môže zísť funkcia počítajúca vzdialenosť medzi dvoma bodmi v rovine, t.j. dĺžku úsečky. Tá môže v C vyzerať napríklad takto:

double dlzka(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

Týmto sme zadefinovali funkciu s názvom dlzka so štyrmi vstupnými argumentmi x1, y1, x2, y2 typu double, reprezentujúcimi súradnice dvojice bodov v rovine. Pred samotný názov funkcie sme zadali návratový typ funkcie, ktorým je double – táto funkcia teda bude vracať na výstupe reálne čísla. Nakoniec sme zadefinovali samotné telo funkcie, tentokrát pozostávajúce z jediného špeciálneho príkazu return, ktorým funkcia vracia svoju výstupnú hodnotu.

Kompletný program na výpočet obvodu trojuholníka môže s použitím funkcie dlzka vyzerať napríklad takto:

#include <stdio.h>
#include <math.h>

/* Definicia funkcie dlzka: */
double dlzka(double x1, double y1, double x2, double y2) {
    return sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2));
}

int main() {
    double Ax, Ay, Bx, By, Cx, Cy;
    
    printf("Zadaj suradnice vrcholu A: "); 
    scanf("%lf %lf", &Ax, &Ay);
    printf("Zadaj suradnice vrcholu B: ");
    scanf("%lf %lf", &Bx, &By);
    printf("Zadaj suradnice vrcholu C: ");
    scanf("%lf %lf", &Cx, &Cy);
    
    // Spocitaj dlzky jednotlivych stran:

    // Volanie funkcie dlzka s argumentmi Bx, By, Cx, Cy
    double bc = dlzka(Bx, By, Cx, Cy);
    // Volanie funkcie dlzka s argumentmi Ax, Ay, Cx, Cy
    double ac = dlzka(Ax, Ay, Cx, Cy);
    // Volanie funkcie dlzky s argumentmi Ax, Ay, Bx, By
    double ab = dlzka(Ax, Ay, Bx, By);
    
    printf("Obvod trojuholnika ABC: %f\n", ab + ac + bc);
}

Telo funkcie nemusí pozostávať iba z jediného príkazu. Napríklad ešte jednoduchšie by sme funkciu dlzka mohli napísať takto:

double dlzka(double x1, double y1, 
            double x2, double y2) {
    double dx = x1 - x2;
    double dy = y1 - y2; 
    return sqrt(dx * dx + dy * dy);
}

Cvičenie:

  • V jazyku Pascal existuje funkcia sqr, ktorá na vstupe berie reálne číslo x a výstupom je toto číslo umocnené na druhú. Naprogramujte túto funkciu v C a použite ju na zjednodušenie funkcie dlzka.
  • Mohli by sme použiť aj pow(x,2) (treba ` #include <math.h>), ale môže byť jednoduchšie zrátať x*x`.

Výhody funkcií

Funkcie sú užitočné z viacerých dôvodov:

  • Umožňujú vytvoriť „skratku” pre často používané časti kódu, ktoré tak nie je nutné zakaždým písať nanovo.
  • Umožňujú používanie kusov kódu vytvorených iným programátorom. Napríklad sme použili hotovú funkciu sqrt, ktorá vráti druhú odmocninu svojho vstupu.
  • Funkcie umožňujú rozdeliť písanie programu na menšie časti. Zvlášť sa môžeme sústrediť na telo programu, ktoré používa funkciu a zvlášť na implementáciu samotnej funkcie. Tieto dve časti môžu robiť aj rôzni programátori.

Definícia funkcie

Definícia funkcie pozostáva z nasledujúcich častí:

  • Typ návratovej hodnoty funkcie. Funkcia z úvodného príkladu napríklad vracia vzdialenosť bodov v rovine, teda hodnotu typu double. Podobne môžeme písať funkcie vracajúce iné návratové typy, napríklad int alebo bool. Funkcie, ktoré nemajú vracať žiadnu hodnotu majú špeciálny návratový typ void. Ide typicky o funkcie, ktoré len vykonajú určitú činnosť, napríklad vypísanie textu na konzolu a podobne.
  • Identifikátor funkcie. Funkciu môžeme (v rámci určitých medzí) pomenovať prakticky ľubovoľne, rovnako ako pri premenných. Vhodné je však použiť názov, ktorý vystihuje úlohu danej funkcie (napríklad dlzka).
  • Zoznam vstupných parametrov funkcie. V zátvorkách za názvom funkcie je zoznam typov a identifikátorov vstupných argumentov, ktoré funkcia očakáva. V úvodnom príklade funkcia očakáva súradnice dvoch bodov v rovine, teda štyri hodnoty typu double. Ak funkcia neočakáva žiaden vstupný argument, môžeme do zátvoriek napísať void alebo nechať zoznam argumentov prázdny.
  • Telo funkcie. Do zložených zátvoriek za definíciou funkcie píšeme postupnosť príkazov, ktoré má funkcia vykonať.
  • Príkaz return. Vracia návratovú hodnotu funkcie.
typ_navratovej_hodnoty identifikator_funkcie(zoznam_vstupnych_argumentov) {
    telo_funkcie // Môže obsahovať príkazy return.
}

Ďalšie príklady funkcií

Súčet čísel od a po b

Ukážeme si tri verzie funkcie, ktorá dostane dvojicu celých čísel a, b a spočíta súčet všetkých celých čísel od a po b vrátane.

  • Prvá verzia tento súčet vráti ako návratovú hodnotu.
  • Druhá verzia súčet vypíše, vrátane sčitovaných čísel, jej návratový typ bude void.
  • Tretia verzia súčet aj vypíše aj vráti.
/* Funkcia, ktora vrati sucet a + (a+1) + ... + (b-1) + b */
int sum(int a, int b) {
    int result = 0;
    for (int i = a; i <= b; i++) {
        result += i;
    }    
    return result;
}

/* Funkcia, ktora vypise cisla a, (a+1), ..., b  a ich sucet */
void printNumbers(int a, int b) {
    int result = 0;
    for (int i = a; i <= b; i++) {
        if (i > a) {
            printf(" + ");
        }
        printf("%d", i);
        result += i;
    }    
    printf(" = %d\n", result);
}

/* Funkcia, ktora vypise cisla a, (a+1), ..., b  a ich sucet
 * a tento sucet aj vrati. */
int sumAndPrint(int a, int b) {
    int result = 0;
    for (int i = a; i <= b; i++) {
        if (i > a) {
            printf(" + ");
        }
        printf("%d", i);
        result += i;
    }    
    printf(" = %d\n", result);
    return result;
}

Program využívajúci tieto funkcie môže vyzerať napríklad takto:

#include <stdio.h>

// sem pridu definicie funkcii sum, printNUmbers, sumAndPrint 
// uvedene vyssie

int main() {
    int a, b;
    
    printf("Zadaj dvojicu celych cisel: ");
    scanf("%d %d", &a, &b);

    printf("Test funkcie sum\n");
    printf("Sucet celych cisel od %d po %d: ", a, b);
    // zavolame funkciu a vysledok priamo vypiseme
    printf("%d\n", sum(a, b));
    // vysledok funkcie mozeme ulozit do premennej 
    // a pouzit neskor:
    int sucet = sum(a, b); 
    printf("Druha mocnina suctu cisel je: %d\n", sucet * sucet);

    printf("\nTest funkcie printNumbers\n");
    // tato funkcia nema vysledok, nic neukladame
    printNumbers(a, b);
    
    printf("\nTest funkcie sumAndPrint\n");
    // dalsia funkcia vypise aj vrati hodnotu
    int sucet2 = sumAndPrint(a, b);
    printf("Druha mocnina suctu cisel je: %d\n", sucet2 * sucet2);
    
    // vystupnu hodnotu funkcie mozeme aj odignorovat
    sumAndPrint(a,b); 
}

Príkaz return

Pozrime sa teraz bližšie na špeciálny príkaz return:

  • Po vykonaní príkazu return je funkcia okamžite zastavená a jej výstupná hodnota je výraz za slovom return.
  • Funkcia môže obsahovať aj viacero volaní return. Akonáhle sa však jedno z nich vykoná, funkcia končí s danou návratovou hodnotou. Napríklad:
#include <stdio.h>

int f(void) {
    return 1;
    return 2; // Nikdy sa nevykona.
    return 3; // Nikdy sa nevykona.
}

int main() {
    printf("%d\n", f()); // Vypise 1.
}
  • Funkcia s návratovým typom void môže tiež obsahovať príkaz return, avšak bez návratovej hodnoty.
    • V takom prípade return slúži iba na ukončenie vykonávania funkcie, väčšinou je lepšie prepísať inak
    • Tu je ukážka, kde namiesto return by bolo lepšie použiť else:
#include <stdio.h>

void akeCislo(int n) {
    if (n >= 0) {
        printf("Cislo je nezaporne.\n");
        return;  //lepie by bolo pouzit else namiesto return
    }

    // Vykona sa len v pripade n < 0:
    printf("Cislo je zaporne.\n"); 
}

int main() {
    int n;
    scanf("%d", &n) ;
    akeCislo(n);
}

Napríklad minimum z dvoch čísel môžeme vypočítať dvoma rôznymi spôsobmi:

int minimum1(int a, int b) {
    int minval;
    if (a < b) {
        minval = a;
    } else {
        minval = b;
    }
    return minval;
}

int minimum2(int a, int b) {
    if (a < b) {
        return a;
    } else {
        return b;
    }
}
  • Funkcie s návratovým typom iným ako void je žiadúce písať tak, aby na ľubovoľnom vstupe ich vykonávanie vždy skončilo príkazom return.
    • Ak totiž takáto funkcia skončí inak, než príkazom return, jej výstupná hodnota je nedefinovaná (použije sa „hocijaký nezmysel”), čo môže viesť k zákerným chybám.

Cvičenie: Nájdite hodnotu nasledujúcej funkcie pre n=6 a n=7. Viete stručne popísať, čo funkcia robí pre všeobecné n?

int zahada(int n) {
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return i; 
        }
    }
    return n;
}

Lokálne a globálne premenné

Premenné v C možno rozdeliť na globálne a lokálne:

  • Globálne premenné možno používať vo všetkých funkciách, ktoré sú v programe definované za touto premennou.
  • Lokálne premenné sú definované vo vnútri funkcie a môžu sa používať iba v rámci nej (alebo iba v rámci niektorej časti funkcie, ak je premenná definovaná napríklad v tele cyklu a podobne).

Platí navyše, že:

  • Viaceré funkcie môžu mať lokálne premenné s tým istým názvom – každá funkcia potom používa tú svoju. Toto sa bežne používa.
  • Ak má lokálna premenná rovnaký názov ako nejaká globálna premenná, lokálna premenná prekryje globálnu – funkcia teda používa svoju lokálnu premennú (bližšia košeľa ako kabát). Toto radšej nerobte, môže vzniknúť chaos.

Je silno odporúčané používať predovšetkým lokálne premenné. Väčšie programy s globálnymi premennými môžu byť veľmi neprehľadné.

Nasledujúci program obsahuje globálnu premennú x a v každej funkcii lokálnu premennú s názvom y:

#include <stdio.h>

int x;

void f(void) {
    int y = 10;
    printf("%d %d\n", x, y);      
}

int main() {
    x = 10;
    int y = 20;
        
    f();                           // Vypise 10 10.  
    printf("%d %d\n", x, y);       // Vypise 10 20.
}

Parametre funkcií

Vstupné parametre funkcií sa správajú ako lokálne premenné danej funkcie. Pri volaní funkcie sa každému parametru priradí určitá hodnota. Uvažujme napríklad funkciu

void f(int a, int b) {
    // ...
}

Pri volaní f(1,2) sa parametru a priradí hodnota 1 a parametru b sa priradí hodnota 2. Tieto sa ďalej správajú ako lokálne premenné funkcie f. Možno ich teda meniť, ale táto zmena sa neprejaví na mieste, odkiaľ funkciu voláme.

Príklad:

#include <stdio.h>

void f(int n) {
    n++;
    printf("%d\n", n);
}

int main() {
    int n = 1;
    f(n);               // Vypise 2
    printf("%d\n", n);  // Vypise 1
}

Programy s viacerými funkciami

V programe možno volať iba funkcie, ktoré už predtým boli niekde definované.

Tento program typicky neskompiluje (vo vnútri f1 ešte nepozná f2)

#include <stdio.h>

void f1() {
    f2();
}

void f2() {
    printf("Hello, world!\n");
}

int main() {
    f1();
}

Tento program je v poriadku:

#include <stdio.h>

void f2() {
    printf("Hello, world!\n");
}

void f1() {
    f2();
}

int main() {
    f1();
}

Funkcia main

Špeciálnou funkciou v C je funkcia main.

  • Jej volanie je automatické (začína sa ním beh programu).
  • Výstupná hodnota funkcie main sa interpretuje ako „spôsob ukončenia” programu (napríklad 0 pre korektné ukončenie, iné hodnoty pre chyby).
    • Funkciu main môžeme teda ukončit napríklad príkazom return 0.
    • Náš testovač vyžaduje, aby sa program ukončil s návratovou hodnotou 0, inak predpokladá, že došlo k chybe počas behu programu (napr. deleniu nulou).
  • Funkcia main môže mať aj určité presne špecifikované parametre (o nich neskôr).

Funkcie: zhrnutie

  • Funkcie nám umožňujú rozbiť väčší program na menšie logické časti a tým ho sprehľadniť. Tiež nám umožňujú vyhnúť sa opakovaniu podobných kusov kódu.
  • Hlavička funkcie obsahuje návratový typ (môže byť void), meno funkcie, typy a mená parametrov.
  • V tele funkcie sú samotné príkazy. Vypočítanú hodnotu vrátime príkazom return.
  • Lokálne premenné sú viditeľné len vo funkcii, ktorá ich definuje, globálne vo všetkých funkciách.
  • Parametre sú lokálne premenné inicializované hodnotami, ktoré sa zadajú pri volaní funkcie.