#8 Rekurze a vlastní datové typy

Tato lekce se bude zabývat rekurzivnímu volání funkcí, dále také rozsahu platnosti proměnných. Hlavním bodem tohoto článku budou vlastní datové typy.

Rekurze

Rekurze je proces kde určitý objekt, v případě jazyka C je to funkce, se využívá sebou samým. Funkce obsahuje ve svém těle volání sebe sama a při každém volání jsou upravené předávané hodnoty. Rozlišujeme dva druhy rekurze:

  1. Přímá – uvnitř kódu funkce je volání té samé funkce
  2. Nepřímá – v kódu funkce se nachází volání druhé funkce, která opět zavolá funkci první

Na příkladu si znázorníme použití rekurze a syntaxi kódu, opět použijeme nám již známy výpočet faktoriálu:

#include <stdio.h>

int factorial(int n) {
    if (n > 1) 
        return n * factorial(n - 1);
    else 
        return 1;
}

int main(void)
{
    int number = 0;

    printf("Insert number: ");
    scanf("%d", &number);

    printf("Result: %i\n", factorial(number));

    return 0;
}

Rekurze je určitý druh cyklu, tudíž musí mít ukončovací podmínku, jinak by výpočet rekurze procházel čím dál více do hloubky a mohlo by dojít i k nekonečné hloubce.

Detailnější chování výpočtu rekurze, nám prozradí debugger. Ten obsahuje zajímavou pomůcku, kde si můžeme nastavit podmínku zastavení v rekurzi, jedná se o podmíněný breakpoint.

Vlasnosti breakpointu si zobrazite pravým kliknutím myši na breakpoint a klikněte na Edit breakpoint. Do položky Condition zapíšete podmínku podle které se debugger zastaví na určitém místě.

Rekurzivnímu řešení se raději vyhněte, pokud to samozřejmě není podmínkou příkladu. Jakákoliv rekurze lze zapsat cyklem, nejčastěji while, proto raději preferujte tuto volbu. Rekurze je náročnější vzhledem k výkonu počítače. Při zavolání nové funkce se spouštějí i nové instrukce procesoru a to předání parametru, předání návratové hodnoty a také alokace a dealokace paměti na zásobníku.

Rozsah platnosti proměnné

Deklarace lokální či globální proměnné se nijak neliší. Rozdíl mezi nimi je v rozsahu platnosti v programu.

Lokální proměnná

Pro lokální proměnnou jsou 3 možnosti jak jej deklarovat, myslí se kde, v jaké částí kódu se nachází:

  1. Funkce
  2. Blok
  3. Cyklus

Pří deklaraci proměnné ve funkci, se považuje za platnou až do ukončení funkce. Jedná se pouze o proměnné, které se nacházejí v hlavičce. Pokud bychom deklarovali proměnnou pod hlavičkou, tedy někde uprostřed kódu funkce. Jednalo by se o deklaraci v bloku. Blokem považujeme část kódu, která je ohraničená složenými závorkami. Jako u funkcí i u bloku je to stejné s rozsahem platnosti proměnné, tedy proměnná deklarovaná uvnitř bloku (např.: v podmínce if) zaniká při skončení bloku. Co se týče cyklů, tak proměnná deklarovaná v „hlavičce“ cyklu (platí pouze pro cyklus for) je platná pouze při provádění daného cyklu, po skončení zaniká.

Globální proměnná

Deklarace takovéto proměnné se nachází vně jakékoliv funkce. Platnost proměnné zaniká až při ukončení celého programu.

#include <stdio.h>

int number = 50;

void myFunc()
{
    number += 100;
}

int main(void)
{
    myFunc();
    printf("%d\n", number);
    return 0;
}

Za jakousi globální proměnnou lze považovat i identifikátor vzniklý pomocí direktivy #define. Ačkoliv pojmenování proměnná není správné, jelikož je konstantní a nedá se přepisovat. Rozsah platnosti může být stejný jako u globální proměnné, za předpokladu že se v kódu neprovede direktiva #undef, která by tento identifikátor odstranila.

Překrytí identifikátorů (proměnných)

Existuje možnost překrytí či zastínění proměnné. Například globální identifikátor může být překrytý lokálním identifikátorem (např.: v cyklu), který má sejný název. Používání překrývaní proměnných je nevhodné a zhoršuje se přehlednost kódu.

Vlastní datové typy

Prozatím jsme používali pouze vestavěné datové typy, jako např.: int, double, char. Nyní si však ukážeme jak vytvářet vlastní typy, pomocí kterých lze v jazyce C vytvářet rozsáhlejší konstrukce. Definice všech vlastních typů se zapisují mimo jakoukoliv funkci. Dle konvence se zapisuje ještě před definicí první funkce programu. Existují 4 druhy klíčových slov, bez kterých se vytvoření vlastního datového typu neobejde, jsou to:

  • enum
  • struct
  • union
  • typedef

1. Enum

Nejprve se zaměříme na enum. Pomocí něj lze vyvářet výčtový typ. Jednoduše lze říct, že enum slouží k pojmenování konstant. Obecná deklarace typu enum vypadá takto (zároveň vidíme i deklaraci proměnné, která používá nově definovaný typ):

