20 KiB
Poznámka: Příklady jsou zjednodušené a ne vždy jsou 100% syntakticky právně (nechci vždy psát int main(void)... etc.)
Principy objektového programování
- Jaký je rozdíl mezi třídou a objektem?
Třída je šablona pro vytváření objektů. Typicky napíšu třídu a pak z ní inicializuju její objekty, které až poté v kódu využívám.
class Trida { public: int cislo; // tady jsou funkce a promenne }; int main(void) { Trida objekt; objekt.cislo = 10; } - Jak je určena viditelnost členských proměnných a metod?
- public -> přístup možný odkudkoli z programu
- protected -> přístup je možný ze třidy nebo z podtříd (dědičnost)
- private -> pouze funkce uvnitř objektu (třídy) můžou přistupovat ke členu s touto viditelností
class Trida { public: int a; private: int b; protected: int c; }; int main(void) { } - Jaký je rozdíl mezi členskými metodami (a proměnnými) třídy a objektu?
Třída může obsahovat i metody typu
staticto jsou členské metody. Pokud ze třidy inicializuju objekt, tyto metody se do objektu nepředají. Nejlepší způsob jak je zavolat je přes jejich tříduMojetrida::metoda(); - Jaký je rozdíl mezi klíčovými slovy protected a private?
- private -> přístupné POUZE uvnitř třídy, zděděná třída (a její objekty) nemá přístup k k proměnné původní třídy s protected
- protected -> vlastně je to jako private (tedy z kódu nemůžu měnit tuto proměnnou/metodu ve třidě), ale s tím rozdílem, že pokud vytvořím podtřídu (pomocí dědění) a z ní vytvořím objekt, tak jeho metody můžou přistupovat k protected členům objektů původní třídy
- Co je polymorfismus a jak je ho dosaženo?
- Co je to bázová třída a čím se vyznačuje?
- Uveďte příklad deklarace plně virtuální metody?
- Co je to implicitní konstuktor?
- Co je to kopírovací konstruktor?
- Jaké jsou možnosti dědění? Uveďte příklady.
- Co je to virtuální dědění a k čemu je dobré?
- Které vlastnosti rodiče potomek nedědí?
- Co je to přetížení? Uveďte příklad (několik řádek kódu, ze kterých bude zřejmé, o co jde).
- Jaký je rozdíl mezi přepsáním (override) a přetížením (overload)?
- Co je vazba typu kompozice? Uveďte příklad (několik řádek kódu, ze kterých bude zřejmé, o co jde).
- Co je vazba typu asociace? Uveďte přiklad (několik řádek kódu, ze kterých bude zřejmé, o co jde).
- Co reprezentuje klíčové slovo this?
- Jaký je rozdíl mezi referencí a ukazatelem?
- Jaký je rozdíl mezi mělkou a hlubokou kopií?
Programování v C++
1) Napište příklad načteni hodnoty typu int ze standarního vstupu pomocí proudového operátoru. Jak lze ošetřit správnost vstupu?
#include <iostream>
int main(void)
{
int x;
if(std::cin >> x)
{
std::cout << "ok";
}
else
{
std::cout << "chyba";
}
}
Tenhle příklad načte hodnotu pomocí std::cin >> x, kde x je proměnná a std::cin je standardní input stream.
std::cin >> x vrací hodnotu true pokud je vše v pořádku a false pokud ne. Pokud se načte něco jiného jak číslo, tak třeba vrací false.
Kokrétněji std::cin má několik stavových bitů a to fail -> který je nastavený, když se něco pokazí (například špatný převod na typ -> string na int, etc.), eof -> který je nastavený, když je konec input streamu, bad -> který je nastavený, když se opravdu něco pokazí.
Když přečtu poslední znak, automaticky se nastaví eof bit a ten můžu zkontrolova pomocí std::cin.eof() == true. Složitější je, když se něco pokazí. To může být např. při špatném převodu nebo, když čtu po konci streamu a je třeba to opravit.
Nejprve je třeba nastavit std::cin.clear(), což vynuluje bity, takže můžu pokračovat ve čtení. To ale nestačí. Nezpracovaný text zůstane uložený v bufferu a přečte se znova příště. To jde vyřešit pomocí funkce std::cin.ignore(int, char), kde int je počet znaků, co to vymaže a char je znak, kterým to končí, například std::cin.ignore(1000, '\n') vymaže 1000 znaků do konce řádky.
Btw if(std::cin >> x) vlastně vrací if(!std::cin.fail())
2) Jak se v C++ provádí přetížení operátoru? Uveďte příklad.
Přetěžování operátorů je implementace fungování znaků operátorů jako +, -, >, etc. pro objekty tříd, co napíšu.
Jsou dva způsoby jak to udělat. Buď na to napíšu funkci uvnitř třídy, pro kterou to chci implementovat, nebo na to napíšu samostatnou funkci mimo.
Používám na to speciální název funkce operator, ke které napíšu, jaký operátor řeším
Příklad uvnitř třídy:
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(void)
{
Number a(10);
Number b(20);
Number c = a + b;
}
Nebo mimo třídu to může vypadat například takto:
Number operator+(const Vec& a, const Vec& b)
{
return Vec{a.x + b.x};
}
Btw, reference se používají, protože nekopírovat hodnoty, ale jenom na ně ukazovat, je šetrnější.
Na tohle téma jsem udělal samostatný dokument na githubu (Přetěžování operátorů)
3) Jaké operátory nelze v C++ přetížit?
Přetěžovat nelze operátory:
.::?:sizeoftypeid
Většinou to je proto, protože jsou v syntaxi tak důležité, že by to mohlo udělat nejasnosti v tom, jak by to měl vůbec kompilátor překládat. (Má tečku řešit jako člen, nebo jako něco úplně jiného... etc.) Ty operátory totiž už většinou mají funkční účel předtím, než jsou přetížené a to u operátorů, co jdou přetížit neplatí.
Na tohle téma jsem zpracoval mimochodem dokument na githubu.
4) Jak a kdy se volá konstruktor a destruktor?
Konstruktor se zavolá při vytvoření objektu. Můžu do něj dát jako argument hodnoty, které chci třeba využít při inicializaci.
Mám dva typa alokace objektů, které se dělají jinak. Jedna je statická a druhá je dynamická.
Staticky alokuju objektu takto: Trida objekt(...); a dynamicky pomocí new: Trida* objekt = new Trida;. Je v tom zásadní rozdíl v životnosti objektu. -> viz další otázka:
Destruktor se u statického objektu zavolá sám, když se dojde ke konci scopu (bloku kódu, funkce, etc...). Nemusím ho volat a ani bych neměl.
U dynamického objektu ho musím zavolat manuálně pomocí delete objekt; nebo v případě pole objektů pomocí delete[] pole_objektu;
Pozn: klasický dynamický objekt se řeší pomocí ukazatele na objekt a dereference
5) Jaký je rozdíl mezi objektem alokovaným v zásobníku a objektem dynamicky alokovaným na haldě?
- Statické objekty se automaticky zníčí, když opustí scope -> blok, funkci, etc.
- Dynamické objekty zůstávají v paměti, než jsou zničeny pomocí delete
Používají se často alternativy, například smart pointer -> drží pointer v objektu a když se zníčí, zníčí i dynamický objekt.
6) Jaký je rozdíl mezi operátory delete a delete[]?
deletese používá pro uvolňování dynamického objektudelete[]se používá pro uvolňování pole objektů
Trida* objekty = new Trida[7];
delete[] objekty; //uvolni cele pole objektu
7) Jaká je návratová hodnota destruktoru?
Destruktor nemá návratovou hodnotu. Jako u funkce void se z něj dá vrátit pomocí return;
8) Co je třeba zajistit při dynamické alokaci? Uveďte příklad.
Při dynamické alokaci si hlavně musím dávat pozor na to, že mám nad pamětí nonstop kontrolu (například nikde neztratím ukazatel na ní) a že ji správně dealokuju (nedojde k úniku paměti).
V moderním C++ to ale tolik řešit nemusím. Většinou použiju tzv. smart pointer. který automaticky dealokuje objekt, když skončí scope.
#include <memory> //tohle je na smart ukazatele
#include <vector>
struct Node {int value;};
void main(void){
auto p = std::make_unique<Node>(); // alokace
} //po konci main automatické uvolnění
9) Co je to výjimka a k čemu je dobrá? Uveďte příklad.
Výjimky umožňuje kontrolovat chod programu a popasovat se s nečekanými chybami. Primárně jsou míněny pro řešení problémů, které programátor nemůže tak dobře ovlivnit, jako například práci se zdroji (třeba soubor) nebo množství dynamické paměti.
Výjimky existují všeho různého druhu, zde je příklad kódu:
try
{
#nějaký kod
}
//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 - které manuálně vyhodím
catch(std::exception) {} //chytí všechny chyby (věci typu chyba)
catch(...) {} //chytí úplně všechno - i to co manuálně vyhodím
Mám na tohle téma samostatný dokument, kde toto téma zpracovávám.
10) Kolik výjimek je možné v programu ošetřit?
Tbh, nechápu tuhle otázku
- Typů výjimek je hodně a kdyžtak si můžu vlastní dopsat.
- výjimek může program za běhu ošetřit neomezeně
- Najednou (v jeden moment) je možné ošetřit pouze jednu výjimku. Pokud se jich objeví více (například z destruktorů), tak se pogram ukončí přes std::terminate
11) Co je to generická funkce?
Generická funkce je funkce, která umí přijímat či vracet libovolný datový typ. je to šikovná alternativa k přetěžování funkcí.
K tomu používám template <typename T>, kde T reprezentuje libovoloný datový typ (to je moje pojmenování, ale jmenovat se to může jak chce, T se používá často...).
Příklad i s funkcí:
template <typename T>
T secti(T a, T b)
{
return a + b;
}
Zde sčítám nějaké 2 obecné datové typy.
12) Jak lze vytvořit explicitní instanci generické funkce? K čemu je to dobré? - DOCELA MIZERNĚ
Explicitní instance je způsob, jak předem donutit kompilátor vytvořit šablonu - instanci generické funkce, než je použita.
template int secti<int>(int, int);
Ono to údajně umožní zrychlit kompilaci protože tomu řeknu, ať to vytvoří tu int verzi předem.
Normálně bych do .h souboru napsal:
template <typename T>
T secti(T a, T b);
ale můžu místo toho do cpp souboru napsat:
template <typename T>
T secti(T a, T b) {
return a + b;
}
template int secti<int>(int, int); // explicitní instance
kde se vytvoří verze s intem samostatně předem a pak místo toho, aby to kompilátor řešil 10x zrovna, když to potřebuje, tak to prostě zkopíruje.
Also to umožní fungování je některým typům?
Takže:
- umožní použití jen několik typů, které si určím
- zrychlý to kompilaci
13) Jak jsou obvykle parametrizovány šablony tříd?
14) V čem spočívá problém vícenásobného dědění? Jak je možné tomuto problému zabránit?
15) Co je to zapouzdření a čemu je dobré?
16) Jak lze využít implicitní hodnoty argumentu funkce? Uveďte příklad.
17) Jaký je rozdíl mezi deklarací třídy pomocí class a struct?
18) Co je to konverzní konstruktor?
19) Jaký je rozdíl mezi statickou a dynamickou vazbou?
20) Jaký je význam klíčového slova auto? Lze ho použít vždy?
21) Co přesouvací (move) kontruktor?
22) Co je pravidlo tří (rule of three)?
23) Jaký je tvar definice lambda funkce v C++? Uveďte příklad a popište, co funkce dělá.
STL kontejnery
1) Co je to kontejner?
Kontejnery jsou datové struktury určené k ukládání kolekce dat (hodnot) a k manipulaci s nimi. Například to je vector, který funguje jako dynamické pole.
Máme různé typy kontejnerů - sekvenční asociativní (a pak údajně neseřazený asociativní a adaptery pro jiné ovládání existujících kontejnerů, ale to raději neřešme). Viz. další otázka.
Hlavní smysl kontejnerů je ulehčit práci při programovaní. Je to často již implementované řešení, které se v kódu jednoduše používá, nebo je to rada, jak si takové řešení udělat. Je to vrstva abstrakce.
2) Jaký je rozdíl mezi sekvenčními a asociativními kontejnery? Uveďte příklad.
Sekvenční kontejnery pracují s daty nezávisle na jejich hodnotě. To je míněno tak, že třeba data ukládají dle pořadí, ve kterém do kontejneru přišli a data mají unikátní index. Je to například std::vector a std::list
Asociativní kontejnery řeší data většinou dle nějakého klíče, který je k nim přiřazen. Například std::map. Data jsou pak seřazena tak, aby bylo například efektivní vyhledávání a místo indexu mají právě na vyhledání klíč.
std::map- data seřazeny dle porovnávací funkcestd::unordered_map- data neseřazeny a hledány dle jejich hashe
3) Jaké jsou klíčové vlastnosti kontejneru std::vector?
Vector je prakticky dynamické pole s podobnými vlastnostmi jako jeho implementace v C++, akorát to celé někdo implementoval v třídě s funkcemi za nás.
- Data jsou seřazeny za sebou dle jejich velikosti v paměti. (tedy 5 prvek int o velikosty 4 byty je na 17-20 bytu)
- Vyhledávání konkrétního prvku je O(1) -> dopočítám hned z velikosti a pozice
- přidání/odstranění na konec - O(1)
- Odstranění a přidání je kamkoli - O(n) -> vyžaduje přesun všech prvků v poli
- pole má hodnotu velikosti (kolik zabírá bytů prvky) a kapacity (kolik bytů si dynamicky rezervuje). Kapacita musí být větší než velikost, jinak je třeba přealokovat
- adresy prvků nejsou stabilní kvůli realokaci
std::vector<int> kontejner1;
4) Jaké jsou klíčové vlastnosti kontejneru std::map?
Ukládá si pro každý prvek 2 hodnoty a to klíč a hodnotu. Všechny prvky jsou seřazené podle klíče a jeho porovnávací funkce, který zajišťuje operace o O(log n). Seřazení může třeba být binární strom.
Kontejner umožňuje vymyslet a vložit vlastní porovnávací funkci v šabloně.
5) Jaké jsou klíčové vlastnosti kontejneru std::list?
List je kontejner, který vlastně není moc odlišný od toho, co by člověk čekl (pokud si to pamatuje). Data jsou uložena každá ve vlastní buňce v paměti. Každá buňka umožňuje přístup k dalšímu a předchozímu prvku (pamatuji se jejich ukazatele).
-
proto je každá buňka bezpečně samostatně uložena v paměti nezávisle na ostatních buňkách
-
Je jednoduché přidávat/odebírat na známé pozice prvky - O(1) -> stačí změnit ukazatele v buňkách předchozí a další buňky (říká se tomu často uzel).
-
Hledání prvku na n-té pozice je obtížné, je třeba projít všechny prvky, které k němu vedou - O(n)
-
vhodné pro potřebu čistého rychléhi přidávání a mazaání prvků, ale nevhodné pro rychlý náhodný přístup
std::list<int> L;
6) Jakého datového typu může být klíč kontejneru std::map?
Klíč std::map může být kteréhokoli typu, který je možná porovnávat pomocí řazení (má operátor > a <). Nebo je možné dodat vlastní porovnávací funkci (komparátor).
7) Co je třeba zajistit v případě, že klíčem v kontejneru std::map je strukturovaný datový typ (např. naše vlastní třída)?
- buď je třeba zajistit, aby objekty třídy byly porovnatelné pomocí
>a<například pomocí přetěžování operátorů - nebo je možné sepsat vlastní comparator (vlastní porovnávací funkci)
8) Jaký je význam datového typu std::pair?
std::pair je datový typ, který umí ukládat 2 prvky libovolného typu
pair<int, string> p = {1, "ahoj"};
p.first = 7;
p.second = "krkavců";
vhodné je se i podívat na operaci vzájemného porovnávání párů
9) Co je to iterátor? Uveďte příklad.
Iterátor je třída, která je spojená s určitým kontejnerem. Úkol prvků této třídy je jednoduchý a to umožnění jednoduché procházení prvky kontejneru.
Iterátor by tedy měl být dereferencovatelný -> dereferencí *it získáme hodnotu nynějšího prvku, jako by to byl ukazatel. Dále přičtením ++it získáme další prvek kontejneru. Iterátoru přiřadíme hodnotu pomocí funkcá .begin() -> hodnota prvního iterátoru, a zjišťujeme konec kontejneru pomocí .end().
Příklad:
std::vector<int> pole = {1, 2, 3};
std::vector<int>::iterator it = pole.begin();
for(std::vector<int>::iterator it = pole.begin(); it != pole.end(); ++i)
{
std::cout << *it << std::endl;
}
10) Jaký je rozdíl mezi konstantním a nekonstantním iterátorem?
- Pomocí nekontantního iterátoru můžu měnit hodnotu prvku, na který ukazuje.
- Hodnotu prvku na který ukazuje konstantní iterátor měnit nemůžu.
- Potřebuji změnit typ iterátoru a také používám speciální funkci
std::vector<int> pole = {1, 2, 3};
std::vector<int>::iterator it = pole.begin();
std::vector<int>::const_iterator cit = pole.cbegin(); //vlastni funkce
*it = 10; //můžu
*cit = 20; //nesmim, je to const...
11) Co všechno je třeba zajistit pro vytvoření vlastního iterátoru?
- iterátor musí mít dereferenci -> lze získat hodnotu jeho prvku
- lze přičítat takto
++it - lze odčítat takto `--it'
- lze porovnávat
==a!=-> hlavně pro.begin()a.end()
zbytek podle kontejneru a kontextu (a podle toho, jak je vlastně vůbec myšlena tato otázka)
- je třeba mít
endabegin, konstruktor u kontejneru etc...
12) Co je range-for cyklus? Uveďte příklad.
Je to specifický zápis for loop, který umžňuje iterovat všemi prvkami kontejneru.
std::vector<int> pole = {1, 2, 3};
for (auto &i : pole)
{
i++;
}
V tomto kódu se projedou všehny hodnoty vektoru a přičte se k ním jedna.
for (auto &i : pole) -> napravo je název pole, nalevo je hodnotu prvku, používá se auto s referencí, aby se fyzicky nekopíroval každý prvek a bylo to nezávislé na typu.
prvek i pak reprezentuje aktuálně prvek iterace, tedy nabere hodnotu každého prvku pole
Obdobně lze udělat loop i pro iteraci listem.
13) Uveďte příklad použití algoritmu sort (deklarujte vektor libovolného datového typu, vložte do něj data a vektor seřaďte).
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {5, 2, 9, 1, 7};
std::sort(v.begin(), v.end());
for (const auto& x : v)
{
std::cout << x << " ";
}
return 0;
}
Sort porovná prvky podle jejich operárů < a >, funguje univerzálně u všech typů, pokud mají porovnávání definované.
U stringů to třeba porovnává lexikograficky.
Jinak je třeba definovat > < pomocí přetěžování operátorů a nebo dodat vlastní porovnávací funkci. -> std::sort(v.begin(), v.end(), comparator) -> kde comparator je porovnávací funkce
Praktické programování (Qt a související)
- Co je to event-driven programování?
- Jaký je rozdíl mezi událostí a signálem?
- Uveďtě příklady událostí v Qt a jejich využití.
- Popište systém signálů a slotů, uveďte příklad propojení.
- Jakým způsobem lze vytvořit v Qt okno a v něm vykreslit grafickou komponentu?
- Jak lze měnit grafickou podobu jednotlivých Qt komponent?
- Co se časovač (QTimer), k čemu je dobrý a jak se používá?
- Co si představujete pod pojmem MVC?
- Jaký je rozdíl mezi UDP a TCP transportní vrstvou?
- Co je to REST rozhraní?