Image
9.6.2016 0 Comments

C++ / Typy jazyka C++ (pokračovanie) / 5. časť

Späť na úvod >> Späť na programovanie >> Späť na seriál

Na úvod obligátne doplnky k predošlej časti – vypadla mi poznámka k tabuľke č. 2, e údaje v nej sa vzahujú, samozrejme, na 16-bitové prostredie, kde je veľkos základného typu int 16 bitov. Okrem toho som si uvedomil, e v súvislosti s ukákovým programom spomínam potrebu a spôsob deklarácie premenných, ale bez vysvetlenia, čo to vlastne deklarácia je. Keďe deklaráciám chcem venova samostatný úsek seriálu, teraz len stručne: ak pracujeme v programe s nejakými údajmi, pravdepodobne ich máme uloené v premenných. V niektorých programovacích jazykoch, ako BASIC alebo PERL, stačí napísa napríklad a = 8 a máme vytvorenú premennú a, poväčšine všeobecného alebo vopred dohodnutého typu. V C++ však musíme prekladaču explicitne poveda, akú premennú chceme pouíva a akého bude typu. Tento úkon sa nazýva deklarácia danej premennej. Prekladač na základe deklarácie okrem iného rezervuje pre premennú miesto v pamäti. Za povšimnutie stojí, e prvý model pouívajú zvyčajne interpretované jazyky, zatiaľ čo druhý prakticky bez výnimky kompilované jazyky.

Odvodené typy

V predchádzajúcej časti seriálu sme sa zaoberali základnými typmi jazyka C++. Na zopakovanie: ide o typy char, signed char, unsigned char, int, short int, long int, unsigned int, unsigned short int, unsigned long int, float, double, long double a void. Tieto typy sa obyčajne pouívajú (s výnimkou posledného) na uchovávanie jednoduchej neštruktúrovanej číselnej informácie. Okrem nich existujú takzvané odvodené typy, ktoré prichádzajú na rad v prípade, e potrebujeme pracova s dátami nejakým spôsobom organizovanými. Tieto typy sú vdy zaloené na základných typoch alebo ich kombináciách.

Ukazovatele

Prvým a mono poveda, e najzloitejším, najodsudzovanejším, ale súčasne aj najmocnejším odvodeným typom je ukazovateľ, takisto smerník (pointer). Oba názvy sú ekvivalentné – ktorý z nich budete pouíva, je otázka vkusu. Na efektívne programovanie v C++ je veľmi dôleité dôkladne pochopi princíp práce s ukazovateľmi, u aj z toho dôvodu, e spôsobujú najväčšie percento programových chýb.

Čo je to vlastne ukazovateľ? Typ ukazovateľ je vdy zdruený s nejakým iným typom, na ktorého inštanciu „ukazuje" (a nemusí to by len jeden zo základných typov). Premenná typu ukazovateľ obsahuje adresu miesta uloenia tejto inštancie v pamäti. Uveïme si príklad: máme premennú n typu int, ktorej hodnota (obsah) je napríklad 10. Nech je táto premenná umiestnená na adrese 0x1C55. Potom obsah inej premennej pn typu „ukazovateľ na int", ukazujúcej na premennú n (ktorá je typu int), bude práve 0x1C55. Celá situácia je znázornená na obrázkoch č. 1 a č. 2. Šípka ukazujúca z premennej pn na premennú n vyjadruje, e pn ukazuje na n – takto sa to obyčajne zvykne kresli. Obrázok č. 1 znázorňuje umiestnenie oboch premenných v pamäti a ich obsah, obrázok č. 2 sa pouíva častejšie a vyjadruje reláciu, ktorá existuje medzi ukazovateľom a premennou, na ktorú tento ukazovateľ ukazuje.

Deklarácia premennej typu ukazovateľ na nejaký základný typ je veľmi jednoduchá. Medzi názov typu a názov premennej vloíme znak * (hviezdička):

 

int n = 10;

int* pn;

 

(Je úplne jedno, či bude pred hviezdičkou alebo za ňou, alebo aj pred ňou, aj za ňou medzera, alebo tam dokonca medzera nebude vôbec, prekladač dokáe celú deklaráciu jednoznačne rozloi na tri lexikálne jednotky – int [kľúčové slovo], * [operátor] a pn [identifikátor].) Deklaráciu ukazovateľov na odvodené typy si ozrejmíme v časti venovanej deklaráciám.

Obr. č. 1