enum nazev_typu {hodnota1,hodnota2,…}
enum nazev_typu nazev_promenne;

Při deklaraci typu jsou jednotlivé hodnoty ve složených závorkách oddělené čárkou. Při deklaraci proměnné nesmíme zapomínat napsat i zde klíčové slovo enum.

#include <stdio.h>
enum cisla {nula, jedna, dva};

int main(void)
{
    enum cisla cislo;
    cislo = dva;
    //cislo = tri; //error tri neni soucasti vyctoveho typu

    printf("%d\n", cislo);
    return 0;
}

Po spuštění ukázkového programu, ihned zjistíme, že konstrukce enum je vytvořená pomocí celočíselného typu, konkrétněji je to typ int. Dle výchozího nastavení se první položka inicializuje na hodnotu 0, další položka má hodnotu 1, atd.

Tyto hodnoty je možné změnit a to tak, že např.: jedné z položek ve výčtu přiřadíme konkrétní hodnotu.

enum muj_enum {automobil, letadlo = 5, kolo};
// auto = 4, letadlo = 5, kolo = 6

Hodnoty mohou být i stejné.

enum muj_enum {automobil = 7, letadlo = 7, kolo};
// auto = 7, letadlo = 7, kolo = 8

2. Struct

Jak již z pojmenování vyplývá, budou se pomocí slova struct vytvářet datové struktury. Jedná se o množinu proměnných různého datového typu, které jsou navzájem spojené. Každá proměnná má sice své pojmenování, ale celkové spojení musí mít také svůj název.

Obecná deklarace struktury:

struct nazev_typu {
	datovy_typ1 promenna1;
	datovy_typ2 promenna2;
	…
};

Vytvoření proměnné ve funkci:

struct nazev_typu promenna;

Podle obecné definice se nám může zdát, že je to v podstatě to samé co enum, ale není to úplné pravda, struct může mít libovolný datový typ ke každé své proměnné, třeba i další struct.

Ukázkový příklad nám napoví, k čemu vlastně slouží struct.

#include <stdio.h>

struct person_t {
    char name[255];
    unsigned short age;
};

int main(void)
{
    struct person_t person1 = {"Jan", 25};
    printf("%s, %d\n",person1.name,person1.age);

    struct person_t person2 = {.age = 20};
    printf("%s, %d\n",person2.name,person2.age);
    return 0;
}

Struct můžeme použít například k evidenci osob, protože samotná osoba má více položek, které je potřeba uchovat.

V příkladu jsou použity 2 různé deklarace proměnné, které používají stejný datový typ. První přiřazení je klasické, ale u druhého je použitá tečková notace, stejné jako při výpisu jednotlivých proměnných.

Právě pomocí operátoru tečka, přistupujeme k jednotlivým položkám ve structu.

Struct je tedy vhodný na již zmíněnou evidenci osob, ale abychom nemuseli deklarovat každou osobu zvláště, je jednoduší vytvořit pole. Deklaraci pole sice známe, ale pouze pro upřesnění je zde syntaxe:

struct person_t persons[1000];
persons[485].age = 25;

Také jako obyčejnou proměnnou i celé struktury lze kopírovat.

struct person_t person1 = {"Jan", 25};
struct person_t person2;
person2 = person1;

Musíme si však dát pozor, pokud struktura obsahuje datový typ ukazatel. Kopírování totiž způsobí, že 2 proměnné budou ukazovat na stejné místo v paměti a mohlo by docházet k nesprávným výsledkům.

3. Union

Typ union je stejný jako struct, jeho deklarace je totožná, s tím rozdílem, že se zamění klíčové slovo. Union se moc nepoužívá a je vhodnější použít struct. Odlišnost mezi structem a unionem je v tom, že struct zabírá v paměti tolik místa kolik je potřeba pro uchování všech jeho položek, kdežto union zabírá pouze tolik paměti jako jeho největší položka, je tedy jasné, že je možné použít pouze jednu položku v jeden moment. Je tedy možné říct, že všechny položky unionu sdílí jedno místo v paměti.

4. Typedef

Tento nástroj neslouží k vytvoření nějaké struktury různých datových typů jako předešlé 3 případy. Typedef slouží k pojmenovávání různých datových typů, šetří se tím zejména velikost kódu a stává se tím i přehlednější.

Obecný tvar:

typedef datovy_typ nazev_typu;

Pomocí typedef je možné si zkrátit zápis některých deklarací např.:

typedef unsigned short ushort;
typedef int matrix[3][3];

Lze také využít u deklarace structu, poté při deklaraci proměnné typu struct nebude již nutné vepisovat klíčové slovo, ale zapíše se pouze jeho zkrácený zápis vytvořený pomocí typedef:

typedef struct person_t {
    char name[255];
    unsigned short age;
} person;

person person1 = {"Jan", 25};

To bude vše k dnešní lekci, snad jste se zase něco nového dozvěděli a naučili.

Napsat komentář