IB001 – Seminární skupiny 6, 7 a 24

Týden 9

Viditelnost identifikátorů

Identifikátory jsou v program v jazyce C viditelné podle umístění, kde je definujeme. Identifikátor může být vidět ve všech funkcích programu nebo jen v některé funkci nebo jen v některém bloku programu. Podle viditelnosti jsou identifikátory:

  • globální - jsou vidět v celém programu
  • lokální - jsou vidět jen v některé části (funkce, blok)

Příklad je zde:

// globalni promenna - identifikator x
int x = 5;
// globalni identifikator main
int main(void)
{
    // lokalni promenna - identifikator a
    int a;
    return 0;
}

Funkce II

Zápis funkce můžeme rozdělit na dvě části:

  • hlavička funkce
  • tělo funkce

Hlavička funkce většinou začíná návratovým typem a končí kulatou závorkou, která uzavírá argumenty funkce. Tělo funkce se pak zapíše mezi složené závorky za hlavičku funkce a obsahuje samotný program funkce.
Zde je příklad:

// hlavicka funkce
void sayHello(char name[])
// telo funkce
{
    printf("Hello %s!", name);
}

Zatím jsme zapisovali funkce tak, že jsme je celé (hlavičku i tělo) zapsali před funkci main, čímž jsme umožnili viditelnosti jména funkce v těle funkce main. Zápisu hlavičky i těla funkce se říká definice funkce.
V jazyce C je možné funkce definovat až za tělem metody main. V takovém případě je nutné funkci definovat před tělem funkce main, tak abychom ji mohli v main použít. K deklaraci funkce stačí zapsat její hlavičku a ukončit ji středníkem.

#include  
// deklarace funkce - hlavicka ukoncena strednikem
void sayHello(char[]);
// funkce main - hlavni telo programu
int main(void)
{
    sayHello("Felix");
    return 0;
}
// definice funkce - hlavicka nasledovana telem
void sayHello(char name[])
{
    printf("Hello %s!", name);
}

Předávání polí funkcím

Funkcím lze předávat i pole - jednorozměrná i vícerozměrná. Při předávání polí nemusí kompilátor znát první rozměr pole. Chceme-li předat pole jednorozměrné, může být argument typu pole bez rozměru - má prázdné hranaté závorky (viz výše). Pokud bychom chtěli předat dvourozměrné pole, musíme uvést druhý rozměr pole, pokud třírozměrné pak druhý a třetí atd.
Jazyk C ve standardu C99 umožňuje, aby funkce měly jako argumenty pole jejichž délka je zapsána v nějakém předcházejícím argumentu. Zvlášť u vícerozměrných polí se nám toto hodí v případě, kdy nechceme aby rozměry pole, které musíme kompilátoru zadat, byly konstantní. Několik příkladů:

// predavame pole ale i promenlivy pocet prvku v poli, ktery by funkce jinak neznala
double sum(double numbers[], int count)
{
    double s = 0;
    for (int i = 0; i < count; i++)
    {
        s = s + numbers[i];
    }
}

// predavame dvourozmerne pole, musime zadat druhy rozmer!
bool isRegular(double matrix[][10], int rows, int columns)
{
    // telo funkce...
}

Výše uvedený zápis umožňuje předávat funkci isRegular jen taková pole, která mají druhý rozměr 10. Mohli bychom tedy předat pole (reprezentující matice) o rozměrech 3x10, 1x10 nebo třeba 15x10. Nemůžeme ale funkci isRegular předat např. pole o rozměrech 2x2 nebo 3x3, což její použití velmi omezuje. Toto omezení lze v C99 řešit následujícím zápisem:

// predavame dvourozmerne pole, zadame rozmer/y promennymi
bool isRegular(int rows, int columns, double matrix[][columns])
{
    // telo funkce...
}

nebo pokud chceme uvést i rozměr, který můžeme vynechat pak takto:

// predavame dvourozmerne pole, zadame rozmer/y promennymi
bool isRegular(int rows, int columns, double matrix[rows][columns])
{
    // telo funkce...
}

Návrat hodnot z funkce

Nejčastěji vrací funkce hodnotu pomocí příkazu return a typ hodnoty je určen návratovým typem funkce. Návratový typ může být jakýkoliv primitivní datový typ (např. bool, int, double) a jiné typy, které ještě neznáme. Návratovým typem funkce nemůže být pole.
Také jsme se už setkali s několika případy, kdy funkce ukládala více než jednu hodnotu (scanf) nebo kdy jsme sami tušili, že by bylo vhodné vrátit z funkce více než jednu hodnotu (funkce na výpočet minima a maxima z řady čísel). Uložení více vypočtených hodnot může funkce řešit pomocí pointrů:

// funkce, ktera vydeli dve cela cisla a ulozi vysledek i zbytek do pameti odkazovane pointry quot a rem
void div(int a, int b, int * quot, int * rem)
{
    *qout = a / b;
    *rem = a % b;
}
// volani funkce - pri tretim a ctvrtem parametru se uzije operatoru reference
int x = 3;
int qu, re;
div(x, 2, &qu, &re);
// nyni x == 3, quot == 1 a rem == 1

Čeho si výše všimnout? První a druhý parametr jsou předávány hodnotou - hodnota proměnné x (3) je zkopírována při volání funkce add do jejího argumentu a. Stejně je tomu s konstantou 2. Třetí a čtvrtý parametr jsou předávány jako pointry na proměnné qu a re. Při volání add se zkopíruje adresa proměnných qu do quot a re do rem, takže např. pointer quot pak ukazuje na oblast paměti, kde leží proměnná qu. Pomocí pointru quot pak obsah proměnné qu změníme. Naopak jakkákoliv změna hodnot argumentů a a b má efekt pouze ve funkci div.
Velmi podobné je to s polem, vždy se předává adresa pole v paměti, nikoliv hodnoty jeho prvků:

// funkce vynuluje vektor predany jako parametr
void zeros(int vector[], int lenght)
{
    for (int i = 0; i < lenght; i++)
    {
        vector[i] = 0;
    }
}