Programovanie (1) v C/C++
1-INF-127, ZS 2024/25
Letný semester, prednáška č. 2
Obsah
Oznamy
- Na test pre pokročilých sa v prípade záujmu treba prihlásiť do utorka 23. februára, 11:30.
- Prvú bonusovú úlohu treba odovzdať do stredy 24. februára, 11:30.
Základné koncepty objektovo orientovaného programovania
Objekty a triedy
Dvoma najzákladnejšími konceptmi objektovo orientovaného programovania (OOP) sú triedy a objekty.
- Trieda (angl. class) je typ, ktorý podobne ako struct v C/C++ môže združovať údaje rôznych typov. Okrem toho ale obvykle obsahuje aj definície metód na manipuláciu s týmito údajmi.
- Objekt (angl. object) je inštancia triedy – obsahuje teda už nejakú konkrétnu sadu údajov vyhovujúcu definícii triedy, na ktorú možno aplikovať metódy triedy.
- Triedu teda možno chápať ako „vzor”, podľa ktorého sa vytvárajú objekty.
Príklad: nasledujúca trieda Fraction reprezentuje jeden zlomok. Obsahuje dve premenné numerator a denominator zodpovedajúce čitateľu a menovateľu zlomku a metódu na vyhodnotenie zlomku.
public class Fraction {
int numerator;
int denominator;
double evaluate() {
return (double) numerator / denominator;
}
}
Inštanciami tejto triedy, t.j. objektmi typu Fraction, sú konkrétne realizácie triedy Fraction (napr. zlomok s čitateľom 2 a menovateľom 3). O spôsobe ich vytvorenia si povieme o chvíľu. Avšak v prípade, že už máme nejakú inštanciu fraction triedy Fraction vytvorenú, môžeme hodnotu zlomku vypísať napríklad nasledovne:
Fraction fraction;
// Sem pride vytvorenie instancie triedy Fraction a jej priradenie do premennej fraction.
System.out.println(fraction.evaluate());
Príklad: časť triedy reprezentujúcej zásobník implementovaný pomocou poľa (čo je v Jave značne suboptimálne riešenie) by mohla vyzerať napríklad nasledovne.
public class MyStack {
int data[];
int pocet;
int pop() {
pocet--;
return data[pocet];
}
// Dalsie metody (napr. push) ...
}
Ak si opäť odmyslíme vytvorenie samotného zásobníka, môžeme so zásobníkom typu MyStack pracovať napríklad takto:
MyStack stack;
// Sem pride vytvorenie zasobnika a napriklad niekolko prikazov push.
int x = s.pop();
Neskôr uvidíme, že medzi štandardnými triedami jazyka Java možno nájsť aj množstvo dátových štruktúr a medzi nimi aj triedu Stack pre zásobníky. Príklad vyššie je teda iba ilustračný a tvorbe tried podobného druhu je vo všeobecnosti lepšie sa vyvarovať.
Príklad: v Jave sú všetky typy okrem primitívnych triedami a ich inštancie sú teda objektmi. S výnimkou veľmi špecifického prípadu polí (o ktorom si viac povieme neskôr) pôjde o triedy a objekty v podobe, v akej si ich predstavíme na tejto prednáške.
Referencie na objekty
Premenná, ktorej typom je trieda, obsahuje referenciu na objekt, ktorý je inštanciou tejto triedy.
- Podľa toho sa teda správajú operátory = a ==.
- K premenným a metódam objektu, na ktorý príslušná referencia ukazuje, pristupujeme pomocou operátora . a píšeme napríklad fraction.numerator alebo fraction.evaluate().
Fraction fraction1, fraction2;
// ...
fraction1 = fraction2; // Obidve premenne ukazuju na to iste miesto v pamati.
fraction1.numerator = 3; // Zmeni sa aj hodnota fraction2.numerator.
- Do premennej, ktorej typom je trieda, možno priradiť hodnotu null – v takom prípade ide o referenciu, ktorá neukazuje na žiaden objekt.
Konštruktory a inicializácia objektov
Často je potrebné súčasne s vytvorením objektu vykonať rôzne inicializačné úkony – napríklad pri zásobníku typu MyStack alokovať pole, pri zlomkoch typu Fraction inicializovať premenné na vhodné hodnoty, a pod. Na takúto inicializáciu objektov v Jave slúžia špeciálne metódy – takzvané konštruktory. Volanie konštruktorov je neodmysliteľne späté s vytváraním objektov.
- Názov konštruktora je vždy rovnaký ako názov triedy, ku ktorej patrí (špeciálne teda ide o jediné metódy, ktorých názov podľa konvencie začína veľkým písmenom).
- Do hlavičky konštruktora sa nepíše návratová hodnota (konštruktor žiadnu nemá). V opačnom prípade pôjde o bežnú metódu (nie o konštruktor), čo môže viesť k pomerne nepríjemným chybám.
- Prípadné argumenty sa zapisujú rovnako ako pri ktorejkoľvek inej metóde.
- Pre jednu triedu možno definovať aj viacero konštruktorov, ktoré sa však musia líšiť postupnosťou typov argumentov (aby bolo pri volaní jasné, o ktorý z konštruktorov ide).
Príklad: pre triedu Fraction môžeme napísať napríklad nasledujúce dva konštruktory.
public Fraction() {
numerator = 0;
denominator = 1;
}
public Fraction(int num, int denom) { // Neskor uvidime, ze nie je nutne pre argumenty konstruktora a premenne instancie volit ine nazvy
numerator = num;
denominator = denom;
}
- Ak pre triedu nedefinujeme žiaden konštruktor, automaticky sa vytvorí konštruktor bez parametrov, ktorý v princípe sám o sebe neurobí nič, ale je možné ho zavolať (bez čoho objekt nevytvoríme).
Samotné vytvorenie inštancie triedy sa realizuje pomocou operátora new, za ktorým nasleduje volanie niektorého konštruktora.
Fraction f1 = new Fraction();
Fraction f2;
f2 = new Fraction(2, 3);
System.out.println(f1.evaluate());
System.out.println(f2.evaluate());
- Operátor new dynamicky alokuje pamäť pre objekt, zavolá príslušný konštruktor a vráti referenciu na vytvorený objekt.
- Nie je potrebné starať sa o neskoršie odalokovanie pamäte – túto úlohu v JVM vykonáva tzv. garbage collector.
Na rozdiel od lokálnych premenných sú premenné inštancií alokované automaticky, a to na hodnoty 0, false, alebo null v závislosti od typu premennej. Prípadne je možné niektoré premenné inštancií inicializovať aj explicitne na odlišné hodnoty:
public class Fraction {
int numerator; // Inicializuje sa na nulu.
int denominator = 1;
// ...
}
Alternatívne možno premenné inicializovať v rámci konštruktora, rovnako ako v jednom z vyššie uvedených príkladov. Pri vytváraní inštancie triedy pomocou operátora new sa ale jednotlivé procesy vykonajú v nasledujúcom poradí:
- Najprv sa vykoná automatická alebo explicitná inicializácia premenných (a to aj v prípade, že nebol definovaný žiaden konštruktor triedy a je tak volaný jej východzí konštruktor bez parametrov).
- Až následne sa spustí volaný konštruktor.