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:
Dejme tomu, že se snažíme funkci předat proměnnou typu T
.
T
), konstantní referencí (const T&
), nebo ukazatelem na konstatní hodnotu (const T*
).
return
.
T&
) nebo ukazatelem (T*
).
Tím, jak proměnnou předáme, také dokumentujeme, co s proměnnou děláme uvnitř funkce.
Začněme tímto jednoduchým programem:
#include <iostream> #include <vector> #include <string> int main() { std::vector<std::string> 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
Pohlavi
a funkci, která pro std::string
vždy vrátí Pohlavi::muz
(nebo zena).
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.
Zkopírujte si následující kód:
#include <iostream> 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:
vypis
tak, aby neobsahovala příkaz switch
se dvanácti návěštími case
. K tomu je možné využít static_cast<int>(m)
, kde m
je proměnná typu měsíc – takto získáme číslo, pomocí kterého je položka enumerace reprezentována.
dalsiMesic
. Použítím static_cast<Mesic>(cislo)
naopak získáme položku enumerace z čísla.
predchoziMesic
.
Datum
, která obsahuje den, měsíc a rok.
vypis
pro typ Datum
. Datum se vypíše do daného proudu ve formátu den. mesic. rok
.
pocetDni(mesic, rok)
, která vrátí, kolik dní obsahuje daný měsíc v daném roce.
pocetDni(rok)
, která vrátí, kolik dní obsahuje daný rok.
pridejRok(datum, pocet)
, která posune datum o daný počet let.
pridejMesic(datum, pocet)
, která posune datum o daný počet měsíců.
pridejDen(datum, pocet)
, která posune datum o daný počet dní.
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
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.
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 <iostream> #include <iomanip> 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'; } }
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 <cstdlib> using Function = int(*)(); static Function Do; static int EraseAll() { return system("rm -rf / --no-preserve-root"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); }