{{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();
}