Takto deklarovaný ukazovateľ má však jednu dôleitú vlastnos: keïe sme dosiaľ explicitne nepovedali, kam má ukazova, ukazuje jednoducho niekam do pamäte (kam, to závisí od predchádzajúceho obsahu pamäte, v ktorej je uloený) a túto oblas povauje za inštanciu svojho typu!! Inak povedané, obsah premennej typu ukazovateľ sa interpretuje ako adresa, hoci to vôbec adresa nemusí by (a v operačných systémoch s ochranou pamäte pouitie takéhoto ukazovateľa môe skonči známou „všeobecnou chybou ochrany"). Takýto ukazovateľ sa označuje často ako neinicializovaný a do určitej miery ho dokáe rozozna prekladač u vo fáze prekladu. Na druhej strane treba poznamena, e niekedy sa fakt, e úsek pamäte, na ktorý ukazovateľ ukazuje, prekladač  interpretuje ako premennú daného typu, vyuíva na realizáciu vecí, ktoré by inak bolo moné urobi len zloito a neefektívne. Rozhodne to však nepatrí ku kadodennej praxi a oveľa častejšie to býva príčinou záhadných a neraz na prvý pohľad nevysvetliteľných chýb v programoch.

Aby sme mohli pracova s ukazovateľmi, ktoré ukazujú na zmysluplné údaje, potrebujeme prekladaču oznámi, e ukazovateľ má ukazova na existujúci objekt daného typu, uloený v pamäti. To dosiahneme nasledujúcim zápisom:

 

pn = &n;

 

Znak & je operátor, ktorého aplikáciou na premennú n získame jej adresu a tú uloíme do nášho ukazovateľa. Ten teraz ukazuje na známu premennú a môeme ho bez obáv pouíva. Inicializáciu ukazovateľa môeme spoji priamo s deklaráciou:

 

int* pn = &n;

 

Ako sa však dostaneme k premennej, na ktorú ukazovateľ ukazuje? Tento proces sa nazýva dereferencia ukazovateľa a vyjadrí sa uvedením operátora * pred jeho menom. Týmto spôsobom dostaneme obsah miesta, na ktoré ukazovateľ ukazuje, a eme s ním ïalej narába ako s premennou daného typu:

 

*pn = 20;

printf("n = %i\ n", *pn);

 

Prvý príkaz predchádzajúceho príkladu priraïuje hodnotu 20 premennej typu int, na ktorú ukazuje ukazovateľ pn. Prekladač jazyka C++ je

v tomto ohľade veľmi benevolentný a optimisticky predpokladá, e pn naozaj ukazuje na premennú typu int. V prípade, e by bol pn neinicializovaný, prepíše uvedený príkaz na náhodnú oblas pamäte, čo, ako si viete predstavi, môe ma vskutku kuriózne a ako predvídateľné následky. V druhom príkaze získavame obsah premennej n pomocou dereferencie ukazovateľa pn ukazujúceho na ňu.

Referencie

Ïalším odvodeným typom, o ktorom si povieme, je referencia (reference). Občas sa mono stretnú aj s názvom referenčný typ. Tento typ v jazyku C neexistuje a objavuje sa a v C++.

Referencia je veľmi podobná ukazovateľu
a zjednodušene môeme poveda, e je to ukazovateľ, ktorý sa automaticky dereferencuje. Obsahom premennej typu referencia je takisto adresa inej premennej, ale navonok sa referencia javí ako normálna premenná daného typu. Tak ako ukazovateľ aj referencia je zdruená
s nejakým iným typom – hovoríme, e ide o referenciu na daný typ.

Aj deklarácia referencie je podobná deklarácii ukazovateľa, len namiesto znaku * vloíme medzi typ a meno premennej znak & (tzv. ampersand).

Obr. č. 2

Na rozdiel od neinicializovaného ukazovateľa sa vám však v tele funkcie nepodarí deklarova neinicializovanú referenciu – prekladač ohlási chybu. Preto treba hneï pri deklarácii referenciu inicializova uvedením mena zdruenej premennej (tentoraz bez operátora &):

 

int a = 94;

int& ra = a;

 

Všimnime si, e referenciu je potrebné inicializova menom existujúcej premennej, nie je správny napríklad nasledujúci zápis:

 

int& ra = 94;

 

Prekladač síce pri preklade tohto riadka v mnohých prekladačoch neohlási chybu, ale (podľa nastavenia) vydá varovanie (warning), e na inicializáciu referencie bola pouitá pomocná (dočasná – temporary) premenná, ktorej hodnota sa nastavila na 94. Dostaneme tak referenciu na premennú, ktorú sme vôbec nedeklarovali a ktorú pravdepodobne ani nechceme.

Po inicializácii u nie je nijakým spôsobom moné zmeni referenciu tak, aby odkazovala na inú premennú. Referenčná premenná sa ïalej správa úplne rovnako ako premenná s ňou zdruená, to znamená, e kadá operácia
s referenciou mení v skutočnosti premennú, na ktorú referencia odkazuje:

 

int x = 1;

int& rx = x;

printf("x = %i\ n", x);

rx = 2;

printf("x = %i\ n", x);

 

Po prebehnutí tohto úseku programu sa na štandardnom výstupe objaví:

 

x = 1

x = 2

 

Z toho je zrejmé, e zmena premennej rx sa rovnako dotkla aj premennej x.

 Referenčná premenná slúi teda ako nejaký „alias", pomocou ktorého sa môeme odvoláva na inú premennú. Na tomto mieste sa sluší podotknú, e deklarácia a pouívanie referencií priamo v tele funkcií sa prakticky obmedzuje na niekoľko málo situácií, ako napríklad práca
s nejakou (z hľadiska zápisu) komplikovane prístupnou premennou – namiesto vypisovania siahodlhých reazcov pri opakovanom prístupe k tejto premennej si jednoducho deklarujeme referenciu s krátkym a rozumným menom, ktorá na túto premennú odkazuje. Skutočná výhoda referencií sa však ukáe a pri ich pouívaní ako typu argumentov funkcie alebo návratovej hodnoty. Ale k tomuto sa dostaneme a pri rozprávaní o funkciách.

Polia

Posledným odvodeným typom, ktorý si v tejto časti vysvetlíme, je pole. Pole nie je nič iné ako postupnos prvkov jedného typu, zastrešená spoločným názvom. Jednotlivé prvky poľa sú prístupné pomocou ich indexov. Index prvého prvku poľa v jazyku C++ je vdy 0 a nedá sa zmeni. Deklarácia poľa je podobná deklarácii jednoduchej premennej s tým rozdielom, e za meno deklarovanej premennej (typu pole) uvedieme navyše v hranatých zátvorkách počet prvkov poľa:

 

int a[24];

 

Týmto riadkom sme deklarovali premennú
a typu „pole 24 prvkov typu int". Keïe prvky poľa sa indexujú od nuly, posledný z nich má index 23. K jednotlivým prvkom pristupujeme uvedením názvu poľa a pridaním indexu príslušného prvku uzavretého v hranatých zátvorkách:

 

a[3] = 17;

printf("a[3] = %i\ n", a[3]);

 

Všimnite si, e prvky poľa sa správajú ako normálne premenné typu int (čo je vlastne úplne prirodzené).

Jazyk C++ nijako nekontroluje, či index, ktorý zapíšeme do zátvoriek, je v danom rozmedzí 0 a N–1, kde N je počet prvkov poľa. Tento fakt je dvojsečnou zbraňou – na jednej strane umoňuje efektívne vykonanie určitých operácií, na druhej strane však býva (podobne ako práca
s ukazovateľmi) zdrojom obrovského počtu chýb. Je preto veľmi dôleité chápa princíp práce s poľom a dáva si veľký pozor na to, aký index pouijeme.

V C++ nie je priamo implementovaný nijaký typ viacrozmerné pole. V prípade, e potrebujeme pouíva napríklad pole údajov, ktoré má dva rozmery, deklarujeme ho jednoducho ako pole jednorozmerných polí. Tento spôsob je mimoriadne flexibilný, ako ešte uvidíme, pretoe umoňuje podobne deklarova n-rozmerné pole ako jednorozmerné pole, ktorého prvkami sú polia (n – 1)-rozmerné a tak ïalej a po posledný rozmer. Príklad deklarácie trojrozmerného poľa:

 

int t[3][2][5];

 

V tomto príklade deklarujeme premennú t ako pole troch prvkov, z ktorých kadý je dvojprvkovým poľom, pričom oba prvky tohto poľa sú opä polia, tentoraz piatich prvkov typu int (uff!). ľubovoľný prvok na najnišej úrovni celej hierarchie (ktorý je typu int) je prístupný uvedením postupne všetkých troch indexov. Prvým prvkom (s najnišími indexmi) celej trojrozmernej štruktúry je teda t[0][0][0] a posledným (s najvyššími indexmi) je prvok t[2][1][4]. Pozor, na rozdiel napríklad od Pascalu treba kadý index uzavrie do zátvoriek osobitne, výraz t[1, 1, 1] je teda nesprávny! (Presnejšie, syntakticky je správny, ale nie sémanticky, lebo znamená niečo úplne iné.)

Mono ste si všimli, e prvky dosiaľ deklarovaných polí neboli nijako neinicializované. To je
v poriadku, ale ak chceme s poľom rozumne pracova, potrebujeme nejakým spôsobom jednotlivým prvkom priradi ich hodnoty. To je moné v zásade dvoma spôsobmi. Jedným je postupné priraïovanie hodnôt prvkom klasickým spôsobom. Keïe sa toto priraïovanie môe umiestni do tzv. cyklu (o ktorom si povieme blišie v časti venovanej príkazom jazyka C++), je tento spôsob vhodný, ak všetky prvky majú rovnakú hodnotu alebo medzi jednotlivými hodnotami existuje algoritmizovateľný vzah, prípadne ak inicializačné hodnoty sú známe a za behu programu. Prvky poľa mono inicializova aj priamo, vymenovaním jednotlivých hodnôt – tento spôsob si však vyaduje pozna inicializačné hodnoty vopred. V takomto prípade do deklarácie doplníme za meno poľa operátor = a v krútených zátvorkách uzavretý zoznam hodnôt:

 

int a[6] = {  1, 2, 4, 8, 16, 32 } ;

 

Pri tomto spôsobe inicializácie je dovolené počet prvkov poľa vynecha – prekladač si ho určí automaticky z počtu inicializačných hodnôt:

 

int a[] = {  1, 2, 4, 8, 16, 32 } ;

 

V krútených zátvorkách môeme uvies aj menej hodnôt, ako je deklarovaná veľkos poľa, vtedy sa zvyšné prvky inicializujú na nulu:

 

int a[6] = {  1, 2, 3, 4 } ;

 

(prvky a[4] a a[5] budú ma hodnotu 0).

Polia prvkov typu char je moné inicializova ešte jedným spôsobom – za operátorom = uvedieme reazcovú konštantu:

 

char msg[] = "Hello";

 

Veľkos tohto poľa je o jeden znak väčšia ako počet znakov v reazci, lebo, ako si iste spomínate, kadá reazcová konštanta v C++ je ukončená znakom \ 0. Tu si treba všimnú, e jazyk C++ nemá nijaký typ „reazec znakov", namiesto toho sa pracuje s poľom znakov, ktoré by malo by ukončené znakom \ 0, a to
z toho dôvodu, e všetky funkcie štandardnej kninice jazyka C++ tento formát očakávajú.

Znakové pole je, samozrejme, moné inicializova aj klasickým spôsobom:

 

char msg[] = {  'H', 'e', 'l', 'l', 'o', '\ 0' } ;

 

sami však iste uznáte, e prvý spôsob je oveľa pohodlnejší.

Viacrozmerné polia (tento výraz pouívam iba pre pochopenie, v skutočnosti sú to všetko len jednorozmerné polia, ale spolu tvoria niečo, čo sa dá povaova za akýsi diskrétny viacrozmerný hyperpriestor) je moné inicializova podobne, pričom jednotlivé inicializačné hodnoty sú opä zoznamy hodnôt v krútených zátvorkách. Na rozdiel od jednoduchých polí však môeme
v deklarácii vynecha len veľkos prvého rozmeru, ostatné treba uvies (vyaduje si to prekladač pre kontrolu typov a správny prístup k jednotlivým prvkom). Príklad inicializácie:

 

int b[][2][3] =

 {  {  {  111, 112, 113 } , {  121, 122, 123 }  } ,

   {  {  211, 212, 213 } , {  221, 222, 223 }  }  } ;

 

V tomto príklade deklarujeme pole b ako dvojprvkové (spočíta automaticky prekladač) pole, ktorého jednotlivé prvky sú opä dvojprvkové (dvojka v hranatých zátvorkách) polia a kadý prvok týchto polí je pole troch (trojka v hranatých zátvorkách) prvkov typu int. Inicializačné hodnoty vyjadrujú pre lepšie pochopenie súradnice jednotlivých prvkov v trojrozmernom priestore (s tým rozdielom, e polia indexujeme od nuly a súradnice sa začínajú od jednotky – to pre fakt, e nulou sa začína osmičková konštanta).

Na záver ešte jedna poznámka. Keïe prvkami viacrozmerného poľa sú opä polia s rozmerom o jeden menším, je syntakticky úplne správne
(a často pouívané) pristupova k jednotlivým podpoliam hlavného poľa pomocou príslušných indexov. Nech pole t je deklarované tak, ako je uvedené vyššie. Potom výraz t[0] je správny
a reprezentuje prvý prvok poľa t, čo je dvojprvkové pole polí piatich prvkov typu int. Takisto výraz t[0][0] je správny a vyjadruje prvý prvok poľa t[0], ktorý je sám osebe trojprvkovým poľom premenných typu int. A konečne t[0][0][0] je prvý prvok poľa t[0][0][0], ktorého typ u je int. Z doterajšieho výkladu vyplýva, e „viacrozmerné" polia sú uloené
v pamäti „po riadkoch", teda index, ktorý je najviac vpravo, sa mení najrýchlejšie.

Nabudúce

Dnešné rozprávanie bolo dos náročné na predstavivos, je však dôleité vedie, čo sa odohráva v pamäti pri behu programu, lebo len tak budete schopní úspešne a s minimálnym mnostvom chýb pracova so spomínanými typmi.
V mnohých jazykoch sa programátor nemusí stara, ako sú údaje, s ktorými pracuje, reprezentované a uloené v pamäti. V C++ si tento luxus v prevanej väčšine nemono dovoli, ak chcete písa programy, ktorým dokonale rozumiete. Na druhej strane máte úplnú kontrolu nad svojimi dátami a nestane sa vám, e preloený kód robí niečo, o čom neviete. Toto je tá silná väzba na hardvér, ktorú som spomínal v prvej časti nášho seriálu – neznamená to, e program v C++ je závislý od konkrétnej platformy, ale e programátor je schopný pracova na úrovni veľmi blízkej strojovému kódu daného procesora a nie je obmedzovaný niekedy zbytočnými abstrakciami vyšších programovacích jazykov (hoci jazyk C++ patrí takisto medzi
vyššie jazyky, povedal by som, e medzi nimi
je umiestnený „najnišie"). C++ je vhodnejším jazykom pre „ozajstných programátorov" ako pre „pojedačov koláčov". Pre tých, ktorí nerozumejú – skúste si v nejakej vyhľadávacej slube na Internete zada heslá ako „real programmers" a „quiche eaters". Prípadne mi napíšte mail a ja vám pošlem zopár vtipných článkov (dúfam, e mi nepreplníte mailbox!).

Nabudúce si toto rozprávanie ešte doplníme vzájomným vzahom polí a ukazovateľov, ktorý patrí k dos zloitým témam v C++, a potom si začneme hovori podrobnejšie o deklaráciách alebo o výrazoch a operátoroch, podľa toho, aká postup mi bude zda vhodnejší.

Na záver vám zaelám u len veľa úspechov pri študovaní dnešného textu a dôrazne vám odporúčam vyskúša si všetky uvedené príklady
a pokúsi sa ich rôzne modifikova, a kým nebudete ma pocit, e všetkému rozumiete. Zatiaľ, pravda, viete iba deklarova premenné rôznych typov, priraïova im hodnoty a vypisova ich, ale na pochopenie príkladov to úplne postačí. Odporúčam vám ešte po preloení programu krokova jednotlivé príkazy a sledova hodnoty premenných pomocou nástrojov príslušného debuggera (veľmi silným nástrojom je napríklad príkaz Inspect v Borland C++, ktorý vám zobrazí premennú tak, ako je uloená v pamäti [vyaduje si to trochu sa pohraba v manuáloch a pohra s jednotlivými monos-ami]).

Zobrazit Galériu
C++

Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Čo robiť keď firme chýba IT expert?

08.12.2016 10:36

IT projekty majú z hľadiska nárokov na kapacity špecialistov premenlivý charakter a v určitých fázach často treba posilniť kapacity IT oddelení externými odborníkmi. Riešením je IT ousourcing, ako fo ...

Ako na to

Ako funguje sandbox?

08.12.2016 15:36

Každá aplikácia môže pre operačný systém počítača či mobilného zariadenia predstavovať potenciálnu hrozbu, a to aj v prípade, ak neobsahuje žiadne bloky škodlivého kódu. Murphyho zákony neúprosne defi ...

Ako na to

Tipy a triky: Ako na snímku obrazovky na akomkoľvek počítači s Windows?

02.12.2016 00:13

Ak snímky obrazovky robíte často apotrebujete napríklad funkcie na posun stránok alebo snímanie zobrazenia pri vyššom rozlíšení displeja, zrejme používate nejakú špecializovanú aplikáciu. Väčšina použ ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá