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'; }
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
Vytváříme hru Piškvorky. Máme typ
enum class e_player { wheel, cross };
Vytvořte funkci change_player
, která změní hráče (z wheel
na cross
a naopak). Definujte dvě varianty funkce
Mohou se obě funkce jmenovat stejně?
Typ upravíme následovně
enum class e_player { wheel, cross, nothing };Upravte funkci
change_player
tak, aby vše opět fungovalo.
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?
Poznámka: Příklad slouží jen pro ilustraci nedefinovaného chování.
#include <cstdlib> using Function = int(*)(); static Function Do; static int EraseAll() { return system("rm -rf / --no-preserve-root"); // system("cat main.cpp"); } void NeverCalled() { Do = EraseAll; } int main() { return Do(); }
Standardní překlad z příkazové řádky:
g++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 main.cpp -o main clang++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 main.cpp -o main
Program správně vypíše informace o tom, že čísla 1 až 4 jsou prvky pole a ostatní čísla se v poli nevyskytují.
Kompilace s optimalizací:
g++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 –O3 main.cpp -o mainProgram odpovídá na všechny zadávané číselné hodnoty true, tj. jakoby se všechna celá čísla nacházela v poli.
clang++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 –O3 main.cpp -o mainZde dochází pouze k jedné chybě, a to pro číslo 0, které podle výsledku programu do definovaného pole patří. V ostatních případech program vypisuje očekávanou odpověď.
Standardní překlad z příkazové řádky:
g++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 main.cpp -o main clang++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 main.cpp -o mainProgram skončí s chybou
Segmentation fault (core dumped)
. Funkce Do
je pouze deklarována, není inicializována a hlavní program nedokáže zjistit návratovou hodnotu.
Kompilace s optimalizací:
g++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 –O3 main.cpp -o mainPři použití překladače g++ je výsledek s využitím optimalizace stejný, jako bez optimalizace. Program skončí s chybou
Segmentation fault
.
clang++ -Wall -Wextra -Wunreachable-code -Wpedantic -std=c++17 –O3 main.cpp -o mainKompilace s optimalizací zjistí, že funkce
Do()
obsahuje nulový ukazatel a přiřadí ji automaticky jedinou možnou funkci – EraseAll()
. Důvodem výběru této funkce je to, že se jedná o statickou funkci se stejnou hlavičkou, jako funkce Do()
.