Image
12.6.2016 0 Comments

C++ / 15. časť

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

Viete, že vám závidím? Keď čítate tieto slová, vonku by mala byť, ak dobre počítam, jar v plnom prúde. Možno nebudete so mnou súhlasiť, ale pre mňa je jar najkrajším obdobím roka. Nie sú už také šialené mrazy ako v zime a nie sú ešte také šialené horúčavy ako v lete. Celá príroda sa zobúdza a rozkvitá a ja pomaly prechádzam z fázy zimného spánku do fázy jarnej únavy (potom prídu tie letné horúčavy, po nich klasické jesenné depresie a môžem sa opäť uložiť späť… :–). Ale dosť pesimizmu, zobuďme sa aj my a vrhnime sa po pätnásty raz do spletitých zákutí jazyka C++.

Ako som minule sľúbil, naposledy sa budeme venovať neobjektovej polovici C++. Budeme si rozprávať o tzv. štandardnej knižnici jazyka C, ale skôr ako začneme, dovolil by som si ešte pár slov k otázke spustenia a ukončenia programu v C++.

Prológ a epilóg

Vieme už, že beh programu v C++ sa točí okolo jednej špecifickej funkcie s názvom main(). Práve ona obsahuje našu „pridanú hodnotu“, teda kód, ktorý sme naprogramovali. Samozrejme, že časť kódu sa môže nachádzať v samostatných funkciách, ktoré v príhodnom okamihu z funkcie main() zavoláme (a tak ďalej, rekurzívne do takmer ľubovoľnej hĺbky). Dôležité však je, že nami ovplyvnená časť programu sa začína tam, kde sa začína funkcia main(), a z normálnych okolností sa končí tam, kde sa táto funkcia končí.

Pri návrhu štruktúry programu neraz dôjde k situácii, keď potrebujeme vykonávanie programu ukončiť predčasne – teda skôr, ako program prirodzene dospeje ku koncu funkcie main(). Ak sa táto situácia vyskytne v kóde, ktorý je súčasťou main(), môžeme použiť, tak ako v každej inej funkcii, príkaz return, voliteľne nasledovaný návratovou hodnotou. Na pohľad sa bude realizovať bežný návrat z funkcie; no návrat z main() sa rovná ukončeniu programu. Prípadná návratová hodnota sa odovzdá nejakým spôsobom operačnému systému, ktorého prostriedkami ju budeme môcť neskôr zistiť.

Oveľa častejšie sa však stane, že budeme potrebovať ukončiť program v niektorej z vnorených funkcií – v takom prípade nám pomôže funkcia exit(). Jej prototyp sa nachádza v hlavičkovom súbore <stdlib.h> a obyčajne aj v súbore <process.h> a vyzerá takto:

void exit(int);

Vidíme, že funkcia exit() má jediný argument typu int, tento argument je už spomínanou návratovou hodnotou programu. Obyčajne sa dodržuje konvencia, že nulová návratová hodnota indikuje ukončenie programu bez chýb, nenulová signalizuje nejakú chybu (napríklad program na kopírovanie súborov môže nenulovou návratovou hodnotou oznamovať, že požadovaný súbor neexistuje).

Volanie funkcie exit() bude mať za následok (okrem ukončenia programu) spláchnutie všetkých výstupných prúdov, zavretie všetkých otvorených súborov a zavolanie všetkých funkcií registrovaných pomocou volania funkcie atexit(). Táto funkcia s prototypom

void atexit(void (*)());

ktorý sa nachádza v <stdlib.h>, umožňuje definíciu „obslužných rutín“ ukončenia programu. Takéto obslužné rutiny môžu mať na starosti prípadné aditívne činnosti, ktoré treba vykonať skôr, než program odíde do „večných lovísk“ (typicky uvoľnenie alokovaných prostriedkov a pod.). Každá registrovaná funkcia musí mať prototyp

void fnc();

funkcii atexit() sa odovzdáva ukazovateľ na danú funkciu, resp. priamo identifikátor funkcie, ktorý sa v takomto prípade automaticky konvertuje na ukazovateľ. Ukážeme si príklad:

 
#include <stdio.h>
#include <stdlib.h>
 
void fnc1()
{
    printf("Handler #1\n");
}
 
void fnc2()
{
    printf("Handler #2\n");
}
 
void main()
{
    atexit(fnc1);
    atexit(fnc2);
    printf("main()\n");
    exit(0);
}

Po spustení programu si všimnime, že obslužné funkcie sa vyvolávajú v opačnom poradí, ako boli registrované (t. j. stratégia LIFO).

