vypracovani temat z 3. prednasky

This commit is contained in:
2026-03-06 10:20:36 +01:00
commit c2bedb027e
5 changed files with 491 additions and 0 deletions

85
3. týden.md Normal file
View File

@@ -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 <exception>`
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
- `<exceptions>`
- `std::exception` - bázová třída
- `virtual what()` - vrací řetězec výjimky
- `terminate()` - ukončí program, když se něco pokazí?
- `<stdexcept>`
- `std::out_of_range`
- nestihl
- nestihl
- `<new>`
- 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

View File

@@ -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<int> vec1 = {10, 20, 30, 40, 50}; //vektory jsou jako pole
vector<int> 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
```

View File

@@ -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 <iostream>
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ě...

51
Reference.md Normal file
View File

@@ -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.**

98
Výjimky.md Normal file
View File

@@ -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?
- `<exception>` - základní funkcionalita chyb
- `<stdexcept>` - seznam typů chyb, které můžeme využít na "házení" při problémech
- `<new>` - umí vyhazovat chyby typické u práci s pamětí (třeba nedostatek místa)
Funkcionalitu musíme přidat pomocí `#include <exceptions>`
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 `<stdexcept>`.
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...