{{page>courses:b6b36pjc:styles#common&noheader&nofooter}} {{page>courses:b6b36pjc:styles#cviceni&noheader&nofooter}} ===== Předávání parametrů ===== Při programování v C%%++%% se s předáváním parametrů setkáváme neustále. Zároveň je mnoho způsobů, jak parametr předat. Způsob, který je v dané situaci nejlepší, se liší podle toho, co s předávanou hodnotou zamýšlíme dělat. Proto parametry funkcí rozlišujeme na: * vstupní -- parametry s hodnotami, které funkce potřebuje //pouze získat//, * výstupní -- parametry s hodnotami, které funkce potřebuje //pouze poskytnout//, * vstupně-výstupní -- parametry s hodnotami, které funkce potřebuje //získat, upravit a poskytnout//. Dejme tomu, že se snažíme funkci předat proměnnou typu ''T''. * Pokud se jedná o vstupní parametr, použijeme předání hodnotou (''T''), konstantní referencí (''const T&''), nebo ukazatelem na konstatní hodnotu (''const T*''). * Pokud se jedná o výstupní parametr, vrátíme jej příkazem ''return''. * Pokud se jedná o vstupně-výstupní parametr, použijeme předání referencí (''T&'') nebo ukazatelem (''T*''). Tím, jak proměnnou předáme, také dokumentujeme, co s proměnnou děláme uvnitř funkce. ==== Příklad: Oslovení ==== Začněme tímto jednoduchým programem: #include #include #include int main() { std::vector jmena = { "Petr", "Jan", "Jana", "Karel", "Katka" }; for (const auto& s : jmena) { std::cout << s << '\n'; } } Tento program vypíše: Petr Jan Jana Karel Katka Naším cílem bude upravit tento program tak, aby přidal oslovení ke každému jménu, a následně je vypsal: pan Petr pan Jan slecna Jana pan Karel slecna Katka Doporučujeme postupovat tak, že * Vytvoříte enum ''Pohlavi'' a funkci, která pro ''std::string'' vždy vrátí ''Pohlavi::muz'' (nebo zena). * Vytvoříte funkci, která vezme vektor jmen a vrátí vektor jmen rozšířených o oslovení. * Implementujete rozhodovací pravidlo, které správně ohodnotí jména v příkladu. * Vypíšete jméno rozšířené o oslovení. Mohou se hodit některé z těchto metod ''vector''u a ''string''u: * ''.size()'' poskytne počet prvků ve vektoru, příp. počet znaků v řetězci. * ''.empty()'' rozhodne, zda počet prvků ve vektoru je 0, příp. počet znaků v řetězci je 0. * ''.front()'' poskytne referenci na první prvek, příp. znak. * ''.back()'' poskytne referenci na poslední prvek, příp. znak. Navíc ''string'' má k dispozici operátor ''+'', který zřetězí (spojí) dva řetězce. {{:courses:b6b36pjc:cviceni:cviceni_3_osloveni.zip|Řešení}} ==== Příklad: Datum ==== Zkopírujte si následující kód: #include enum class Mesic { leden, unor, brezen, duben, kveten, cerven, cervenec, srpen, zari, rijen, listopad, prosinec }; void vypis(std::ostream& out, Mesic m) { switch (m) { case Mesic::leden: out << "1."; break; case Mesic::unor: out << "2."; break; case Mesic::brezen: out << "3."; break; case Mesic::duben: out << "4."; break; case Mesic::kveten: out << "5."; break; case Mesic::cerven: out << "6."; break; case Mesic::cervenec: out << "7."; break; case Mesic::srpen: out << "8."; break; case Mesic::zari: out << "9."; break; case Mesic::rijen: out << "10."; break; case Mesic::listopad: out << "11."; break; case Mesic::prosinec: out << "12."; break; } } Mesic dalsiMesic(Mesic m) { switch (m) { case Mesic::leden: return Mesic::unor; case Mesic::unor: return Mesic::brezen; case Mesic::brezen: return Mesic::duben; case Mesic::duben: return Mesic::kveten; case Mesic::kveten: return Mesic::cerven; case Mesic::cerven: return Mesic::cervenec; case Mesic::cervenec: return Mesic::srpen; case Mesic::srpen: return Mesic::rijen; case Mesic::zari: return Mesic::zari; case Mesic::rijen: return Mesic::listopad; case Mesic::listopad: return Mesic::prosinec; case Mesic::prosinec: return Mesic::leden; } } int main() { Mesic m1 = Mesic::leden; Mesic m2 = Mesic::prosinec; vypis(std::cout, m1); std::cout << '\n'; vypis(std::cout, m2); std::cout << '\n'; vypis(std::cout, dalsiMesic(m1)); std::cout << '\n'; vypis(std::cout, dalsiMesic(m2)); std::cout << '\n'; } Úkoly: * Najděte chybu v programu. * Změňte implementaci funkce ''vypis'' tak, aby neobsahovala příkaz ''switch'' se dvanácti návěštími ''case''. K tomu je možné využít ''static_cast(m)'', kde ''m'' je proměnná typu měsíc -- takto získáme číslo, pomocí kterého je položka enumerace reprezentována. * Podobně zkraťte funkci ''dalsiMesic''. Použítím ''static_cast(cislo)'' naopak získáme položku enumerace z čísla. * Implementujte funkci ''predchoziMesic''. * Vytvořte strukturu ''Datum'', která obsahuje den, měsíc a rok. * Vytvořte přetížení funkce ''vypis'' pro typ ''Datum''. Datum se vypíše do daného proudu ve formátu ''den. mesic. rok''. * Vytvořte funkci ''pocetDni(mesic, rok)'', která vrátí, kolik dní obsahuje daný měsíc v daném roce. * Vytvořte funkci ''pocetDni(rok)'', která vrátí, kolik dní obsahuje daný rok. * Vytvořte funkci ''pridejRok(datum, pocet)'', která posune datum o daný počet let. * Vytvořte funkci ''pridejMesic(datum, pocet)'', která posune datum o daný počet měsíců. * Vytvořte funkci ''pridejDen(datum, pocet)'', která posune datum o daný počet dní. * Upravte ''pridejRok'', ''pridejMesic'' a ''pridejDen'' tak, aby mohl být počet přičtených let/měsíců/dní záporný. //Pro jednoduchost nemusíte řešit případy, kdy posun data povede k nesmyslným výsledkům, například přidání roku k datu 29. 2. 2016// {{:courses:b6b36pjc:cviceni:cviceni_3_datum.zip|Řešení}} ===== Koutek nedefinovaného chování ===== Na prvním cvičení jsme se zmínili o tom, že přístup mimo alokované pole je nedefinované chování, ale neukázali si, k čemu to může vést. Tentokrát si to ukážeme. ==== Příklad 1 ==== Následující program obsahuje drobnou chybu v přístupu do pole čísel, která může vést k přístupu mimo pole (a tudíž k nedefinovanému chování). Zkuste si ho zkompilovat a spustit, nejdříve bez optimalizací, poté s nimi: #include #include int elements[] = {1, 2, 3, 4}; bool contains(int elem) { for (int i = 0; i <= 4; ++i) { if (elements[i] == elem) { return true; } } return false; } int main() { int num; while (std::cin >> num){ std::cout << std::boolalpha << contains(num) << '\n'; } } ==== Příklad 2 ==== Následující program, přeložený s optimalizacemi, se vám na Linuxu pokusí smazat všechna data. Již znáte vše co potřebujete, abyste mohli vymyslet, proč je to validní interpretace programu... Proč se tedy výsledný program pokusí smazat všechna data? #include using Function = int(*)(); static Function Do; static int EraseAll() { return system("rm -rf / --no-preserve-root"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); }