Ak vo funkcii main() použijeme príkaz return, bude jeho účinok ekvivalentný volaniu funkcie exit() s príslušným argumentom (return bez návratovej hodnoty je totožný s volaním exit(0)). V tejto súvislosti je vhodné spomenúť, že funkcia main() môže byť deklarovaná s návratovým typom void alebo int. V prvom prípade, ak chceme operačnému systému vrátiť nenulovú hodnotu, musíme vždy použiť funkciu exit(). Naproti tomu v druhom prípade nezávisle od toho, či chceme alebo nechceme vracať nejakú hodnotu, musíme vždy použiť buď funkciu exit(), alebo príkaz return, a to aj vtedy, keď za normálnych okolností by program sám dospel ku koncu funkcie main(). Niektoré benevolentnejšie prekladače pri chýbajúcom príkaze return vydajú iba varovanie, ide však o chybu, pretože funkcia, ktorá je deklarovaná s návratovým typom iným ako void, musí vrátiť nejakú hodnotu. Preto, ak si spomínate, náš prvý program v C++ vyzeral takto:

int main()
{
    printf("Hello, world!\n");
    return 0;
}

Ak by sme chceli vynechať príkaz return, museli by sme deklarovať funkciu main() ako

void main() { ... }

Popri funkcii exit() môžeme program ukončiť i podobnou funkciou _exit(). Prototypy oboch funkcií sú okrem názvu zhodné, líšia sa však účinkom – volanie funkcie _exit() ukončí program bez spláchnutia výstupných prúdov, uzavretia otvorených súborov či volania funkcií registrovaných pomocou atexit(), o čom sa ľahko môžete presvedčiť modifikáciou uvedeného programu.

Program sa dá ukončiť ešte jednou funkciou, abort(). Na rozdiel od predchádzajúcich dvoch však táto funkcia ukončuje program násilne, bez ohľadu na alokované prostriedky, na konzolu obyčajne vypíše správu o takomto násilnom ukončení a v operačných systémoch typu Unix vyprodukuje aj tzv. core dump, čo je, zjednodušene povedané, do súboru zaznamenaný obraz pamäte, ktorá bola pridelená danému procesu. Funkcia abort() sa používa obyčajne len v prípade, keď dôjde k takej závažnej chybe, že program jednoducho nemôže pokračovať ďalej a nedokáže sa ani z chyby zotaviť. Prototyp funkcie

void abort()

sa nachádza v hlavičkovom súbore <stdlib.h> či <process.h>.

Dosiaľ sme sa pohybovali v rámci funkcie main(). Program však skôr, než zavolá túto funkciu, musí vykonať ešte niekoľko prípravných prác. O čo presne pôjde, to závisí od prekladača, ktorým sme program preložili. My ako programátori do tohto úseku programu zasahovať priveľmi nemôžeme – vlastne jediný spôsob, ako zmeniť tento tzv. prológ programu, je zmeniť príslušný objektový súbor, ktorý sa prilinkuje k výslednému programu. Vo všeobecnosti prológ pripravuje prostredie na beh funkcie main() – inicializuje statické objekty, spracuje príkazový riadok a vytvorí z neho argumenty pre main(), otvorí štandardné súbory (stdin, stdout, stderr) a priradí im príslušné deskriptory a tak ďalej. Potom bežným spôsobom zavolá našu funkciu main(), po ktorej skončení nastupuje záverečný kód (epilóg). Tento kód robí práve to upratovanie, ktoré sme si opísali pri funkcii exit(). Po jeho skončení sa už riadenie vracia operačnému systému.

Štandardná knižnica jazyka C

Termín štandardná knižnica jazyka C treba chápať pomerne voľne – myslím ním tie funkcie, ktoré dostaneme automaticky k dispozícii spolu s prekladačom a ktoré by sa mali vyskytovať v každej implementácii nezávisle od platformy či výrobcu. Navyše túto množinu obmedzíme na funkcie, ktoré boli k dispozícii už v jazyku C, a teda nebudeme uvažovať funkcie vyžadujúce C++.

Nie je veľmi jednoduché určiť, ktoré funkcie možno považovať za štandardné a ktoré sú rozšírením, špecifickým pre ten-ktorý prekladač. Mnohé funkcie „zľudoveli“ a vyskytujú sa vo viacerých prekladačoch, hoci sú napríklad závislé od platformy. V nasledujúcom prehľade sa teda pokúsim vymenovať a prípadne stručne opísať také funkcie, o ktorých si myslím, že sú dostatočne všeobecné a sú k dispozícii naozaj s každým prekladačom. (Pre jazyk C existuje norma ANSI/ISO, ktorá, myslím, opisuje aj množinu funkcií, ktoré sa musia povinne dodávať spolu s prekladačom, ale, bohužiaľ, nemôžem toto tvrdenie momentálne ničím podložiť, pretože nemám spomínanú normu k dispozícii.) Celkom určite niektoré menej používané funkcie vynechám, pretože cieľom tejto časti nie je vymenovať všetko, čo máme k dispozícii, ale skôr poskytnúť vám určitý náhľad na to, s čím môžete pri písaní vlastných programov rátať (a nemusíte to programovať sami).

Matematické a konverzné funkcie

Prvou kategóriou funkcií, o ktorých si povieme, sú funkcie na výpočet rôznych matematických výrazov, prípadne na prevod medzi rôznymi typmi údajov. Prototypy týchto funkcií sa nachádzajú prevažne v hlavičkových súboroch <math.h>, <stdlib.h> a iných.

