Table of Contents

Cvičení 3: 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:

Dejme tomu, že se snažíme funkci předat proměnnou typu 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 <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

Mohou se hodit některé z těchto metod vectoru a stringu:

Navíc string má k dispozici operátor +, který zřetězí (spojí) dva řetězce.

Řešení

Příklad: Datum

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 k procvičeníÚkoly:

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

Řešení

 Úkol k procvičení

Úkoly k procvičení

Úkol 1

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.

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 <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';
	}
}

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?

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

 Řešení a odpovědi

Řešení a odpovědi k nedefinovanému chování

Příklad 1 - index mimo hranice pole

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 main
Program 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 main
Zde 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ěď.


Příklad 2 - neinicializovaný nebo nulový ukazatel

Před experimentováním s programem nezapomeňte změnit příkaz na smazání všech dat z disku!

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 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 main
Př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 main
Kompilace 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().