11. Funkce I

 
  1. Funkce jsou základem programování v C a C++. Funkce jsou částí kódu oddělené od hlavního programu. Tyto části kódu jsou volány (prováděny), když je zapotřebí provést jistou akci v programu. Např. můžeme mít funkci, která přebírá dvě hodnoty, provede s nimi jisté výpočty a vrátí výsledek. Nebo můžeme mít funkci přebírající řetězec, rozdělí jej a vrací část tohoto řetězce. Nejjednodušší typ funkce nemá parametry a vrací void (což znamená, že nevrací nic). Jiné funkce mohou mít jeden nebo více parametrů a mohou vracet hodnotu.  Každý program musí mít právě jednu funkci se jménem main (nebo WinMain) označující vstupní bod programu. Funkce jsou obvykle deklarovány jako prototypy ve standardních nebo uživatelských hlavičkových souborech, nebo uvnitř zdrojových souborů. Funkce jsou definovány ve zdrojových souborech programu nebo ve zpřístupněných knihovnách. Nedefinující deklarace funkcí používají formát funkčního prototypu poskytující překladači detailní informace o parametrech a tím mu umožňují provést kontrolu počtu parametrů, kontrolu typů parametrů a použití případných automatických převodů typů. Prototypy funkcí se v C++ musí používat a doporučuje se je používat i v C.

  2. V jazyku C mohla být funkce původně deklarována buď implicitně svým výskytem ve funkčním volání, nebo explicitně zápisem:
    typ fce();
    kde typ určuje návratový typ funkce (implicitně int), přičemž funkce může vracet libovolný typ s výjimkou pole nebo funkce. Tento styl deklarací však neumožňuje, aby překladač vedle kontroly návratového typu prováděl také kontrolu počtu a typů parametrů funkce. Tento problém byl vyřešen zavedením prototypu funkce. Prototyp funkce zapisujeme takto:
    typ fce(seznam-deklarací-parametrů);
    Deklarace specifikují typ každého parametru. Překladač tyto informace používá pro kontrolu správnosti volání funkce. V některých případech je překladač rovněž schopen převést typ parametru na požadovaný typ. Podívejte se na následující část programu:
    long lmax(long v1, long v2);        // prototyp
    void main(int argc, char **argv)
    {
      int limit = 32;
      char ch = 'A';
      long mval;
      mval = lmax(limit, ch);            // volání funkce
    }
    Protože překladač zná prototyp funkce lmax, jsou parametry funkce (limit a ch) nejprve použitím standardních pravidel převedeny na typ long (požadovaný typ parametrů) a teprve potom jsou předány funkci. Bez použití prototypu, překladač nezná datové typy parametrů a neprovádí žádný převod. Funkci jsou tedy předány limit a ch jako typy int a char, což funkce lmax neočekává a dostáváme se do problémů.
    Prototypy funkcí také přispívají k lepší dokumentaci programu. Např. funkce strcpy (kopírování řetězce znaků) má dva parametry: zdrojový a cílový řetězec. Otázkou je, který je zdrojový a který je cílový? Prototyp funkce
    char *strcpy(char *cil, const char *zdroj);
    vše objasní. Jména parametrů v prototypu mají pouze dokumentační účel. Prototypy funkcí zapisujeme v hlavičkových souborech nebo na začátku zdrojového textu programu.
    Definice funkce obsahuje tělo funkce, tj. kód, který se bude při volání funkce provádět. Deklarace formálních parametrů mají stejný zápis jako deklarace proměnných. V C++ je možno v deklaraci parametrů uvést implicitní hodnoty parametrů. Parametry s implicitními hodnotami musí být posledními paramery. V deklaracích parametrů je možné použít i modifikátor const. Definice funkce je popsána na následujícím obrázku:

    Pokud funkce nevrací žádné informace, pak její typ je void. Funkce typu void mají příkaz return bez výrazu určujícího vrácenou hodnotu.
    Při volání funkce jsou skutečné parametry funkce uvedeny ve stejném pořadí jako parametry v definici funkce. Počet parametrů musí souhlasit. Typy parametrů musí být kompatibilní do té míry, aby mohly být převedeny. Pokud uvedeme definici funkce dříve než funkci použijeme, pak prototyp funkce není nutno uvádět.
  3. Použití funkce ukazuje následující konzolová aplikace:

  4. #include <iostream.h>
    #include <conio.h>
    #pragma hdrstop
    //---------------------------------------------------------------------
    #pragma argsused
    int nasob(int, int);
    void zobrazVysledek(int);
    int main(int argc, char **argv)
    {
      int x, y, vysledek;
      cout << endl << "Zadej první hodnotu: ";
      cin >> x;
      cout << "Zadej druhou hodnotu: ";
      cin >> y;
      vysledek = nasob(x, y);
      zobrazVysledek(vysledek);
      cout << endl << endl << "Stiskni libovolnou klávesu ... ";
      getch();
      return 0;
    }
    int nasob(int x, int y)
    {
      return x*y;
    }
    void zobrazVysledek(int vysl)
    {
      cout << "Výsledek je: " << vysl << endl;
    }
    Tento program vyžaduje zadání dvou čísel, volá funkci nasob k vynásobení zadaných hodnot a potom volá funkci zobrazVysledek k zobrazení výsledku. Prototypy těchto funkcí jsou uvedeny na začátku programu (před funkcí main). V prototypech je uveden pouze návratový typ, jméno funkce a datové typy parametrů funkce. Toto je minimální požadavek na deklaraci funkce. Je-li to zapotřebí, prototyp funkce může obsahovat jména parametrů, které mohou být použity k dokumentování činnosti funkce. Např. deklarace funkce nasob může být zapsána také takto:
    int nasob(int prvniCislo, int druheCislo);
    V našem případě je jasné co funkce dělá a není zapotřebí žádná dokumentace. Definice této funkce je také jednoduchá. Naše funkce může být volána několika způsoby. Mohou ji být předány konstanty, hodnoty proměnných a výsledek může být také předán jiné funkci. Následuje několik příkladů volání této funkce:
    vysledek = nasob(5, 2);
    vysledek = nasob(x, y);
    zobrazVysledek(nasob(x, 5));
    nasob(3, x);
    V posledním příkladě vrácená hodnota není použita. Ignorování vrácené hodnoty je v našem případě nesmyslné, ale v některých případech je to často používané. Je mnoho funkcí provádějících jisté akce a vrácená hodnota indikuje stav volání. Pokud tato hodnota není důležitá pro náš program, pak ji můžeme ignorovat. Např. v našich programech jsme zatím ignorovali hodnotu vracenou funkcí getch (ASCII hodnota stisknuté klávesy).
    Funkce může volat jiné funkce. Funkce může také volat sama sebe. Toto nazýváme rekurze.
  5. Následující konzolová aplikace zjišťuje, zda zadané číslo je prvočíslo. Zda se jedná o prvočíslo určuje funkce je_prvocislo. Tato funkce používá pomocnou funkci zjišťující dělitelnost čísel. Obě tyto funkce mají v programu uveden prototyp. Prostudujte si tento výpis, program vyzkoušejte a snažte se pochopit jak jednotlivé funkce pracují.

  6. int je_prvocislo(int);
    int je_delitelno(int, int);
    int main(int argc, char **argv)
    {
      int n;
      cout << "Zadej přirozené číslo: ";
      cin >> n;
      cout << "číslo " << n << (je_prvocislo(n) ? " je" : " neni")
           << " prvočíslo." << endl;
      getch();
      return 0;
    }
    int je_prvocislo(int n)
    {
      for (int i = 2; i*i <= n; i++)
        if (je_delitelno(n, i)) return 0;
      return 1;
    }
    int je_delitelno(int n, int m)
    {
      return !(n % m);
    }
  7. Následující funkce určuje největší společný dělitel dvou přirozených čísel. Použijte tuto funkci v konzolové aplikaci, kde přečtete čitatel a jmenovatel zlomku a provedete případné zkrácení zlomku. V programu uveďte i prototyp této funkce.

  8. int nsd(int a, int b)
    {
      int pom;
      while (b) {
        pom = a % b;
        a = b;
        b = pom;
      }
      return a;
    }
  9. Napište funkci vypisující řádek obsahující n znaků z. n a z jsou parametry funkce. Tuto funkci použijte v programu pro výpis např. 50-ti znaků podtržení a 20-ti znaků hvězdiček.
  10. Přejdeme opět k aplikacím GUI. Vytvoříme aplikaci, kde na formuláři budou tři editační ovladače (vyprázdníme je a před ně umístíme texty ?První operand:?, ?Druhý operand:? a ?Výsledek:?) a tlačítka ?Součet?, ?Rozdíl?, ?Součin? a ?Podíl?. Obsluha události stisku tlačítka ?Součet? bude tvořena příkazy:

  11. int x, y;
    x = Edit1->Text.ToInt();
    y = Edit2->Text.ToInt();
    Edit3->Text = AnsiString(x + y);
    Vytvořte obsluhy i pro ostatní tlačítka a program vyzkoušejte.
  12. Do předchozí aplikace přidáme ještě další editační ovladač, ve kterém budeme počítat počet provedených výpočtů (vyprázdníme jej a před něj umístíme text ?Počet provedených výpočtů:?). Pro počítání provedených výpočtů musíme zavést globální proměnou, nazveme ji např. Pocet (globální proměnné deklarujeme mimo funkce; je nutno ji deklarovat před první funkcí, ve které ji používáme). Do všech obsluh stisknutí tlačítek přidáme příkazy:

  13. Pocet++;
    Edit4->Text = AnsiString(Pocet);
    Bylo by vhodné naši proměnnou Pocet na počátku výpočtu vynulovat. Můžeme to provést tak, že její deklaraci spojíme s inicializací, tzn. před první obsluhu vložíme:
    int Pocet = 0;
  14. Jistě jste si povšimli, že když stisknete některé tlačítko a nemáte zadané operandy je signalizována chyba. Na začátku obsluh stisku tlačítek budeme testovat, zda oba operandy jsou zadány. Toto testování je nutno provádět ve všech obsluhách stisku tlačítek a tedy vytvoříme funkci, která otestuje editační ovladač zadaný parametrem funkce (pokud editační ovladač je prázdný vrátí true, jinak vrátí false). Tato funkce bude vypadat takto:

  15. bool NeniHodnota(TEdit *EditOvl){
      if (EditOvl->Text == "") {
        EditOvl->Color = clRed;
        EditOvl->Text = "Zadej hodnotu";
        EditOvl->SetFocus();
        return true;
      }
      else {
        EditOvl->Color = clWindow;
        return false;
      }
    }
    Parametr funkce je typu ukazatel na TEdit, což je typ editačního ovladače (všechny typy komponent začínají písmenem T) a jako skutečný parametr tedy budeme moci použít Edit1 i Edit2. Pokud v editačním ovladači není zapsán žádný text, je změněna barva ovladače na červenou a je zde vypsán text ?Zadej hodnotu. PříZadej EditOvl->SetFocus(); zajistí vybrání testovaného editačního ovladače a můžeme tedy do něj ihned zapisovat hodnotu. Pokud editační ovladač není prázdný, pak je změněna jeho barva na normální barvu okna (clWindow). Tuto funkci musíme ještě vyvolat na začátku všech obsluh stisků tlačítek. Na začátek všech těchto obsluh tedy přidáme příkaz:
    if (NeniHodnota(Edit1) || NeniHodnota(Edit2)) return;
    Proveďte výše uvedené změny pro všechna tlačítka a vyzkoušejte. Není to ale ideální řešení, neboť nezajišťuje aby v editačním ovladači byla zapsána číselná hodnota.
  16. V další aplikaci se seznámíme s používáním komponenty Timer (časovač - je ji možno použít k definování časového intervalu). Na formulář umístíme tuto komponentu (je na stránce System Palety komponent). Jedna z vlastností této komponenty určuje časový interval v milisekundách (tato vlastnost se jmenuje Interval). Do programu vložíme deklaraci globální proměnné X typu bool a vytvoříme obsluhu události OnTimer časovače s příkazy:

  17. if (X) Color = clRed;
    else Color = clGreen;
    X = !X;
    Zjistěte, co provádí tato aplikace.
  18. Vytvořte aplikaci, ve které se pokusíte simulovat házení hrací kostkou. Každou sekundu generujte hod a jeho výsledek zobrazte na formuláři (číselně). Inicializaci generátoru náhodných čísel proveďte v obsluze události OnCreate formuláře.

Nové pojmy:

 11. Funkce I