Na výpočet absolútnej hodnoty čísla slúži funkcia abs(). Existuje obyčajne v niekoľkých variáciách, ktoré sa líšia typom argumentu; keďže ide o funkcie jazyka C, musia sa tieto variácie líšiť aj názvom funkcie (takže máme funkcie fabs(), labs() a iné). Nebudem (ani v ďalšom texte) uvádzať prototypy funkcií, pretože by som asi zbytočne duplikoval manuály k prekladačom, takže presný opis typov argumentov a návratovej hodnoty a takisto hlbší opis činnosti funkcií nájdete v príslušnej dokumentácii.

Na výpočet maxima či minima z dvoch čísel máme logicky k dispozícii funkcie max() a min(). Pri ich použití pozor, pretože sú často definované aj ako makrá, ktoré, samozrejme, zneprístupnia skutočné funkcie. Ako tieto makrá odstrániť, to sa zvyčajne uvádza v dokumentácii k prekladačom (prinajhoršom napíšete niečo ako #undef max).

Na výpočet goniometrických funkcií môžeme použiť množstvo funkcií – základné sin(), cos(), tan(), ich inverzné protipóly (podľa správnosti ide o cyklometrické funkcie) asin(), acos(), atan() a hyperbolické funkcie sinh(), cosh(), tanh().

Do triedy exponenciálnych a logaritmických funkcií patria funkcie exp() (výpočet ex), pow() (výpočet xy), sqrt() (odmocnina), log() (prirodzený logaritmus), log10() (dekadický logaritmus). Všeobecný logaritmus logz x môžeme vypočítať podľa známeho vzorca ako log(x)/log(z), prípadne ln(x)/ln(z).

Často máme k dispozícii funkcie na prácu s komplexnými číslami, ale tie obyčajne používajú na ich reprezentáciu štruktúry, o ktorých sme si nehovorili, pretože sú veľmi podobné triedam a považoval som za vhodnejšie nechať si rozprávanie o nich práve na nasledujúcu časť seriálu. Obyčajne ide o funkcie ako real() a imag() na určenie zložiek komplexného čísla, conj() na výpočet konjugovaného čísla a cabs() a arg() na určenie absolútnej hodnoty a argumentu.

Medzi matematické funkcie patrí aj niekoľko ďalších funkcií, ako ceil() na zaokrúhľovanie nahor a floor() na zaokrúhľovanie nadol, div() a ldiv() na výpočet podielu a zvyšku po delení dvoch celých čísel, funkcie rand() a random() na generovanie pseudonáhodných čísel (rozdiel medzi nimi – pozri dokumentáciu) a randomize() a srand() na inicializáciu tohto generátora.

Nakoniec si spomenieme ešte tzv. konverzné funkcie. Tie používame v prípade, že potrebujeme previesť číslo na reťazec či reťazec na číslo. Pre prvý prípad máme k dispozícii funkcie itoa(), ltoa(), ultoa() na prevod celočíselných typov a ecvt(), fcvt(), gcvt() na prevod reálnych typov, pre druhý prípad zase funkcie atoi(), atol(), atof(), strtol(), strtoul(), strtod(). Názvy týchto funkcií ostatne hovoria samy za seba.

Medzi konverzné funkcie môžeme zaradiť aj funkcie toupper(), tolower() a toascii(), ktoré prevádzajú svoj znakový argument na veľké písmeno, malé písmeno, resp. 7-bitový znak ASCII (orezaním 8. bitu).

Klasifikačné funkcie

Tieto funkcie sú často k dispozícii aj ako makrá, ktorým sa, samozrejme, pri použití dá prednosť – ak chceme naozaj použiť funkciu, musíme príslušné makro oddefinovať (#undef). Každá z týchto funkcií dostane ako argument znak (typ char) a vráti nenulovú hodnotu, ak tento znak spĺňa určité kritérium. Funkcií je spolu dvanásť: isalnum(), isalpha(), isdigit(), isxdigit(), isascii(), iscntrl(), isprint(), isgraph(), islower(), isupper(), ispunct(), isspace(); ich prototypy sa nachádzajú v súbore <ctype.h>. Prvá z nich, isalnum(), „rozpoznáva“ alfanumerické znaky, teda písmená a číslice, isalpha() iba písmená (nezávisle od ich veľkosti), isdigit() zase iba číslice, isxdigit() takisto číslice, ale hexadecimálne (nezávisle od veľkosti písmen A až F). isascii() kladne klasifikuje „čisté“ znaky ASCII (s nulovým 8. bitom), iscntrl() riadiace znaky (s kódom 0x00 až 0x1F, plus znak 0x7F), isprint() „tlačiteľné znaky“ s kódom 0x20 až 0x7E, isgraph() je to, čo isprint(), ale bez znaku 0x20 (t. j. bez medzery), islower() identifikuje malé písmená, isupper() veľké, isspace() rozpoznáva „biele“ znaky (s kódom 0x09 až 0x0D a 0x20) a ispunct() je konjunkciou iscntrl() a isspace() (tzv. „punctuation characters“).

Funkcie na prácu s reťazcami a pamäťou

Manipulácia s reťazcami je v C++, jemne povedané, krkolomná, a preto nečudo, že máme k dispozícii veľké množstvo funkcií na jej uľahčenie. Všetky tieto funkcie považujú ako reťazec ľubovoľne dlhé pole znakov ukončené nulou (teda presnejšie znakom '\0'). Často sa pre takýto reťazec používa výraz ASCIIZ string alebo zero-terminated string. Prototypy funkcií na prácu s reťazcami sa prakticky všetky nachádzajú v súbore <string.h>.

Asi najznámejšou funkciou z tejto triedy je funkcia strlen(), ktorá vracia dĺžku reťazca (nerátajúc ukončovací nulový znak). Na spájanie reťazcov použijeme funkciu strcat(), ktorá pripojí jeden reťazec k druhému. Tento druhý reťazec musí mať, samozrejme, „za sebou“ dostatočne dlhý úsek pamäte, aby sa doň pripájaný prvý reťazec zmestil. Variantom je funkcia strncat(), ktorá umožňuje zadať max. počet pripájaných znakov (vtedy sa však môže stať, že sa nepripojí ukončovacia nula a reťazec zostane takpovediac „visieť vo vzduchu“).

Na kopírovanie reťazcov slúži funkcia strcpy(), ktorá skopíruje jeden reťazec do druhého (ten musí byť opäť dostatočne dlhý, inak si prepíšeme to, čo nechceme). Jej činnosť sa skončí prekopírovaním ukončovacej nuly zo zdrojového reťazca. Aj k tejto funkcii existuje reštriktívna alternatíva strncpy(), pre ktorú takisto platí poznámka z predchádzajúceho odseku. Ak chceme vytvoriť duplikát reťazca, použijeme funkciu strdup(), ktorá za nás aj alokuje príslušnú pamäť (tú však budeme musieť explicitne uvoľniť sami).

Na porovnávanie reťazcov máme k dispozícii funkciu strcmp(). Táto funkcia porovnáva reťazce znak po znaku a končí sa vtedy, ak nájde prvú nezhodu alebo ak narazí aspoň v jednom z reťazcov na ukončovaciu nulu. Porovnanie je bezznamienkové, na základe číselných hodnôt znakov (čiže na základe kódu ASCII). Funkcia vracia nulovú hodnotu pre rovnaké reťazce a zápornú či kladnú, ak je jeden z reťazcov lexikograficky menší či väčší ako druhý. Ak chceme obmedziť max. počet porovnávaných znakov, použijeme funkciu strncmp(), na porovnávanie nezávisle od veľkosti písmen v reťazcoch slúžia funkcie stricmp() a strnicmp().

Ak potrebujeme nastaviť jednotlivé znaky reťazca na určitú hodnotu, môžeme použiť funkciu strset(), resp. strnset(). Na konverziu reťazca na veľké či malé písmená (in situ, teda v rámci samotného reťazca) slúžia funkcie strupr() a strlwr(). Revertovať reťazec môžeme pomocou funkcie strrev().

Na vyhľadávanie znaku v reťazci máme k dispozícii funkcie strchr() (vracia prvý výskyt znaku) a strrchr() (vracia posledný výskyt). Na vyhľadávanie celého podreťazca môžeme použiť funkciu strstr(). Medzi ďalšie vyhľadávacie funkcie patria strtok() a strspn().

Na prácu s pamäťou máme k dispozícii podobný komfort. Na kopírovanie úseku pamäte slúžia funkcie memcpy() a memmove(). Obe majú podobný účinok, tá druhá navyše ošetruje prípad, keď sa pôvodný úsek a jeho kópia v pamäti prekrývajú, takže by mohlo dôjsť k prepísaniu nežiaducich údajov (tí, čo si ešte pamätáte inštrukcie procesora Z80, spomeňte si, ako sa pomocou tohto faktu a inštrukcie LDIR dala krásne vynulovať pamäť… nedá mi, aby som tu ten kúsok kódu z nostalgie neuviedol:

ld   HL,start
ld   DE,start+1
ld   BC,length
ldir

(Ach jaj, to boli časy!) Rozdiel oproti podobným funkciám na prácu s reťazcami je ten, že kopírovanie sa nekončí žiadnou nulou a dĺžka úseku sa musí explicitne uviesť v argumentoch týchto funkcií.

Porovnávanie dvoch úsekov pamäte realizuje funkcia memcmp(), resp. memicmp(), nastavenie úseku pamäte na nejakú hodnotu funkcia memset(). Vyhľadávať konkrétnu hodnotu v pamäti môžeme pomocou funkcie memchr(), ale aj pomocou funkcií lsearch() (lineárne hľadanie) a bsearch() (binárne hľadanie), ktoré majú trochu univerzálnejšiu sémantiku. Pri tejto príležitosti treba spomenúť aj funkciu qsort(), implementujúcu usporadúvací algoritmus Quick Sort.

Čo sa týka alokácie pamäte, pre ňu nebola v jazyku C nijaká konštrukcia podobná operátoru new z C++, takže bolo potrebné použiť knižničné funkcie malloc(), calloc() či realloc() na alokovanie bloku pamäte a free() na jej uvoľnenie. Na zistenie dostupnej takto alokovateľnej pamäte slúžila funkcia coreleft(). Používať tieto funkcie v C++ nemá veľký význam, preto ich ani nebudem bližšie opisovať.

Vstupno-výstupné funkcie

Realizácia vstupu a výstupu údajov (z hľadiska programu) nie je súčasťou jazyka C++ ani C, preto nečudo, že štandardná knižnica obsahuje zrejme najväčší podiel práve vstupno-výstupných funkcií. Rozdeliť ich môžeme zhruba na funkcie pracujúce so štandardným vstupom a výstupom, funkcie na vstup a výstup údajov z a do údajových prúdov, funkcie na prácu s diskovými súbormi a funkcie na prácu s konzolou. Prototypy funkcií nájdeme predovšetkým v súbore <stdio.h> a aj v mnohých ďalších.

Prvou oblasťou sú funkcie, ktoré čítajú údaje zo štandardného vstupu, resp. zapisujú ich na štandardný výstup. Medzi ne patrí notoricky známa funkcia printf(), ktorá realizuje formátovaný výstup do súboru stdout, implicitne smerovaného na konzolu (t. j. na obrazovku). Syntax tejto funkcie už isto poznáte a viete, že prvým argumentom je formátovací reťazec, podľa jeho pokynov sa vypisujú nasledujúce argumenty. Súčasťou tohto reťazca sú riadiace špecifikátory, ktorých presný opis nájdete v dokumentácii ku každému prekladaču. Je dobré preštudovať si tento opis, objavíte tak nespočetné možnosti funkcie printf() a jej rôznych variácií (bude o nich reč o chvíľu). Z nich najpodobnejšia je funkcia vprintf(), ktorá má namiesto premenného počtu argumentov jediný argument (samozrejme, okrem formátovacieho reťazca) špeciálneho typu va_list, slúžiaceho na platformovo nezávislý prístup k argumentom funkcie deklarovanej s výpustkou. O chvíľu si o ňom povieme bližšie.

Na zápis neformátovaného reťazca na štandardný výstup máme k dispozícii funkciu puts(), na výstup jediného znaku zase funkciu fputchar() a makro putchar(), ktoré má rovnaký účinok. Tu dochádza k istej nekonzistencii v pomenovávaní, lebo hoci názov funkcie fputchar() sa začína znakom f, nejde o zápis do súboru.

Komplementom funkcie printf() je tiež kedysi spomínaná funkcia scanf(), ktorá realizuje formátované čítanie údajov zo štandardného vstupu. Pre jej formátovací reťazec platia mierne odlišné pravidlá, preto treba opäť preštudovať dokumentáciu, v ktorej sú opísané. Vieme už, že argumentmi tejto funkcie musia byť ukazovatele na premenné, do ktorých budú zapisované údaje vložené. Podobnou funkciou, ktorá pracuje s argumentom typu va_list, je funkcia vscanf().

Čítanie reťazca zo štandardného vstupu má na starosti funkcia gets(). Treba mať na pamäti, že táto funkcia načíta celý riadok, končiaci sa znakom '\n', ktorý nahradí ukončovacou nulou, teda znakom '\0'. Vzhľadom na krkolomnosť a pomerne veľkú nepružnosť funkcie scanf() je výhodnejšie používateľský vstup z konzoly realizovať práve pomocou gets() a následne získaný reťazec spracovať vo vlastnej réžii. Argumentom gets() je ukazovateľ, ktorý musí ukazovať na dostatočne veľký úsek pamäte. Teoreticky, pokiaľ operačný systém nelimituje dĺžku jedného riadka, napísaného na konzole, nikdy nevieme, aký dlhý reťazec naozaj bude. Vtedy si musíme pomôcť rôznymi trikmi (napríklad napísať vlastnú verziu funkcie gets()). Na načítanie jedného znaku zo štandardného vstupu máme k dispozícii funkciu fgetchar() a makro getchar(), ktoré však vzhľadom na fakt, že operačný systém vstup z klávesnice prakticky vždy ukladá do riadkovej vyrovnávacej pamäte, nemajú priveľký zmysel (musíme totiž každý vstup ukončiť stlačením klávesu Enter). Azda iba v prípade, že z toho vopred načítaného riadka potrebujeme získavať jednotlivé znaky.

Ďalšia skupina funkcií pracuje s pojmom údajový prúd. V podstate nejde o nič iné ako o pomyselnú „rúru“, z ktorej čítame alebo do ktorej zapisujeme údaje. Jeden koniec vidíme my (t. j. program), druhý je napojený na diskový súbor, na konzolu či napr. na tlačiareň. Dôležité je, že z hľadiska programu stále ide o jeden údajový typ. Takto môžeme rovnakými funkciami zapisovať napríklad do súboru i na obrazovku. Abstrakciu údajového prúdu vytvára používateľský typ FILE, ktorý je de facto štruktúrou (struct). Funkcie, ktoré s ním pracujú, vyžadujú ukazovateľ naň (teda typ FILE*). (Poznámka: Nemýľte si spomínanú „rúru“ so skutočnými rúrami, existujúcimi v normálnych operačných systémoch a slúžiacimi na komunikáciu medzi procesmi, hoci prístup k nim je rovnako možné realizovať pomocou údajových prúdov.)

Na štandardný vstup, výstup a chybový výstup máme automaticky v programe k dispozícii tri premenné (typu FILE*) stdin, stdout a stderr, s ktorými môžeme pracovať rovnako ako s akýmikoľvek inými prúdmi.

Prv, než budeme môcť pracovať s údajovým prúdom, musíme ho najprv otvoriť. Vtedy sa vlastne vytvorí spojenie s protiľahlým objektom a inicializujú sa určité polia štruktúry FILE. Na otvorenie prúdu slúži funkcia fopen(), ktorej argumentom je okrem názvu súboru či zariadenia (ako napr. "PRN:" v DOS-e) aj mód otvorenia prúdu (čítanie, zápis, pridávanie, textový/binárny prenos údajov). Na „znovuotvorenie“ súboru, resp. na prepojenie rúry na iný objekt slúži funkcia freopen(), vhodná napríklad na presmerovanie stdout do súboru.

Podobné funkcie ako pre zápis na štandardný výstup existujú aj pre zápis do údajového prúdu. Formátovaný výstup tak realizujú funkcie fprintf() a vfprintf(), ktoré majú navyše argument predstavujúci príslušný prúd. Reťazec zapíšeme pomocou funkcie fputs() a znak pomocou funkcie fputc(), resp. makra putc(). Ďalej máme k dispozícii funkciu na zápis bloku údajov (resp. bloku bajtov) fwrite(), ktorej argumentmi sú okrem iného ukazovateľ na tento blok a jeho dĺžka.

Čítanie z dátového prúdu, naopak, zabezpečujú funkcie fscanf(), vfscanf() (formátovaný vstup), fgets() (čítanie reťazca), fgetc(), getc() (čítanie znaku – funkcia a makro) a čítanie bloku údajov funkcia fread().

Niekoľko ďalších funkcií nám umožňuje operovať s aktuálnou pozíciou v súbore (t. j. miestom, odkiaľ sa budú čítať, resp. kam sa budú zapisovať nasledujúce dáta). Funkcia ftell() túto aktuálnu pozíciu vracia, fseek() ju zase nastavuje. Podobný význam majú funkcie fgetpos() a fsetpos(), ktoré však pracujú so špeciálnym typom fpos_t. Pomocou funkcie feof() zisťujeme, či sme už na konci súboru (to značí, či posledná operácia čítania/zápisu detegovala koniec súboru), funkciou rewind() previnieme ukazovateľ pozície späť na začiatok súboru.

Funkcia ferror() zisťuje, či pri poslednom čítaní/zápise došlo k nejakej chybe, clearerr() takýto príznak chyby vynuluje. V prípade, že potrebujeme explicitne „spláchnuť“ vyrovnávacie pamäte údajového prúdu [teda odpratať ich fyzicky na disk (to nie je celkom presné, údaje zapisované na disk sú totiž vyrovnávané ešte samotným operačným systémom)], použijeme funkciu fflush().

Po skončení práce s údajovým prúdom ho musíme vždy explicitne zavrieť. To má za následok okrem iného vyprázdnenie vyrovnávacích pamätí a, samozrejme, zavretie súboru na úrovni operačného systému, teda aktualizáciu metaúdajov na disku (adresár, alokačná tabuľka a pod.). Na tieto účely slúži funkcia fclose().

Na tomto mieste ešte spomeniem funkcie, ktoré umožňujú realizovať formátovaný vstup a výstup z a do reťazcov v pamäti. Efekt je rovnaký ako pri čítaní/zápise z/do súboru, ibaže sa pracuje s existujúcim reťazcom, resp. alokovaným miestom v pamäti. Funkcie sú štyri, a to: sprintf(), vsprintf(), sscanf() a vsscanf(). Jedným z ich argumentov je reťazec predstavujúci požadovaný zdroj či spotrebič údajov.

Okrem prístupu k súborom prostredníctvom údajových prúdov k nim môžeme pristupovať takmer na úrovni operačného systému, a to pomocou nasledujúcich funkcií. V nich je identifikátorom otvoreného súboru kladné celé číslo, tzv. deskriptor súboru (file handle). Za pozornosť stojí, že tak ako sú štandardné prúdy prístupné pomocou premenných stdin, stdout a stderr, sú im priradené po rade deskriptory 0, 1 a 2. Pri otváraní ľubovoľného súboru sa mu priradí automaticky najnižšie nepoužité číslo deskriptora.

Súbor otvoríme použitím funkcie open(). Aj tentoraz môžeme určiť mód otvoreného súboru a typ prístupu k nemu. Podobná funkcia sopen() berie do úvahy možnosť viacnásobného otvorenia (v multipoužívateľskom prostredí nič nezvyčajné) a umožňuje nastaviť mód zdieľania otvoreného súboru. Zaujímavé sú dve funkcie dup() a dup2(). Prvá z nich vytvára nový deskriptor pre už otvorený súbor, zatiaľ čo pomocou druhej môžeme existujúcemu deskriptoru priradiť iný súbor (tak sa dá presmerovať napr. štandardný výstup).

Vytvoriť nový súbor môžeme jednak pomocou vhodných argumentov funkcie open(), jednak pomocou špeciálnej funkcie creat(). Jej účinok je inak okrem vytvorenia nového súboru zhodný s funkciou open(), t. j. takisto dostaneme ako návratovú hodnotu deskriptor novo otvoreného súboru.

Na čítanie a zápis údajov z/do súborov nemáme priveľa funkcií– vlastne iba dve: read() a write(), ktoré sú sémantikou podobné funkciám fread() a fwrite(). Aktuálnu pozíciu v súbore zistíme pomocou tell() a nastavíme pomocou lseek(). Na detekciu konca súboru slúži funkcia eof().

K dispozícii máme funkcie, pomocou ktorých môžeme zistiť údaje o otvorenom súbore: filelength() vracia jeho dĺžku, fstat() rôzne iné údaje v štruktúre stat. A nakoniec po skončení práce so súbormi ich uzavrieme pomocou funkcie close().

V každom prostredí prekladača máme obyčajne k dispozícii funkcie na prácu s neotvorenými súbormi, adresármi, zisťovanie aktuálneho adresára, prácu s diskmi a podobne. Tieto funkcie však často bývajú minimálne platformovo, ak nie priamo prekladačovo závislé. Spomeniem preto len niekoľko – rename() na premenovávanie súborov, unlink() či remove() na ich mazanie, chmod() na zmenu prístupových práv, mkdir() a rmdir() na vytváranie a vymazávanie adresárov a mnohé iné. Bližšie informácie a hlavne ďalšie funkcie nájdete celkom určite vo vašej dokumentácii.

Konečne funkcie na prácu s konzolou sú rovnako často závislé od prekladača a ešte výraznejšie od platformy. Preto ich tu nebudem menovať vôbec, aby som neznevýhodnil tých, ktorí pracujú napríklad v Linuxe a borlandovské špeciality nemôžu používať. Ostatne dve z nich, getch() a kbhit(), som v jednom zo svojich programov už použil aj opísal.

Funkcie na prácu s procesmi

Dve z tejto triedy funkcií sme už v tomto článku preberali – sú to funkcie exit() a abort(). Popri nich najvýznamnejšími zástupcami sú funkcie exec...() a spawn...() (resp. ich všetky varianty). Obe slúžia na spúšťanie programov (lepšie povedané, nových procesov). Rozdiel medzi nimi je ten, že prvá z nich novým programom prekryje ten, v ktorom sa nachádzala, takže po jeho skončení sa pôvodný program k slovu vôbec nedostane (lebo už ani neexistuje v pamäti), zatiaľ čo druhá vytvorí pre nový program samostatný subproces a čaká na jeho skončenie. Nie som si istý, či v operačných systémoch typu Unix funkcia spawn() existuje, ale ak nie, tak jej použitie je zhruba ekvivalentné dvojici fork() a exec().

Každá z funkcií má osem modifikácií, ktoré sa líšia príponou (preto tie tri bodky v názvoch funkcií): execl(), execle(), execlp(), execlpe(), execv(), execve(), execvp(), execvpe() a podobne pre spawn(). Tie funkcie, ktoré obsahujú písmeno 'l', sú deklarované s premenným počtom argumentov, predstavujúcich argumenty príkazového riadka spúšťaného programu, naopak, tým, ktoré obsahujú písmeno 'v', sa tieto argumenty odovzdávajú vo forme poľa ukazovateľov. Funkcie obsahujúce v názve písmeno 'e' navyše musia dostať ako argument pole ukazovateľov na premenné prostredia. A konečne funkcie obsahujúce 'p' pri hľadaní spúšťaného programu prezerajú okrem zadanej cesty, resp. aktuálneho adresára aj adresáre uložené v premennej prostredia PATH.

Medzi známejšie patria aj dve funkcie signal() a raise(), ktoré pochádzajú z Unixu a predstavujú v podstate niečo ako mechanizmus softvérových prerušení. Ide o tému na dlhšie vysvetľovanie, a ak vás zaujímajú bližšie detaily, skúste si preštudovať okrem dokumentácie k prekladaču nejakú knihu o programovaní v Unixe.

Funkcie na prácu s dátumom a časom

V tejto oblasti vládne tak trochu chaos – čo prekladač, to iné funkcie. Medzi tie, ktoré nájdeme takmer všade, patrí funkcia strftime(), ktorá formátuje zadaný čas a dátum na reťazca podľa požadovaných pravidiel (pracuje so špeciálnou štruktúrou tm), ďalej mktime() na prevod medzi štruktúrou tm a typom time_t, gmtime() na opačný prevod, funkcie ctime() a asctime() na neriadený prevod času na reťazec, difftime() na zistenie rozdielu v sekundách medzi dvoma časmi a predovšetkým funkcia time() na zistenie systémového času a dátumu a stime() na jeho nastavenie. Popri nich isto nájdeme aj funkcie viac na mieru šité danému operačnému systému, ktoré budú často aj vhodnejšie svojou sémantikou.

Funkcie na prácu so zoznamom argumentov

Poslednou skupinou, o ktorej si dnes povieme, sú tri funkcie (v skutočnosti sú to makrá) na prácu s argumentmi funkcie deklarovanej s výpustkou. Taká funkcia má vždy aspoň jeden známy (pevný) argument. Všetky tri funkcie pracujú so špeciálnym typom va_list, spolu s ním sú definované v súbore <stdarg.h>.

Prácu so zoznamom argumentov začneme volaním prvej funkcie, va_start(). Jej parametrami sú jednak deklarovaná premenná typu va_list (interne to je obyčajne nejaký ukazovateľ na zásobník), jednak posledný pevný argument funkcie. Jednotlivé argumenty potom sprístupňujeme volaním druhej funkcie, va_arg(). Táto funkcia okrem parametra typu va_list (ktorý by mal byť pred tým inicializovaný pomocou va_start(), inak je výsledok nepredvídateľný) očakáva typ sprístupňovaného argumentu (keďže ide o makro, je to v poriadku – normálne by sme názov typu nemohli odovzdať do funkcie ako jej argument). Samozrejme, nerobí sa nijaká kontrola, či to, čo nám va_arg() vráti, je naozaj platná premenná požadovaného typu, túto kontrolu musíme prípadne robiť sami. Jednoducho sa vezme kus pamäte (na zásobníku), pretypuje sa na daný typ a vráti ako výsledok. Súčasne sa modifikuje premenná typu va_list tak, aby ďalšie volanie va_arg() sprístupnilo zase ďalší argument. Takisto nikto nekontroluje, či to s volaniami va_arg() „neprešvihneme“ a nezačneme čítať nejaké nezmysly.

Po prečítaní všetkých argumentov by sme mali zavolať tretiu funkciu, va_end(), ktorá má jediný argument, a to našu premennú typu va_list. Obyčajne táto funkcia, resp. makro nič nerobí, ale človek nikdy nevie a podľa normy je volanie va_end() povinné.

Na záver si ešte ukážeme krátky príklad na použitie spomínaných funkcií, a to funkciu, ktorá spočíta svoje argumenty typu int a vráti ich súčet. Prvým argumentom funkcie bude počet sčítancov:

#include <stdio.h>
#include <stdarg.h>
 
int sum(int n, ...)
{
    va_list arg;
    int s = 0;
 
    va_start(arg, n);
    while (n--)
        s += va_arg(arg, int);
    va_end(arg);
 
    return s;
}
 
int main()
{
    printf("sum1 = %i\n", sum(3, 1, 2, 3));
    printf("sum2 = %i\n", sum(5, 7, 6, 5, 4, 3));
 
    return 0;
}

Dúfam, že vám je program jasný a nepotrebuje komentár.

Hor’ sa na objekty

Ale na tie si budete musieť počkať až do ďalšieho čísla. Verím, že vám doterajší výklad poskytol dostatok vedomostí na to, aby ste boli schopní písať viac či menej funkčné programy a hlavne aby ste boli pripravení na všetky tie drobné záludnosti, ktoré nám vie C++ prichystať. Ak máte pocit, že vám seriál niečo dal, alebo dokonca ak ste sa podľa neho „naučili“ C++ (teda tú jeho neobjektovú časť), nájdite si chvíľku času a napíšte mi, adresa je v tiráži časopisu. Skúste sa vyjadriť aj k forme či obsahu seriálu, či vám niečo prekáža, prípadne či vám niečo, naopak, chýba. Poväčšine od vás dostávam samé pochvalné maily, ktoré síce potešia a touto cestou vám za ne ďakujem, ale z nich sa nedozviem, či má seriál nejaké nedostatky.

C++

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

Mohlo by Vás zaujímať

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ž ...

Ako na to 1

Tipy a triky: Ako aplikácii prednastaviť spúšťanie s administrátorskými právami?

30.11.2016 00:10

Väčšina aspoň trochu skúsenejších používateľov vie, že aj keď máte na operačnom systéme Windows vytvorený administrátorský účet, aplikácie pre bezpečnosť nefungujú vždy splnými administrátorskými práv ...

Ako na to 2

Tipy a triky: Ako vypnúť uzamykaciu obrazovku vo Windows 10?

29.11.2016 00:10

Rozčuľuje vás, že pred každým prihlásením doúčtu vášho počítača musíte prejsť uzamykacou obrazovkou? Windows 10 na tejto obrazovke ukazuje čas,dátum anejakú zaujímavú fotografiu zrôznych kútov sveta. ...

Žiadne komentáre

Vyhľadávanie

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

Najnovšie videá