Dáta v štruktúrovanom dátovom type sú reprezentované jediným identifikátorom (premennou). Aby sa dalo pristupovať k členom musia štruktúrované dátové typy poskytovať možnosť prístupu k členom.
Zoskupenie dát do logických celkov sprehľadňuje výsledný kód. U programovacích jazykov s podporou objektovo orientovaného programovania môžeme používať dátový typ trieda obsahujúci okrem dátových členov aj funkcie na prácu s nimi (metódy).
Štruktúra (struct)
Dátový typ štruktúra môže uchovávať niekoľko na sebe nezávislých dátových členov. Typ aj počet dátových členov je ľubovoľný.
Deklarácia a definícia štruktúry
Deklarácia aj definícia štruktúry začínajú kľúčovým slovom struct. Ďalej nasleduje názov štruktúry, ktorým deklarácia končí. Definícia pokračuje telom štruktúry, ktoré je uzatvorené v zložených zátvorkách a skladá sa z deklarácie členských premenných. Za telom štruktúry je možné deklarovať jej inštancie. Za deklaráciou aj definíciou nasleduje bodkočiarka. Syntax definície štruktúry vyzerá nasledovne:
struct [Názov_štruktúry] { // deklarácie členských premenných } [Deklarácia inštancií];
Hranaté zátvorky označujú nepovinné časti definície. Názov štruktúry budeme potrebovať v prípade, že chceme vytvárať jej inštancie kdekoľvek v kóde (nie len bezprostredne za telom štruktúry). V prípade, že nám stačí vytvorenie inštancií v mieste definície nemusíme uvádzať meno štruktúry – vznikne anonymná štruktúra.
// Štruktúra s názvom osoba struct Osoba { // Anonymná štruktúra struct { string krstneMeno; string priezvisko; } meno; // Členská premenná meno // Členská premenná vek int vek; }; // Deklarácia a definícia štruktúry struct Osoba novaOsoba;
V tomto príklade je definovaná komplexná (skladajúca sa z ďalších štruktúrovaných dátových typov) štruktúra Osoba. Skladá sa z anonymnej štruktúry, ktorá je členskou premennou s názvom meno a členskej premennej vek.
Inicializácia
Štruktúru je možné inicializovať buď zároveň s definíciou, alebo priradením hodnôt dátovým členom za jej definíciou.
Pri definícii je možné inicializovať štruktúru priradením inicializačného zoznamu. Inicializačný zoznam je zoznam hodnôt oddelených čiarkami uzavretý do zložených zátvoriek. V prípade komplexných štruktúr je možné zložené zátvorky vnoriť do seba. Poradie položiek v inicializačnom zozname musí byť rovnaké ako poradie členských premenných v štruktúre. Rovnakým spôsobom sa inicializujú polia (v tom prípade má samozrejme každá položka rovnaký typ). Inštanciu štruktúry Osoba je možné inicializovať nasledujúcim spôsobom:
struct Osoba novaOsoba = {{"Meno", "Priezvisko"}, 99};
Štruktúru môžeme inicializovať aj postupným priraďovaním hodnôt jednotlivým zložkám. Takáto inicializácia býva pomalšia než inicializácia pri definícii. Nasledujúci kód inicializuje štruktúru na rovnakú hodnotu, ako kód s inicializačným zoznamom.
struct Osoba novaOsoba; novaOsoba.meno.krstneMeno = "Meno"; novaOsoba.meno.priezvisko = "Priezvisko"; novaOsoba.vek = 99;
Použitie smerníkov so štruktúrami
Štruktúry môžu obsahovať veľké množstvo dát. Objemné dáta nie je vhodné staticky alokovať na zásobníku. Dynamickou alokáciou na halde sa môžeme vyhnúť problémom s obmedzenou veľkosťou zásobníka. Smerníky sa používajú aj na zvýšenie efektivity programu. Pri volaní funkcie sa tak prenáša len smerník namiesto kopírovania celej štruktúry.
K prvkom štruktúry môžeme pristupovať po dereferencii rovnako ako v prípade staticky alokovanej štruktúry. Dôležité je aby sa dereferencia vykonala pred prístupom k prvku. Operátor prístupu k prvku (".") má vyššiu prioritu než operátor dereferencie ("*"). Pri zápise *smerník_na_štruktúru.prvok
sa kompilátor najskôr pokúsi pristúpiť k prvku bez dereferencie. Prioritu operátorov musíme zmeniť pomocou zátvoriek: (*smerník_na_štruktúru).prvok
. Pre zjednodušenie tohto zápisu bol zavedený nový operátor ->
. Nasledujúce zápisy sú ekvivalentné:
(*smerník_na_štruktúru).prvok smerník_na_štruktúru->prvok
Dynamická alokácia novej štruktúry, práca s ňou a jej zrušenie vyzerá takto:
struct Osoba *novaOsoba = new Osoba; novaOsoba->meno.krstneMeno = "Meno"; novaOsoba->meno.priezvisko = "Priezvisko"; novaOsoba->vek = 99; delete novaOsoba;
V tele štruktúry už kompilátor pozná jej deklaráciu. Prvkom štruktúry preto môže byť aj smerník na rovnakú štruktúru. Typickým príkladom takejto štruktúry je prvok zreťazeného zoznamu.
struct Prvok { struct Prvok *dalsi; }
Skrátenie zápisu pomocou typedef
Doteraz sme pri deklarácii každej inštancie štruktúry používali kľúčové slovo struct. Pomocou typedef je možné urobiť alias pre štruktúru. Pred deklaráciou nám v C kóde vypadne struct. V C++ kóde pre skrátený zápis nie je nutné použiť typedef.
typedef struct { // deklarácie členských premenných } Struktura; Struktura novaStruktura;
Cvičenie
Implementujte pomocou štruktúry zreťazený zoznam. Prvok zoznamu sa bude skladať z dát, ktoré prvok obsahuje a smerníku na ďalší prvok. Napíšte funkcie na výpis zoznamu, pridanie prvku a uvoľnenie alokovanej pamäte. Na nasledujúcom obrázku je diagram jednoduchého zreťazeného zoznamu:
Ak neviete ako začať postupujte podľa nasledujúcich bodov:
- Vytvorte štruktúru Prvok obsahujúcu smerník na ďalší prvok a dáta (string).
- Deklarujte premennú typu
struct Prvkok *
odkazujúcu na prvý prvok zoznamu.- Pre zrýchlenie pridávania prvku je dobré udržiavať aj smerník na posledný prvok.
- Smerníky na prvý a posledný prvok je možné zoskupiť do štruktúry Zoznam.
- Nakoniec napíšte samotný kód pre vkladanie a výpis.
- Ak všetko prebehne v poriadku pokúste sa napísať kód pre uvoľnenie pamäte.
Pre inšpiráciu môžete využiť tento program s chýbajúcou implementáciou zreťazeného zoznamu.
Union
Zatiaľ čo štruktúra mala vyhradené miesto pre každú položku zvlášť u unionu sa položky navzájom prekrývajú – každá položka začína na tom istom pamäťovom mieste. Po priradení hodnoty niektorej z položiek už nie je možné ostatné položky používať pretože sa ich obsah prepíše. Ak by sme sa pokúšali prečítať inú položku dostali by sme zdanlivo nezmyselné hodnoty. V niektorých prípadoch môže byť táto vlastnosť užitočná.
Typ union v pamäti zaberá rovnaký priestor ako najväčšia položka unionu. Vďaka tomu je union vhodným typom ak chceme ušetriť pamäť, ktorú by zaberali všetky položky štruktúry. Syntax je podobná štruktúre. Namiesto kľúčového slova struct sa používa union.
Jazyk C++ nerieši identifikáciu typu uložené v unione. Riešenie tohto problému je ponechané programátorovi. Jedným z možných riešení je obalenie unionu do štruktúry, ktorá obsahuje naviac informáciu o type uložených dát v unione. V nasledujúcom príklade je union použitý v štruktúre spolu s typom údaju.
struct Udaje { int typ; union { char ico[8]; char r_cislo[10]; } udaj; };
Anonymný union
V špecifických prípadoch môže byť vhodné, ak niekoľko lokálnych premenných začína na tom istom pamäťovom mieste. Toto správanie je možné dosiahnuť použitím anonymného union-u.
union { int cislo; double desatinne; }; cislo = 3; desatinne = 3.14;
Triedy
Jedným z hlavných rozdielov medzi C a C++ je podpora objektovo orientovaného programovania. Jazyk C neposkytuje prakticky žiadnu podporu pre objekty, čo ale neznamená, že by sa v ňom nedalo programovať „objektovo“.
Objektovo orientované programovanie
Pri objektovo orientovanom programovaní (OOP) vytvára programátor systém objektov a tried, ktorý má podobné vlastnosti ako systém v reálnom svete. Programy sa tak neskladajú zo samostatných funkcií a dát ako pri procedurálnom programovaní, ale zo sústavy objektov, ktoré majú svoje atribúty a metódy a môžu medzi sebou komunikovať.
Definíciu triedy môžeme považovať za šablónu, podľa ktorej kompilátor vytvára objekty. Trieda môže mať ľubovoľný počet inštancií. Triedami v reálnom svete sú napr. auto, mačka, zviera, strom … Ich inštancie budú mať atribúty a metódy definované v triede. Ak definujeme triedu Auto s atribútmi rýchlosť, najazdené kilometre a metódami ako zmeň rýchlosť, potom každá inštancia triedy Auto bude mať tieto atribúty a metódy.
Deklarácia, definícia a použitie tried
Deklarácia triedy začína kľúčovým slovom class. Syntax je identická ako u štruktúry. Trieda ekvivalentná štruktúre Osoba má nasledujúci kód:
class Osoba { public: class { public: string krstneMeno; string priezvisko; } meno; int vek; }; Osoba novaOsoba;
Viditeľnosť
Atribúty a metódy triedy sú štandardne privátne (private) – prístup k nim je možný len z metód triedy. Aby boli atribúty prístupné aj mimo triedy je nutné explicitne nastaviť viditeľnosť na public.
V nasledujúcej tabuľke sú druhy viditeľnosti, ktoré podporuje C++.
Typ viditeľnosti | Popis |
---|---|
public (verejné) | členy prístupné všade, kde je prístupná samotná trieda |
protected (chránené) | členy prístupné z metód triedy a metód odvodených tried |
private (privátne) | členy prístupné výhradne z metód triedy |
V časti o štruktúrach som popisoval štruktúru ako ju poznáme z jazyka C. V C++ sú tieto typy rovnocenné a jediný rozdiel je v implicitnej viditeľnosti členov. V nasledujúcej tabuľke je prehľad implicitnej viditeľnosti a možnosti jej predefinovania.
Typ | Štandardná viditeľnosť | Možnosť predefinovania |
---|---|---|
struct | public | Áno |
union | public | Nie |
class | private | Áno |
Metódy
Metódy sú podobné funkciám ako ich poznáme z procedurálnych jazykov. Majú prístup k všetkým členom triedy (aj privátnym). Platia pre ne rovnaké pravidlá viditeľnosti ako pre dáta objektu.
Syntax
Syntax metódy je rovnaká ako syntax funkcie. V triede sa môže vyskytovať buď celá definícia funkcie, alebo len jej deklarácia. Definícia funkcie nemusí byť v tom istom súbore ako definícia triedy. V nasledujúcom príklade je zápis metódy priamo v tele triedy:
class Trieda { public: void metoda() { }; };
Metódu je možné definovať aj mimo triedy tak, že v triede sa nachádza len deklarácia. Niekoľko tried môže mať metódu s rovnakým názvom, preto sa u metód definovaných mimo triedy určuje trieda, ktorej metóda patrí. Pred názov metódy sa pridáva názov triedy, ktorej metóda patrí nasledovaný operátorom rozsahu (::
).
class Trieda { public: void metoda(); }; void Trieda::metoda() { }
Zapuzdrenie (Encapsulation)
Princípom zapuzdrenia je zamedzenie priameho prístupu k interným dátam a funkciám objektu. Komunikovať s objektom je možné len cez presne definované rozhranie nezávislé od internej reprezentácie dát v objekte. Medzi výhody zapuzdrenia patria:
- Vyššia flexibilita
- V budúcnosti bude možné zmeniť implementáciu objektu bez zmeny jeho rozhrania.
- Nižšia komplexnosť
- Pracuje sa len s presne definovaným rozhraním objektu. Znižujú sa tak závislosti medzi komponentmi systému.
- Zamedzenie vzniku neprístupných stavov
- Nie je možné nastaviť hodnoty interných premenných priamo, ale len cez metódy na to určené, ktoré môžu kontrolou zamedziť nastaveniu neprístupných hodnôt.
Inline funkcie
Medzi definíciou metódy v tele triedy a mimo triedy je jeden podstatný rozdiel, ktorý som nespomenul – metódy definované v tele triedy sú automaticky inline.
Pri volaní bežnej funkcie dochádza k skoku na pamäťové miesto, kde sa funkcia nachádza. Telo funkcie je v programe jeden krát. U inline funkcií sa na miesto volania funkcie vkladá celé telo funkcie. Funkcia sa tak vyskytuje na každom mieste, kde je volaná. Použitie inline funkcie síce väčšinou zväčší program (pri malých funkciách môže dôjsť k zmenšeniu programu), ale zníži sa tým počet skokov. Inštrukcia skoku patrí medzi najpomalšie inštrukcie a jej vynechanie na správnych miestach môže značne zvýšiť výkon aplikácie.
Štandardnú funkciu je možné zmeniť na inline pridaním kľúčového slova inline pred jej definíciu.
inline void funkcia() { // … }
Inline metódy sú metódy definované v tele triedy a metódy s kľúčovým slovom inline pred jej definíciou.
Kompilátor potrebuje pri kompilácii každej časti, v ktorej sa vyskytuje inline funkcia jej definíciu. Obvykle sa preto definícia inline funkcie vkladá priamo do hlavičkového súboru s deklaráciou.
Tvorba a zánik objektov
Životný cyklus objektu sa skladá z niekoľkých fáz:
- Vytvorenie objektu
-
- Alokácia pamäte
- Inicializácia
Ak bol objekt vytvorený na zásobníku pamäťové miesto sa mu pridelí automaticky. Pri dynamickej alokácii sa prideľuje pamäťové miesto použitím operátora new. Pre inicializáciu objektov sa používajú špeciálne metódy – konštruktory.
- Používanie objektu
-
- Volanie metód
- Používanie a zmena premenných
- Zánik objektu
-
- Uvoľnenie prostriedkov
- Uvoľnenie alokovanej pamäte
Miesto alokované pre objekt sa uvoľňuje automaticky na konci bloku, kde bol definovaný u staticky alokovaných objektov a pri použití operátoru delete u dynamicky alokovaných objektov. Pred uvoľnením pamäte sa vykoná špeciálna metóda – deštruktor.
Konštruktor
Úlohou konštruktora je inicializovať počiatočný stav objektu – nastavenie hodnôt dátových členov, pridelenie prostriedkov (napr. otvorenie súboru) a ďalšie operácie potrebné pre inicializáciu objektu.
Implicitný konštruktor
Každá trieda má minimálne jeden konštruktor. Ak nedefinujeme žiaden konštruktor kompilátor automaticky vytvorí štandardný implicitný konštruktor, ktorý inicializuje objekt. Implicitný konštruktor však neinicializuje žiadne užívateľské premenné, ich hodnoty sú po vytvorení objektu náhodné.
Explicitný konštruktor
Pre inicializáciu užívateľských premenných musíme definovať explicitný konštruktor. Syntax konštruktora sa podobá na syntax metódy s rovnakým názvom ako má trieda, ale bez návratovej hodnoty (konštruktor nevracia ani void). Napriek podobnosti s metódou nie je možné konštruktor volať ako štandardnú metódu (ani vo vnútri iného konštruktora). S deklaráciou explicitného konštruktora sa zruší štandardný implicitný konštruktor.
Trieda môže mať niekoľko konštruktorov. Správny konštruktor vyberá kompilátor podľa parametrov použitých pri vytváraní objektu. Pre výber konštruktora platia rovnaké pravidlá ako pre výber preťaženej funkcie.
Nasledujúci príklad demonštruje použitie konštruktorov v triede:
class Kruh { public: Kruh(double x, double y, double r); Kruh(double x1, double y1, double x2, double y2, double x3, double y3); private: double m_x; double m_y; double m_r; }; // vytvorenie kruhu so stredom v x, y // s polomerom r Kruh::Kruh(double x, double y, double r) { m_x = x; m_y = y; m_r = r; } // vytvorenie kruhu zo súradníc 3 bodov, // cez ktoré prechádza kružnica Kruh::Kruh(double x1, double y1, double x2, double y2, double x3, double y3) { // výpočet x, y, r podľa zadaných 3 bodov m_x = x; m_y = y; m_r = r; } int main(int argc, char *argv[]) { Kruh k1(0, 0, 1); // zavolá sa prvý konštruktor Kruh k2(0, 1, 1, 0, 0, -1); // zavolá sa druhý konštruktor Kruh k3; // chyba, štandardný konštruktor bol // zrušený explicitným konštruktorom Kruh k4(0, 1, 2, 3); // chyba, neexistuje vyhovujúci // konštruktor }
Trieda kruh má dva konštruktory. Prvý konštruktor vytvorí inštanciu zo súradníc stredu a polomeru. Druhý konštruktor prijíma ako argumenty 3 body, cez ktoré prechádza kružnica a podľa nich vypočíta súradnice stredu a polomer.
Pri vytváraní objektu k1 kompilátor vyberie prvý konštruktor. Typ atribútov sa automaticky konvertuje na double. Týmto argumentom vyhovuje jedine prvý konštruktor. Objekt k2 sa vytvára z pozície troch bodov. Jediný konštruktor, ktorý zodpovedá týmto atribútom je druhý konštruktor. Objekt k3 sa nedá vytvoriť, pretože trieda nemá žiaden štandardný konštruktor(konštruktor bez parametrov). Štandardný implicitný konštruktor sa automaticky zruší v prípade, že bol definovaný akýkoľvek explicitný konštruktor. Pri objekte k4 kompilátor nenájde žiaden konštruktor zodpovedajúci použitým argumentom.
Deštruktor
Uvoľnenie prostriedkov, ktoré používal objekt má na starosti deštruktor. Podobne ako konštruktor nemá žiadnu návratovú hodnotu. Deštruktor nemá žiadne argumenty (nie je teda možné definovať niekoľko deštruktorov). Syntakticky je zhodný so štandardným konštruktorom. Názov deštruktora sa skladá zo znaku tilda (~) a názvu triedy. V nasledujúcej ukážke je deštruktor použitý na uvoľnenie alokovanej pamäte.
class Trieda { public: Trieda() { m_text = new char[200]; } ~Trieda() { delete []m_text; } private: char *m_text; }
Cvičenie
Implementujte pomocou tried zreťazený zoznam z predchádzajúceho cvičenia. Program sa bude skladať z tried Zoznam a Prvok. Trieda Zoznam bude podporovať operácie pridania prvku a výpisu. Po zrušení zoznamu deštruktor zruší všetky jeho prvky. Do zoznamu môžete pridať aj ďalšie operácie ako napr. získanie prvku podľa jeho indexu, alebo vymazanie zvoleného prvku.
Záver
Tento diel seriálu bol ľahkým úvodom do problematiky objektovo orientovaného programovania. Budúcu časť začneme popisom statických členov tried. Podrobnejšie sa budeme zaoberať inicializáciou objektov. Nakoniec si preberieme dedičnosť, včasnú a neskorú väzbu a virtuálne funkcie.
Pre pridávanie komentárov sa musíte prihlásiť.