From c2bedb027edb249f0af6dc4888910b97b1a6ae82 Mon Sep 17 00:00:00 2001 From: Lugaricci Date: Fri, 6 Mar 2026 10:20:36 +0100 Subject: [PATCH] vypracovani temat z 3. prednasky --- 3. týden.md | 85 +++++++++++++++++ Funktory a Lambda funkce.md | 74 +++++++++++++++ Přetěžování operátorů.md | 183 ++++++++++++++++++++++++++++++++++++ Reference.md | 51 ++++++++++ Výjimky.md | 98 +++++++++++++++++++ 5 files changed, 491 insertions(+) create mode 100644 3. týden.md create mode 100644 Funktory a Lambda funkce.md create mode 100644 Přetěžování operátorů.md create mode 100644 Reference.md create mode 100644 Výjimky.md diff --git a/3. týden.md b/3. týden.md new file mode 100644 index 0000000..a3802cf --- /dev/null +++ b/3. týden.md @@ -0,0 +1,85 @@ +s- výjimky +- reference +- konstantní metody/atributy +- přetížené operátory +- indexování + volání funkce (oboje jsou operátory) +- lambda funkce + +- cvičení + - conteinery: vector, list, mapa + - +# Výjimky +- Pozn: "Častokrát je možná lepší, když ten program spadne, než když se chová špatně." +- Řešení neočekávaných chyb +- Může se to stát náhodně (např. poškozený/přeplněná paměť) +- používá se **try** a **catch** +``` +try { +# kod kde nastane vyjimka / problem +} +catch (typ vyjimky) { +# kod, ktery se s vyjimkou popasuje +} +``` + +knihovna: `#include ` +Pak existuje ještě třetí operátor a to operátor `throw`, který vyvolává výjimku. +např `throw "Deleni nulou.\n` -> vyvolá výjimku **Deleni nulou\n** +`` +## Ošetření více výjimek +``` +catch (typ vyjimky) { +# kod, ktery se s vyjimkou popasuje +} +catch (...) { +# univerzalni reseni vyjimek pro vsechno ostatní +} +``` +## Typy výjimek +- `` + - `std::exception` - bázová třída + - `virtual what()` - vrací řetězec výjimky + - `terminate()` - ukončí program, když se něco pokazí? +- `` + - `std::out_of_range` + - nestihl + - nestihl +- `` + - nestihl + - nestihl + +# Reference +- ukazatelé byly označeny komunitou C++ jako nebezpečné např. kvůli nejasné syntaxi +- Reference je obdoba konstantního ukazatele +- nelze vytvořit prázdnou referenci, je svázána s hodnotou a ne adresou (?) +- Pokud možno **pls používat reference** + +- Reference +# Přetížené operátory +- stejně jako funkce lze přetížit operátory (protože to jsou vlastně takové funkce) + +- operátory definované pro dané datové typ nelze přetížit +- týká se to hlavně operátorů pro instance tříd +- např nelze přetížit: `.` `.*` `::` `?:` `sizeof` + +## Přetížení binárních operátorů +např: `+, -, *, ..., +=, -=, ...` +používá se `@` +např: `x @ y` + +## Přetížení unárních operátorů + +## Přetížení postfixových operátorů +# Lambda funkce + +- existuje něco jako funktor :C +- místo funktoru mám lambda funkce +- každou lambda funkci můžu přepsat do funktoru + +# Conteinery + +## Vector + +## List + +## Map diff --git a/Funktory a Lambda funkce.md b/Funktory a Lambda funkce.md new file mode 100644 index 0000000..5c320af --- /dev/null +++ b/Funktory a Lambda funkce.md @@ -0,0 +1,74 @@ +# Funktory +Jsem línej to víc rozepsat... pozn. pro sebe dodělat funktory potom. Teď mám přibližné vysvětlení v Přetěžování operátorů. +Prostě to je objekt, co se chová jako funkce, ale je uložený jako objekt (jako proměnná) v kódu a může mít vnitřní funkce a proměnné. Užitečné to je třeba když chci funkci, co si umí pamatovat data z každého zavolání (třeba sčítá všechny čísla, co do ní jsou poslány jako argumenty). +# Lambda funkce +- lambda funkce jsou primárně způsob, jak zjednodušit zápis funktoru. +- [lambda gfg](https://www.geeksforgeeks.org/cpp/lambda-expression-in-c/) +- [lambda capture gfg](https://www.geeksforgeeks.org/cpp/lambda-capture-clause-in-cpp/) + +Lambdy jsou divný... ale užitečný. Jde o to, že někdy chci mít možnost napsat hodně rychlou funkci, bez toho, abych ji někde musel definovat. Vytvořím tedy vlastně funkci do proměnné, a využiji ji přímo na místě a pak ji třeba hned zahodím. + +Syntax: +```cpp +[](int a, int b) {return a + b;} +``` +což je `[capture](parametry) -> návratový_typ { tělo }` ale hodně je optional... (třeba capture) +![obrzazek gfg](https://media.geeksforgeeks.org/wp-content/uploads/20260123115647200091/lambda_expression_in_c_.webp) + +Příklad: Řekněme, že si někde potřebuju nutně definovat sčítání (protože mám hloupý cpp a zapomnělo to, nebo idk :( ) +```cpp +auto add = [](int a, int b) {return a + b;}; + +int x = add(3,4); +``` + +Mám zde funkci, co je vlastně "uložená v proměnné". Ve skutečnosti je to ale jenom **funktor** tedy alternativa pro: +```cpp +class __Lambda123 +{ +public: + int operator()(int x, int y) + const { + return x + y; + } +}; + +__Lambda123 add; +``` + +**Lambda funkce se musí ukládat do proměnných typu `auto`!! + +Co je *capture*? +Lambda funkce umožňuje přijmout hodnoty/proměnné z lokálního bloku a dále s nimi pracovat jako s vlastními proměnnými. +Mám 2 typy *capture* a to *capture by reference* a *capture by value*. Jedno mi umožňuje vzít referenci k proměnným/objektům a v lambdě je měnit. Druhé mi umožňuje hodnoty do lambdy prostě zkopírovat. + +Př: (ukradeno z geeks for geeks) +```cpp +int main() { + vector vec1 = {10, 20, 30, 40, 50}; //vektory jsou jako pole + vector vec2 = {1, 2, 3, 4, 5}; + + auto lambda = [&vec1, vec2]() //& je ref. a druhé je pouze hodnota + { + for (int& num : vec1) {num *= 10;} + for (int num : vec2) {cout << num * 10 << " ";} + cout << endl; + }; + + lambda(); + + cout << "Vector 1: "; + for (int num : vec1) cout << num << " "; + + cout << "\nVector 2: "; + for (int num : vec2) cout << num << " "; + + return 0; +} +``` + +Další možnosti: +```cpp +auto lambda1 = [&](int x){}; //VŠECHNY externí proměnné jako refernec +auto lambda2 = [=](int x){}; //VŠECHNY externí proměnné jako hodnotu +``` \ No newline at end of file diff --git a/Přetěžování operátorů.md b/Přetěžování operátorů.md new file mode 100644 index 0000000..afe12e8 --- /dev/null +++ b/Přetěžování operátorů.md @@ -0,0 +1,183 @@ +- *operator overloading* + - prakticky definování operací s operátory (např. +, -, /, etc.) pro objekty + - [oficiální dokumentace](https://isocpp.org/wiki/faq/operator-overloading/1000) + - [geeks for geeks](https://www.geeksforgeeks.org/cpp/operator-overloading-cpp/) + - [krátké YT video](https://www.youtube.com/watch?v=9tHu4mWtrnM&pp=ygUYY3BwIG9wZXJhdG9yIG92ZXJsb2FkaW5n) které jsem trochu vykradl + +Když si vytvoříme třídu, chceme někdy mít jednoduchou práci při pracování s více jejími objekty. Pokud mám třeba třídu `ComplexNumber`" a chci sečíst 2 její objekty, musel bych normálně vytvořit vlastní funkci, která mi to umožní. Přetěžování operátorů mi ale zjednoduší práci. **Místo sepsání funkce pro součet si nadefinuji, že pokud sečtu 2 objekty pomocí operátoru `+`, tak se sečtou.** + +Příklad: +```cpp +class Number +{ +public: + int n; + + Number(int set_n) {n = set_n;} +}; + +int main() +{ + Number a(5); + Number b(10); + + Number c = a + b; //TOTO BY SE NÁM HODILO, ALE VYHODÍ TO ERROR + + return 0; +} + +``` +V kódu se snažím sečíst dva různé objekty, ale nejde to, protože součet dvou objektů typu `Number` není definován (stejně jako není definován součet 2 objektů typu chyba, nebo čehokoli jiného). +**Dostanu error ve stylu:** `Invalid operands to binary expression ('Number' a...)` + +**Já to ale můžu spravit tím, že si operaci pro součet sám definuju...** +```cpp +#include + +class Number +{ +public: + int n; + + Number(int set_n) {n = set_n;} + + Number operator+(const Number &numA) //reference na objekt napravo od + + { + return Number(this->n + numA.n) + } +}; + +int main() +{ + Number a(5); + Number b(10); + + Number c = a + b; //TED FUNGUJE + + std::cout << c.n << std::endl; + return 0; +} + +``` + +Takže co se děje... +Tady je příklad binárního operátoru `+`. V podstatě to je ekvivalent zavolání funkce z objektu `a`, která by se mohla jmenovat `vrat_muj_soucet_s_necim_jak_objekt` (hrozné jméně, já vím). **V podstatě `+` je nyní funkce objektu nalevo a to co je napravo od `+` je argument této funkce.** + +Je dobrý ale vědět, že operátory se nemusí přetěžovat jenom přes funkci objektu/třídy. Přetěžovat se dá i pomocí **nečlenské funkce** (normální funkce, která není součástí žádné třídy). + +Příklad: +```cpp +Number operator+(const Vec& a, const Vec& b) +{ + return Vec{a.x + b.x}; +} +``` +Toto funguje, i když to není v třídě `Number`. Pomocí nečlenské funkce lze mít součty 2 různých objektů, kde nalevo není objekt třídy, např: `5 + a`. + +# Typy operátorů a jejich přetěžování +Existuje více typů operátorů valná většina z nich se dá přetěžovat. Přetěžovat ale nelze operátory: +- `.` +- `::` +- `?:` +- `sizeof` +- `typeid` +## Binární operátory +- operáty s více stranami +- `např +, -, /, %` etc. + +Ve třídě: +```cpp +class Vec +{ +public: + int x; + + Vec operator+(const Vec& other) + const { + return Vec{x + other.x}; + } +}; +``` +`a + b` je prakticky `a.operator+(b)` + +Mimo funkci: +``` cpp +Vec operator+(const Vec& a, const Vec& b) +{ + return Vec{a.x + b.x}; +} +``` +`a + b` je prakticky `operator+(a, b)` + +## Unární operátory (prefixové) +- mají pouze jeden člen +- operátory prefixové se píšou před členem +- př: `-a, ++a` + +Ve třídě: +```cpp +class Vec +{ +public: + int x; + + Vec operator-() + const { + return Vec{-x}; + } +}; +``` + +`-a` je prakticky funkce `a.operator-()` +`const` v tomto případě znamená, že se nezmění objekt `this`. Ono to v tomto případě ani nedává smysl, jelikož chceme vrátit `-a`, ale nechceme změnit `a` na `-a`... Tho, ten syntax je matoucí. +**Přesněji** `const` před `{}` znamená, že metoda **nesmí měnit objekt** na kterém byla zavolaná. +Nesmím pak měnit proměnné objektu a volat jeho funkce, co nejsou stejně `const`. **Umožní mi to ale volat funkci i na const objektu**. + +## Unární operátory (postfixové) +- pouze jeden člen +- píší se za členem +- mají dummy parametr -> to je trik jak odlišit `++a` od `a++` +- např: `a++` + +```cpp +Vec operator++(int) +{ + Vec old = *this; + x++; //kde x je promenna Vec + return old; +} +``` +`a++` je prakticky `a.operator++(0)` +`this` je ukazatel a já nechci kopírovat ukazatel, ale to na co ukazuje, proto: +`Vec old = *this;` + +**Proč se to sakra píše jako:** `operator++(int)` **s int?** (`a.operator++(0)` -> dělá kompilátor). Protože by jinak nešlo rozlišit `++a` a `a++`. Proto je konvence, že `a++` je `operator++(int)` a `++a` je `operator++()`. + +## Ostatní +Operátor přístupu: +```cpp +int& operator[](size_t i) +{ + return data[i]; +} +``` +`a[i]` -> `a.operator[](i)` +`int&` protože jsem šetrný a vracím referenci na prvek -> něco co ukazuje na jeho hodnotu a ne jeho + +**Funktor**: +```cpp +class Functor +{ +public: + int operator()(int x) { + return x * 2; + } +}; +``` +`a(i)` -> `f.operator()(i)` + +Funktory jsou důležité! Funktory jsou objekty, které se chovají jako funkce a hádám (teď to nedokážu říct), že o nich uslyšíme ještě dost. +Jejich hlavní smysl je **být něco mezi objektem a funkcí**. Funkce nemá žádnou paměť, což znamená, že vždy dává stejný výsledek při stejném vstupu (i mean ne nutně, jako když použiju náhodná čísla, tak ne... buuuhuuu, ale chápeme se...). +Funktor mi umožňuje udělat objekt, který "předstírá, že je funkce". Pamatuje si svoje operace a může ukládat do vnitřních proměnných při každém zavolání/upravě, etc... + +Dále jde přetěžovat třeby `->`. Operátorů je opravdu hodně... \ No newline at end of file diff --git a/Reference.md b/Reference.md new file mode 100644 index 0000000..bb1df87 --- /dev/null +++ b/Reference.md @@ -0,0 +1,51 @@ +Reference je v podstatě jako **konstantní ukazatel** s jistými omezeními: + +- musí být inicializována při vytvoření +- nemůže být null +- nedá se měnit, kam ukazuje (stejně jako konst. uk.) a neumí aritmetiku + +**Používá se jako normální proměnná se jménem (bez \*)** + +Zásadní mentální rozdíl taky můžeme hledat v tom, co to vlastně je. Ukazatel je proměnná s adresou v paměti, zatímco reference je alias pro objekt/proměnnou. + +``` +int x = 5; # nějaká proměnná x + +int& r = x; #inicializace reference, musí mít hodnotu. +#int& r; #není možné + +int* p; # ukazatel to umí - pro srovnání +p = &x; +``` + +Referenci nelze přesměrovat na jinou adresu/proměnnou +``` +int a = 1; +int b = 2; + +int* p = &a; #ukazatel ukazuje na a +p = &b; # ukazatel ukazuje na b + +int& r = a; #reference ukazuje na a +r = b; #reference nabírá hodnoty b, teď a = b +# a = b; je ekvivalentní +``` + +- Reference nemá vlastní adresu, není to proměnná a nebo objekt. Pokud se pokusím získat adresu reference, dostanu adresu objektu, na který ukazuje. + +K čemu to je dobré? +- stále můžu používat referenci na předání funkci. **To je hlavní důvod existence referencí - bezpečnější a čitelnější předávání parametrů místo ukazatelů.** +``` +void increment(int& r) +{ + r++; #melo by zvysit jakoukoli promennou, co je predana jako parametr +} + +int main() +{ + int x = 42; + increment(x); #zde predam proste puvodni promennou +} +``` + +- **Je to omezenější než ukazatel a proto je tu menší šance, že se uživatel střelí do nohy. Když to stačí, mělo by se to používat místo ukazatele. Doporučuje se to.** \ No newline at end of file diff --git a/Výjimky.md b/Výjimky.md new file mode 100644 index 0000000..f39ab21 --- /dev/null +++ b/Výjimky.md @@ -0,0 +1,98 @@ +Výjimka je způsob jak aktivně kontrolovat správný chod programu a popasovat se s nečekanými chybami. V podstatě mě zajímají 3 hlavní operátory a to: +- **try** -> blok, ve kterém se snažím zachytit chybu +- **catch** -> blok, který řeší co se stane, když nastane chyba +- **throw** -> operátor, který umí chybu vyhodit + +Dále se u základní C++ setkáme s třemi knihovnami, které s chybami souvisí a to? +- `` - základní funkcionalita chyb +- `` - seznam typů chyb, které můžeme využít na "házení" při problémech +- `` - umí vyhazovat chyby typické u práci s pamětí (třeba nedostatek místa) + +Funkcionalitu musíme přidat pomocí `#include ` + +Hlavní smysl chyb je zachytit nečekané věci v programu. Neměli bychom řešit problémy s kterými počítáme. Zatímco můžeme řešit třeba dělení nulou, které nestane při očekáváném vstupu od uživale (a nějaký typ chyby na to existuje), tak protože to čekáme a připravujeme se na to, tak bychom to měli řešit spíše jinak. Chyba má smysl hlavně v případech, které nelze odtušit a to například: +- otevření souboru, který je prázdný +- nedostatek paměti +- přetečení +Chyby jsou praktické, ale zatěžují program. Proto je třeba s nimi být opatrný a nepoužívat je na všechno zbytečně (a také samozřejmě kvůli přehlednosti). + +Pro embedded systémy existuje alternativní způsob jak řešit chyby, který není tak náročný a to `std::optional` a `std::expected` v C++23. + +# Základ + +Strukturálně program s chytáním chyb vypadá: +```cpp +try +{ +# zde je blok kde hledám chybu +} +catch +{ +# zde je blok kde řeším chybu +} +``` + +## Try +`try` funguje jednoduše, všechno co je uvnitř je zpracovaný výjimka a můžu řešit, co s ní pak udělám pomocí `catch`. S tím pak můžu řešit důmyslnou logiku výjimek, kdy na různých vrstvách programu řeším různé problémy dle toho, kdy se mi to hodí. +Například na nízké vrstvě můžu vyhodit nějakou chybu ze špatné operace. na střední vrstvě se rozhodnu, jestli je řešitelná (můžu ji třeba napravit) a nebo jestli ne. Pokud ne, tak ji pošlu do vysoké vrstvy, která ji chytí a vypíše uživateli a ukončí proces, kde vznikla. + +Všechny chyby, které nejsou chyceny pomocí `try` spustí `std::terminate`, což způsobí pád programu. + +Tohle je vhodné si spojit ještě s konceptem **stack unwinding**. To je jak chyba cestuje -> stále nahoru v cestě programu (stack -> cesta volání - spíš paměť, kde je cesta uložena). Všechno putuje nahoru a když to skončí úplně nahoře v main, tak je průser. + +## Throw +`throw` vyhazuje chybu. Může to být text, např: `throw "Tady je chyba\n";` , ale obecně je spíš vhodné vyhodit nějaké oficiální objekt představující nějaký typ chyby z ``. + +např: ` throw std::invalid_argument( "received negative value" );` +- zde je možné poslat společně s typem chyby argument se zprávou... + + +## Catch +V bloku `catch`, který následuje za `try` se řeší, jak se zachovat v případě nějaké chyby +```cpp +try +{ + #něco +} + +#catch("Text") {} #nedává smysl, nechytá se hodnota, ale typ +catch(const std::bad_alloc& e) {} #vyjímka špatné alokace +catch(int) {} #chytí všechny integery +catch(std::exception) {} #chytí všechny chyby +catch(...) {}#chytí úplně všechno +``` + +`Catch` funguje jako funkce. Má argumenty, které nejsou hodnoty, ale typy hodnot, které jdou "zavolat". Proto nemůžu chytat text, ale můžu chytat např. `const char* e` -> a `e` je to co se předá. + +v případe `catch(const std::bad_alloc& e) {}` se používá [[Reference]] protože objekt může být teoreticky velký a my ho nechceme kopírovat. +## What() +`.what()` je funkce chybových objektů. Ta vypisuje jejich textovou hodnotu. +Například pro `throw std::invalid_argument( "received negative value" );` +vypíše `"received negative value"`. + +Volal bych třeba +```cpp +catch(const std::bad_alloc& e) +{ + std::cout << e.what() +} +``` + +## set_terminate a terminate() +Když nastane situace, kdy se dojde k nějakému fatálnímu programu, na který program není připraven, zavolá se `std::terminate()`. Ten zavolá `abort()` a ukončí program. +To může nastat, když výjimka není zachycena, ale případů je víc: +- během stack unwindingu (tedy při opouštění funkcí po `throw`) dojde k další výjimce +- vyhodí se výjimku z destruktoru během unwindingu +- porušení pravidla `noexcept` (funkce označená jako `noexcept` vyhodí výjimku) + +`terminate` je poslední funkce, která se zavolá před ukončením programu a nevrací dále dál tok programu. +My pokud chceme můžeme nastavit vlastní funkci `terminate`, pro větší kontrolu nad programem pomocí `std::set_terminate(my_func);` + +**Pozn**: destruktory nesmí házet výjimky proto, protože při výjimce nastane situace, kdy se ukončuje funkce a destruktory se volají. Pokud by destruktor vyhodil výjimku, mohla by nastat situace, kdy jsou 2 výjimky najednou, což je problém. +# Typy chyb + +- runtime chyby -> range, overflow, etc. +- logické chyby -> domain, out_of_range... +- bad_cast +- bad_function_call +- bad_alloc -> většinou nedostatek paměti od new... \ No newline at end of file