1 Úvod
- Z histórie jazyka C
- Štruktúra programu v jazyku C
1.1 Z histórie jazyka C
Programovací jazyk C vznikol na začiatku sedemdesiatych rokov v Bellových laboratóriách firmy AT&T (American Telephone and Telegraph). Autorom prvotného návrhu C je Dennis Ritchie, ktorý v tej dobe spolupracoval s Kenom Thompsonom na návrhu operačného systému UNIX. Pre jeho implementáciu sa autori rozhodli použiť jazyk dostatočne efektívny z hľadiska kódu a pritom nezávislý na konkrétnom procesore. Najprv uvažovali o použití jazyka BCPL (Basic Combined Programming Language), neskôr navrhol K. Thompson variant pod označením jazyk B (1970). V tomto jazyku boli implementované prvé časti OS UNIX. Tento jazyk bol efektívny z hľadiska prekladu, ale nebol dostatočne univerzálny - vyhovoval len na riešenie určitého okruhu problémov. D. Ritchie navrhol preto jazyk s označením C, v ktorom bolo neskôr zapísané asi 90 % kódu OS UNIX (zvyšná časť závisí na hardware konkrétneho počítača a musí byť napísaná v asembleri).
Jazyk C nie je rozsiahly, ale nepatrí pritom medzi jazyky, vyznačujúce sa dobrou čitateľnosťou. Svojím pôvodom je zameraný na systémové programovanie. Na jednej strane je ho možné zaradiť do skupiny vyšších programovacích jazykov, na druhej strane je možné program napísaný v jazyku C preložiť do veľmi efektívneho strojového kódu (niekedy sa preto o ňom hovorí ako o štruktúrovanom asembleri). Jazyk C má pomerne málo jednoduchých pravidiel, pomocou ktorých je možné vytvárať a skladať jednotlivé úseky programov do väčších a väčších celkov.
Jazyk C sa ďalej mierne vyvíjal. V roku 1978 napísali Ritchie a Kernighan knihu Programovací jazyk C, ktorá sa stala štandardom staršej verzie jazyka. V dnešnej literatúre sú na tento štandard referencie pod označením Kernighan - Ritchie C, resp. K & R. V roku 1984 vzniká prvá verzia americkej národnej normy jazyka pod označením ANSI C, ktorá mala nevýhodu najmä v slabšej typovej kotrole Od tejto chvíle dochádza k určitému oddeleniu jazyka C od OS UNIX a jazyk sa začína vyvíjať ako samostatný produkt. Definitívna verzia normy ANSI C vyšla vo februári 1990 pod označením X3J11 / 90 - 013 [Richta93]. Zavedením prísnejšej typovej kontroly pri volaní funkcií a vyhodnocovaní výrazov nie je možné napr. volať funkciu skôr, ako sa uvedie jej deklarácia alebo priamo definícia.
Jazyk C je implementovaný pre všetky typy procesorov a spolu so systémom UNIX predstavuje dnes jeden z hlavných trendov vo svete. Vznikol tiež celý rad rozšírení jazyka C - napr. Objective C pre objektové programovanie, Concurrent C pre paralelné programovanie, ale najperspektívnejším sa zdá byť objektovo orientované rozšírenie jazyka pod označením C ++ (označenie vychádza zo syntaxe jazyka C a znamená prechod na nasledujúci prvok), ktoré navrhol Bjarne Stroustrup z AT&T pri vývoji riadiaceho systému pre telefónne ústredne [Mareš94]. Do jazyka boli pridané konštrukcie pre tvorbu obecných funkcií a tried (šablony) a pre spracovanie výnimočných stavov. V súčasnej dobe ešte nie je oficiálne vydaná norma C++ a ako štandardná implementácia pre ostatné kompilátory slúži prekladač použitý na systéme USL firmy AT&T. Je to len preprocesor, ktorého výstupom je kód v jazyku C; preto sa tento preprocesor označuje ako Cfront. Komisie ANSI a ISO, ktoré pripravujú normu jazyka C++ (označuje sa ako ANSI/ISO C++), sa opierajú predovšetkým o knihu The Annotated C++ Reference Manual od Ellisa a Stroustrupa, vydanú v r. 1990 nakladateľstvom Addison - Wesley. Táto norma bola oficiálne prijatá až okolo roku 1996. Kompilátory jazyka C++ dnes ponúkajú všetky významné firmy (Borland - Borland C++, Microsoft - Microsoft Visual C++). V súčastnosti sa kladie veľký dôraz na objektovo orientované programovanie (OOP), podporu databáz priamo cez ovládač v operačnom systéme ODBC (Open Database Connectivity), poprípade DAO (Data Access Object) a taktiež na podporu výmeny objektov medzi aplikáciami OLE (Object Link Embedding) alebo DDE (Dynamic Data Exchange).
1.2 Štruktúra programu v jazyku C
Základnou programovou jednotkou v jazyku C je funkcia. Veľké množstvo štandardných funkcií je predom pripravených v knižniciach prekladača. Tieto funkcie vychádzajú na jednej strane z príslušnej normy ANSI, na druhej strane z "kuchyne" firmy - autora kompilátora. Každá štandardná funkcia je spravidla sprevádzaná svojím tzv. include - súborom (jedným alebo viacerými). Tento súbor má príponu .h od slova header (hlavička) a obsahuje súhrn informácií o určitej skupine štandardných funkcií (ich definície, globálne premenné a pod). Do zdrojového textu programu sú tieto súbory zaradené pomocou príkazu preprocesora #include (podrobnejšie viď kap. 2). Rovnakým spôsobom môžu byť do zdrojového textu programu zaradené užívateľom definované funkcie (podprogramy), ktoré je tiež možné združovať do knižnice.
Ďalším objektom, ktorý sa môže vyskytovať v štruktúre programu v jazyku C, sú globálne dátové objekty, ktoré sú dostupné vo všetkých funkciách daného programu. Doporučujeme však použitie týchto globálnych premenných vynechať, resp. obmedziť, pretože zhoršujú možnosti štruktúrovania programu.
Program v jazyku C je teda súhrnom definícií funkcií a deklarácií globálnych objektov. Jedna z funkcií sa musí volať main (označuje hlavný program). Jazyk C je typický svojou blokovou štruktúrou (napr. telo každej funkcie tvorí jeden blok). Blokom je zdrojový text, uzavretý v dvojici zátvoriek {, }. V rámci bloku je možné pracovať s globálnymi i lokálnymi premennými, ktoré sú definované len v rámci bloku (lokálne môžu byť len dátové objekty, všetky funkcie sú vždy globálne).
Štruktúra programu v jazyku C má teda obecný tvar :
- Skupina hlavičkových súborov štandardných funkcií.
- Definície užívateľských funkcií.
- Deklarácie globálnych premenných.
- Funkcia main.
- Ostatné užívateľské funkcie.
Štandardné knižničné funkcie, ktoré sa vyskytujú v zdrojovom texte programu (časť IV, resp. V), sú v celkovej štruktúre programu reprezentované svojimi hlavičkovými súbormi, spravidla uvedenými na začiatku programu (časť I).
Najjednoduchší úvodný program pre výpis textu na obrazovku má tvar :
/* priklad p1_1.c uvodny program */
#include <stdio.h>
main()
{
printf("ahoj\n");
}
a pozostáva teda len z časti I (1. riadok) a časti IV, ktorá je tvorená jedinou štandardnou funkciou printf. Jej definícia je uvedená v súbore stdio.h. Pred programom je jeden riadok komentára.
Ďalší príklad vykonáva kopírovanie znaku zadaného z klávesnice (štandardný vstup) na obrazovku (štandardný výstup). Kopírovanie je ukončené zadaním znaku medzera na vstupe. Príklad je uvedený v piatich postupne sa vyvíjajúcich verziách, ktoré dokumentujú základné črty a spôsoby práce v jazyku C (viď príklady pr1_2.c až pr1_6.c). Čítanie znaku zo vstupu vykonáva štandardná funkcia getchar, výstup znaku funkcia putchar. Obidve sú popísané v súbore stdio.h. Prvá verzia príkladu využíva opakovanie pomocou príkazu goto, druhá využíva štandardný príkaz while. V tretej verzii je vytvorený podprogram - funkcia copy, ktorá vykonáva samotné kopírovanie a v hlavnom programe je iba vyvolaná (bez prenosu parametrov). V štvrtej verzii sa okrem toho spočítavajú kopírované znaky a v hlavnom programe sa vypíše ich počet. Posledná verzia je doplnená ešte pomocnými výpismi na obrazovku. Kopírované znaky sú vypísané dvakrát (raz príkazom printf, druhý krát príkazom putchar).
/* pr1_2.c kopirovanie znaku z klavesnice na obrazovku */
#include <stdio.h>
void main(void)
{
char z;
znovu:
z=getchar();
if(z != ' ')
{
putchar(z);
goto znovu;
}
}
príklad pr1_3.c
/* priklad pr1_3.c kopirovanie znaku z klavesnice na obrazovku */
#include <stdio.h>
void main(void)
{
char z;
while((z=getchar())!=' ') putchar(z);
}
príklad pr1_4.c
/* priklad pr1_4.c kopirovanie znaku z klavesnice na obrazovku
podprogram copy */
#include <stdio.h>
void copy(void);
void main(void)
{
copy();
}
void copy(void)
{
char z;
while((z=getchar())!=' ') putchar(z);
}
príklad pr1_5.c
/* priklad pr1_5.c kopirovanie znaku z klavesnice na obrazovku
podprogram copy + pocitanie znakov */
#include <stdio.h>
int copy(void);
void main(void)
{
printf("Pocet nacitanych znakov:%d\n",copy());
}
int copy(void)
{
char z;
int pocet=0;
while((z=getchar())!=' ')
{
pocet++;
putchar(z);
}
if(z==' ')putchar(z);
return(pocet);
}
príklad pr1_6.c
/* priklad pr1_6.c kopirovanie znaku z klavesnice na obrazovku
podprogram copy + pocitanie znakov + texty */
#include <stdio.h>
int copy(void);
void main(void)
{
printf("\nPocet nacitanych znakov:%d\n",copy());
}
int copy(void)
{
char z;
int pocet=0;
printf("Zadaj znaky (koniec medzera a Enter):\n");
while((z=getchar())!=' ')
{
pocet++;
printf("\n%d. znak:%c",pocet,z);
putchar(z);
}
if(z==' ')putchar(z);
return(pocet);
}
2 Preprocesor jazyka C
- Definícia symbolických konštánt a makier
- Vkladanie textu
- Podmienený preklad
Pod pojmom preprocesor jazyka C rozumieme štandardne zavedenú množinu príkazov, ktoré sú spracované pred vlastnou kompiláciou samotného zdrojového textu. Toto predspracovanie má na starosti tzv. preprocesor. Príkazy preprocesora umožňujú vkladanie a skladanie textu z rôznych súborov, podmienený preklad a definíciu symbolických konštánt a makier. Každý príkaz preprocesora musí byť napísaný na samostatnom riadku a začínať znakom #, za ktorým nasleduje (bez medzery) príkaz a jeho parametre, oddelené medzerami (alebo tabelátormi). Príkazy nie sú ukončené bodkočiarkou (nejde o príkazy jazyka C). Parametre príkazov preprocesora je zvykom písať malými písmenami.
2.1 Definícia symbolických konštánt a makier
Príkaz na definíciu symbolických konštánt má tvar
#define NAME konstantný_výraz
kde NAME je názov symbolickej konštanty
konstantný_výraz je hodnota, ktorú symbolická konštanta nadobúda
Typ konštantného výrazu určuje typ symbolickej konštanty. Použitie tohoto príkazu umožňuje sústrediť (spravidla na začiatku programu) niektoré dôležité konštanty, čo umožňuje v prípade potreby ich modifikáciou zmeniť celý rad príkazov v programe (je to analógia príkazu CONST v jazyku Pascal, s tým rozdielom, že sa musí písať pred každou definíciou). Príkaz sa najčastejšie používa na definíciu rôznych konštánt, napríklad :
#define MAX 100
#define LINE 80
#define PI 3.14
#define NAME1 Pavel
#define NAME2 "Peter"
respektíve
#define 4PI 4*PI
#define MAXLIN 10 + LINE/2
Prvé tri sú číselné symbolické konštanty. MAX nadobudne hodnotu 100, LINE hodnotu 80, PI 3,14. NAME1 je znaková konštanta s hodnotou Pavel (všade namiesto NAME1 sa dosadí Pavel), NAME2 je reťazec "Peter".
Príkaz define môže v rámci konštantného výrazu použiť skôr definovanú symbolickú konštantu (viď posledné dva príklady). Príkaz define sa často používa pre definíciu rozmerov polí, napríklad :
#define M 10
#define N 5
...
int a[M];
float b[N], c[M][N];
char d[M+1];
Definícia makra spočíva v pridaní jedného či viacerých parametrov k názvu symbolickej konštanty (symbolickú konštantu je možné pokladať za makro bez parametrov) a doplnení výrazu pre jeho výpočet :
príklad
/* priklad p2_1.c
pouzitie makier pre vypocet objemov telies */
#include <stdio.h>
#define PI 3.14159
#define KOCKA(a) (a*a*a)
#define VALEC(v,p) (PI*p*p*v)
#define GULA(r) (4*PI/3*r*r*r)
void main(void)
{
float hr=2,v=3;
printf("Objem kocky so stranou %f je %f\n",hr,KOCKA(hr));
printf("Objem valca (r=%f, v=%f) je %f\n",hr,v,VALEC(v,hr));
printf("Objem gule (r=%f) je %f\n",hr,GULA(hr));
}
Uvedené makrá slúžia pre výpočet objemu príslušných geometrických telies. Identifikátory v hlavičke makra a, v, p, r predstavujú formálne parametre, ktoré sú pri vyvolaní makra textovo nahradené skutočnými. Počet formálnych a skutočných parametrov musí byť rovnaký. Poradie vykonávania jednotlivých operácií v rámci makra možno riadiť zátvorkami.
Ďalší príklad ilustruje použitie štandardnej funkcie printf v tele makra. Všimnite si, že je v takom prípade zakončená bodkočiarkou - je to normálny príkaz jazyka C.
/* priklad p2_2.c
pouzitie makra s prikazom tlace */
#include <stdio.h>
#define PM(n) printf("Parameter makra PM = %d\n",(n))
void main(void)
{
PM(1);
}
Program vypíše :
Parameter makra PM = 1
Posledný príklad ilustruje možnosť spojovania na textovej úrovni - parametre i, j v makre SP pomocou dvojice znakov ##. Je samozrejme na programátorovi, že objekt vytvorený uvedeným mechanizmom spojenia musí byť v programe deklarovaný. V opačnom prípade kompilátor hlási chybu - nedeklarovanú premennú. Tento príklad súčasne ilustruje možnosť dosadenia hodnoty parametra makra v textovej forme pomocou znaku # (#n v makre PM) :
/* priklad p2_3.c
makro na spojenie plus dosadenie v textovej forme */
#include <stdio.h>
#define SP(i,j) (i##j)
#define PM(n) printf("Hodnota "#n" = %d\n",(n))
void main(void)
{
int i1=1, i2=2, i3=3;
PM(SP(i,2));
}
Program vypíše hodnotu premennej i2 v tvare:
Hodnota SP(i,2) = 2
Vykonaním makra SP vznikne premenná i2. Dosadenie parametra makra PM v textovej forme je znázornené textom "SP(i,2)". Symbolickú konštantu možno definovať aj bez hodnoty príkazom #define a zrušiť jej definíciu príkazom #undef (na príslušnom mieste programu)
#define DEBUG
...
#undef DEBUG
Takéto definovanie, resp. zrušenie definície je možné využívať v ďalších príkazoch preprocesora - pri podmienenom preklade, vetvení, zaradzovaní rôznych (ladiacich) výpisov a pod. (viď kap. 2.3).
2.2 Vkladanie textu
Príkaz na vkladanie textu #include [meno súboru] spôsobí vloženie obsahu súboru "meno súboru" pred prekladom do zdrojového súboru, v ktorom sa vyššie uvedený príkaz nachádza. To umožňuje vkladanie tak systémových súborov (tzv. hlavičkových súborov, s príponou .h, napr. <stdio.h>), ako aj vlastných užívateľských súborov, čo predstavuje jednu z možností modulárnej výstavby programu.Príkaz na vloženie môže mať dva tvary (v prvom prípade je názov súboru uvedený v lomených zátvorkách, v druhom prípade v dvojitých uvodzovkách):
#include <file.h>
#include "path_file"
Prvý tvar sa používa pre vkladanie systémových súborov, ktoré sú uložené v príslušnom systémovom podadresári (INCLUDE). Všetky štandardné funkcie, konštanty a makrá v jazyku C sú deklarované v takýchto súboroch. Názov príslušného súboru, resp. súborov je uvedený vždy pri popise príslušnej funkcie. Vloženie príslušného súboru umožňuje prekladaču kontrolu správnosti volania štandardnej funkcie.
Druhý tvar sa používa pre vkladanie užívateľských súborov, pričom sa prehľadáva aktuálny adresár, resp. adresár, uvedený v špecifikácii vkladaného súboru.
2.3 Podmienený preklad
Príkazy pre podmienený preklad majú tvar:
#if podmienka
#ifdef symb_konštanta
#ifndef symb_konštanta
#else
#endif
a umožňujú v závislosti na splnení daných podmienok zaradzovať, resp. vynechávať určité časti programu z prekladu. Tieto podmienky sa môžu vzťahovať napr. na ladenie programu, rôzne výpisy, prítomnosť hardware (napr. koprocesor), príp. programového vybavenia. Každý z príkazov #if, #ifdef, resp. #ifndef musí byť ukončený príkazom #endif. Nasledujúci príklad je ukážkou možnosti jednoduchého zaradzovania ladiacich výpisov v programe:
/* priklad p2_4.c ladiace výpisy */
#include <stdio.h>
#define DEB
void main(void)
{
int i1=1, i2=2, i3=3;
#ifdef DEB
printf("Hodnota i1 (krok 1) = %d\n",i1);
#endif
i1=i2*i3;
#ifdef DEB
printf("Hodnota i1 (krok 2) = %d\n",i1);
i1=i1*i3;
printf("Hodnota i1 (krok 3) = %d\n",i1);
#endif
}
Druhá dvojica príkazov #ifdef - #endif obsahuje okrem výpisov aj priradzovací príkaz, čo nemusí byť algoritmicky správne - je potrebné to zvážiť podľa situácie.
Program vypíše (lebo je definavaná premenná DEB - príkaz #define):
Hodnota i1 (krok 1) = 1
Hodnota i1 (krok 2) = 6
Hodnota i1 (krok 3) = 18
3 Premenné
3.1 Identifikátory premenných
Identifikátory označujú jednotlivé objekty jazyka C - konštanty, premenné, funkcie, typy, selektory položiek štruktúrovaných typov, parametre funkcií a pod. Identifikátor je postupnosť písmen a číslic (a niektorých špeciálnych znakov, napr. '_'), ktorá sa začína písmenom, resp znakom '_'. Viacslovný identifikátor nie je prípustný a preto sa do jedného celku zvykne spájať práve uvedeným znakom '_', ktorý zvyšuje čitateľnosť zdrojového textu programu (napr. to_je_premenna). Prípustné sú iba znaky ASCII kódu. Dĺžka identifikátora je daná typom kompilátora, ANSI norma doporučuje maximálne 31 znakov. Jazyk C rozlišuje veľké a malé písmená, preto identifikátory Suma a suma sú rozdielne identifikátory (pozor na to !).
Ako identifikátory nie je možné používať tzv. kľúčové slová jazyka C:
auto enum short
break extern sizeof
case float static
char for struct
continue goto switch
default if typedef
do int union
double long unsigned
else register void
entry return while
ANSI norma vynecháva z tohoto zoznamu kľúčové slovo "entry" a doplňuje kľúčové slová "const", "signed" a "volatile". V literatúre sa niekedy doporučuje pre odlíšenie používať pre konštanty a užívateľom definované typy veľké písmená, pre všetky ostatné objekty malé písmená (premenné, funkcie).
3.2 Deklarácia premenných
Obecný tvar deklarácie premenných v jazyku C má tvar:
popis typu deklarované objekty [=počiatočné hodnoty]
Popis typu môže pozostávať z dvoch častí:
- definícia pamäťovej triedy
- definícia typu premennej
Pamäťové triedy
Definícia pamäťovej triedy (storage class) určuje požadovaný, resp. doporučený spôsob uloženia premennej. Jazyk C rozlišuje štyri možnosti: extern, auto, register, static.
Trieda extern
Ako sme už spomenuli v úvode, jazyk C má blokovú štruktúru. To znamená, že deklarácia uvedená vo vnútri bloku je lokálna v tomto bloku a zvonku (mimo bloku) nie je prístupná. Premenné deklarované mimo funkcie sú globálne a prístupné zo všetkých funkcií. Lokálna deklarácia "zatieňuje" globálnu deklaráciu, t.j. má pred ňou prednosť, ako to ilustruje aj nasledujúci príklad:
/* priklad pr3_1.c prekrývanie (tienenie) deklaráci */
int z = 1; /* globálna premenná */
void main(void)
{
int z =2; /* lokálna premenná */
printf("z=%d\n",z);
}
Program vytlačí hodnotu z=2.
Pre referencovanie globálnych premenných v jednotlivých funkciách či moduloch (modulom rozumieme súbor funkcií a deklarácií globálnych objektov, spravidla súvisiacich) sa využíva pamäťová trieda extern. Použitie tejto triedy v tvare, ktorý umožňuje aj separátnu kompiláciu, ilustruje príklad:
/* priklad pr3_2.c použitie externej deklarácie */
int z; /* globálna premenná */
int fncia(void);
void main(void)
{
extern int z; /* referencia glob. premennej */
z=0;
printf("z=%d\n",fncia());
}
int fncia(void)
{
extern int z; /* referencia glob. premennej */
return(z+1);
}
Program vytlačí hodnotu z=1.
Trieda auto
Všetky lokálne premenné sú implicitne považované za tzv. automatické premenné, t.j. premenné pamäťovej triedy auto. To znamená, že pamäťovú triedu auto nie je potrebné pri deklarácii premenných vôbec uvádzať. Premenné tejto pamäťovej triedy vznikajú automaticky v okamihu potreby, teda pri vstupe do bloku, v ktorom sú deklarované. Mimo tohoto bloku neexistujú. V okamihu opustenia daného bloku uvedené premenné automaticky zanikajú. Automatické premenné sú realizované prostredníctvom zásobníka (stack) pri aktivácii bloku, pri výstupe z bloku sa zásobník uvoľňuje. Automatické premenné základných typov (char, int, float, double) je možné inicializovať (priradiť počiatočné hodnoty). U iných typov premenných, resp. u základných, ak sme inicializáciu nepredpísali, hodnota premennej v okamihu vzniku nie je definovaná a musí byť programovo ošetrená. Preto príklad:
/* priklad pr3_3.c inicializácia premennej v bloku */
void main(void)
{
int z;
{
int z=1;
}
printf("z=%d\n",z);
}
môže vytlačiť ľubovoľnú hodnotu (premenná z je inicializovaná len v rámci vnútorného bloku, v rámci funkcie main jej hodnota nie je definovaná).
Trieda register
Používa sa pri veľmi častom využívaní niektorej automatickej premennej za účelom urýchlenia výpočtu. Použitie tejto triedy umožní priradiť danej premennej určitý register procesora. Ak takýto register nie je k dispozícii, premenná je spracovávaná ako automatická.
Trieda static
Používa sa v prípadoch, keď potrebujeme uchovať hodnotu premennej aj po opustení bloku. Statická premenná je lokálna v bloku, v ktorom je deklarovaná s tým, že má trvale pridelenú pamäť (nie je realizovaná pomocou zásobníka, ale ako pamäťová bunka). Inicializáciu statickej premennej vykonáva prekladač počas kompilácie zdrojového textu programu. Program teda začne pracovať s nastavenou počiatočnou hodnotou a k inicializácii sa už viacej nevracia. Ak inicializáciu nepredpíšeme, vykoná prekladač implicitné vynulovanie premenných (premenné typu char znakom '\0', premenné typu int hodnotou 0, premenné typu float hodnotou 0.0 a premenné typu double hodnotou 0.0L).
Pretože statické premenné zaberajú trvalé miesto v pamäti, je potrebné ich použitie dobre zvážiť a podľa možnosti obmedziť na minimum.
3.3 Typy premenných
Definícia typu premenných môže obsahovať:
- základný preddefinovaný typ (char, int, float, double)
- odvodený typ (smerník, pole, struct, union)
- vymenovaný typ (enum)
- užívateľom definovaný typ (typedef)
- prázdny typ (void)
Základné typy môžu byť modifikované klasifikátorom (short, long, unsigned), ktorý vyjadruje použitú podmnožinu alebo rozšírenie daného typu.
Základné typy
-typ int:
Používa sa pre špecifikáciu celých čísel. Oborom hodnôt typu int je súvislá množina celých číse v intervale -2**(N-2) až 2**(N-2), kde N je počet bitov slova pre celé číslo. Obvykle sa pre celé čísla využíva 16 bitové slovo, čomu zodpovedá rozsah -32768 až 32767. Rozsah pre daný kompilátor je uvedený v tvare symbolických konštánt INT_MIN, INT_MAX (hlavička limits.h).
Pre zadávanie celých čísel v programe je možné využiť dekadickú, oktálovú i hexadecimálnu sústavu. Dekadické číslo je zadané v bežnom tvare, napríklad:
int dek=1500;
Oktálové čísla sa začínajú číslicou 0 (môže nasledovať 0 až 7), napríklad:
int okt=010; (dekadicky 8)
int okt1=0377; (dekadicky 255)
Hexadecimálne čísla sa začínajú dvojicou 0x, resp. 0X (môže nasledovať 0 až F), napríklad:
int hex=0xA; (dekadicky 10)
Typy short int, long int majú rovnaké vlastnosti ako základný typ int. Môžu mať iný rozsah oboru hodnôt, ktorý je uvedený v tvare symbolických konštánt SHRT_MIN, SHRT_MAX pre typ short a LONG_MIN, LONG_MAX pre typ long. Použitím typu unsigned int sa presúva "int" rozsah do kladných hodnôt 0 až 65535.
-typ char:
Používa sa pre špecifikáciu znakových premenných. Obvyklá dĺžka typu je 1 byt (8 bitov), z čoho vyplýva dekadický rozsah 0 až 255. Umožňuje pracovať so všetkými znakmi ASCII tabuľky. Zadávanie znakových konštánt sa zapisuje pomocou apostrofov, napr. char z='a'; Existuje niekoľko tzv. zmenových postupností s týmto významom a spôsobom zápisu:
\a alert zvuk (beep)
\b backspace posuv o 1 znak späť
\f formfeed nová strana
\n newline nový riadok
\r carriage return návrat vozíka - nový riadok
\t hor. tabelator tabelátor
\v vert. tabelator tabelátor zvislý
\\ backslash zobrazí opačné lomítko
\' single quote zobrazí apostrof
\" double quote zobrazí uvodzovky
\? question mark zobrazí otáznik
\ddd bitový obrazec
Zmenová postupnosť \ddd pozostáva z opačného lomítka, za ktorým následuje jedna, dve alebo tri osmičkové číslice, udávajúce hodnotu požadovaného znaku. Špeciálnym prípadom tejto konštrukcie je \0 (bez ďalších číslic), ktorá reprezentuje prázdny znak (používa sa na označenie ukončenia reťazca - poľa znakov). Ak za opačným lomítkom nenasleduje jeden z uvedených znakov, opačné lomítko sa ignoruje.
-typ float a double:
Používa sa pre špecifikáciu reálnych čísel v pohyblivej rádovej čiarke. Konštanty sa zapisujú obvyklým spôsobom s desatinnou bodkou, resp. v semilogaritmickom tvare (mantisa plus exponent), napráklad
float f=12.76, ff=12.76e-3;
Odvodené typy
-typ smerník:
Používa sa pre špecifikáciu adresy dátového objektu. Formálne sa označuje znakom * (operátor dereferencie). Smerník obsahuje vždy dve základné informácie o objekte - jeho adresu a jeho dĺžku (vyplýva z typu objektu). V zápise int *si; označuje si smerník na typ int, *si znamená hodnotu objektu, na ktorú smerník si ukazuje.
-typ pole:
Používa sa pre špecifikáciu homogénnej dátovej štruktúry daného typu. Pole je v jazyku C jednorozmerné, indexované od 0. Viacrozmerné pole vzniká ako pole jednorozmerných polí , napr.
float p1[3],p2[2][4];
Názov poľa predstavuje smerník na jeho začiatok, takže zápis *(p1+2) má rovnaký význam ako tradičné p1[2].
-typ struct:
Používa sa pre špecifikáciu nehomogénnej dátovej štruktúry (obdoba dátového typu záznam z jazyka Pascal), napr. zápis
struct clen
{
char meno[25];
int vek;
float vaha;
}
špecifikuje typ štruktúry clen (údaj o mene, veku a hmotnosti). Deklarácia premenných typu struct má tvar
struct clen pr1, pr2[5], *pr3;
Táto deklarácia vytvára premennú pr1 typu clen, pole štruktúr pr2 s piatimi prvkami a smerník pr3 na štruktúru clen. Štruktúra môže obsahovať aj smerník sama na seba, čo sa využíva pri vytváraní zoznamov (viď kap. 9.1), napr.
struct clen
{
char meno[25];
int vek;
float vaha;
struct clen *dalsi;
}
-typ union:
Vzniká analogicky ako štruktúra s tým, že namiesto kľúčového slova struct sa použije kľúčové slovo union. Rozdiel medzi nimi spočíva v spôsobe uloženia položiek struct a union v pamäti. Kým položky objektu struct sú uložené všetky za sebou v pamäti, objektu union (zjednotenie) je pridelená iba pamäť, potrebná pre najdlhšiu položku. Jednotlivé položky unionu sa prekrývajú, preto v unione môže byť v jednom okamihu iba jedna položka. Použitie unionu je pomerne zriedkavé. Syntax ako aj prístup k položkám unionu je analogický ako v prípade štruktúry
typedef union
{
char meno[25];
int vek;
float vaha;
} typun;
typun a, *sa = &a;
a.vek = 15;
sa->vaha = 75.5;
Praktické použitie unionu ilustruje príklad pr3_4.c na ukážke výpočtu objemov geometrických telies.
/* pr3_4.c pouzitie unie a pristup k jej zlozkam */
/* program bol kompilovany pod Borland C v3.0*/
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <math.h>
typedef enum {NIC, GULA, VALEC, KOCKA, KVADER} T;
struct kvader{
double dlzka;
double sirka;
double vyska;
};
struct valec{
double polomer;
double vyska;
};
union teleso{
double rozmer;
struct kvader a;
struct valec b;
};
union teleso t, *pt=&t;
T typ;
double objem;
char c;
void vstup(void);
void vypocet(void);
void main(void)
{
do
{
clrscr();
puts("Vypocet objemu telesa");
vstup();
vypocet();
printf("Pokracovanie (A/N) :");
c = toupper(getche());
} while(c == 'A');
return 0;
}
void vstup(void)
{
printf("gula...0 kocka...1 kvader...2 valec...3 ");
printf("\nteleso: ");
c=getche();
switch (c)
{
case '0' :
printf("\npolomer gule : ");
scanf("%lf",&(t.rozmer));
typ = GULA;
break;
case '1' :
printf("\nstrana kocky : ");
scanf("%lf",&(pt->rozmer));
typ = KOCKA;
break;
case '2' :
printf("\ndlzka kvadra : ");
scanf("%lf",&(t.a.dlzka));
printf("sirka kvadra : ");
scanf("%lf",&(t.a.sirka));
printf("vyska kvadra : ");
scanf("%lf",&(t.a.vyska));
typ = KVADER;
break;
case '3' :
printf("\npolomer valca : ");
scanf("%lf",&(pt->b.polomer));
printf("2vyska valca : ");
scanf("%lf",&(t.b.vyska));
typ = VALEC;
break;
default :
printf(" Nezname teleso \a\n");
typ = NIC;
break;
}
}
void vypocet(void)
{
if (typ == GULA)
{
objem = 4./3*M_PI*pow(t.rozmer,3);
printf("Objem gule: %10.2f \n",objem);
}
else if (typ == KOCKA)
{
objem = pow(t.rozmer,3);
printf("Objem kocky: %10.2f \n",objem);
}
else if (typ == KVADER)
{
objem = t.a.dlzka*t.a.sirka*t.a.vyska;
printf("Objem kvadra: %10.2f \n",objem);
}
else if (typ == VALEC)
{
objem = M_PI*pow(t.b.polomer,2)*t.b.vyska;
printf("Objem valca: %10.2f \n",objem);
}
}
-typ enum (vymenovaný typ):
Používa sa pre špecifikáciu užívateľom vymenovaných hodnôt (obdobne ako v Pascale), ktoré sú reprezentované podmnožinou oboru hodnôt typu int. Jednotlivým položkám vymenovaného typu sú priradzované celé čísla od nuly a zväčšujúce sa o jedna, pokiaľ nie je predpísané inak, napr.
enum ZNAK {pismeno, cislica, ine};
enum ZNAK p1;
bude mať obor hodnôt [0,1,2]. S položkami vymenovanéh typu pracujeme pri priradzovaní i pri inicializácii, napr.
enum FARBA {biela, modra, cervena} f = biela;
...
f = modra;
-typ typedef (Užívateľom definovaný typ):
Používa sa pre špecifikáciu užívateľom vytvorených typov, ktoré sa v ďalšom používajú presne tak, ako typy jazyka C. Názvy vytvorených typov sa píšu spravidla (pre odlíšenie) veľkými písmenami, napr.
typedef char *STRING;
STRING s;
Premenná s je pokladaná za premennú typu char *.
-typ void (Prázdny typ):
Používa sa pre špecifikáciu skutočnosti, že prenášaný typ je prázdny, najčastejšie v spojitosti s funkciou, napr. zápis
void f(void)
označuje funkciu f, ktorá nemá žiadne parametre ani nevracia žiadnu hodnotu.
Môže sa však použiť aj pri smerníkoch - smerník na nezámy (všeobecný typ).
Typové modifikátory
Ľubovoľná premenná určitej pamäťovej triedy a dátového typu môže byť modifikovaná tzv. typovým modifikátorom, ktorým môže byť const a volatile.
-modifikátor const:
Udáva, že daná premenná nesmie po jej inicializácii meniť svoju hodnotu. Nie je možné využiť ju pri definovaní hraníc poľa (ako symbolickú konštantu), ale často sa využíva pri definícii formálnych parametrov funkcií, kedy takto označený parameter je spracovaný iba ako vstupný parameter (jeho hodnota sa v rámci funkcie nezmení).
-modifikátor volatile:
Udáva, že daná premenná môže byť modifikovaná bližšie nešpecifikovanou asynchronnou udalosťou (i mimo programu, napr. prerušením). Kompilátor nemôže urobiť žiadny záver o konštantnosti alebo možnosti zmeny takejto premennej. Použitie takto modifikovaných premenných je veľmi zriedkavé.
Prideľovanie pamäte
Všetky používané dátové štruktúry (premenné) môžeme podľa doby ich trvania rozdeliť na statické a dynamické. Statické vznikajú vyhradením miesta kompilátorom počas prekladu a trvajú po celý čas vykonávania programu. Dynamické vznikajú a zanikajú podľa potreby počas vykonávania programu. Dynamická premenná vzniká alokáciou pamäte, zaniká uvoľnením tejto pamäte. Na to sa využívajú štandardné knižničné funkcie malloc a free. Funkcia malloc má jeden parameter typu unsigned int, ktorý udáva požadované množstvo dynamicky pridelenej pamäte. Vracia smerník na pridelenú pamäť (typu char *, resp. void *), cez ktorý program komunikuje s takto vytvorenou dynamickou premennou. Táto pamäť je až do jej uvoľnenia rezervovaná. Uvoľnenie pamäte sa vykoná pomocou funkcie free, ktorej zadáme smerník na už nepotrebnú pamäť. Nasledujúci príklad ilustruje vytvorenie, naplnenie aj zrušenie takejto dynamickej premennej d:
/* priklad pr3_5.c práca s dynamickou premennou */
#include <stdio.h>
#include <string.h>
#include <malloc.h>
void main(void)
{
char sr[50], *s=sr,*d;
strcpy(s,"retazec");
printf("Obsah statickej premennej: %s\n",s);
d=(char*)malloc(strlen(s)+1);
strcpy(d,s);
printf("Obsah dynamickej premennej: %s\n",d);
free(d);
}
Bitové polia
Vymenovaný typ sa používa pre špecifikáciu užívateľom vymenovaných hodnôt (obdobne ako v Pascale), ktoré sú reprezentované podmnožinou oboru hodnôt typu int. Bitové pole sa používa podobne ako vymenovaný typ s tým, že umožnuje úspornejšiu reprezentáciu zložiek vo forme daného počtu bitov, ktoré sa umiestňujú v rámci jedného, resp. niekoľkých celých čísel. Bitové pole môže byť položkou štruktúry. Deklarácia bitového poľa má tvar
unsigned nazov : počet_bitov
typedef unsigned int UInt;
typedef struct twobytes
{
UInt a : 4;
UInt b : 3;
UInt c : 7;
UInt d : 2;
}
/*sucet musi byt n nasobok 16 (velkost int)*/
Príklad pr3_6.c uvádza možnosť použitia bitového poľa s úsporou dvoch položiek typu int. Prvá položka pohl zaberá 1 bit, položka stav 2 bity (číselne 0 až 3), položka vek 5 bitov (umožňuje zadávať vek 0 až 31). Posledná položka no sa nepoužíva, jej uvedenie nuluje príslušné bity. Ak ju neuvedieme, bity sú nastavené na 1. Všeobecne sa doporučuje túto položku udávať a to kôly prenositeľnosti (niektorý kompilátor môže vyžadovať, aby bol súčet násobkom veľkosti použitého typu).
/* priklad pr3_6.c použitie bitového poľa */
#include <stdio.h>
#define m 1
#define z 0
struct ziak
{
unsigned pohl : 1; /* pohlavie */
unsigned stav : 2; /* slob, žen, rozv, vdov */
unsigned vek : 5; /* vek do 32 rokov */
unsigned no : 8; /* nepouzite - do poctu (16) */
}
void main(void)
{
struct ziak x;
x.pohl = m;
x.stav = 3;
x.vek = 12;
/*...*/
printf("pohl=%d x=%o\n", x.pohl, x);
}
Taktiež je možné, že nie každý kompilátor podporuje prenášanie bitov, príklad:
typedef struct fourbytes
{
unsigned a : 15;
unsigned b : 2; /*nesmie byt*/
unsigned c : 15;
}
4 Lexikálne prvky jazyka
Program v jazyku C pozostáva z postupnosti lexikálnych prvkov, ktoré môžu na seba nadväzovať alebo byť oddelené oddeľovačmi. Ak je možné tieto prvky rozlíšiť, oddeľovač sa môže (ale nemusí) použiť, v ostatných prípadoch je samozrejme jeho použitie potrebné. Za oddeľovač je v jazyku C považovaná neprázdna postupnosť:
- medzier
- tabelátorov
- znakov prechodu na nový riadok
- znakov prechodu na novú stránku
- komentárov
Prvé štyri kategórie oddeľovačov sa nazývajú tzv. biele znaky (v angličtine white spaces). Komentár v jazyku C je postupnosť, ktorá sa začína dvojicou znakov /* a končí tou istou dvojicou v opačnom poradí */ (v tom istom alebo v hociktorom ďalšom riadku programu. Komentáre nie je možné do seba vnárať.
Lexikálne prvky jazyka C tvoria
- identifikátory
- kľúčové slová
- konštanty
- interpunkčné znaky
- reťazce
- operátory
Identifikátory i kľúčové slová boli popísané v predchádzajúcej kapitole v odstavci 3.1.
Konštanty sa používajú pre označenie konkrétnej hodnoty jedného dátového objektu daného typu. Preto sa u konštanty vždy rozlišuje jej typ, ktorý je daný typom jej konkrétnej hodnoty. Konštanty sa podľa typu delia na konštanty:
- celočíselné
- znakové
- v pohyblivej rádovej čiarke
- vymenovaného typu
- typu reťazec
-Celočíselné konštanty môžu byť dekadické, oktálové (začína '0') a hexadecimálne (začína '0x'). Ak presiahne hodnota konštanty rozsah typu int, považuje sa za konštantu typu long (explicitná prípona pre typ long je 'l', resp. 'L', napr. 010L, 0X1EL).
-Znakové konštanty môžu byť vyjadrené ako znak medzi apostrofmi ('z') alebo svojou oktálovou hodnotou za obráteným lomítkom ( napr. '\65' a 'a' sú ekvivalentné, podobne '\12' a '\n' atď.).
-Konštanty v pohyblivej rádovej čiarke môžu byť zapísané v bežnom tvare (celá a desatinná časť, oddelená desatinnou bodkou) alebo v semilogaritmickom tvare (mantisa a exponent).
-Konštanta vymenovaného typu môže nadobúdať jednu z hodnôt tohoto typu.
-Konštanta typu reťazec je postupnosť znakov medzi dvojicou znakov "", napr. "Typ retazec". Každá konštanta tohoto typu je uložená v statickom poli typu char s dĺžkou o jedna väčšou ako dĺžka konštanty, pretože ukončovacím znakom reťazca je vždy znak '\0' (binárna nula). Súčasťou reťazca môžu byť aj znaky zmenových postupností (začínajú znakom '\'). Reťazec musí byť napísaný v jednom riadku. V prípade dlhého reťazca sa tento na prechode riadkov musí ukončiť a na ďalšom riadku opäť začať znakom apostrof ("). V takom prípade sa dvojica apostrofov a znak prechodu na nový riadok vypúšťa z obsahu reťazca.
Interpunkčné znaky (symboly) jazyka C sú [ ] ( ) { } * , : = ; ... #
Pre reťazce platí všetko, čo bolo uvedené v odstavci Konštanta typu reťazec.
4.1 Priorita operátorov
Operátory sú vyhodnocované podľa ich priority a asociativity. Vyššia priorita znamená skoršie vyhodnotenie operátora. Pri rovnakej priorite sa operátory vyhodnocujú podľa ich asociativity (-> znamená zľava doprava, <- naopak).
priorita symbol typ operácie asociativita
12 () [] . -> výraz ->
- ~ ! * & unárne operátory <-
++ -- inkrement, dekrement <-
(cast) pretypovanie <-
(typ)
(roly)
sizeof operátor dĺžky <-
* / % multiplikatívne operátory ->
11 + - aditívne operátory ->
10 << >> operátory posuvu ->
9 < > <= >= operátory relačné ->
8 == != rovnosť, nerovnosť ->
7 & bitový súčin AND ->
6 ^ bitový exkl. súčet XOR ->
5 | bitový súčet OR ->
4 && logický súčin AND ->
3 || logický súčet OR ->
2 ? : podmienený operátor <-
1 = *= /= %= priradenie <-
+= -= <<=
>>= &= |= ^=
, čiarka ->
Operátory slúžia na vytváranie výrazov z konštánt a premenných. Podľa počtu argumentov sa delia na operátory :
- unárne
- binárne
- ternárne
4.2 Unárne operátory
Unárny operátor má jeden argument, pred ktorý sa zapisuje. V prípade viacerých operátorov sa tieto vyhodnocujú sprava doľava. Operátory ++ a -- je možné písať i za argument.
Unárne operátory sú: | |||
* | -> | dereferencia (získanie objektu podľa adresy) | |
& | -> | referencia (získanie adresy objektu) | |
- | -> | aritmetické mínus | |
! | -> | logická negácia (0 -> 1, nenulové -> 0) | |
~ | -> | bitový doplnok (v bitovej reprezentácii zmení 0 na 1 a naopak) | |
++ | -> | inkrementácia hodnoty pred vyhodnotením nasledujúceho, resp. po vyhodnotení predchádzajúceho operandu | |
-- | -> | dekrementácia hodnoty pred vyhodnotením následujúceho, resp. po vyhodnotení predchádzajúceho operandu | |
([typ]) | -> | explicitný prevod na typ v zátvorke(pretypovanie) | |
sizeof | -> | získane dĺžky objektu alebo typu(v byte-och) |
Operátor * predpokladá ako argument smerník na objekt a vracia ako hodnotu tento objekt (ak s je smerník na typ int, *s je tiež typu int). Operátor & vracia adresu premennej, ktorá sa môže priradiť smerníku. Teda platí že v = *&v (hodnota premennej v sa rovná obsahu adresy premennej v), ale neplatí v = &*v, pretože v nie je smerník. Operátor logickej negácie ! je možné aplikovať i na aritmetické typy a smerníky. Výsledok negácie je typu int. Aj bitový doplnok ~ sa používa iba na typ int. Operátor ++, resp. -- znamená zvýšenie, resp. zníženie hodnoty objektu o 1 v závislosti na type objektu. Pre typ int platí ++i = i + 1, pre typ smerník na pole znamená presun smerníka na následujúci prvok poľa podľa jeho typu. Funkcia strlen vracia dĺžku reťazca:
int strlen(char *s) /* *s - smerník na reťazec */
{
int i=0;
while(*s++) i++;
return(i);
}
Pre pretypovanie objektu sa používa operátor ([typ]) (podľa K&R operátor (roly)), ktorý vykoná konverziu z jedného dátového typu na druhý (ktorý je uvedený v zátvorkách). Určité rozumné konverzie vykonáva kompilátor automaticky, napr. typ int na typ float. Operátor sizeof vracia dĺžku objektu alebo typu v bytoch. Argumentom sizeof môže byť aj pole, štruktúra alebo zjednotenie.
4.3 Binárne operátory
Binárny operátor sa zapisuje medzi dva argumenty. Binárne operátory sa členia na operátory:
aritmetické sčítanie +
odčítanie -
násobenie *
delenie /
modulo % (zvyšok po celočíselnom delení)
relačné menší ako <
väčší ako >
menší alebo rovný <=
väčší alebo rovný >=
rovný ==
nerovný <>
logické logický súčin (AND) && +---+---+-----+-----+-----+
logický súčet (OR) || | x | y | x&x | x^y | x|y |
bitové bitový súčin (AND) & +---+---+-----+-----+-----+
bit. exkluzívny súčet (XOR) ^ | 0 | 0 | 0 | 0 | 0 |
bitový súčet (OR) | | 0 | 1 | 0 | 1 | 1 |
priradenia priradenie = | 1 | 0 | 0 | 1 | 1 |
posuvu posuv vľavo << | 1 | 1 | 1 | 0 | 1 |
posuv pravo >> +---+---+-----+-----+-----+
Aritmetické operátory sa vyhodnocujú sprava doľava. Operátor + a - je možné aplikovať aj na smerníky (smerníková aritmetika: ak s je smerník na nejaký objekt, potom s+1 ukazuje na nasledujúci objekt, s-1 na predchádzajúci objekt danej dĺžky; je to ekvivalentné s++, resp. s--). Odčítaním dvoch smerníkov dostaneme vzdialenosť dvoch objektov v jednotkách ich dĺžky.
Relačné operátory sa používajú pre porovnávanie objektov. Výsledkom porovnania je splnenie relácie (hodnota 1) alebo jej nesplnenie (hodnota 0). Uvedené hodnoty sú typu int. Na rovnosť, resp. nerovnosť je možné porovnávať aj smerníky.
Logickéoperátory sa vyhodnocujú vždy zľava doprava. Pre hodnotu a typ výsledku platí predchádzajúci odstavec. Pozor na rozdiel medzi logickými a bitovými operátormi.
Bitové operátory slúžia na vykonávanie základných operácií (logického súčinu, súčtu či nonekvivalencie) po bitoch. Sledujme hodnoty logického i bitového súčtu i súčinu v nasledujúcom príklade. Vidíme, že bitové operácie dávajú v konečnom dôsledku číselný výsledok, zatiaľ čo logické operácie poskytujú "logický" výsledok, teda hodnoty 0 (false) a 1 (true).
Príklad:
/* priklad pr4_1.c logicke a bitové operátory */
void main(void)
{
int x=1,y=2;
printf(" x = %d y = %d x&y = %d\n",x,y,x&y);
printf(" x = %d y = %d x&&y = %d\n",x,y,x&&y);
printf(" x = %d y = %d x|y = %d\n",x,y,x|y);
printf(" x = %d y = %d x||y = %d\n",x,y,x||y);
}
Operátor priradenia sa môže vyskytovať okrem základného tvaru = aj v jednom z tvarov +=, -=, /=, *=, %=, >>=, <<=, &=, ^=, |=. Sú to tzv. kombinované tvary, ktoré skracujú zápis, napr.
a = a+2 je ekvivalentné a += 2
Operátor priradenia možno použiť viacnásobne, napr.
a=b=c=0 je ekvivalentné a=(b=(c=0))
Operátory posuvu umožnujú vykonávať posuv po bitoch doprava alebo doľava o počet bitov, ktorý je typu int. Pri posuve doľava sú bity sprava nulované pre objekty typu unsigned, resp. sa kopíruje znamienkový bit.
Operátor čiarka (operátor zabudnutia) oddeľuje dva výrazy a vyhodnocuje sa zľava doprava. Používa sa predovšetkým ako oddeľovač v príkaze cyklu typu for, napr.
for(i=0,j=100;i<25;i++,j--)
4.4 Ternárne operátory
Operátor ? je jediným operátorom jazyka, spájajúcim tri argumenty. Jeho význam ilustruje nájdenie menšej z dvoch hodnôt x,y zápisom :
min = x < y ? x : y;
ktorý interpretujeme takto:
Do premennej min ulož hodnotu x, ak je splnená podmienka x < y, inak tam ulož hodnotu premennej y.
4.5 Výrazy
Výraz v jazyku C je tvorený postupnosťou operátorov a operandov. Ich vyhodnotenie vedie na výpočet adresy alebo hodnoty. Preto sa v jazyku C rozlišuje tzv. adresový výraz - lvalue, ktorý sa používa na ľavej strane priradzovacieho príkazu (odtiaľ left value) a hodnotový výraz - rvalue, ktorý sa používa (vyskytuje, vyhodnocuje) na pravej strane príkazu.
Vyhodnotenie výrazu v C môže mať vedľajší efekt, ktorému sa snažíme zabrániť, napr. používaním výrazov, ktoré behom výpočtu nespôsobia viacnásobnú zmenu obsahu toho istého pamäťového miesta.
Výrazy sa vytvárajú z operandov pomocou operátorov, pričom operandom môže byť konštanta, premenná alebo opäť výraz. Základom výrazov sú tzv. primárne výrazy, kam patrí:
- identifikátor
- konštanta
- reťazec
- výraz v okrúhlych zátvorkách
- indexový výraz, t.j. primárny výraz nasledovaný výrazom v hranatých zátvorkách
- volanie funkcie, t.j. primárny výraz nasledovaný výrazom v okrúhlych zátvorkách s prípadným zoznamom argumentov
- výraz selekcie položiek (príkazy výberu) v tvare:
[primárny_výraz].[identifikátor_položky]
Je možný aj iný spôsob selekcie položky s využitím smerníka na položku v tvare:
[smerník na štruktúrovaný objekt]->[identifikátor_položky]
Ak s je smerník na nejakú štruktúru, prístup na jej zložku x zapíšeme s->x, čo je to isté ako (*s).x
Konštantné výrazy spravidla využívajú pamäťovú triedu static. Vyhodnotenie takého výrazu sa vykoná už počas kompilácie programu. Pri volaní funkcie musí byť dodržaný typ a počet argumentov funkcie a taktiež typ návratovej hodnoty funkcie. V prípade, že funkcia nie je v programe deklarovaná, kompilátor ju automaticky označí za externú funkciu s návratovým parametrom typu int, t.j
extern int f();
čo môže spôsobiť nedefinované chovanie programu. Podľa normy ANSI musí byť každá použitá funkcia deklarovaná.
5 Riadiace štruktúry
Základnou jednotkou riadiacej štruktúry je príkaz. Riadiace štruktúry predpisujú poradie vykonávania dielčich výpočtov. Príkazy možno rozdeliť na
- výrazový príkaz
- blok
- podmienený príkaz
- prepínač
- príkaz cyklu
- príkaz skoku
5.1 Výrazový príkaz
Výrazový príkaz vzniká pridaním znaku ";" za výraz (viď kap. 4.5). Pretože výrazom je aj volanie funkcie (napr. štandardnej), v nasledujúcom programe sú tri výrazové príkazy:
/* priklad pr5_1.c výrazové prkazy */
void main(void)
{
int a,b;
a = 1; /* prvy vyrazovy prikaz */
b += 1+3*a; /* druhy vyrazovy prikaz */
printf("a=%d b=%d\n",a,b); /* treti vyrazovy prikaz */
}
5.2 Blok
Blok je tvorený postupnosťou príkazov jazyka C, uzavrenou v dvojici zložených zátvoriek {,}. V bloku je taktiež možné deklarovať lokálne premenné, ktoré majú platnosť iba v rámci daného bloku. Z iných častí programu, resp. blokov nie sú takéto premenné viditeľné. Vnorený blok môže, ale nemusí byť ukončený znakom bodkočiarka. Blok, ktorý obsahuje iba príkazy bez deklarácií premenných, označujeme ako zložený príkaz.
5.3 Podmienený príkaz
Podmienený príkaz umožňuje vykonávať vetvenie priebehu programu. Má dva tvary - skrátený a úplný :
if (výraz) príkaz; /* skrátený */
if (výraz) príkaz1;
else príkaz2; /* úplný */
Vetvenie sa vykoná na základe vyhodnotenia podmienky, ktorou je výraz v zátvorkách. Ak je hodnota výrazu nenulová (odpovedá pascalovskému true), vykoná sa príkaz, resp. v prípade úplného tvaru príkaz1. Ak je hodnota výrazu nulová (odpovedá false), vykoná sa iba v druhom prípade príkaz2. Výber minima dvoch čísel môžeme preto zapísať v tvare :
int min(int a, int b)
{
if (a < b) return(a);
else return(b);
}
alebo
int min(int a, int b)
{
if(a < b) return(a);
return(b);
}
alebo pomocou podmieneného výrazu
int min(int a, int b)
{
return((a < b)? a : b);
}
Pri vnáraní podmienených príkazov sa vzťahuje else vždy k najbližšiemu predchádzajúcemu if, preto musíme správne použiť vnorený blok. Príklad :
{
if(y != 0)
if (x < 0) x = -x;
else printf("Delenie nulou\n")
z = x/y;
}
nie je správne zapísaný. Musíme použiť vnorený blok alebo zmeniť podmienku prvého príkazu if :
{
if(y != 0)
{
if (x < 0) x = -x;
z = x/y;
}
else printf("Delenie nulou\n")
}
resp.
{
if (y == 0) printf("Delenie nulou\n");
else
{
x = -x;
z = x/y;
}
}
5.4 Prepínač
Prepínač umožňuje vykonávať vetvenie programu na viac ako dve vetvy. Vetvenie sa rovnako ako v predchádzajúcom príkaze if vykonáva na základe vyhodnotenia výrazu, ktorý je celočíselného typu. Ak sa nájde zhoda medzi hodnotou výrazu a cestou case, pokračuje výpočet touto vetvou (až po príkaz break), inak je skok na default vetvu. Tvar príkazu je :
switch (výraz)
{
case hodn1 : prikaz1;
...
case hodnn : prikazn;
default : prikaz;
}
resp.
switch (výraz)
{
case hodn1 : prikaz1;break
...
case hodnn : prikazn;break;
default : prikaz;break;
}
V prvom prípade sa vykonajú všetky príkazy od zvolenej vetvy až po default, v druhom prípade je výpočet každej vetvy ukončený príkazom break, to znamená že výpočet sa príkazom switch (v Pascale ekvivalent CASE) rozvetví a po jeho ukončení opäť spojí. Spôsob ošetrenia vetvy default záleží na programátorovi.
V uvedenom príklade vytlačí funkcia slovne hodnotu známky, zadanej číslom:
void znslovo(int zn)
{
switch(zn)
{
case 1 : printf("vyborne");break;
case 2 : printf("velmi dobre");break;
case 3 : printf("dobre");break;
case 4 : printf("nevyhovel");break;
default : printf("nepripustna znamka %d\n",zn);break;
}
}
5.5 Príkazy cyklu
Príkaz cyklu sa používa pre riadenie opakovania nejakého výpočtu. Určuje spôsob a podmienku ukončenia opakovania. Jazyk C pozná tri varianty príkazu cyklu:
- cyklus while
- cyklus do while
- cyklus for
Cyklus while má tvar :
while (výraz) príkaz;
Výraz tvorí podmienku ukončenia cyklu. Cyklus sa vykonáva, kým je výraz nenulový (true). Telo cyklu sa teda nemusí vykonať ani raz. Príkaz ilustrujeme funkciou na výpočet celočíselnej mocniny celého čísla.
/* priklad pr5_2.c celociselna mocnina pomocou while */
int imoc(int x, int n);
void main(void)
{
printf("Mocnina (%d)%d = %d\n",3,4,imoc(3,4));
}
int imoc(int x, int n)
{
int y=1;
while (n--) y *=x;
return (y);
}
Cyklus do while má tvar :
do
príkaz
while (výraz);
Výraz tvorí podmienku ukončenia cyklu. Cyklus sa vykonáva, kým je výraz nenulový (true), najmenej však jedenkrát. Použitie príkazu do while je pomerne zriedkavé v porovnaní s ostanými dvoma príkazmi cyklu. Príkaz ilustrujeme rovnakým príkladom funkcie na výpočet celočíselnej mocniny celého čísla :
/* priklad pr5_3.c celociselna mocnina pomocou do while */
int imoc(int x, int n);
void main(void)
{
printf("Mocnina (%d)%d = %d\n",3,4,imoc(3,4));
}
int imoc(int x, int n)
{
int y=1;
do
y *= x;
while (n--) ;
return (y);
}
Cyklus for má tvar :
for (init ; koniec ; zmena) prikaz;
kde init je výraz, ktorý sa vyhodnotí raz na začiatku cyklu, obvykle sa tu nastavujú počiatočné hodnoty cyklu.
Koniec je podmienka ukončenia cyklu (kým je pravdivá - nenulová).
Zmena sa vyčísli po každom vykonaní cyklu, slúži pre zmenu riadiacej premennej cyklu. Táto zmena riadiacej premennej sa nemá uskutočňovať v tele cyklu, ale iba v hlavičke cyklu.
Príkaz ilustrujeme rovnakým príkladom funkcie na výpočet celočíselnej mocniny celého čísla :
/* priklad pr5_4.c celociselna mocnina pomocou for */
int imoc(int x, int n);
void main(void)
{
printf("Mocnina (%d)%d = %d\n",3,4,imoc(3,4));
}
int imoc(int x, int n)
{
int y;
for(y=1; n--; y *= x);
return (y);
}
Často sa používa príkaz for s prázdnym telom, ktorý predstavuje nekonečný cyklus :
for ( ; ; );
Taktiež je možné vynechať v hlavičke cyklu hociktorý (jeden alebo dva) z výrazov init, koniec a zmena. Vynechanie je označené bodkočiarkou, ktorá musí byť prítomná vždy. Vyššie uvedený príklad nekonečného cyklu vlastne predstavuje vynechanie všetkých troch parametrov cyklu. Ukončenie takého cyklu (vyskočenie z tela cyklu) potom musí byť zabezpečené iným spôsobom (viď napr. príkazy skoku).
5.6 Príkazy skoku
Príkazy skoku sa používajú pre ošetrenie, resp. riešenie zvláštnych či výninočných situácií v programe. Ide o situácie, ktoré je spravidla potrebné ošetriť jednorázovo. Medzi príkazy skoku možno zaradiť príkazy :
- break
- continue
- return
- goto
Príkazy break a continue sa používajú predovšetkým v spojení s príkazmi cyklu.
Príkaz break ukončuje najvnútornejšiu neukončenú slučku cyklu a opúšťa okamžite telo cyklu, to znamená, že spôsobuje skok z príkazu cyklu na ďalší príkaz. Ďalšie použitie príkazu break je v príkaze switch (viď kap. 5.4).
Príkaz continue preruší vykonávanie najbližšej nadradenej štruktúrovanej konštrukcie cyklu (najvnútornejšiu neukončenú slučku cyklu) a spôsobí prechod na ďalší krok cyklu bez toho, aby sa predchádzajúci krok cyklu ukončil. Dôležité je, že nespôsobuje skok z tela cyklu von. Použitie príkazov break a continue ilustrujú nasledovné príklady pre čítanie a tlač znaku:
/* priklad pr5_5.c tanie a tla znakov pomocou while */
#include <stdio.h>
void main(void)
{
int c;
while((c = getchar()) < 'z')
{
if (c >= ' ') putchar(c);
}
printf("Koniec citania\n");
}
/* priklad p5_6.c tanie a tla znakov pomocou while, break, continue */
#include <stdio.h>
void main(void)
{
int c;
while (1)
{
if ((c = getchar()) < ' ') continue; /* zahodit neviditelny znak */
if (c == 'z') break; /* koniec po znaku z */
putchar(c);
}
printf("Koniec citania\n");
}
Príkaz return spôsobí ukončenie vykonávania nejakej funkcie a súčasne môže exportovať hodnotu výrazu do nadradenej úrovne ako deklarovaný typ hodnoty funkcie. Môže sa vyskytovať v jednom z tvarov
return výraz;
return (výraz);
return;
Hodnota výrazu sa stáva hodnotou funkcie (prvé dva tvary príkazu return sú ekvivalentné). Tretí tvar príkazu return sa používa v prípade, keď funkcia je typu void, to znamená, že nevracia žiadnu hodnotu.
Príkaz goto umožňuje vykonať skok tak v rámci jednej funkcie, ako aj medzi jednotlivými funkciami. Je však otázka, aký veľký zmysel takého skoky majú. Obecne je možné vykonať skok z bloku aj do bloku, a samozrejme von z cyklu. Všeobecne sa doporučuje používať príkaz goto čo najmenej a skutočne len vo výnimočných prípadoch. Príkaz, na ktorý sa má uskutočniť skok, musí byť označený návestím, za ktorým nasleduje dvojbodka. Pri skoku do bloku je hodnota všetkých lokálnych premenných daného bloku nedefinovaná. Skok do bloku ilustruje príklad :
/* priklad p5_7.c skok do bloku goto */
#include <stdio.h>
void main(void)
{
int c=5;
goto nav1;
{
int c = 2;
nav1:
printf("c=%d\n", c);
goto nav2;
}
nav2:
printf("c=%d\n", c);
}
Program vytlačí najprv c=2, potom c=5.
5.7 Funkcie
Funkcia predstavuje základnú stavebnú jednotku C jazyka. Obecný tvar funkcie je
Typ výsledku identifikátor(deklarácia argumentov)
blok
Typ výsledku aj deklarácia argumentov môžu byť aj prázdne, t.j. typu void. Ak neuvedieme typ funkcie, implicitná hodnota je priradená prekladačom(všeobecne by mala byť int, no nemusí). Typ a počet formálnych a skutočných argumetov funkcie musí byť samozrejme rovnaký (pri volaní). Pretože každá funkcia vracia aspoň jednu hodnotu (okrem void), mal by sa v tele funkcie vyskytovať aspoň jeden príkaz return.
5.8 Príklady
Príkazy cyklov sa využívajú v nasledovných programoch pre prácu so znakmi (čítanie, výpis, počítanie znakov, riadkov, slov, číslic atď).
/* priklad pr5_8.c pocet znakov zadanych z terminalu*/
#include <stdio.h>
void main(void)
{
int nc=0;
while(getchar()!='\n') nc++;
printf("Pocet zadanych znakov = %d\n",nc);
}
/* priklad pr5_9.c pocet cislic, bielych a ostatnych znakov */
#include <stdio.h>
void main(void)
{
int c,i,nwhite,nother;
int ndigit[10];
nwhite=nother=0;
for(i=0;i<10li++) ndigit[i]=0;
while((c=getchar())!='\n'('k'))
{
if(c>='0' && c<='9') ++ndigit[c-'0'];
else if(c==' '||c=='\n'||c=='\t') ++nwhite;
else ++nother;
printf("Pocty zadanych cislic = \n");
for(i=0;i<10;i++) printf("%d",ndigit[i]);
printf("Pocet bielych znakov = %d\n",nwhite);
printf("Pocet ostatnych znakov = %d\n",nother);
}
}
/* priklad pr5_10.c pocet znakov, slov a riadkov */
#include <stdio.h>
#define YES 1
#define NO 0
void main(void)
{
int c,nl,nw,nc,inword;
nw=nc=nl=0;
inword=NO;
while((c=getchar())!='k')
{
nc++;
if(c>=='\n') nl++;
if(c==' '||c=='\n'||c=='\t') inword=NO;
else if (inword==NO)
{
inword=YES;
nw++;
}
}
printf("Pocet riadkov = %d\n",nl);
printf("Pocet slov = %d\n",nw);
printf("Pocet znakov = %d\n",nc);
}
/* priklad pr5_11.c vstup riadkov, tlac najdlhsieho */
#include <stdio.h>
#define MAXLINE 100 /* max. dlzka vstupn. riadku */
int getline(char s[],int lim);
void copy(char s1[], s2[]);
void main(void)
{
int len,max; /* dlzka bezneho a max. riadku */
char line[MAXLINE], save[MAXLINE];
max=0;
while((len=getline(line,MAXLINE))>=0)
{
if (len > max)
{
max=len;
copy(line,save);
}
}
if(max > 0)
printf("Najdlhsi riadok : %s\n",save);
printf("Jeho dlzka : %d\n",max);
}
int getline(char s[],int lim)
{
int c,i;
for(i=0;i<lim-1&&(c=getchar())!='k'&&c!='\n';i++) s[i]=c;
if(c=='\n')
{
s[i]=c;
i++;
}
s[i]='\0';
return(i);
}
void copy(char s1[], s2[])
{
int i=0;
while((s2[i]=s1[i])!='\0') i++;
}
/* priklad pr5_12.c jednoduche umocnenie */
#include <stdio.h>
void main(void)
{
int i;
for(i=0;i<10;i++)
printf(" %d (%d)%d = %d (%d)%d = %d\n",i,2,i,power(2,i),3,i,power(3,i));
}
int power(int x,int n)
{
int i,p=1;
for(i=1;i<=n;i++) p=p*x;
return(p);
}
/*
Iná verzia funkcie power:
int power(int x,int n)
{
int p;
for(p=1;n>0;n--) p=p*x;
return(p);
}
*/
6 Štandardný vstup a výstup
Prostriedky pre vstup a výstup (so štandardnými zariadeniami - klávesnica a obrazovka, i so súbormi) nie sú priamou súčasťou C jazyka, ale sú poskytované vo forme štandardných knižníc, t.j. include súborov. Označenie štandardných zariadení pre vstup a výstup : stdin pre vstup, stdout pre výstup a stderr pre výstup chybových správ.
6.1 Vstupno-výstupná komunikácia
Vstupno - výstupná komunikácia sa môže uskutočňovať na troch úrovniach :
- znakov
- reťazcov
- formátovaných hodnôt
- Úroveň práce so znakmi je podporovaná dvoma funkciami :
int getchar(void) pre vstup znaku
int putchar(int) pre výstup znaku
Funkcia getchar nemá argument a vracia ako hodnotu kódovú reprezentáciu znaku, ktorý bol na vstupe (stdin). Ak na stdin sa nenachádza žiadny znak (koniec súboru), getchar vráti hodnotu EOF.
Funkcia putchar má argument typu int, ktorý sa zapíše do výstupu stdout. Ako hodnotu vracia zapísaný znak, ak zápis prebehol normálne, inak vracia EOF (pri chybe počas výstupu).
- Úroveň práce s reťazcami je tiež podporovaná dvoma funkciami :
char *gets(char *string);
int puts(char *string);
Funkcia gets slúži pre vstup reťazca do premennej string zo vstupu stdin, funkcia puts naopak pre výstup reťazca string na výstup stdout. Funkcia puts pridáva k výstupnému reťazcu nový riadok. Vracia nulu, ak nie je chyba, inak EOF.
- Úroveň formátovaných hodnôt - formátovaný vstup - výstup je tiež podporovaná dvoma štandardnými funkciami :
int printf(const char *format,arg1,...,argn);
int scanf(const char *format,&arg1,...,&argn);
Tieto funkcie umožňujú výstup, resp. vstup údajov v predpísanom tvare. Počet výstupných (vstupných) údajov (t.j. argumentov funkcií print, scanf) je voliteľný, je možný aj počet n = 0. V prípade výstupu stačí uvádzať argumenty vo forme jednoduchých identifikátorov, resp. konštánt.
Pokiaľ sa jedná o vstup, všimnime si, že funkcie scanf vyžaduje zadanie adries jednotlivých argumentov (znak &). V prípade nedodržania tohoto požiadavku vznikajú nepríjemné chyby.
Funkcia printf vracia počet vytlačených znakov, resp. záporné číslo pri chybe.
Tieto funkcie vracajú počet vykonaných formátových konverzií. Spracovanie je ukončené pri výskyte prvého konfliktného znaku alebo konca súboru EOF, vracajú EOF ak nastane chyba na vstupe pred vykonaním formátovej konverzie. Predpísaný tvar výstupu jednotlivých argumentov obsahuje reťazec format. V tomto reťazci sa okrem znakov (textu), ktorý sa automaticky okopíruje na výstup, musí nachádzať pre každý argument jedna tzv. formátová konverzia, ktorá obsahuje konkrétny predpis pre prevod hodnoty argumentu z vnútorného (binárneho) tvaru na požadovaný vonkajší tvar. Každá formátová konverzia sa povinne začína znakom %, po ktorom nasleduje vlastný popis konverzie. Všetky znaky, ktoré nepatria do formátovej konverzie, sa kopírujú na výstup. Popis konverzie (jednotlivé parametre sa uvádzajú bez medzier, hranaté zátvorky znamenajú voliteľné použitie) má obecný tvar :
%[príznak][šírka][.presnosť][modifikátor]konverzia
pričom zložky popisu konverzie možno rozdeliť na povinné a nepovinné. Samotná konverzia je vždy povinná, ostatné zložky sú nepovinné. Význam jednotlivých zložiek je tento :
- Povinné:
celé čísla: %d celé dekadické číslo typu signed int %i celé dekadické číslo typu signed int %u celé dekadické číslo typu unsigned int %o celé oktálové číslo typu unsigned int %x celé hexadecimálne číslo typu unsigned int (malé písmo) %X celé hexadecimálne číslo typu unsigned int (veľké písmo) čísla v pohyblivej rádovej čiarke: %f číslo v desatinnom tvare typu float i double %e číslo v semilogaritmickom tvare typu float i double %E číslo v semilogaritmickom tvare typu float i double (s veľkým E) %g použije sa jedna z konverzií f, e. Výstupný tvar sa volí podľa hodnoty exponentu (ak je menší ako -4 alebo väčší ako presnosť, použije sa e, inak f) %G ako konverzia g, len sa použije konverzia E znaky a reťazce: %c jeden znak typu char %s reťazec ukončený znakom '\0' (netlačí sa) %% tlač znaku % smerníky: %p adresa argumentu - smerník %n argument musí byť typu smerník na int; na adresu, na ktorú ukazuje, ukladá počet doteraz vytlačených znakov - Nepovinné:
príznak (môže sa ich kombinovať i niekoľko, nezáleží na poradí): - výsledok konverzie bude zarovnaný doľava (inak doprava) + výsledok konverzie typu signed bude mať znamienko (inak iba znamienko - pre záporné čísla) ' ' výsledok konverzie bude zľava doplnený znakmi ' ', ak nemá znamienko # má vplyv podľa typu konverzie : d i u c s žiadny
o pred číslo pridá znak '0'
x X pred číslo pridá znak '0x', resp. '0X'
f e E výsledok vždy obsahuje desatinnú bodku
g G ako e, E (koncové nuly sa neodstraňujú)šírka: Dekadické číslo, ktoré udáva minimálny počet znakov na výstupe. Ak je počet platných číslic argumentu väčší ako udaná šírka, neberie sa na ňu ohľad. Minimálny počet tlačených znakov môže byť určený aj nepriamo pomocou znaku '*', ktorý spôsobí, že sa šírka nastaví podľa nasledujúceho (predchádzajúceho) argumentu v zozname argumentov. Ak pred šírkou je uvedená nula, doplní sa výstup zľava nulami. presnosť: Dekadické číslo, ktoré pre konverzie d, i, o, u, x a X znamená minimálny počet číslic na výstupe, pre konverzie e, E a f počet číslic za desatinnou bodkou, pre konverzie g a G maximálny počet významných číslic, pre konverziu s maximálny počet znakov (orezanie reťazca). Samotný znak '.' má význam nuly. Namiesto dekadického čísla môže byť rovnako ako v prípade šírky použitý znak '*'. modifikátor: Je to písmeno, ktoré mení konverziu takto: h konverzia d, i - číslo sa prevedie na typ short int
konverzia o, u, x, X - číslo sa prevedie na typ unsigned short int
konverzia n - prevedie sa na smerník na short int
l konverzia d, i - číslo sa prevedie na typ long int
konverzia o, u, x, X - číslo sa prevedie na typ unsigned long int
konverzia n - prevedie sa na smerník na long int
L konverzia e, E, f, g, G - číslo sa prevedie na typ long double
Vstup a výstup reťazca pomocou všetkých troch úrovní práce ilustruje nasledujúci príklad:
/* priklad pr6_1.c vstup - vystup retazca */
#include <stdio.h>
void main(void)
{
charf s2[30],s3[30],*s1=s3;
int i=0;
printf("Vstup retazca 1.po znakoch:");
while ((s2[i] = getchar()) != '\n') i++;
s2[i++] = '\n';
s2[i] = '\0';
for (i = 0 ; s2[i] != '\0' ; i++) putchar(s2[i]);
printf("Vstup retazca 2.po znakoch:");
while ((*s1++ = getchar()) != '\n');
*s1++ = '\n';
*s1 = '\0';
for (i = 0 ; s3[i] != '\0' ; i++) putchar(s3[i]);
printf("Vstup retazca 1.gets:");
gets(s2);
puts(s2);
printf("Vstup retazca 2.gets:");
s1 = s3;
gets(s1);
puts(s1);
printf("Vstup retazca 1.scanf:");
scanf("%s", s2);
printf("Printf s2 = %s\n", s2);
printf("Vstup retazca 2.scanf:");
scanf("%s", s1);
printf("Printf s1 = %s\n", s1);
}
6.2 Vstup z pamäte a výstup do pamäte
V niektorých prípadoch sa používa formátovaný vstup - výstup v spojitosti nie s vonkajšími zariadeniami (stdin, stdout), ale s reťazcom. Tento vstup - výstup teda umožňuje vykonávať prevod medzi vnútorným tvarom a znakovou reprezentáciou (ASCII) tohoto tvaru. K dispozícii sú pre tento účel dve štandardné funkcie sprintf a sscanf, ktoré sa od vyššie popísaných funkcií printf a scanf líšia iba ďalším argumentom str, ktorý označuje príslušný reťazec (použitý vo funkcii vstupu - výstupu). Ich tvar je :
int sprintf(char *str,const char *format,arg1,...,argn);
int sscanf(char *str,const char *format,&arg1,...,&argn);
pričom str je smerník na príslušný vstupný (výstupný) reťazec, ostatné parametre viď vyššie. Prácu s týmito funkciami ilustruje príklad prevodu čísel do reťazca a späť :
/* priklad pr6_2.c vstup - vystup do retazca */
#include <stdio.h>
void main(void)
{
char s2[30],s3[30];
int i=0;
float r;
i=3;
sprintf(s2,"Hodnota i = %d",i);
/* obsahom reťazca s2 bude: Hodnota i = 3 */
printf("Retazec s2 = %s\n",s2);
/* obsahom reťazca s3 bude: 33 3.14 */
strcpy(s3,"33 3.14");
printf("Retazec s3 = %s\n",s3);
sscanf(s3,"%d %f",&i,&r);
/* obsahom premenných bude i:33, r:3.14 */
printf("Hodnota i = %d r = %f\n",i,r);
}
7 Reťazce
7.1 Predstava o reťazcoch
Reťazec je jednorozmerné pole znakov, deklarované napr. ako :
char str[length];
Podobne ako u každého poľa predstavuje názov reťazca str smerník na začiatok tohoto poľa (t.j. str[0]).
Konvencia jazyka C určuje, že všetky reťazce sú štandardne zakončené znakom '\0' (t.j. hodnotou \0), čím sa reťazec líši od iných typov polí. Z toho vyplýva, že efektívna dĺžka reťazca, ktorý možno do daného poľa uložiť, je 0 až length-2 (posledný znak length-1 je potrebné rezervovať pre už spomínanú binárnu nulu). Reťazce teda možno považovať za objekty premennej dĺžky (danou polohou znaku '\0'), ktorá je zhora ohraničená rozsahom deklarovaného poľa (length).
Pri deklarácii prekladač túto koncovú nulu automaticky doplňuje :
char str[50] = "Iny retazec";
char *sm = "string";
char str[] = "STRING";
V prvom prípade sa alokuje pole o 50-ich byte-och a naplní sa reťazcom, samozrejme ukončeným znakom '\0'. V druhom prípade má smerník sm ako počiatočnú hodnotu deklarovanú adresu reťazca "string". V treťom prípade prekladač automaticky alokuje miesto o 6. byte-och :
0 1 2 3 4 5 6
S T R I N G \0
printf("%s", "abc");
Vyššie uvedený príkaz vytlačí všetky znaky reťazca, t.j. znaky abc, zatiaľ čo príkaz :
printf("%s","abc"+1);
vytlačí znaky reťazca, ktorý začína na adrese o 1 väčšej ako v predchádzajúcom prípade, teda znaky bc.
Dôležité je si stále uvedomovať rozdiel medzi jedným znakom, napr. 'a' a reťazcom dľžky 1, napr. "a", ktorý samozrejme musí byť ukončený binárnou nulou a preto predstavuje pole znakov o dĺžke 2.
V oboch prípadoch kompilátor automaticky doplní binárnu nulu na koniec príslušného reťazca. S reťazcami veľmi často pracujeme pomocou smerníkov, napr. v tvare
7.2 Funkcie pre prácu s reťazcami
Práca s reťazcami je podporovaná celým radom štandardných funkcií, ktoré sa vyskytujú prakticky vo všetkých implementáciách jazyka C. Sú to funkcie pre kopírovanie, spájanie, porovnávanie, vyhľadávanie a určenie dĺžky reťazca. V nižšie uvedených základných funkciách sú parametre s, s1 a s2 typu char * (smerník na char), c je typu int a n typu unsigned int. Všetky funkcie sú definované v hlavičkovom súbore <STRING.H>.
int strcmp(char *s1, char *s2) (string compare)
- lexikograficky porovnáva reťazce s1 a s2. Vracia číslo >0, ==0, resp. <0 podľa toho, či s1 > , =, resp. < ako s2.
int strncmp(char *s1, char *s2, unsigned int n) (string max n compare)
- lexikograficky porovnáva maximálne n znakov reťazcov s1 a s2. Vracia číslo >0, ==0, resp. <0 podľa toho, či s1 > , =, resp. < s2.
unsigned int strlen(char *s) (string length)
- vracia dĺžku reťazca so smerníkom s (bez binárnej koncovej nuly).
char *strcpy(char *s1, char *s2) (string copy)
- kopíruje jednotlivé znaky reťazca s2 do s1, vrátane koncovej nuly. Vracia smerník na reťazec s1, obsah ktorého sa prepíše. Dĺžka s1 musí byť dostatočná.
char *strncpy(char *s1, char *s2, unsigned int n) (string max n copy)
- kopíruje jednotlivé znaky reťazca s2 do s1, vrátane koncovej nuly, ak s2 je kratšie ako n. Inak sa kopíruje iba prvých n znakov reťazca s2 a s1 nie je zakončené binárnou nulou. Vracia smerník na reťazec s1, obsah ktorého sa prepíše. Dĺžka s1 musí byť dostatočná.
char *strcat(char *s1, char *s2) (string concatenation)
- pripojí jednotlivé znaky reťazca s2 za reťazec s1, vrátane koncovej nuly (pôvodná koncová nula v s1 je prepísaná prvým znakom s2). Vracia smerník na reťazec s1, obsah ktorého sa prepíše. Reťazec s2 ostáva bez zmeny. Dĺžka s1 musí byť dostatočná.
char *strncat(char *s1, char *s2, unsigned int n) (string max n concatenation)
Pripojí maximálne n znakov reťazca s2 za reťazec s1 (pôvodná koncová nula v s1 je prepísaná prvým znakom s2, za posledný znak s1 doplní novú koncovú nulu). Vracia smerník na reťazec s1, obsah ktorého sa prepíše. Reťazec s2 ostáva bez zmeny. Dĺžka s1 musí byť dostatočná.
char *strchr(char *s, int c) (string search character)
- vracia smerník na prvý výskyt znaku c v reťazci s. Ak sa znak c v reťazci s nevyskytuje, vracia hodnotu NULL.
char *strrchr(char *s, int c) (string right search character)
Vracia smerník na posledný výskyt znaku c v reťazci s, t.j. na prvý výskyt znaku c sprava. Ak sa znak c v reťazci s nevyskytuje, vracia hodnotu NULL.
char *strstr(char *s1, char *s2) (string search string)
- vracia smerník na prvý výskyt reťazca s2 v reťazci s1. Ak sa reťazec s2 v reťazci s1 nevyskytuje, vracia NULL.
Všetky funkcie je možné jednoducho zapísať v jazyku C. Funkciu pre určenie dĺžky reťazca pozri príklad v kap. 4.2. Funkciu pre pripojenie reťazca s2 za s1 možno zapísať takto :
char *strcat(char *s1, char *s2)
{
char *p;
p=s1;
while (*p++); /* nastavi smernik na koniec s1 */
--p; /* nastavi smernik na koncovu nulu */
while (*p++ = *s2++); /* prikopíruje s2 za s1 */
return (s1);
}
Ďalší príklad ilustruje vyhľadanie podreťazca s2 v reťazci s1 :
/* priklad pr7_1.c vyhľadanie podreťazca */
#include <stdio.h>
#include <string.h>
void main(void)
{
char *s1 = "Text v ktorom hladame vzor";
char *s2 = "vzor", *pom;
pom = strstr(s1, s2);
if (pom != NULL)
printf("Podretazec %s najdeny v %s na pozicii %d\n", pom,s1,pom - s1);
else
printf("Podretazec %s sa v %s nenachdza\n",pom,s1);
}
Reťazec je možné spracovávať i ako pole po jednotlivých znakoch, vrátane koncovej nuly, napr. :
s[0]='A'; s[1]='B'; s[2]='C'; s[3]='\0';
printf("%s %c %s",s,*(s+1),s+1);
Príkaz printf vytlačí ABC B BC (smerníková aritmetika). Najčastejší spôsob inicializácie reťazca využíva funkciu strcpy
V ďalšom príklade program číta reťazec, vytlačí ho od konca a určí počet znakov 'a' v tomto reťazci :
/* priklad pr7_2.c čítanie a tlač reťazca, počet znakov */
#include <stdio.h>
int charcount(char *s, char c);
void main(void)
{
char a='a',b,c[80];
int i=-1;
printf("Zadaj retazec = ");
while((c[++i]=getchar()) != '\n');
c[i]='\0';
printf("Retazec %s v opacnom poradi :\n",c);
while(i) putchar(c[i--]);
printf("\nobsahuje %d vyskytov znaku %c\n", charcount(c,a);
}
int charcount(char *s, char c)
{
int count=0;
while(*s!='\0') if(*s++ == c) count++;
return(count);
}
Načítanie reťazca, vymazanie všetkých znakov 'a' v tomto reťazci a vytlačenie upraveného reťazca realizuje nasledujúci príklad :
/* priklad pr7_3.c čítanie a tlač reťazca, vyhodit znaky */
#include <stdio.h>
void remchar(char *s, char c);
void main(void)
{
char a='a',c[80];
printf("Zadaj retazec = ");
scan("%s",c);
printf("Retazec : %s\n",c);
remchar(c,a);
printf("Retazec bez znakov %c :%s\n",c,a);
}
void remchar(char *s, char c)
{
char *pom=s;
while(*s!='\0')
{
if(*s++ != c) *pom++ = *s;
s++;
}
*pom = '\0';
}
Posledný príklad v tejto časti uvádza porovnanie mechanizmu vytvorenia reťazca príkazom sprintf v prvej časti príkladu a štandardným spôsobom s využitím funkcií pre prácu s reťazcami v druhej časti :
/* priklad pr7_4.c vstup - vystup retazca formatovy retazec */
#include <stdio.h>
#include <string.h>
void main(void)
{
char s2[30],s3[30];
int i;
/* vytvorenie retazca prikazom sprintf */
printf("Vstup retazca:");
gets(s2);
i=strlen(s2);
switch(i)
{
case 1:
{
sprintf(s3,"Dlzka <%s> je %d znak",i);
break;
}
case 2:
case 3:
case 4:
{
sprintf(s3,"Dlzka <%s> je %d znaky",i);
break;
}
case 0:
default:
{
sprintf(s3,"Dlzka <%s> je %d znakov",i);
break;
}
}
printf("%s\n",s3);
/* klasicky pristup - standardne funkcie */
strcpy(s3,"Dlzka <");
strcat(s3,s2);
strcat(s3,"> je ");
sprintf(s2,"%d",i);
strcat(s3,s2);
strcat(s3," znak");
switch(i)
{
case 2:
case 3:
case 4:
{
strcat(s3,"y");
break;
}
case 0:
default:
{
strcat(s3,"ov");
break;
}
}
printf("%s\n",s3);
}
8 Súbory
Okrem práce so štandardnými vstupnými a výstupnými zariadeniami je možné vykonávať zápis a čítanie informácií zo súborov. Táto činnosť je podporovaná dvoma skupinami štandardných funkcií pre dve úrovne práce so súbormi.
Nižšiu úroveň reprezentuje skupina funkcií, ktoré priamo využívajú služby operačného systému. Celú réžiu s tým spojenú musí zabezpečovať samotný program.
Vyššiu úroveň predstavuje práca s prúdmi (stream) údajov. Táto úroveň sa používa najčastejšie a je užívateľsky prijateľnejšia ako prv spomínaná nižšia úroveň.
Každá činnosť spojená s komunikáciou so súborom môže pozostávať z týchto krokov :
- otvorenie súboru
- manipulácia so súborom (čítanie, zápis, nastavenie)
- zatvorenie súboru
Pri práci so súbormi sa stretávame s niektorými preddefinovanými konštantami :
EOF | koniec súboru |
FOPEN_MAX | maximálny počet súčasne otvorených súborov |
FILENAME_MAX | maximálna dĺžka názvu súboru |
TMP_MAX | maximálny počet súčasne otvorených pracovných prúdov |
8.1 Práca s prúdmi (stream I/O)
Je charakteristická použitím dátového typu FILE pre identifikáciu súboru. Tento typ (presnejšie smerník na typ) obsahuje všetky informácie o spracovávanom prúde. Užívateľ, ktorý chce pracovať s týmto typom, musí pre svoj súbor deklarovať premennú typu FILE * (vždy veľkými písmenami), v ktorej budú udržované informácie o danom prúde. Inicializácia tejto štruktúry sa vykonáva pri vytváraní alebo otváraní prúdu, spätný prenos sa uskutoční pri uzatváraní prúdu. Štandardné zariadenia vstupu - výstupu podľa kap. 7 sú deklarované tiež ako prúdy v tvare :
FILE *stdin
FILE *stdout
FILE *stderr
Podľa spôsobu práce existujú dva režimy prúdov - textový a binárny. Voľba režimu prúdu je vecou užívateľa v závislosti na charaktere úlohy. V textovom režime sa pomocou príslušných knižničných funkcií čítajú a zapisujú obyčajné textové riadky, kým v binárnom režime sa jedná o vnútorný tvar zápisu. Obsah binárneho súboru nie je funkciami čítania - zápisu nijako ovplyvňovaný. Práca s binárnym súborom je rýchlejšia a zaberá menej miesta v pamäti. Textový súbor je však na druhej strane možno prezerať, resp. i vytvárať bežnými prostriedkami (editormi). Je nutné poznamenať, že aj tu je rozdiel pri použití kompilátora. No najväčší rozdiel je cítiť pri prechode medzi rôznymi platformami (pod UNIX-om nie je žiaden rozdiel medzi textovým a binárnym režimom).
Pre otvorenie prúdu je k dispozícii funkcia:
FILE *fopen(const char *name, const char *mode);
name - | názov otváraného (vytváraného) súboru |
mode - |
r textový súbor čítanie |
Ak je operácia otvorenia úspešná, vráti funkcia smerník na daný súbor, pomocou ktorého ďalej so súborom pracujeme. V opačnom prípade vráti hodnotu NULL, ktorú je možné i potrebné testovať.
Test na koniec súboru pri práci s prúdmi namiesto kombinácie EOF využíva funkciu
int feof(FILE *stream)
ktorá vracia hodnotu != 0 (true), ak je aktuálna pozícia nastavená na koniec prúdu. V opačnom prípade vracia hodnotu 0 (false).
Na zatvorenie súboru (po skončení práce s daným súborom) je k dispozícii funkcia :
int fclose(FILE *stream);
Pre ďalšiu prácu s prúdmi platí všetko, čo bolo uvedené v kap. 6 pre štandardný vstup - výstup s tým, že sa to vzťahuje na príslušný prúd. Pre prácu so znakmi sú k dispozícii funkcie :
int getc(FILE *stream);
int ungetc(int c,FILE *stream);
int putc(int c,FILE *stream);
Nová je funkcia ungetc, ktorá umožňuje vrátiť prečítaný znak späť do prúdu. Vstup a výstup reťazcov podporujú funkcie :
char *fgets(char *string, FILE *stream);
int fputs(char *string, FILE *stream);
Podobne formátovaný vstup - výstup podporujú funkcie :
int fprintf(FILE *stream,char *format,arg1,arg2,...,argn);
int fscanf(FILE *stream,char *format, &arg1,&arg2,...,&argn);
Ďalej sú k dispozícii funkcie pre vstup - výstup celých čísel :
int getw(FILE *stream);
int putw(int i, FILE *stream);
a pre prenos údajov po blokoch :
int fread(char *buffer,int size,int count,FILE *stream);
int fwrite(char *buffer,int size,int count,FILE *stream);
cez pole buffer. Veľkosť bloku je (size x count).
Pre nastavenie pozície v súbore, od ktorej sa bude čítať, resp. zapisovať, slúži funkcia :
int fseek(FILE *stream,long int offset,int mode);
offset - | veľkosť posunu v súbore od pozície, danej parametrom mode |
mode - |
SEEK_SET od začiatku súboru |
Aktuálnu pozíciu v súbore je možné zistiť pomocou funkcie :
long int ftell(FILE *stream);
Uchovanie a obnovenie aktuálnej pozície v súbore pomocou dátovej štruktúry preddefinovaného typu fpos_t umožňujú funkcie :
int fgetpos(FILE *stream, fpos_t *position);
int fsetpos(FILE *stream, const fpos_t *position);
Pre prácu so súbormi typu stream je k dispozícii ešte celý rad ďalších funkcií pre testovanie vzniku chyby, vyprázdnenie bufra, nastavenie na začiatok súboru, riadenie práce s prúdom, chybové správy (podrobne viď popis knižnice jazyka C). Spoločným znakom všetkých týchto funkcií je to, že všetky sa začínajú písmenom "f" a všetky pracujú s identifikátorom súboru typu FILE *.
8.2 Práca so súbormi na nižšej úrovni (basic I/O)
Podstatným aj formálnym rozdielom pri práci so súbormi na nižšej úrovni vzhľadom na súbory typu stream je použitie identifikátora súboru typu int (tzv. manipulačné číslo) namiesto FILE *, pomocou ktorého pracujeme so súborom v jednotlivých štandardných funkciách.
Pre otvorenie súboru je k dispozícii funkcia :
int open(char *name,int mode);
kde name - meno súboru
mode - prístup k súboru (čítanie, zápis, oboje)
(podrobne viď popis knižnice jazyka)
Funkcia vracia celé číslo - identifikátor súboru v prípade úspešného otvorenia, v opačnom prípade vracia -1. Tento identifikátor (handle) potom je používaný vo všetkých ďalších funkciách.
Pre zatvorenie súboru je k dispozícii funkcia :
int close(int handle);
Čítanie a zápis do súboru sa realizuje funkciami read a write s využitím bufra a jeho veľkosti v tvare :
int read(int handle,char *buffer,int size);
int write(int handle,char *buffer,int size);
kde handle identifikátor súboru
buffer názov bufra
size dĺžka bufra
Nastavenie pozície v súbore je možné s využitím funkcie :
int lseek(int handle,long int offset,int mode);
ktorá je úplne analogická vyššie popísanej funkcii fseek.
8.3 Parametre programu
V jazyku C je definovaná možnosť dovozu parametrov do programu pri jeho spustení. Na tento účel sa využíva hlavička programu s dvoma parametrami v tvare :
void main(int argc, char *argv[])
kde argc skutočný počet parametrov dovážaných do programu, zväčšený o 1
argv[] pole reťazcov, obsahujúcich jednotlivé dovážané parametre
argv[0] obsahuje názov programu, argv[1] prvý parameter, atď.
Príklad použitia uvedeného aparátu viď v nasledujúcej kapitole.
8.4 Príklady práce so súbormi
Uvedená príklady riešia postupne formátovaný výstup do súboru, výstup zo súboru po znakoch, čítanie zo súboru po znakoch, čítanie zo súboru po slovách, po riadkoch, tlač súboru na tlačiareň, kopírovanie súborov a rôzne ich kombinácie, nastavovanie i vyhľadávanie pozície v súbore. Časť príkladov pracuje s pevne zadanými názvami súborov, druhá časť je univerzálnejšia - názvy súborov sú prenášané formou parametrov (viď kap. 8.3).
/* priklad pr8_1.c formatovany vystup */
#include <stdio.h>
void main(void)
{
FILE *fp;
char stuff[25];
int index;
fp = fopen("S.TXT","w"); /* open for writing */
strcpy(stuff,"To je riadok.");
for (index = 1;index <= 10;index++)
fprintf(fp,"%s Cislo riadku %d\n",stuff,index);
fclose(fp); /* close the file before ending program */
}
/* priklad pr8_2.c vystup po znakoch */
#include "stdio.h"
void main(void)
{
FILE *fp;
char others[35];
int indexer,count;
strcpy(others,"Pridane riadky.");
fp = fopen("s.txt","a"); /* open for appending */
for (count = 1;count <= 5;count++)
{
for (indexer = 0;others[indexer];indexer++)
/* kym others[indexer] != 0 */
putc(others[indexer],fp); /* output a single character */
putc('\n',fp); /* output a linefeed */
}
fclose(fp);
}
/* priklad pr8_3.c citanie po znakoch */
#include "stdio.h"
void main(void)
{
FILE *fp;
char c;
fp = fopen("S.TXT","r");
if (fp == NULL) printf("File S.TXT doesn't exist\n");
else
{
do
{
c = getc(fp); /* get one character from the file */
putchar(c); /* display it on the monitor */
} while (c != EOF); /* repeat until EOF (end of file) */
}
fclose(fp);
}
/* priklad pr8_4.c citanie po znakoch a cislovanie riadkov (posledny riadok nie) */
#include "stdio.h"
void main(void)
{
FILE *fp;
char c;
int pocet=1;
fp = fopen("S.TXT","r");
if (fp == NULL) printf("File S.TXT doesn't exist\n");
else
{
printf("%d: ",pocet);
do
{
c = getc(fp); /* get one character from the file */
if(c=='\n')
{
c=getc(fp);
if(feof(fp)==0) printf("\n%d: ",++pocet);
ungetc(c,fp);
}
else putchar(c); /* display it on the monitor */
} while (feof(fp) == 0); /* repeat until end of file */
}
fclose(fp);
}
/* priklad pr8_5.c citanie po slovach */
#include "stdio.h"
void main(void)
{
FILE *fp;
char oneword[100];
char c;
fp = fopen("S.TXT","r");
if (fp == NULL) printf("File S.TXT doesn't exist\n");
else
{
do
{
c = fscanf(fp,"%s",oneword); /* get one word from the file */
printf("%s\n",oneword); /* display it on the monitor */
} while (c != EOF); /* repeat until EOF (end of file) */
}
fclose(fp);
}
/* priklad pr8_6.c spravne citanie po slovach */
#include "stdio.h"
void main(void)
{
FILE *fp;
char oneword[100];
char c;
fp = fopen("S.TXT","r");
if (fp == NULL) printf("File S.TXT doesn't exist\n");
else
{
do
{
c = fscanf(fp,"%s",oneword); /* get one word from the file */
if(c != EOF) printf("%s\n",oneword); /* display it on the monitor */
} while (c != EOF); /* repeat until EOF (end of file) */
}
fclose(fp);
}
/* priklad pr8_7.c citanie po riadkoch */
#include "stdio.h"
void main(void)
{
FILE *fp;
char oneword[100];
char *c;
fp = fopen("S.TXT","r");
if (fp == NULL) printf("File S.TXT doesn't exist\n");
else
{
do
{
c = fgets(oneword,100,fp); /* get one word from the file */
if(c != NULL) printf("%s",oneword); /* display it on the monitor */
} while (c != NULL); /* repeat until EOF (end of file) */
}
fclose(fp);
}
/* priklad pr8_8.c citanie po slovach so zadanim nazvu suboru */
#include "stdio.h"
void main(void)
{
FILE *fp;
char oneword[100],filename[25];
char *c;
printf("Enter filename: ");
scanf("%s",filename);
fp = fopen(filename,"r");
if (fp == NULL) printf("File [%s] doesn't exist\n",filename);
else
{
do
{
c = fgets(oneword,100,fp); /* get one word from the file */
if(c != NULL) printf("%s\n",oneword); /* display it on the monitor */
} while (c != NULL); /* repeat until EOF (end of file) */
}
fclose(fp);
}
/* priklad pr8_9.c tlac suboru po znakoch na tlaciaren */
#include "stdio.h"
void main(void)
{
FILE *fp,*printer;
char fname[25];
char c;
printf("Enter filename: ");
scanf("%s",filename);
fp = fopen(filename,"r");
printer=fopen("$LPT","w");
/* printer=fopen("PRN","w"); pre DOS */
if (fp == NULL) printf("File doesn't exist\n");
else if (printer==NULL)printf("PRN doesn't exist\n");
else
{
do
{
c = fgetc(fp); /* get one character from the file */
if (c != EOF)
{
putchar(c);
putc(c,printer);
}
} while (c != EOF); /* repeat until EOF (end of file) */
}
fclose(fp);
fclose(printer);
}
/* priklad pr8_10.c citanie po slovach
so zadanim nazvu suboru cez parametre*/
#include "stdio.h"
void main(int argc, char *argv[])
{
FILE *fp;
char oneword[100];
char *c;
if(argc==1) printf("Volanie: %s filename\n",argv[0]);
else
{
if(argc > 2)printf("Pozor! Spracuje sa iba 1. parameter!\n");
fp = fopen(argv[1],"r");
if (fp == NULL) printf("File [%s] doesn't exist\n",argv[1]);
else
{
do
{
c = fgets(oneword,100,fp); /* get one word from the file */
if(c != NULL)
printf("%s",oneword); /* display it on the monitor */
} while (c != NULL); /* repeat until EOF (end of file) */
}
fclose(fp);
}
}
/* priklad pr8_11.c cislovanie riadkov
so zadanim nazvu suboru cez parametre*/
#include "stdio.h"
#define MAX 80
void main(int argc, char *argv[])
{
FILE *fp;
char line[MAX];
int ln;
if (argc == 1) printf("Volanie: %s filename\n",argv[0]);
else
{
if (argc > 2)printf("Pozor! Spracuje sa iba 1. parameter!\n");
fp = fopen(*++argv,"r");
if (fp == NULL)
{
printf("Can't open [%s]\n",*argv);
exit();
}
else
{
ln=0;
while (fgets(line,MAX,fp)) /* get one line from the file */
printf("%3d : %s",++ln,line);
fclose(fp);
}
}
}
/* priklad pr8_12.c kopirovanie so zadanim
nazvov suborov cez parametre*/
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#define MAXB 512
void main(int argc, char *argv[])
{
char buf[MAXB];
int in,out,lng;
if (argc == 1) printf("Volanie: %s file1 file2\n",argv[0]);
else if (argc == 2)printf("Volanie: %s file1 file2\n",argv[0]);
else
{
if((in=open(*++argv,O_RDONLY)) == -1)
{
printf("Can't open [%s]\n",*argv);
exit();
}
if((out=open(*++argv,O_CREAT|O_RDWR,S_IWRITE)) == -1)
{
printf("Can't open [%s]\n",*argv);
exit();
}
while((lng=read(in,buf,MAXB)) > 0) write(out,buf,lng);
close(in);
close(out);
}
}
/* priklad pr8_13.c zapis a citanie v rezime text,
binarny a na nižšej úrovni IO
porovnanie prístupu k súboru */
#include "stdio.h"
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
void main(void)
{
FILE *fp;
char c[2];
int pocet=1,h;
fp = fopen("SS.TXT","w");
if (fp == NULL) printf("File SS.TXT doesn't exist\n");
else
{
fprintf(fp,"%d",pocet);
fclose(fp);
}
fp = fopen("SS.TXT","rb");
if (fp == NULL) printf("File SS.TXT doesn't exist\n");
else
{
fscanf(fp,"%d",&pocet);
fclose(fp);
}
printf("Pocet (text) = %d\n",pocet);
fp = fopen("SS.TXT","rb");
if (fp == NULL) printf("File SS.TXT doesn't exist\n");
else
{
fread(c,sizeof(pocet),1,fp);
fclose(fp);
}
sscanf(c,"%d",&pocet);
printf("Pocet (blok) = %d c=%c%c\n",pocet,c[0],c[1]);
h = open("SS.TXT",O_RDONLY);
if (h == -1) printf("File SS.TXT doesn't exist\n");
else
{
read(h,c,sizeof(pocet));
close(h);
}
sscanf(c,"%d",&pocet);
printf("Pocet (IO uroven) = %d c=%c%c\n",pocet,c[0],c[1]);
}
/* priklad pr8_14.c zapis do suboru p.txt
so zadanim nazvu suboru cez parametre*/
#include <stdio.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#define MAX 10
void main(int argc, char *argv[])
{
int in,out,lng;
int pocet=1,min=1,max=99;
float x[MAX],y[MAX];
for(lng=0;lng<MAX;lng++)
{
x[lng]=lng+1;
y[lng]=2.5+lng;
}
if (argc == 1) printf("Volanie: %s file1 file2\n",argv[0]);
else if(argc == 2) printf("Volanie: %s file1 file2\n",argv[0]);
else
{
if ((in = open(*++argv,O_RDONLY)) == -1)
{
perror("Error ");
printf("Can't open [%s]\n",*argv);
exit();
}
if((out=open(*++argv,O_CREAT|O_TRUNC|O_BINARY,S_IWRITE)) == -1)
{
perror("Error ");
printf("Can't open [%s]\n",*argv);
exit();
}
write(out,&pocet,sizeof(pocet));
write(out,&min,sizeof(min));
write(out,&max,sizeof(max));
for(lng=0;lng<MAX;lng++)
{
write(out,&x[lng],sizeof(float));
write(out,&y[lng],sizeof(float));
printf("i=%d x=%f y=%f\n",lng,x[lng],y[lng]);
}
close(in); close(out);
}
}
/* priklad pr8_15.c zapis do suboru nacitanim z 2 suborov
so zadanim nazvu suboru cez parametre
1.subor - vytvoreny ulohou pr8_14 (p.txt)
2.subor - ASCII, pocet+1 riadkov (p1.txt)
3.subor - kombinacia 1. a 2.suboru (p2.txt) */
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#define MAX 50
void main(int argc, char *argv[])
{
FILE *in2;
int in1,out,lng;
int pocet,min,max;
float x,y;
char line[MAX];
if (argc < 4) printf("Volanie: %s file1 file2 file3\n",argv[0]);
else
{
if ((in1 = open(*++argv,O_RDONLY)) == -1)
{
perror("Error ");
printf("Can't open [%s]\n",*argv);
exit();
}
if ((in2 = fopen(*++argv,"r")) == NULL)
{
perror("Error ");
printf("Can't open [%s]\n",*argv);
exit();
}
if((out=open(*++argv,O_CREAT|O_TRUNC,S_IWRITE)) == -1)
{
perror("Error ");
printf("Can't open [%s]\n",*argv);
exit();
}
read(in1,&pocet,sizeof(pocet));
read(in1,&min,sizeof(min));
read(in1,&max,sizeof(max));
write(out,&pocet,sizeof(pocet));
write(out,&min,sizeof(min));
write(out,&max,sizeof(max));
for(lng=0;lng<pocet+1;lng++)
{
fgets(line,MAX,in2);
min=strlen(line);
write(out,&min,sizeof(int));
write(out,line,strlen(line));
}
min=0;
while((lng=read(in1,&x,sizeof(float)))>0)
{
read(in1,&y,sizeof(float));
printf("%d x=%f y=%f\n",min++,x,y);
write(out,&x,sizeof(float));
write(out,&y,sizeof(float));
}
close(in1);
fclose(in2);
close(out);
}
}
/* priklad pr8_16.c citanie zo suboru
so zadanim nazvu suboru cez parametre
vytvoreneho ulohou pr8_15 */
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#define MAX 50
void main(int argc, char *argv[])
{
int in1,lng;
int pocet,min,max;
float x,y;
char line[MAX];
if (argc == 1) printf("Volanie: %s file1\n",argv[0]);
else if (argc > 2) printf("Volanie: %s file1\n",argv[0]);
else
{
if((in1=open(*++argv,O_RDONLY)) == -1)
{
perror("Error ");
printf("Can't open [%s]\n",*argv);
exit();
}
read(in1,&pocet,sizeof(pocet));
read(in1,&min,sizeof(min));
read(in1,&max,sizeof(max));
printf("Pocet = %d\n",pocet);
printf("Min = %d Max = %d\n",min,max);
for(lng=0;lng<pocet+1;lng++)
{
read(in1,&min,sizeof(int));
read(in1,line,min);
line[min]='\0';
printf("%d. %s",lng,line);
}
while((lng=read(in1,&x,sizeof(float)))>0)
{
read(in1,&y,sizeof(float));
printf("x,y: %f %f\n",x,y);
}
close(in1);
}
}
/* priklad pr8_17.c citanie
zo suboru vytvoreneho ulohou p7_13 (p2.txt)
prikazy tell a seek */
#include <stdio.h>
#include <string.h>
#include <io.h>
#include <fcntl.h>
#include <sys/stat.h>
#define MAX 50
void main(void)
{
int in1,lng;
int pocet,min,max;
float x,y;
char line[MAX];
if ((in1 = open("p2.txt",O_RDONLY)) == -1)
{
perror("Error ");
printf("Can't open [p2.txt]\n");
exit();
}
read(in1,&pocet,sizeof(pocet));
read(in1,&min,sizeof(min));
read(in1,&max,sizeof(max));
printf("Pocet = %d\n",pocet);
printf("Min = %d Max = %d\n",min,max);
max=tell(in1);
printf("Poloha 1 v subore je %d\n",max);
for(lng=0;lng<pocet+1;lng++)
{
read(in1,&min,sizeof(int));
read(in1,line,min);
line[min]='\0';
printf("%d. %s",lng,line);
}
printf("Poloha 2 v subore je %d\n",tell(in1));
lseek(in1,max,SEEK_SET);
printf("Poloha (SEEK_SET) v subore je %d\n",tell(in1));
lseek(in1,0L,SEEK_CUR);
printf("Poloha (SEEK_CUR) v subore je %d\n",tell(in1));
lseek(in1,0L,SEEK_END);
printf("Poloha (SEEK_END) v subore je %d\n",tell(in1));
close(in1);
}
9 Smerníky
- Smerník a premenná
- Smerník a pole
- Smerník a reťazec
- Smerník a parametre funkcie
- Smerník a dynamické prideľovanie pamäte
- Smerník a funkcie
- Smerník na štruktúru
- Polia smerníkov
Charakteristickým rysom jazyka C je možnosť komplexného využívania smerníkov (pointrov) a smerníkovej aritmetiky. S použitím smerníkov sa stretávame pri spracovaní
- premenných
- polí
- reťazcov
- parametrov funkcií
- dynamických objektov
- funkcií (smerník na funkciu).
Správne pochopenie a používanie smerníkov je na jednej strane silnou stránkou a prednosťou jazyka C, kým na druhej strane ich nesprávne použitie sa mení v rukách užívateľa na nebezpečnú zbraň, čo môže v lepšom prípade mať za následok prístup k "nesprávnyn" údajom, v horšom prípade i haváriu úlohy. "Voľnosť" užívateľa pri práci so smerníkmi je podmienená jeho vedomosťami a disciplínou, pretože systém niektoré okolnosti nekontroluje (ako ukážeme v ďalšom na konkrétnom príklade).
Smerník je deklarovaný pomocou operátora * (* označuje operátor dereferencie, resp. indirekcie, ktorý sprístupňuje obsah uložený na danej adrese) v tvare
typ *meno;
kde typ - ľubovoľný typ jazyka C
meno - identifikátor premennej typu smerník
Smerník teda obsahuje dve základné informácie o objekte, na ktorý ukazuje:
- adresu, ktorá identifikuje umiestnenie objektu (meno)
- veľkosť (dĺžku) objektu (typ).
Môže nadobúdať hodnoty z množiny prirodzených čísel (adries), alebo prázdnu adresu NULL ( = 0 ). So smerníkmi súvisia 2 základné operátory:
- & - referencia (získanie adresy objektu)
- * - indirekcia, dereferencia (získanie hodnoty, uloženej na adrese).
Príklad p9.1 ilustruje použitie smerníka.
/* p9_1.c ilustracia pouzitia smernika (pointra) */
main()
{
int index,*pt1,*pt2;
index = 39; /* any numerical value */
pt1 = &index; /* the address of index */
pt2 = pt1;
printf("1. hodnota %d %d %d\n",index,*pt1,*pt2);
*pt1 = 13; /* this changes the value of index */
printf("2. hodnota %d %d %d\n",index,*pt1,*pt2);
}
9.1 Smerník a premenná
V príklade
int i, *ip;
i=3;
ip=&i;
je i premenná typu int, ip je premenná typu smerník na int. Adresa premennej i je uložená do premennej ip, ktorá v dôsledku toho ukazuje na hodnotu 3, uloženú v i. Adresa i nesúvisí s hodnotou 3, ako to ukazuje príkaz
printf("Hodnota i = %d Adresa i = %x\n",*ip,ip);
výsledkom ktorého je
Hodnota i = 3 Adresa i = fff4 (hexa)
Smerník môže byť inicializovaný už pri deklarácii, v našom prípade
int i=3, *ip=&i;
Majme
int a,b,*c;
Vzájomnú súvislosť operátorov * a & ilustrujú následujúce príkazy
c=&a; /* c obsahuje adresu a */
b=*c; /* b obsahuje obsah adresy, uloženej v c */
čo je to isté ako
b=*&a;
a teda
b=a;
Smerník môže mať priradenú i konštantnú hodnotu v tvare c = NULL; alebo c = (data_type *) MEM_LOC;
V prvom prípade sa využíva skutočnosť, že smerník nemôže mať platnú hodnotu 0. Priradením symbolickej konštanty NULL (ktorá obsahuje 0) sa indikuje nejaká chybová podmienka. Predpokladajme napríklad, že funkcia má vrátiť smerník na polohu podreťazca v rámci daného reťazca. Ak sa takýto podreťazec v danom reťazci nenachádza, smerník môže na indikáciu tejto okolnosti vrátiť NULL.
V druhom prípade ide o priradenie absolútnej adresy s využitím typovej konverzie na typ, aký má definovaný smerník. Majme napr.
#define MEM_LOC B000
(začiatok videopamäte na PC, decimálne 720896L) a chceme inicializovať smerník typu char:
char *c;
c = (char *) MEM_LOC;
resp. stručnejšie
char *c = (char *) MEM_LOC;
Smerník však nemôže ukazovať na konštanty, výrazy, resp. registrové premenné, t.j. na objekty, ktoré nie sú prístupné cez adresy pamäte. Avšak aj formálne správne použitie smerníka môže viesť k chybnému výsledku, ako môžeme vidieť v príklade
float x,y;
char *c;
c=&x;
y=*c;
Uvedená postupnosť nezabezpečí priradenie x do y v dôsledku rôznych typov (a teda aj dĺžok) x,y (float) a c (char). Pri presune dochádza k zmene rozsahu presúvaných hodnôt, čo ale systém nestráži, ale musí si to zabezpečiť programátor.
Smerníková aritmetika disponuje 4 aritmetickými operátormi +, -, ++, --. Ak p je smerník na daný typ, potom výraz p + 1 ukazuje na adresu bezprostredne následujúcu po p (vzdialenú práve o dĺžku typu p). Pri aritmetických operáciách so smerníkmi sa zohľadňuje dĺžka typu, na ktorý smerník ukazuje. Rozdiel medzi vzdialenosťami objektov v pamäti a ich skutočnou vzdialenosťou v bytoch ilustruje práklad 8.2. Majme napr.
float *fp;
pričom nech adresa fp je napr. 65524 a jeho dĺžka 4 byte. Potom výraz p + 1 ukazuje na následujúcu premennú typu float, t.j. na adresu 65528.
Smerníky je možné odčítať, nie však sčítať. Je však možné pripočítať i odpočítať celočíselnú konštantu, napr.:
fp + i, resp. fp - i
/* p9_2.c smernikova aritmetika 1 */
main()
{
char c,*cp1,*cp2;
int i,*ip1,*ip2;
cp1=&c;
cp2=cp1+1;
ip1=&i;
ip2=ip1+2;
printf("ip1 ip2: %x %x\n",ip1,ip2);
printf("Vzdialenost objektov %d %d\n",cp2-cp1,ip2-ip1);
printf("Vzdialenost v byte %d %d\n",
(int)cp2-(int)cp1,(int)ip2-(int)ip1);
}
9.2 Smerník a pole
Pole je množina prvkov rovnakého typu, ktorá je označená spoločným názvom. Pole je uložené v operačnej pamäti spojite, pričom prvý prvok (index 0) má najnižšiu a posledný prvok najvyššiu adresu. Medzi poľami a smerníkmi existuje úzka súvislosť. Práca s poľom a jeho prvkami formou používania názvu poľa a indexov je jednoduchšia, názornejšia a možno povedať apriori akceptovateľná. Používanie smerníkov vedie k rýchlejšiemu behu programu, umožňuje dynamickú alokáciu pamäte a môže redukovať celkové požiadavky na pamäť.
Meno poľa je v skutočnosti symbolická konštanta, ktorej hodnota je smerník na umiestneni prvého prvku poľa. Teda pre pole data[] identifikátor data znamená to isté ako &data[0], data + i to isté ako &data[i], čo môžeme zapísať v tvare
data + i == &data[i]
Aplikáciou operátora indirekcie * na obe strany výrazu dostaneme
*(data+i) == data[i]
Ak sa i zvyšuje o 1, referencované miesto pamäte sa zvyšuje o počet bytov (dĺžku), daný veľkosťou daného typu.
Jazyk C netestuje hranice polí, čo umožňuje zápis i čítanie mimo rozsahu poľa, pravda, so všetkými z toho vyplývajúcimi dôsledkami. Za kontrolu dodržania rozsahu poľa je preto zodpovedný programátor.
Použitie smerníka a poľa si ilustrujeme časťou programu
int *p, data[10];
p=&data[0];
potom *p = data[0] a my môžeme namiesto data[0] v ľubovoľnom výraze používať *p, resp. pre prvok data[i] *(p + i), lebo
p + i == &data[i]
*(p + i) == data[i]
Z uvedeného by sa mohlo zdať, že identifikátory data a p sú ekvivalentné. Je tu však jeden podstatný rozdiel, ktorý spočíva v tom, že data je symbolická smerníková konątanta, zatiaľ čo p je smerníková premenná. Preto operácie ako p++, p = data sú prípustné, zatiaľ čo data++, resp. data = p nie sú povolené. Majme napr.
float array[100];
fp = &array[0];
potom fp++ ukazuje na array[1]. Podobne fp = fp + 10 bude ukazovať na array[10]. Predpokladajme, že
fp1 = &array[100];
Potom fp-- ukazuje na array[99], resp. fp1 = fp1 - 10 alebo fp1 -= 10 bude ukazovať na array[90].
Na smerníky (ak sa vzťahujú na prvky toho istého poľa) je možné aplikovať aj 4 relačné operátory (<, <=, >, >= ) a testovať ich rovnosť, resp. nerovnosť ( ==, != ). Použitie týchto operátorov ilustruje následujúci príklad pre nájdenie najväčšieho prvku v poli data.
/* p9_3.c smernikova aritmetika 2 najst maximalny prvok pola */
#include <STDIO.H>
int max(int *p,int n);
main()
{
int i,data[]={1,0,3,4,7,9,6,8,5,2};
i=max(data,10);
printf("Index najvacsieho prvku pola 'data': %d\n",i);
printf("Hodnota najvacsieho prvku pola 'data': %d\n",data[i]);
}
int max(int *p,int n)
{
int *scan=p, *loc=p; /* *scan - pracovny smernik,
*loc - maximum */
while(scan-p < n){
if(*scan > *loc)
loc=scan;
scan++;
}
return(loc-p);
}
Funkcia max(p,n) dostane smerník na pole p a jeho dĺžku n. Nájdenie najväčšieho prvku je realizované porovnávaním hodnôt poľa s hodnotou najväčšieho prvku (na začiatku je to hodnota 0 - prvku). Keď sa nájde väčšia hodnota, jej index sa uloží do smerníka loc. Na začiatku hľadania obidva smerníky scan i loc ukazujú na začiatok poľa, potom smerník scan postupne ukazuje na ďalšie prvky poľa, až kým scan - p = n, kedy sa príkaz while ukončí. V každom kroku prehľadávania sa porovnáva hodnota prvku, na ktorý ukazuje scan, s priebežným maximom, na ktoré ukazuje loc. Ak nastane *scan > *loc, loc sa nastaví na scan,. Takto po skončení cyklu while bude loc ukazovať na najväčší prvok poľa data. Poloha, resp. index najväčšieho prvku je vrátená ako posun od začiatku poľa v tvare loc - p. Následujúci obrázok ilustruje pohyb smerníkov po poli.
Je dôležité uvedomiť si, že deklarácia int data[100]; deklaruje pole a alokuje miesto pre jeho prvky, zatiaľ čo deklarácia int *p; deklaruje premennú typu smerník na int a rezervuje miesto iba pre túto premennú, nie pre prvky poľa. V takom prípade je potrebné priradiť
p = data;
a potom môžeme pracovať s prvkami poľa jedným z nasledujúcich spôsobov
data[i], *(data+i), p[i], *(p+i).
Ako príklad uvedieme časť programu pre súčet prvkov poľa:
#define N 40
int data[N],i,sum=0;
for (i=0;i<N;) PRE < sum+="data[i++];">
Posledný príkaz môže mať aj tvar
sum += *(data + i++);
resp. v prevedení so smerníkom
int data[N], *p=data, *q=data+N, sum=0;
for(;p<Q;++P) PRE < +="*p;" sum>
alebo v ešte kompaktnejšom tvare
for (;p < q;) sum += *p++;
pričom *p++ znamená pripočítať hodnotu, na ktorú ukazuje *p a potom zvýšiť p tak, aby ukazoval na ďalšiu hodnotu. Všimnime si, že nemôžeme použiť
sum += *data++;
9.3 Smerník a reťazec
Reťazec je jednorozmerné pole znakov, t.j. prvkov typu char, ktoré je deklarované v tvare
char s[N];
Konvencia jazyka C určuje, že reťazec je štandardne ukončený binárnou nulou - znakom '\0', čím sa reťazec líši od ostatných polí a preto je uvádzaný ako samostatný typ. Konštanty typu reťazec zapisujeme v tvare "abc". Dĺžka tohoto reťazca je 4 (počíta sa aj miesto pre posledný znak binárna nula). Hodnotou konštanty typu reťazec je adresa v pamäti, na ktorej je reťazec uložený. Porovnajme príkazy printf ("%s","abc"); a printf ("%x", "abc"); Výstupom prvého príkazu budú všetky znaky reťazca (bez koncového '\0'), t.j. abc. Výstupom druhého príkazu bude adresa uloženia reťazca v pamäti v hexa tvare, napr. d3. Pretože sa jedná o smerníkovú konštantu, sú povolené rôzne konštrukcie, napr. "abc"+1:
printf ("%s","abc"+1);
printf ("%x","abc"+1);
Prvý príkaz vytlačí reťazec, ktorý začína na adrese o 1 väčšej ako predtým, t.j. výstupom bude bc. Druhý príkaz vytlačí adresu o 1 väčšiu, ako je adresa "abc", napr. d4.
Všimnime si rozdiel medzi medzi konštantou "a" typu reťazec a konštantou 'a' typu char. Konštanta "a" je reťazec, musí byť preto zakončená znakom '\0' a je to preto pole 2 znakov (má dĺžku 2).
Externé a statické premenné typu reťazec môžu byť inicializované konštantou typu reťazec, napr.
char s[] = "Mama";
(dĺžka poľa sa dopočíta automaticky), čo je ekvivalentné príkazu
char s[] = {'M','a','m','a','\0'};
Všimnite si rozdiel
char *ps = "Mama";
kde ps je smerník na znak (char) s počiatočnou hodnotou adresy reťazca "Mama".
/* p9_4.c smernik a retazec */
main()
{
char strg[40],*there,one,two;
int *pt,list[100],index;
strcpy(strg,"To je retazec znakov (character string).");
one = strg[0]; /* one and two are identical */
two = *strg;
printf("Vystup 1: %c %c\n",one,two);
one = strg[8]; /* one and two are indentical */
two = *(strg+8);
printf("Vystup 2: %c %c\n",one,two);
there = strg+10; /* strg+10 is identical to strg[10] */
printf("Vystup 3: %c\n",strg[10]);
printf("Vystup 4: %c\n",*there);
for (index = 0;index < 100;index++)
list[index] = index + 100;
pt = list + 27;
printf("Vystup 5: %d\n",list[27]);
printf("Vystup 6: %d\n",*pt);
}
Príklad zápisu funkcie pre určenie dĺžky reťazca (viď aj kap. 6.1):
unsigned int strlen(register char *s)
{
register unsigned int n=0;
while (*s++) ++n;
return(n);
}
Majme deklaráciu
char c, *p, s[10];
s[0]='a'; s[1]='b'; s[2]='c'; s[3]='\0';
printf("%s %c %s",s,*(s+1),s+1);
Výstupom posledného príkazu bude abc b bc, lebo *(s+1) je to isté ako s[1], a s++ znamená začať tlač od s[1] po koncový znak.
Priradenie po znakoch je účelné nahradiť zápisom
strcpy(s,"abc");
Spracujme tento reťazec príkazmi
for(p=s; *p; ++*p++);
printf("%s\n",s);
"Čudne vyzerajúci" výraz ++*p++ je povolený výraz a znamená vlastne postupnosť dvoch príkazov
*p = *p +1;
p = p + 1;
takže výstupom bude reťazec bcd (najprv sa zvýši o 1 obsah, na ktorý ukazuje smerník, t.j. a sa zmení na b, potom sa zvýši o 1 hodnota smerníka).
Príklad p9_5 ukazuje použitie funkcie pre zrušenie všetkých výskytov znaku c v reťazci s so súčasným určením počtu týchto výskytov.
/* p9_5.c smernik a retazec rusenie znaku */
#include <STDIO.H>
#include <STRING.H>
int rusznak(char *s, char c);
main()
{
char str[60],*ps,c='e';
strcpy(str,"Priklad pre zrusenie vyskytov znakov v retazci.");
printf("Text pred zrusenim znakov '%c':\n%s\n",c,str);
printf("Pocet znakov '%c' v texte: %d\n",c,rusznak(str,c));
printf("Text po zruseni znakov '%c':\n%s\n",c,str);
}
int rusznak(char *s, char c)
{
char *p=s;
int count=0;
while(*s){
if(*s!=c) *p++=*s;
else count++;
s++;
}
*p='\0';
return(count);
}
Ako je vidieť, funkcia rusznak pracuje len so smerníkmi s a p. Smerník s sa pohybuje nad starým obsahom reťazca, smerník p nad novým. Smerník p sa zvyšuje vtedy, keď sa znak starého poľa nerovná znaku c. V opačnom prípade sa zvyšuje hodnota počítadla výskytu znakov v reťazci count. Nakoniec je reťazec ukončený znakom '\0'. Deklarácia (a tým priestor) pre samotný reťazec musí byť uvedená tam, kde dochádza k volaniu funkcie rusznak, v našom prípade v hlavnom programe. Upozorňujeme na potrebu dostatočnej rezervy v tých prípadoch, kedy dochádza pôsobením funkcií (tak štandardných ako aj vlastných) k predĺženiu reťazca.
9.4 Smerník a parametre funkcie
Pri štandardnom volaní parametrov funkcie hodnotou funkcia nemení hodnoty parametrov a vracia len jednu hodnotu (špecifikovanú pri deklarácii funkcie). Ak použijeme parametre typu smerník, hovoríme o parametroch volaných odkazom. Ich použitím sa odovzdáva funkcii adresa skutočného parametra, čo umožňuje vo vnútri funkcie modifikovať príslušný počet parametrov prostredia, odkiaľ bola funkcia vyvolaná (nadradená funkcia, resp. hlavný program). Príklad p9_6 názorne ilustruje takúto modifikáciu.
/* p9_6.c parametre funkcie hodnotou a odkazom */
void fixup(int nuts,int *fruit);
main()
{
int hruska,mrkva;
hruska = 100;
mrkva = 101;
printf("Pociatocne hodnoty %d %d\n",hruska,mrkva);
fixup(hruska,&mrkva);
printf("Hodnoty po prepocte %d %d\n",hruska,mrkva);
}
void fixup(int nuts,int *fruit)
{
printf("Vstup %d %d\n",nuts,*fruit);
nuts = 135;
*fruit = 172;
printf("Vystup %d %d\n",nuts,*fruit);
}
9.5 Smerník a dynamické prideľovanie pamäte
Každý program pracuje so svojimi dátami a dátovými štruktúrami, ktoré môžu byť statické aj dynamické. Statické existujú po celý čas behu programu, v ktorom sú deklarované príslušnými deklaráciami. Miesto pre nich vyhradí kompilátor už počas prekladu. Dynamické štruktúry vznikajú a zanikajú dynamicky počas činnosti programu.
Pre dynamické prideľovanie, resp. uvoľňovanie pamäte je v rôznych implementáciách jazyka C k dispozícii rôzny počet štandardných funkcií. Na tomto mieste preto uvedieme iba dve základné funkcie - malloc() a free().
Funkcia malloc slúži pre pridelenie (alokáciu) pamäte. Má jeden argument typu unsigned int, ktorý udáva požadovaný rozsah dynamicky pridelenej pamäte. Vracia smerník (adresu) pamäťovej oblasti požadovanej dĺžky. Táto oblasť je pre úlohu rezervovaná až do jej uvoľnenia. Na uvoľnenie slúži funkcia free, ktorá má jeden argument typu smerník na rezervovanú oblasť pamäte. Príklad p9_7 ilustruje dynamické pridelenie pamäte reťazcu a jej uvoľnenie.
/* p9_7.c dynamicke pridelovanie pamate */
#include <STDIO.H>
#include <ALLOC.H>
#include <STRING.H>
main()
{
char str[200];
register char *t;
strcpy(str,"Ukazka dynamickeho pridelovania pamate.");
printf("Retazec str: %s\n",str);
t = (char *)malloc(strlen(str)+1);
strcpy(t,str);
printf("Retazec t: %s\n",t);
free(t);
}
Do reťazca s priradíme text, potom alokujeme dynamickú pamäť pre tento reťazec (veľkosť pamäte je daná dĺžkou reťazca, t.j. strlen(s) +1). Do takto priradenej pamäte tento reťazec skopírujeme - t. Po vytlačení obsahu dynamicky pridelenej pamäte uvoľníme pamäť príkazom free(t).
9.6 Smerník a funkcie
Použitie smerníka na funkciu umožňuje v jazyku C prenášať identifikátor funkcie ako parameter. Deklarácia smerníka na funkciu má tvar
typ (*meno)();
kde typ je typ hodnoty, ktorú funkcia vracia
meno je identifikátor typu smerník na funkciu
Všimnime si rozdiel medzi deklaráciami
int *f1();
int (*f2)();
Prvá deklarácia určuje, že f1 je funkcia, ktorá vracia hodnotu typu smerník na int. Druhá definuje f2 ako smerník na funkciu, ktorá vracia hodnotu typu int. Posledná dvojica zátvoriek () v oboch prípadoch udáva, že sa jedná o funkciu (abc je premenná, abc() označuje funkciu).
Použitie smerníka na funkciu ukážeme na troch príkladoch. Prvý p9_8 ukazuje výpočet hodnôt zadanej funkcie pomocou funkcie tabel. V príklade sú tabelizované dve funkcie f1(x) = 1 + x; a f2(x) = x2;
/* p9_8.c smernik a funkcia tabelacia funkcie */
#include <STDIO.H>
void tabel(float a, float b, float krok, float (*f)(float c));
float f1(float x);
float f2(float x);
main()
{
float z=1,a=1,b=4;
printf("Tabelacia funkcie y = 1 + x\n");
printf(" x y\n");
tabel(b,a,z,f1);
printf("\nTabelacia funkcie y = x*x\n");
printf(" x y\n");
tabel(a,b,z,f2);
}
void tabel(float a, float b, float krok, float (*f)(float c))
{
float p;
if(b<a){p=b;b=a;a=p;};
for(p=a;p<b;p+=krok)
printf("%f %f\n", p, f(p));
}
float f1(float x)
{
return(1.+x);
}
float f2(float x)
{
return(x*x);
}
Tretí príklad p9_10 je prevzatý z [Richta93, str. 117] a zaoberá sa vynechaním komentára zo zdrojového textu programu v jazyku C. Komentár sa začína dvojicou znakov /* a končí dvojicou */. Program sa preto môže nachádzať v jednom zo štyroch možných stavov:
- mimo komentára (stav mimo)
- mimo komentára a práve prečítal znak /, ale ešte nevie, či ide o začiatok komentára (stav zackom)
- vo vnútri komentára (stav vnútri)
- vo vnútri komentára a práve prečítal znak *, ale ešte nevie, či ide o koniec komentára (stav konkom)
Na tieto stavy sa viažu rôzne činnosti. V stave mimo sa kopírujú vstupné znaky na výstup - funkcia kopíruj. V stave vnútri sa vstupné znaky na výstup nekopírujú - funkcia nekopíruj. Pri prechode zo stavu mimo do stavu zackom sa nebude znak / kopírovať, lebo nevieme, či nebude súčasťou komentára. Pri prechode zo stavu zackom do stavu mimo (po znaku / nenasledoval znak *) treba toto lomítko doplniť - funkcia lomítko. Tabuľka prechodov medzi jednotlivými stavmi a volanie príslušných funkcií v závislosti na vstupnom znaku potom má tvar:
Stavy sú v programe reprezentované vymenovaným typom STAV, tabuľka dvojrozmerným poľom tabulka typu POLOZKA, ktorá obsahuje smerník na funkciu a identifikátor nového (následujúceho) stavu. Logika činnosti programu:
stav = mimo
while(nie je koniec vstupu){
čítaj znak
preveď znak na stĺpec tabuľky
vykonaj funkciu podľa tabuľky
prejdi do nového stavu podľa tabuľky
}
Príklad je dobrou ukážkou programu, riadeného dátami. Všimnime si pozorne spôsob zápisu vykonania funkcie podľa tabuľky. Na rozdiel od deklarácie smerníka na funkciu v zátvorkách sú uvedené príslušné parametre funkcie.
Všimnime si tiež rozdiel medzi obidvoma príkladmi. V prvom prípade je použitý priamo identifikátor funkcie bez parametrov, v druhom sa užíva smerník na funkciu s uvedením príslušného parametra. Zapamätajte si, že všetky funkcie, ktoré budú referencované pomocou smerníka na funkciu, musia byť rovnakého typu a mať rovnaký počet parametrov rovnakých typov (viď funkcie f1, f2, resp. kopíruj, nekopíruj a lomítko).
/* p9_10.c smernik a funkcia 2 spracovanie komentara */
#include <STDIO.H>
typedef enum {mimo,zackom,vnutri,konkom} STAV;
typedef struct {
void (*funkcia)();
STAV novy_stav;
} POLOZKA;
void kopiruj(char c);
void nekopiruj(char c);
void lomitko(char c);
main()
{
POLOZKA tab[4][3] = {
{{nekopiruj,zackom},{kopiruj,mimo},{kopiruj,mimo}},
{{lomitko,mimo},{nekopiruj,vnutri},{lomitko,mimo}},
{{nekopiruj,vnutri},{nekopiruj,konkom},{nekopiruj,vnutri}},
{{nekopiruj,mimo},{nekopiruj,vnutri},{nekopiruj,vnutri}}
};
STAV stav;
char znak;
int stlpec;
stav = mimo;
while((znak=getchar()) != EOF){
/* prevod znaku na stlpec tab */
switch (znak) {
case '/' : stlpec = 0; break;
case '*' : stlpec = 1; break;
default : stlpec = 2; break;
};
/* vykonaj funkciu podla tabulky */
(*tab[stav][stlpec].funkcia)(znak);
/* prechod do noveho stavu */
stav = tab[stav][stlpec].novy_stav;
};
if (stav == zackom) putchar('/');
}
void kopiruj(char c)
{
putchar(c);
}
void nekopiruj(char c)
{
}
void lomitko(char c)
{
putchar('/');
putchar(c);
}
9.7 Smerník na štruktúru
Štruktúra je súhrn jednej alebo viacerých premenných aj rôznych typov (vrátane ďalších štruktúr), ktoré sú združené pod jedným názvom. To umožňuje pracovať so súvisiacimi dátami ako s celkom. Prístup k zložkám štruktúry sa zadáva názvom štruktúry a názvom zložky, oddelených bodkou (.). Majme napr. štruktúry o dátume a študentovi
struct datum{
int den;
int mesiac;
int rok;
}
struct student{
char meno[20];
int rodne_cislo;
struct datum dnar;
int rocnik;
float priemer[5];
} st;
Štruktúra s menovkou datum spája údaje o dni, mesiaci a roku, štruktúra student rôzne údaje o študentovi, pričom dátum narodenia je reprezentovaný štruktúrou dnar typu datum. Prístup k zložke ročník má tvar
st.rocnik = 2;
ku zložke rok narodenia
st.dnar.rok = 1973;
Smerníky na štruktúru sa najčastejšie používajú pre prístup k štandardným štruktúram, definovaným v jazyku C. Prístup k zložkám štruktúry student s využitím smerníka na štruktúru ilustrujú následujúce príkazy:
struct student *pst;
...
(*pst).rocnik = 2;
(*pst).dnar.rok = 1973;
Okrúhle zátvorky sú nevyhnutné, lebo operátor bodka (.) má vyššiu prioritu ako operátor indirekcie (*). Pretože používanie smerníka pre prístup k štruktúre v jazyku C je značne frekventované, je k dispozícii špeciálny operátor -> (tvorený dvojicou znakov mínus a väčší), pomocou ktorého môžeme predchádzajúce priradenie vyjadriť v zaužívanom tvare
pst->rocnik = 2;
pst->dnar.rok = 1973;
Obidva spôsoby zápisu sú ekvivalentné.
/* p9_11.c smernik a struktura */
#include <STDIO.H>
main()
{
struct datum{
int den;
int mesiac;
int rok;
};
struct student{
char meno[20];
int rodne_cislo;
struct datum dnar;
int rocnik;
float priemer[5];
} st;
struct student *pst=&st;
st.rocnik = 2;
st.dnar.rok = 1975;
printf("1. vypis rocnik: %d rok narodenia: %d\n",
st.rocnik, st.dnar.rok);
printf("2. vypis rocnik: %d rok narodenia: %d\n",
(*pst).rocnik, (*pst).dnar.rok);
printf("3. vypis rocnik: %d rok narodenia: %d\n",
pst->rocnik, pst->dnar.rok);
(*pst).dnar.den = 13;
pst->dnar.mesiac = 3;
printf("4. vypis den: %d a mesiac narodenia: %d\n",
(*pst).dnar.den, st.dnar.mesiac);
}
9.8 Polia smerníkov
Typické použitie polí smerníkov je spojené s triedením rôznych záznamov, polí, štruktúr a pod. Vtedy je výhodné vytvoriť pole smerníkov tak, že každý jeho prvok ukazuje na príslušný objekt (riadok, položka štruktúry) triedenia. Pri triedení môžeme potom ku každému prvku pristupovať prostredníctvom jeho smerníka a prípadné operácie s prvkami nahradiť operáciami s im odpovedajúcimi smerníkmi. Toto riešenie odstraňuje tak zložitú správu pamäte ako aj veľkú réžiu, spojenú s presúvaním celých objektov. Je to podstatne efektívnejší postup.
V databázových systémoch pri ukladaní dát do súborov sa priebežne vytvára pole smerníkov, ktoré sa po utriedení uloží na disk ako tzv. indexový súbor. V praxi takýto súbor neobsahuje adresy, ale úspornejšie indexy [Galan91]. Takýchto indexových súborov môže byť maximálne toľko, koľko položiek štruktúra obsahuje. Indexové súbory môžu byť aj zložitejšie útvary, napr. lineárne zoznamy alebo binárne stromy. Indexové súbory sú vytvárané za účelom zrýchlenia manipulácie a prístupu k jednotlivým údajom.
Triedenie záznamov s využitím polí smerníkov ilustrujú príklady p9_12 a p9_13.
/* p9_12.c pole smernikov usporiadanie */
#include <STDIO.H>
#include <STRING.H>
#include <STDLIB.H>
#define N 6
main()
{
struct ex1{
char m[6];
int vyska;
int vaha;
int vzdial;
} st[N], *pst[N], *t;
int i,j;
randomize();
/* inicializacia struktury a smernikov */
for(i=0;i<N;I++){ * } if(pst[i]- for(j="i+1;j<N;j++)" for(i="0;i<N-1;i++)" vysky podla zotriedenie pst[i]="&st[i];" st[i].vzdial="random(200);" st[i].vaha="70+i;" st[i].vyska="180-i;" sprintf(st[i].m,?Meno%1d?,i);>vyska > pst[j]->vyska){
t=pst[i];
pst[i]=pst[j];
pst[j]=t;
}
printf("\nUsporiadanie podla vysky\n");
for(i=0;i<N;I++) vzdial='%d\n",pst[i]-' vaha="%d" vyska="%d" printf(?%s>m,
pst[i]->vyska,pst[i]->vaha,pst[i]->vzdial);
/* zotriedenie podla vahy */
for(i=0;i<N-1;I++) if(pst[i]- for(j="i+1;j<N;j++)">vaha > pst[j]->vaha){
t=pst[i];
pst[i]=pst[j];
pst[j]=t;
}
printf("\nUsporiadanie podla vahy\n");
for(i=0;i<N;I++) vzdial='%d\n",pst[i]-' vaha="%d" vyska="%d" printf(?%s>m,
pst[i]->vyska,pst[i]->vaha,pst[i]->vzdial);
/* zotriedenie podla vzdialenosti */
for(i=0;i<N-1;I++) if(pst[i]- for(j="i+1;j<N;j++)">vzdial > pst[j]->vzdial){
t=pst[i];
pst[i]=pst[j];
pst[j]=t;
}
printf("\nUsporiadanie podla vzdialenosti\n");
for(i=0;i<N;I++) vzdial='%d\n",pst[i]-' vaha="%d" vyska="%d" printf(?%s>m,
pst[i]->vyska,pst[i]->vaha,pst[i]->vzdial);
}
/* p9_13.c pole smernikov - indexy usporiadanie */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define N 6
main()
{
struct ex1{
char m[6];
int vyska;
int vaha;
int v;
} st[N], *i1[N], *i2[N], *i3[N], *p;
int i,j;
/* inicializacia struktury a smernikov */
for(i=0;i<N;i++){
sprintf(st[i].m,"Meno%1d",i);
st[i].vyska = 180-i;
st[i].vaha = 40+5*i;
st[i].v = 3*i % 4;
i1[i]=&st[i];
i2[i]=&st[i];
i3[i]=&st[i];
}
/* zotriedenie podla vysky */
for(i=0;i<N-1;i++)
for(j=i+1;j<N;j++){
if(i1[i]->vyska > i1[j]->vyska){
p=i1[i];
i1[i]=i1[j];
i1[j]=p;
}
/* zotriedenie podla vahy */
if(i2[i]->vaha > i2[j]->vaha){
p=i2[i];
i2[i]=i2[j];
i2[j]=p;
}
/* zotriedenie podla vzdialenosti */
if(i3[i]->v > i3[j]->v){
p=i3[i];
i3[i]=i3[j];
i3[j]=p;
}
}
printf("\nZotriedene podla vysky Netriedene hodnoty:\n");
for(i=0;i<N;i++)
printf("%s %d %d %d %s %d %d %d\n",i1[i]->m,
i1[i]->vyska,i1[i]->vaha,i1[i]->v,
st[i].m,st[i].vyska,st[i].vaha,st[i].v);
printf("\nPodla vahy:\n");
for(i=0;i<N;i++)
printf("%s %d %d %d\n",i2[i]->m,
i2[i]->vyska,i2[i]->vaha,i2[i]->v);
printf("\nPodla vzdialenosti:\n");
for(i=0;i<N;i++)
printf("%s %d %d %d\n",i3[i]->m,
i3[i]->vyska,i3[i]->vaha,i3[i]->v);
}
10 Dátové štruktúry
Každý program má dve základné časti, ktoré profesor Wirth zhrnul do známej rovnice
Programy = Datové štruktúry + Algoritmy.
Zdá sa, že jedným zo základných princípov moderných technológií vytvárania programov je práve oddelenie časti, zaoberajúcej sa údržbou informácií v datových štruktúrach od vlastného algoritmu riešenia úlohy . Obidve časti môžu byť v programe v rôznej miere rozptýlené. Sústredením pamäťových prostriedkov a kódu pre manipuláciu s nimi do samostatných modulov získame prehľadnejšie, čitateľnejšie, modifikovateľnejšie, lepšie dokumentované a predovšetkým spoľahlivejšie programy, ktoré sú aj efektívnejšie, lebo nedochádza napr. k opakovaniu kódu. Dá sa povedať, že vhodným návrhom dátovej štruktúry pre danú úlohu je spravidla aj algoritmus riešenia úlohy jednoduchší a prehľadnejší.
V ďalšej časti sa budeme zaoberať základnými dátovými štruktúrami. Podrobnejšie preberieme lineárne zoznamy. Kapitolu uzavrieme stručným popisom stromových štruktúr.
Slovo lineárny udáva, že počet predchodcov aj nasledníkov hociktorej položky zoznamu je maximálne 1. To znamená, že v lineárnom zozname existuje maximálne jedna predchádzajúca a jedna nasledujúca položka. Z toho dôvodu lineárne zoznamy slúžia pre reprezentáciu úplného usporiadania. Podľa spôsobu vkladania a vyberania položiek rozoznávame tri typy lineárnych dátových štruktúr: front, zásobník a zoznam.
10.1 Lineárny zoznam
Pre vzájomné pospájanie položiek danej množiny stačí jediný spojovací článok, ktorý ukazuje na nasledujúcu položku množiny. Týmto článkom je smerník, pričom smerník poslednej položky má hodnotu NULL. Pridanie, resp. zrušenie položky v zozname sa potom jednoducho realizuje príslušnou zmenou jednotlivých smerníkov. Pre pridanie položky 3 do zoznamu medzi položku 1 a 2 značí zmeniť smerník položky 1 tak, aby ukazoval na položku 3 a smerník položky 3 bude ukazovať na položku 2 (nadobudne predchádzajúcu hodnotu smerníka položky 1).
Front je spôsob organizácie, ktorý sa používa vtedy, ak potrebujeme spracovať položky v takom poradí, v akom prichádzajú. Spôsob vkladania a vyberania položiek sa označuje ako FIFO (first-in, first-out).
Front je obyčajne charakterizovaný
- typom položiek
- maximálnym počtom položiek, ktoré je možné do frontu vložiť
- operáciami
- vytvorenie frontu
- vložiť položku do frontu (na koniec)
- vrátiť prvú položku
- odobrať prvú položku (všetky ostatné posunúť)
- test na prázdnosť frontu
- test na plnosť frontu
- vrátiť dĺžku frontu
Je zrejmé, že vložiť položku je možné len do frontu, ktorý nie je plný, a naopak, odobrať prvú položku je možné len z neprázdneho frontu. Odobranie položky znamená skrátenie frontu.
Prvá položka prázdneho frontu nie je definovaná. Dĺžka frontu je daná počtom všetkých položiek. Ak tento počet je rovný maximálnemu počtu položiek, front je plný.
Chybové stavy : pridanie položky do plného frontu výber položky z prázdneho frontu
Programová realizácia frontu môže vychádzať z implementácie pomocou
- poľa (pri odobraní prvej položky presun ostatných položiek)
- kruhového poľa (spojenie prvej a poslednej položky, odobranie prvej položky znamená iba presun začiatku frontu)
- spojových štruktúr (realizované smerníkmi, odobranie prvej položky znamená iba zmenu smerníka na začiatok frontu).
Zásobník je front typu LIFO (last-in first-out) - posledná vložená položka bude vybraná ako prvá. Používa sa tam, kde potrebujeme odložiť informácie a cestou späť sa k nim vrátiť (stack pri volaní funkcií, riešenie rekurzívnych problémov).
Zásobník je obyčajne charakterizovaný
- typom položiek
- max. počtom položiek, ktoré je možné do zásobníka vložiť
- operáciami
- vytvorenie zásobníka
- vložiť položku do zásobníka (na vrchol)
- vrátiť položku z vrcholu zásobníka
- odobrať položku z vrcholu zásobníka
- test na prázdnosť zásobníka
- test na plnosť zásobníka
- vrátiť dĺžku zásobníka
Ak máme pre zásobník vyhradenú dostatočne veľkú pamäť, môžu byť maximálny počet položiek a test na plnosť zásobníka nezaujímavé. Z uvedeného je zrejmé, že vrchol zásobníka tvorí posledná vložená položka. Odobraním položky z neprázdneho zásobníka sa jeho dĺžka skráti (vložením zvýši) o 1. Prázdny zásobník nemá vrchol. Položky sú zo zásobníka vyberané v opačnom poradí, ako sú vkladané.
Chybové stavy : výber položky z prázdneho zásobníka vloženie položky do plného zásobníka
Programová realizácia zásobníka môže vychádzať z implementácie pomocou
- poľa
- spojových štruktúr.
Implementácia pomocou poľa sa používa vtedy, ak poznáme maximálny počet položiek. Na aktuálny vrchol zásobníka ukazuje premenná index, ktorá sa modifikuje príslušným spôsobom pri každom vložení, resp. výbere zo zásobníka.
Implementácia pomocou spojových štruktúr využíva smerníkové spojenie položiek. Pri vložení, resp. výbere sa tu namiesto premennej index príslušným spôsobom modifikuje smerník na vrchol zásobníka. V tomto prípade je výhodné používať trojicu smerníkov, ktoré indikujú aktuálny vrchol zásobníka (ako premenná index) a začiatok i koniec pamäte pre zásobník (analógia s deklaráciou poľa).
Zoznam predstavuje takú postupnosť, do ktorej môžeme pridávať, resp. rušiť položky na ľubovoľnom mieste, ktoré je označené premennou index (tzv. ukazovateľ, ktorý označuje ľubovoľné miesto práce so zoznamom od začiatku až po koniec). Tým sa zoznam líši od frontu, resp. zásobníka, kde je možná aktualizácia iba na koncoch postupnosti. Zoznam je teda všeobecným prípadom lineárnej dátovej štruktúry. Je charakterizovaný
- typom položiek
- maximálnym počtom položiek
- operáciami
- vytvoriť zoznam
- vložiť položku do zoznamu na miesto označené ukazovateľom
- nastaviť ukazovateľ na začiatok zoznamu
- nastaviť ukazovateľ na koniec zoznamu
- nastaviť ukazovateľ na ďalšiu položku zoznamu
- nastaviť ukazovateľ na predchádzajúcu položku zoznamu
- vrátiť položku zoznamu, označenú ukazovateľom
- vrátiť dĺžku zoznamu
- test na prázdnosť zoznamu
- test na plnosť zoznamu
Rovnako ako v predchádzajúcom platí pri dostatočne veľkej vyhradenej pamäti, že maximálny počet položiek a test na plnosť zoznamu môžu byť nezaujímavé. Je zrejmé, že vkladať novú položku je možné len do rozsahu zoznamu a rušiť položku je možné len v neprázdnom zozname. Každým vložením, resp. rušením položky sa príslušne mení dĺžka zoznamu.
Chybové stavy : výber položky z prázdneho zoznamu vloženie položky do plného zoznamu
Programová realizácia môže rovnako ako v predchádzajúcich prípadoch vychádzať z implementácie pomocou
- poľa
- spojových štruktúr.
Implementáciu pomocou poľa ilustruje príklad p10_1, ktorý rieši veľmi jednoduchý telefónny zoznam s dvoma základnými funkciami vloz a zrus. Položka zoznam je reprezentovaná štruktúrou ex1, pozostávajúcou z mena a telefónneho čísla účastníka. Zoznam je tvorený poľom p. Pri práci s poľom sa využívajú premenné ukaz na pohyb po zozname a dĺžka. V prvej časti programu sa generujú mená účastníkov a ich telefónne číslo, v druhej časti sa zruší prvá položka zoznamu a nová položka sa vloží na druhé a pred posledné miesto zoznamu. Všimnite si, že tak pri vkladaní, ako aj pri rušení položky vo funkciách vloz a zrus, dochádza k fyzickému presunu príslušnej časti poľa p jedným alebo druhým smerom.
/* p10_1.c zoznam pomocou pola tel. zoznam */
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
#define N 9
struct ex1{
char m[8];
float cislo;
} p[N],t;
int ukaz,dlzka;
void vloz(struct ex1 q);
void zrus();
struct ex1 read();
main()
{
int i,j;
randomize();
printf("\Telefonny zoznam povodny\n");
for(dlzka=0;dlzka<N;DLZKA++){ i for(i="dlzka;" *
prvok novy pre miesto uvolni } return; plny\n?);
je printf(?Zoznam N){ if(dlzka="=" i;
int { ukaz="dlzka-2;" danu poziciu na polozku vlozit q)
ex1 vloz(struct void }; %.0f\n?,ukaz-1,t.m,t.cislo);
%s printf(?%d. t="read();" ){ for(ukaz="0;ukaz>dlzka;"
upraveny\n?); zoznam printf(?\nTelefonny vloz(t); zrus();
dlzka--; t.cislo="100000+100*random(90)+random(10000);"
sprintf(t.m,?Meno%03d?,N+100); %.0f\n?,dlzka,p[dlzka].m,
p[dlzka].cislo); p[dlzka].cislo="100000+100*random(90)+
random(10000);" sprintf(p[dlzka].m,?Meno%03d?,
dlzka);>ukaz; ) p[i]=p[--i];
p[ukaz++] = q;
dlzka++;
}
void zrus()
/* zrusit polozku na pozicii danej ukaz */
{
register int i;
for(i=ukaz; i<DLZKA; * }
{ polozku ex1 dlzka--;
if(ukaz pozicie danej z citat
read() struct p[i]="p[++i]; " )>= dlzka){
printf("V zozname uz nie je ziadny prvok\n");
return (p[0]);
}
return p[ukaz++];
}
Príklad p10_2 ilustruje implementáciu zoznamu pomocou spojových štruktúr, pričom sa využíva zreťazenie v jednom smere (jeden smerník na ďalšiu položku zoznamu). Náplňou je ten istý jednoduchý telefónny zoznam. Reťazenie zoznamu si vyžiadalo doplniť v strukture ex1 smerník ďalší a ďalšiu pomocnú štruktúru POM, ktorá obsahuje tri smerníky na zoznam, a to na začiatok zoznamu - prvy, na aktuálnu pozíciu v zozname - aktual a na poslednú položku v zozname - posledny. Položky zoznamu nie sú uložené v poli, ale v dynamicky prideľovanej pamäti, čím sa činnosť funkcií pre pridávanie do zoznamu pridaj, vkladanie do zoznamu vloz a pre zrušenie položky zoznamu zrus mení (vzhľadom na príklad p10_1) a spočíva v príslušnej úprave smerníkov a alokácii, resp. uvoľnení pamäte. Vo funkciách musia byť správne ošetrené situácie pre prázdny zoznam, zoznam s jednou položkou a s viacerými položkami, a taktiež vkladanie, resp. rušenie prvej, bežnej aj poslednej položky zoznamu. Výhodou takéhoto prístupu k riešeniu problému je to, že pri zostavovaní programu nepotrebujeme poznať presný počet položiek zoznamu (odpovedajúce deklarácie polí). V hlavnom programe je preverené tak vkladanie, ako aj rušenie položiek v rôznych miestach zoznamu. Všimnite si, že každej manipulácii so zoznamom predchádza nastavenie smerníka ,aktual. Počiatočné generovanie zoznamu je rovnaké, ako v predchádzajúcom príklade.
/* p10_2.c zoznam pomocou smernikov
zretazenie v jednom smere
tel. zoznam */
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
#define N 9
struct ex1{
char m[8];
float cislo;
struct ex1 *dalsi;
} p,t;
struct POM{
struct ex1 *prvy;
struct ex1 *aktual;
struct ex1 *posledny;
int dlzka;
} POM;
void pridaj(struct POM *s, struct ex1 *q);
void vloz(struct POM *s, struct ex1 *q);
void zrus(struct POM *s);
main()
{
int i;
struct ex1 *pex=&p, *t, *tp;
struct POM *pp=&POM;
randomize();
printf("\Telefonny zoznam povodny\n");
for(i=0;i<N;I++){ * 4)pp- if(i="=" pridaj(pp,pex);
p.cislo="100000+100*random(90)+random(10000);
" sprintf(p.m,?Meno%03d?,i);
strukturu naplnit>aktual = pp->posledny;
printf("%2d. %s %.0f\n",i,p.m,p.cislo);
}
sprintf(tp->m,"Meno%03d",N+100);
tp->cislo=100000+100*random(90)+random(10000);
pridaj(pp,tp);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 1. pridat posledny\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
getchar();
zrus(pp);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 2. zrusit 5. polozku\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
getchar();
vloz(pp,pex);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 3. vloz za 6. polozku\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
getchar();
pp->aktual = pp->prvy;
vloz(pp,pex);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 4. vloz za 1. polozku\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
getchar();
zrus(pp);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 5. zrus 1. polozku\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
getchar();
pp->aktual = pp->posledny;
vloz(pp,pex);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 6. vloz za posl. polozku\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
getchar();
pp->aktual = pp->posledny;
zrus(pp);
i=0;
t=pp->prvy;
printf("\nTel. zoznam 7. zrus posl. polozku\n");
while(t){
printf("%2d. %s %.0f\n",i++,t->m,t->cislo);
t=t->dalsi;
};
}
void pridaj(struct POM *s, struct ex1 *q)
/* pridaj novu polozku na koniec zoznamu */
{
register struct ex1 *h;
h = malloc(sizeof(struct ex1)); /* pamat */
if(h == NULL){
printf("\nMalo pamate\n");
return;
};
h->cislo = q->cislo; /* kopia */
strcpy(h->m,q->m);
if(s->dlzka++==0) /* 1. polozka */
s->prvy = h;
else
s->posledny->dalsi = h;
h->dalsi = NULL; /* posledna polozka */
s->posledny = h; /* pripoj polozku */
}
void vloz(struct POM *s, struct ex1 *q)
/* vloz polozku na poziciu aktual */
{
register struct ex1 *h;
h = malloc(sizeof(struct ex1)); /* pamat */
if(h == NULL){
printf("\nMalo pamate\n");
return;
};
h->cislo = q->cislo; /* kopia */
strcpy(h->m,q->m);
if(s->dlzka++ == 0){ /* prazdny zoznam */
/* vloz na zaciatok */
s->prvy = s->aktual = s->posledny = h;
h->dalsi = NULL;
}
if(s->aktual == s->posledny){ /* vloz na koniec */
s->posledny->dalsi = h;
h->dalsi = NULL;
s->posledny = h;
}
else{ /* vloz na poziciu aktual */
h->dalsi = s->aktual->dalsi;
s->aktual->dalsi = h;
};
}
void zrus(struct POM *s)
/* zrus polozku aktual */
{
register struct ex1 *h, *t;
int prvy=1; /* sprac. 1. polozky */
if(s->dlzka == 0) /* prazdny zoznam */
return NULL;
t = s->aktual;
if(s->dlzka == 1) /* zoznam s 1 polozkou */
s->prvy = s->aktual = s->posledny = NULL;
else{
if(s->aktual == s->prvy){ /* 1. polozka */
s->prvy = s->prvy->dalsi;
prvy = 0;
};
if(s->aktual == s->posledny){
/* posledna polozka */
s->posledny = s->prvy;
while(s->posledny->dalsi != s->aktual)
s->posledny = s->posledny->dalsi;
};
if((s->aktual != s->prvy)&&prvy){
h = s->prvy; /* najst predchadzajucu */
while(h->dalsi != s->aktual)
h = h->dalsi;
h->dalsi = s->aktual->dalsi;
}; /* spojit s nasledujucou */
s->aktual = s->aktual->dalsi;
}
s->dlzka--; /* znensit dlzku */
free(t); /* uvolnit pamat */
return ;
}
V príklade p10_3 sa naďalej zaoberáme spomenutým jednoduchým telefónnym zoznamom, realizovaným pomocou spojových štruktúr, ktoré sú zreťazené v obidvoch smeroch. Toto zreťazenie je realizované pomocou dvoch smerníkov v štruktúre ex1. Prvý smerník pred ukazuje na predchádzajúcu položku zoznamu (v prípade 1. položky má hodnotu NULL), druhý smerník dalši ukazuje na nasledujúcu položku zoznamu (v prípade poslednej položky má hodnotu NULL). Zreťazenie položiek zoznamu v obidvoch smeroch umožňuje, resp. zjednodušuje ďalšie operácie so zoznamom (funkcia vypis2 pre výpis zoznamu od konca po začiatok). V tomto príklade pracujeme s tzv. usporiadaným zoznamom (podľa mien), ktorý je výhodný najmä pri vyhľadávaní informácií. Realizáciu usporiadania má na starosti funkcia sortvloz, ktorá vkladá každú novú položku na odpovedajúce miesto v zozname (s využitím lexikografického porovnania reťazcov pomocou štandardnej funkcie strcmp). Pridávanie nových položiek obstaráva funkcia vstup (alokácia pamäte pre položku) pomocou funkcie input pre načítanie reťazca s kontrolou jeho dĺžky. Zrušenie položky (funkcia zrus) spočíva v príslušnej úprave smerníkov susedných položiek (ak je potrebné, aj smerníkov štruktúry POM) a uvoľnení pamäte alokovanej pre položku. Všimnime si, že tak vo funkcii sortvloz, ako aj vo funkcii zrus musia byť ošetrené všetky možné polohy položky v zozname (na začiatku, uprostred a na konci). Funkcia hladaj a najdi slúži pre vyhľadanie položky v zozname podľa mena, resp. podľa čísla. Samotnú realizáciu výpisu údajov danej položky zabezpečuje funkcia display. Pre naznačenie spôsobu praktického využívania takéhoto zoznamu sú uvedené aj funkcie pre zápis všetkých položiek zoznamu do súboru na disk a pre spätné načítanie položiek zoznamu zo súboru do pamäte. Funkcia vstup je navrhnutá pre opakovaný vstup položiek. Zadávanie sa ukončí stlačením klávesa Enter. Celý program je riadený veľmi jednoduchým "menu".
/* p10_3.c zoznam pomocou smernikov
zretazenie v dvoch smeroch
tel. zoznam */
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
struct ex1{
char m[8];
float cislo;
struct ex1 *pred;
struct ex1 *dalsi;
} p;
struct POM{
struct ex1 *prvy;
struct ex1 *aktual;
struct ex1 *posledny;
int dlzka;
} a,*sa=&a;
int menu(void);
struct ex1 *sortvloz(struct POM *sa, struct ex1 *q);
void vstup(void);
void input(char *vyzva, char *s, int count);
void zrus(void);
struct ex1 *najdi(struct ex1 h1, int druh);
void vypis1(void);
void vypis2(void);
void display(struct ex1 *q);
void hladaj(int druh);
void zapis(void);
void citaj(void);
main()
{
for(;;){
switch(menu()){
case 1 : vstup(); break; /* vloz polozku */
case 2 : zrus(); break; /* zrus polozku */
case 3 : vypis1(); /* vypis zoznamu od zac. */
break;
case 4 : vypis2(); /* vypis zoznamu od konca */
break;
case 5 : hladaj(0); /* hladanie mena v zozname */
break;
case 6 : hladaj(1); /* hlad. cisla v zozname */
break;
case 7 : zapis(); /* zapis na disk */
break;
case 8 : citaj(); /* citanie z disku */
break;
default : exit(0); /* koniec */
break;
}
}
}
int menu(void)
{
char s[80];
int c;
printf("1. Vlozit polozku\n");
printf("2. Zrusit polozku\n");
printf("3. Vypis zoznamu vpred\n");
printf("4. Vypis zoznamu odzadu\n");
printf("5. Hladanie mena\n");
printf("6. Hladanie cisla\n");
printf("7. Zapis na disk\n");
printf("8. Citanie z disku\n");
printf("9. Koniec\n");
do{
printf("Volba cinnosti <1-9> : ");
gets(s);
c = atoi(s);
} while ( c< 1 || c > 9);
return c;
}
struct ex1 *sortvloz(struct POM *sa, struct ex1 *q)
/* vloz polozku do zotriedeneho zoznamu */
{
struct ex1 *old, *top, *beg;
beg = sa->prvy;
if(sa->dlzka++==0){ /* 1. polozka */
q->pred = q->dalsi = NULL;
sa->prvy = sa->aktual = sa->posledny = q;
return q;
};
old = NULL;
top = beg;
while(top){
if(strcmp(top->m,q->m) < 0){
old = top; /* najdi kam zaradit */
top = top->dalsi;
}
else { /* zarad medzi old a top */
q->dalsi = top;
q->pred = old;
top->pred = q;
if(!old)
sa->prvy = q;
if(old){
old->dalsi = sa->aktual = q;
};
return q;
};
};
old->dalsi = q; /* posledna polozka */
sa->posledny = q;
q->dalsi = NULL;
q->pred = old;
return q;
}
void vstup(void)
{
struct ex1 *h;
char p[20];
for(;;){
h = malloc(sizeof(struct ex1)); /* pamat */
if(h == NULL){
printf("\nMalo pamate\n");
return;
};
input("Meno : ",h->m,8); /* kopia */
if(!h->m[0]) break; /* koniec vstupov */
input("Telefonne cislo : ",p,6);
h->cislo = atof(p);
a.aktual = sortvloz(sa,h);
}
}
void input(char *vyzva, char *s, int count)
/* vstup retazca o max. dlzke count s vyzvou */
{
char p[80];
printf(vyzva);
gets(p);
if(strlen(p) > count){
printf(" prilis dlhy vstup\n");
p[count] ='\0';
};
strcpy(s,p);
}
void zrus(void)
/* zrusenie polozky */
{
struct ex1 *h,h1;
printf("Meno pre zrusenie : ");
gets(h1.m);
h = najdi(h1,0);
if(h){
if(sa->prvy == h){
sa->prvy = h->dalsi;
if(sa->prvy) sa->prvy->pred = NULL;
else sa->posledny = NULL;
}
else{
h->pred->dalsi = h->dalsi;
if(h != sa->posledny)
h->dalsi->pred = h->pred;
else
sa->posledny = h->pred;
};
free(h);
a.dlzka--;
}
else printf("sa nenaslo\n");
}
struct ex1 *najdi(struct ex1 h1, int druh)
/* najst vyskyt 0 - mena, 1- cisla v zozname */
{
struct ex1 *h;
h = sa->prvy;
while(h){
if(!druh) /* hladaj meno */
if(!strcmp(h1.m,h->m))
return h;
if(druh) /* hladaj cislo */
if(h1.cislo == h->cislo)
return h;
h = h->dalsi;
};
return NULL;
}
void vypis1(void)
/* vypis poloziek zoznamu vpred */
{
register struct ex1 *h;
h = sa->prvy;
while(h){
display(h);
h = h->dalsi;
};
}
void vypis2(void)
/* vypis poloziek od konca zoznamu */
{
register struct ex1 *h;
h = sa->posledny;
while(h){
display(h);
h = h->pred;
};
}
void display(struct ex1 *q)
/* zobrazit jednu polozku */
{
printf("%s %.0f\n",q->m,q->cislo);
}
void hladaj(int druh)
/* najdenie polozky podla 0 - mena, 1 - cisla */
{
char p[80];
struct ex1 *h,h1;
if(!druh){
printf("Meno pre hladanie : ");
gets(h1.m);
}
else{
printf("Cislo pre hladanie : ");
gets(p);
h1.cislo = atoi(p);
}
if(!(h=najdi(h1,druh))) printf(" sa nenaslo\n");
else display(h);
}
void zapis(void)
/* zapis suboru na disk */
{
int t,size;
struct ex1 *h;
char *c;
FILE *fp;
if((fp=fopen("Zoznam","w")) == 0){
perror("Zoznam");
exit(0);
};
printf("\nZapis na disk...\n");
size = sizeof(struct ex1);
h = sa->prvy;
while(h){
c = (char *)h; /* prevod na ukazatel na char */
for(t=0;t<SIZE;++T) h="h-" putc(*c++,fp);>dalsi;
};
putc(EOF,fp);
fclose(fp);
}
void citaj(void)
/* citanie suboru z disku */
{
int t,size;
struct ex1 *h,*tmp=NULL;
char *c,cc[16];
FILE *fp;
if((fp=fopen("Zoznam","r")) == 0){
perror("Zoznam");
exit(0);
};
printf("\nCitanie z disku...\n");
sa->dlzka = 0;
size = sizeof(struct ex1);
sa->prvy = malloc(size);
if(!sa->prvy){
printf("\nNedostatok pamate\n");
return;
};
h = sa->prvy;
c = (char*)h;
while( (*c++ = getc(fp)) != EOF){
for(t=0;t<SIZE-1;++T) sa- *c++="getc(fp);">dlzka++;
h->dalsi = malloc(size); /* priestor
na dalsiu polozku */
if(!h->dalsi){
printf("\nMalo pamate\n");
return;
};
h->pred = tmp;
tmp = h;
h = h->dalsi;
c = (char *)h;
*c='\0';
};
tmp->dalsi = NULL;
sa->aktual = sa->prvy;
sa->posledny = tmp;
sa->prvy->pred = NULL;
fclose(fp);
}
10.2 Strom
Strom je nelineárna dátová štruktúra, ktorá má vrchol s konečným počtom pripojených stromových štruktúr, ktoré sa nazývajú podstromy. Vrchol stromu sa nazýva aj koreň stromu. V stromovej štruktúre teda každý prvok má jedného bezprostredného predchodcu (okrem koreňa stromu) a žiadneho, jedného či viacerých nasledovníkov. Prvok, ktorý nemá nasledovníkov, je koncový prvok alebo list stromu. Prvok, ktorý nie je listom ani koreňom, je vnútorným vrcholom stromu. Ak je počet nasledovníkov rovný dvom, hovoríme o binárnom strome, pri väčšom počte o viaccestnom strome. Počet nasledovníkov udáva stupeň stromu. Lineárny zoznam je z tohoto hľadiska stromová štruktúra s jedným nasledovníkom, ktorá sa preto tiež nazýva degenerovaný strom. Najbežnejší spôsob zobrazenie stromu je v tvare grafu.
V ďalšom sa budeme zaoberať binárnymi stromami. Typickým príkladom binárneho stromu je rodokmeň človeka (s tým, že rodičia sú nasledníkmi). Každý vrchol stromu obsahuje vlastnú informáciu (kľúč) a môže mať dvoch nasledovníkov, ktoré sa označujú ako ľavý a pravý.
Binárne stromy sa často používajú pre reprezentáciu čiastočného usporiadania množiny údajov, ktorej prvky sa majú vyberať na základe daného kľúča. Strom, v ktorom všetky kľúče ľavého podstromu sú menšie ako kľúč vrcholu a kľúče pravého podstromu zase väčšie (pre všetky vrcholy), sa nazýva vyhľadávací, resp. usporiadaný strom. Vyhľadanie prvku v takomto strome je jednoduché a pritom efektívne : začneme pri koreni stromu a pokračujeme v príslušnom podstrome (o druhý podstrom sa viac nestaráme), až kým prvok nenájdeme alebo nedôjdeme na koniec (list) stromu.
Problematika vyhľadávania, pridávania a rušenia vrcholov v strome tvorí náplň základných operácií so stromami. Je obdobná ako u lineárneho zoznamu s tým, že musí zohľadňovať a ošetrovať viac možných situácií, ako napr.:
- prvok s kľúčom sa v strome nenachádza
- prvok s kľúčom má jedného nasledovníka
- uprvok s kľúčom má dvoch nasledovníkov
Príklad p10_4 ilustruje použitie binárneho stromu na zotriedenie poľa reťazcov. Východzie pole reťazcov vytvára s použitím generátora náhodných čísiel funkcia vytvor. Funkcia vlož realizuje rekurzívne vkladanie prvkov do binárneho stromu. Pre nový vrchol (v programe označený ako uzol) alokuje príslušnú pamäť. Usporiadanie stromu je zabezpečené porovnaním nového a starého reťazca (kľúča) s následným uložením nového reťazca vľavo alebo vpravo (podľa výsledku porovnania strcmp). Ak by sme pracovali s číslami ako s kľúčmi, bolo by to obyčajné porovnanie menší, väčší. Funkcia zobraz vykonáva prezeranie vrcholov usporiadaného binárneho stromu.
/* p10_4.c binarny strom pomocou pola
triedenie poli retazcov */
#include <STDIO.H>
#include <STDLIB.H>
#include <STRING.H>
#include <CONIO.H>
#define POCET 30
#define RET 50
typedef struct bstrom uzol;
typedef struct bstrom* puzol;
struct bstrom{
char* m;
puzol lavy;
puzol pravy;
} ;
puzol vloz(puzol, char *);
void vytvor(char [][], int *);
void zobraz(puzol);
main()
{
int i,n;
char ret[POCET][RET+1];
puzol koren = NULL;
printf("Pocet retazcov <MAX. %d> : ",POCET);
scanf("%d",&n);
if( n < 1 || n > POCET)
n = POCET;
vytvor(ret, &n);
puts("vytvaram binarny strom");
for (i=0; i<N; * }
{ koren- koren="vloz(koren," NULL)
{ if(koren="=" stromu binarneho
do prvkov vkladanie rekurzivne *m)
char koren, vloz(puzol puzol zobraz(koren); :\n?);
retazcov pole puts(?zotriedene ret[i]); i++)>m = m;
koren->lavy = koren->pravy = NULL;
}
else{
if(strcmp(m,koren->m) < 0)
koren->lavy = vloz(koren->lavy,m);
else
koren->pravy = vloz(koren->pravy,m);
}
return(koren);
}
void vytvor(char x[][RET+1], int *n)
/* generovanie pola nahodnych retazcov */
{
int i,j;
char pom[RET+1];
randomize();
if(*n < 1 || *n > POCET)
*n = POCET;
for(i=0; i<*n; i++){
for(j=0; j<RET; * } { void stromu rekurzivne
if(koren- bin. uzlov prezeranie koren)
zobraz(puzol ; *(x+i)+j)="\0" *( j);
pom, strncpy(*(x+i), j="random(RET-1);" 0)
(j="=" if pom[RET]="\0" random(26);
+ *(pom+j)="a" j++)>lavy != NULL)
zobraz(koren->lavy);
printf("\t %s \n",koren->m);
if(koren->pravy != NULL)
zobraz(koren->pravy);
}
Obvykle sa pri práci so stromami jednotlivé vrcholy vytvárajú dynamicky a môže byť potrebné vytvoriť celý rad rôznych pomocných funkcií, ako sme to uviedli v príklade p10_3 (hľadať, rušiť, zápis a čítanie stromu a pod.).
11 Triedenie
Triedenie (usporiadanie) množiny prvkov je proces preusporiadania danej množiny v špecifickom poradí (podľa určitej relácie). Je to jedna z najčastejšie sa vyskytujúcich operácií pri spracovaní údajov, resp. dátových štruktúr. Usporiadanie množiny prvkov výrazne zvyšuje efektívnosť vyhľadávania prvkov. Typickými príkladmi triedenia je spracovanie rôznych zoznamov, registrov, skladov, slovníkov a pod.
Problematika triedenia je veľmi prepracovaná. Venovalo sa jej množstvo autorov, za základnú publikáciu je považovaný [Knuth78]. Existuje celý rad metód, ktoré sa líšia rýchlosťou, pamäťovou a operačnou zložitosťou algoritmu, princípom triedenia, prípustným typom triedených prvkov a spôsobom prístupu k triedeným prvkom. Pre ilustráciu uvedieme najznámejšie metódy triedenia na príklade usporiadania množiny celých čísel od najmenšieho po najväčšie (relácia <=). Cieľom ilustrácie je uviesť algoritmy jednotlivých metód v jazyku C vo forme funkcií. Ich podrobnejší popis, zdôvodnenie aj vzájomné porovnanie môže čitateľ nájsť v literatúre [1], [2], [3] i [4]. Vstupným parametrom všetkých funkcií je pole celých čísel a ich počet, výstupom je zotriedené pole. V dvoch prípadoch (heapsort a mergesort) je vo funkciách vstupné pole transformované do pomocného poľa od indexu 1, pretože daný algoritmus s počiatočným indexom 0 nepracuje. V prípade funkcie mergesort má okrem toho pomocné pole dvojnásobnú dĺžku (vyplýva z metódy). Niektoré z uvádzaných algoritmov sú implementované priamo v prekladačoch jazyka C (napr. quicksort pod názvom qsort, binsearch pod názvom bsearch), pričom spôsob porovnávania prvkov je daný užívateľom definovanou funkciou (v závislosti na type prvkov množiny). Parametre týchto funkcií sú uvedené pri ich popise.
Všetky uvedené funkcie sú ilustrovanné v príklade p11_1.C Hlavný program obsahuje výpis celočíselného poľa pred triedením a po ňom a samozrejme volanie príslušnej funkcie. V uvedenom príklade je volaná funkcia insert, ktorá zabezpečí potrebné usporiadanie poľa a a následne funkcia binsearch, ktorá vráti index prvku (12) v usporiadanom (zotriedenom) poli. O jednotlivých funkciách sa stručne zmienime v poradí, v akom sú uvedené v príklade.
Bubblesort realizuje priamu výmenu dvoch prvkov množiny v zmysle zadanej relácie, pričom jeden cyklus porovnávania ide od začiatku, druhý od konca triedeného poľa. Názov metódy pochádza z analógie medzi pohybom triedených prvkov a pohybom bublín vo vode (ak uvažujeme triedené pole vo zvislej polohe).
Uvedený algoritmus je možné vylepšiť sledovaním, či pri jednotlivých prechodoch nastávajú výmeny prvkov pomocou indexov L a R vo funkcii shakersort, ktorá zabezpečuje triedenie pretriasaním. Triedenie je ukončené, keď sa indexy L a R stretnú. Ich počiatočné hodnoty sú začiatok a koniec triedeného poľa. Triedi sa pritom vždy len tá časť poľa, ktorá sa ešte môže meniť (v rozsahu L - R).
Funkcia insert realizuje triedenie priamym vkladaním, ktoré je založené na schopnosti vložiť daný prvok tak, že výsledná postupnosť je znovu zotriedená. Postup má tieto kroky: najprv sa nájde miesto, kde treba prvok vložiť, potom sa ostatné prvky o jedno miesto posunú a na takto získané miesto sa uloží daný prvok (analógia - usporiadanie hracích kariet).
Zlepšenie (pokiaľ ide o zrýchlenie metódy určenia miesta vkladania prvku) realizuje funkcia bininsert, ktorá používa na tento cieľ metódu binárneho hľadania (viď funkciu binsearch). Vychádzame pritom z predpokladu, že cieľová postupnosť je zotriedená.
Funkcia select realizuje triedenie priamym výberom, ktoré vychádza z predpokladu, že najmenší prvok prvok môžeme zaradiť priamo na začiatok triedeného poľa, najmenší prvok zo zvyšku poľa zase na jeho začiatok atď.
Zlepšenie triedenia priamym vkladaním navrhol v r. 1959 D.I. Shell - funkcia Shellsort. Toto pozostáva v triedení prvkov po skupinách so zmenšovaním kroku, pričom sa začína s najväčším krokom a končí krokom 1. Obvykle sa postupnosť krokov uvažuje ako rad mocnín 2, napr.: 4,2,1, ale nie je to podmienkou. Voľba postupnosti krokov však môže podstatne ovplyvniť efektívnosť triedenia.
Funkcia heapsort realizuje stromové triedenie (pomocou binárneho stromu), založené na opakujúcom sa výbere najmenšieho prvku zo všetkých n prvkov potom n-1 prvkov atď. Nevýhodou tohoto spôsobu sú zvýšené nároky na pamäť (minimálne dvojnásobok rozsahu triedeného poľa, ktoré sa zvyčajne realizujú tzv. haldou - anglicky heap, odtiaľ názov metódy).
Ďalšie zlepšenie triedenia výmenou, ktoré predstavuje najlepšiu metódu triedenia v poli, navrhol C.A.Hoare a nazval ju pre jej rýchlosť quicksort. Pod týmto názvom je metóda všeobecne známa (viď funkcia qsort v jazyku C). Vychádza zo skutočnosti, že najefektívnejšie sú výmeny prvkov na čo najväčšie vzdialenosti. Triedenie preto (podobne ako shakersort) začína z oboch koncov poľa a končí, keď sa indexy stretnú. Funkcia quicksort využíva rekuzrívnu triediacu funkciu qs, ktorá zotrieďuje jednotlivé časti triedeného poľa. Použitie rekurzie je veľmi účinná programovacia technika [4], ktorá je v tomto prípade úplne namieste (niekedy však môže zapríčiniť aj nepríjemné situácie - doporučujeme zvýšenú obozretnosť !).
Triedenie metódou priameho zlučovania - funkcia mergesort sa používa predovšetkým v prípadoch triedenia takých veľkých rozsahov údajov (ide o diskové, resp. iné periférne súbory), ktoré sa nezmestia do operačnej pamäte počítača. Triedenie pracuje takto: 1. postupnosť sa rozdelí na dve polovice, 2. ich prvky sa spájajú do usporiadaných dvojíc. 3. na takto vzniknutú postupnosť sa aplikujú kroky 1. a 2., čím dostaneme usporiadané štvorice. 4. opakovaním krokov 1. až 3. dostaneme osmice atď. Algoritmus končí usporiadaním celej postupnosti. Triedenie si vyžaduje pamäť rovnú dvojnásobku veľkosti poľa. V jednej polovici je zdrojová postupnosť, v druhej sa ukladá cieľová.
/* K13_1.c triedenie
metody
bubblesort priama vymena
shakersort pretriasanim
insert priamym vkladanim
bininsert binarnym vkladanim
select priamym vyberom
Shellsort vkladanim so
zmensovanim kroku
heapsort stromove - haldou
quicksort rozdelovanim
mergesort priamym zlucovanim
vyhladavanie
binsearch binarne hladanie
*/
#include <stdio.h>
#include <alloc.h>
void bubblesort(int pole[], int pocet);
void shakersort(int pole[], int pocet);
void insert(int pole[], int pocet);
void insert1(int pole[], int pocet);
void bininsert(int pole[], int pocet);
void select(int pole[], int pocet);
void Shellsort(int pole[], int pocet);
void heapsort(int pole[], int pocet);
void quicksort(int pole[], int pocet);
void mergesort(int pole[], int pocet);
int binsearch(int pole[], int pocet, int cislo);
main()
{
int a[32]={44,55,12,42,94,18,06,67,1,3,3,13,48,56,59,33},i;
printf("\nBegin\n");
for(i=0;i<16;i++)printf("%3d ",a[i]);
insert(a,16);
i=binsearch(a,16,12);
printf("\nResult 12 je a[%d]\n",i);
for(i=0;i<16;i++)printf("%3d ",a[i]);
return 0;
}
void bubblesort(int pole[], int pocet)
/* bublinove triedenie - priamou vymenou */
{
int a,b,pom;
for(a=1; a<pocet; ++a)
for(b=pocet-1; b>=a; --b)
if(pole[b-1] > pole[b]){ /* vymena */
pom = pole[b-1];
pole[b-1] = pole[b];
pole[b] = pom;
}
}
void shakersort(int pole[], int pocet)
/* triedenie pretriasanim - priamou vymenou */
{
int a,b,L,R,pom;
L=1; R=b=pocet-1;
do{
for(a=R; a>=L; a--)
if(pole[a-1] > pole[a]){
pom = pole[a-1];
pole[a-1] = pole[a];
pole[a] = pom;
b=a; /* index vymeny lavy */
}
L = b+1;
for(a=L; a<R+1; a++)
if(pole[a-1] > pole[a]){
pom = pole[a-1];
pole[a-1] = pole[a];
pole[a] = pom;
b=a; /* index vymeny pravy */
}
R = b-1;
} while(L<=R); /* kym sa nestretnu */
}
void insert1(int pole[], int pocet)
/* triedenie priamym vkladanim - verzia s kopiou */
{
int a,b,pom,*ppole;
ppole=malloc(pocet+1); /* pom. pole */
for(a=0;a<pocet;a++)
ppole[a+1]=pole[a]; /* od 1 po pocet */
for(a=1; a<=pocet; a++){
pom = ppole[a];
ppole[0] = pom; /* na 0 minimum */
b = a;
while(pom < ppole[b-1]){
ppole[b] = ppole[b-1];
b--;
}
ppole[b] = pom; /* vymena */
};
for(a=0;a<pocet;a++)
pole[a]=ppole[a+1];
free(ppole);
}
void insert(int pole[], int pocet)
/* triedenie priamym vkladanim */
{
int a,b,pom,*ppole;
for(a=1; a<pocet; a++){
pom = pole[a];
b = a;
while(b>0 && pom < pole[b-1]){
pole[b] = pole[b-1];
b--;
}
pole[b] = pom; /* vymena */
};
}
void bininsert(int pole[], int pocet)
/* triedenie binarnym vkladanim */
{
int a,b,pom,m,L,R;
for(a=1; a<pocet; a++){
pom = pole[a];
L = 0; R = a;
while(L < R){ /* najst miesto pre prvok */
m = (L+R)/2;
if(pole[m] <= pom)
L = m+1;
else
R = m;
}
for(b=a;b>=R+1;b--) /* uvolnit miesto pre prvok */
pole[b]=pole[b-1];
pole[R]=pom;
}
}
void select(int pole[], int pocet)
/* triedenie priamym vyberom */
{
int a,b,m,pom;
for(a=0; a<pocet-1; a++){
pom = pole[a];
m = a;
for(b=a+1;b<pocet; b++)
if(pole[b] < pom){
m = b; /* min. prvok a index */
pom = pole[m];
};
pole[m] = pole[a];
pole[a] = pom; /* vymena */
};
}
void Shellsort(int pole[], int pocet)
/* triedenie vkladanim so zmensovanim kroku - Shell */
{
int a,b,m,k,presun,pom,t=4,h[]={1,2,4,8};
for(m=t-1; m>=0; m--){ /* kroky spracovania */
k = h[m];
do{
presun = 0; /* indikacia presunu */
for(a=k; a<pocet; a++){
pom = pole[a];
b = a-k;
if(pom<pole[b]){ /* porovnanie v kroku */
pole[a]=pole[b]; /* usporiad. v kroku */
pole[b]=pom;
presun = 1;
};
}
}while(presun>0);
}
}
void heapsort(int pole[], int pocet)
/* stromove triedenie - haldou */
{
int L,R,pom,*ppole;
void sift(int L, int R, int pole[]);
ppole=malloc(pocet+1); /* pom. pole */
for(L=0;L<pocet;L++)
ppole[L+1]=pole[L]; /* od 1 po pocet */
L=pocet/2+1; R=pocet;
while(L>1){ /* previerka prvkov */
L--;
sift(L,R,ppole);
};
while(R>1){
pom = ppole[1];
ppole[1]=ppole[R];
ppole[R]=pom;
R--;
sift(L,R,ppole);
};
for(L=0;L<pocet;L++) /* pole spat */
pole[L]=ppole[L+1];
free(ppole);
}
void sift(int L, int R, int pole[])
/* zaradenie prvku L do haldy */
{
int a,b,pom;
a=L; b=2*L;
pom = pole[L];
while(b<=R){
if(b<R)
if(pole[b]<pole[b+1]) b++;
if(pom >= pole[b])break;
pole[a]=pole[b];
a=b;
b=2*a;
}
pole[a]=pom;
/* getchar();
printf("\nS L %d R %d\n",L,R);
for(a=1;a<=16;a++)printf("%3d ",pole[a]);
*/
}
void quicksort(int pole[], int pocet)
/* triedenie rozdelovanim - rekurzivne */
{
void qs(int L, int R, int pole[]);
qs(0,pocet-1,pole);
}
void qs(int L, int R, int pole[])
/* vlastna funkcia triedenia v poli - Hoare */
{
int a,b,pom,pom1;
a=L; b=R; pom=pole[(L+R)/2]; /* vyber prvku */
do{
while((pole[a]<pom)&&(a<R)) a++;
while((pom<pole[b])&&(b>L)) b--;
if(a<=b){
pom1=pole[a];
pole[a]=pole[b];
pole[b]=pom1;
a++; b--;
}
}while(a<=b);
if(L<b)qs(L,b,pole);
if(a<R)qs(a,R,pole);
}
void mergesort(int pole[], int pocet)
/* triedenie metodou priameho zlucovania */
{
int i,j,k,L,t,h,m,p,q,r,up,*ppole;
ppole=malloc(2*pocet+1); /* pom. pole */
for(i=0;i<pocet;i++) /* indexy 1 az 2*pocet */
ppole[i+1]=pole[i]; /* od 1 po pocet */
up = p = 1;
do {
h = 1;
m = pocet;
if(up>0) { /* zdroj 1. cast */
i=1; j=pocet;
k=pocet+1; L=2*pocet;
}
else{ /* zdroj 2. cast */
k=1; L=pocet;
i=pocet+1; j=2*pocet;
};
/* zlucit postupnosti, urcene indexami i,j
do postupnosti urcenej indexom k;
q je dlzka i-postupnosti, r j-postupnosti */
do {
if(m>=p) q=p;
else q=m;
m=m-q;
if(m>=p) r=p;
else r=m;
m=m-r;
while((q!=0)&&(r!=0)){ /* zlucovanie */
if(ppole[i]<ppole[j]){
ppole[k]=ppole[i]; k=k+h;
i++; q--;
}
else{
ppole[k]=ppole[j]; k=k+h;
j--; r--;
};
}
while(r>0){ /* kopia zvysku j postupnosti */
ppole[k]=ppole[j]; k=k+h;
j--; r--;
};
while(q>0){ /* kopia zvysku i postupnosti */
ppole[k]=ppole[i]; k=k+h;
i++; q--;
};
h=-h; t=k;
k=L; L=t;
} while(m!=0);
up=-up; p=2*p;
} while(p<pocet);
if(up<0)
for(i=0;i<pocet;i++)
ppole[i]=ppole[i+pocet];
for(L=0;L<pocet;L++) /* pole spat */
pole[L]=ppole[L+1];
free(ppole);
}
int binsearch(int pole[], int pocet, int cislo)
/* binarne hladanie prvku cislo delenim intervalu
pole musi byt usporiadane - zotriedene !!! */
{
int a,b,pom;
a=0; b=pocet-1;
while(a<=b){
pom=(a+b)/2;
if(cislo<pole[pom])
b=pom-1;
else if(cislo>pole[pom])
a=pom+1;
else /* index prvku */
return pom;
}
return(-1); /* nenaslo sa */
}
12 Terminálové funkcie (QNX)
Sú určené pre prácu v textovom režime pod OS QNX na realizáciu "oknových techník". Predstavujú určitú analógiu funkcií jednotky CRT v Turbo Pascale. Z hľadiska normy jazyka C to nie sú štandardné funkcie, preto ich prenositeľnosť medzi systémami a kompilátormi nie je zaručená. Jednotlivé terminálové funkcie sú podrobne popísané v knižnici jazyka C86. Ich spoločným znakom je to, že ich názvy sa vždy začínajú predponou term.
S celou skupinou terminálových funkcií sú spojené dve inicializačné funkcie. Funkcia term_load načítava charakteristiky terminálu z tabuľky (/config/tcap.dbase). Tieto charakteristiky zahrňujú aj niektoré Escape postupnosti pre zefektívnenie práce terminálu (napr. čistenie obrazovky). Môžu obsahovať aj údaje o rozšírenom súbore znakov.
Druhá funkcia term_video_on vykonáva inicializáciu premenných a alokáciu bufrov, používaných ostatnými terminálovými fukciami. Prítomnosť týchto bufrov umožňuje "čítanie z obrazovky" (napr. funkcie term_save_image, term_get_line). Po vyvolaní funkcie term_video_on môže program užívať iba terminálové funkcie, až dokedy neobnovíme pôvodný režim obrazovky (obyčajne na konci programu).
Viacero terminálových funkcií vyžaduje vypnutie nastavenia ECHO a EDIT na terminále, čo možno vykonať napr. takto:
stary_stav = get_option(stdin);
set_option(stdin,stary_stav & ~(EDIT | EDIT));
Po ukončení práce s terminálovými funkciami je potrebné definovať východzí stav
set_option(stary_stav);
Orientácia riadkov a stĺpcov na obrazovke je daná tým, že horný riadok a ľavý stĺpec sú číslované ako 0. *Nie všetky terminály vracajú kurzor po čistení obrazovky do tohoto bodu. V mnohých terminálových funkciách sa užíva parameter argument s týmto významom jednotlivých bitov:
e enable color farba
bc background color farba pozadia
fc forground color farba popredia
p position cursor
at end of text kurzor na koniec textu
f flush after output vyprázdniť po výstupe
u underline podčiarknúť
i inverse video inerzia
h highlight zvýraznenie
b blink blikanie
Nie všetky atribúty (bity) je možné použiť pre každú funkciu (viď popis funkcií C86). Priradenie farieb je následovné
0 black čierna
1 blue modrá
2 green zelená
3 cyan fialová
4 red červená
5 magenta svetlomodrá
6 yellow žltá
7 white biela
Na termináloch sú podporované iba farby popredia, na konzolách všetky. Príklad p11_1.c ilustruje niekoľko terminálových funkcií - pre menu, text v okne, blikanie, uloženie časti obrazovky do bufra a jej vrátenie. Prácu s menu pozri tiež v príklade pop.c v adresári cii/samples, z ktorého je aj náš príklad odvodený.
/* priklad p11_1.c
** pop - popup window demonstration program
** Copyright 1986 by Quantum Software Systems, Ltd.
** Copyright 1989 by Computer Innovations, Inc.
** ukazka menu,nadpis,inicializacia,okno,text v okne,blikanie */
#include <STDIO.H>
#include <DEV.H>
#include <TCAP.H>
#include <STDLIB.H>
#include <STRING.H>
#define MAX_SCREENS 5
/* maximum # of screens that can be saved */
struct screen_save {/* structure to contain saved windows */
int cursor_x, cursor_y;
int x, y, width, length;
char *data;
};
int blik=0; /* prepinac na blikanie */
int bytes=0; /* # of bytes / saved character */
int top_screen=0; /* number of top window */
struct screen_save screens[MAX_SCREENS];
/* array of saved screens */
unsigned ef=4<<12|7<<8|1<<3|1|0x8000, nf=7<<12|0<<8|0x8000,pf;
char *a_menu[]={ "Init ","Okno ", "Prikaz_okno ", "Text ", "VText/prikaz ", "Blik ", "Quit ", NULL };
char *b_menu[]={ "Ayyy ", "Byyy ", "Cyyy ", "Dyyy ", "Eyyy ", "Quit ", NULL };
char *c_menu[]={ "Azzz ", "Bzzz ", "Czzz ", "Dzzz ", "Ezzz ", "Quit ", NULL };
/* prototypes for the support functions */
void submenu1(char ch, int row);
void submenu2(char ch, int row);
void pop_up(int y, int x, int width, int length, unsigned attr);
void pop_down(void);
int main(int argc, char **argv)
{
char *cp;
if(!term_load(stdout))
error( "pop: Unable to load tcap entry.\n");
term_video_on(); /* enable terminal shadow image */
term_colour(0x17); /* set screen colors (white on blue)*/
if(!strcmp(tcap_entry.term_name, "qnx")){
fput("\033!71\033@71",8,stdout);
fflush(stdout);
}
term_clear(_CLS_SCRH);
/* set screen buffer to a known state */
set_option(stdin, get_option(stdin) & ~(EDIT|ECHO));
term_printf(1,5,nf,
"UKAZKA ZAKLADNYCH PRINCIPOV
PRACE S <TERM_...> FUNCIAMI");
pop_up(3, 7, 59, 3, 0xb102); /* uloz vyrez pre menu */
cp=*a_menu;
for(;;){
cp=term_menu(4, 9, a_menu, cp, 0xb100, NULL, 1);
if((*cp=='Q')||(cp==NULL)) break;
else /*submenu1(*cp, 5+*cp-'A'); */
{
if(*cp=='B'){ if(blik==0){pf=ef;blik=1;}
else {pf=nf;blik=0;}
term_printf(1,5,pf,
"UKAZKA ZAKLADNYCH PRINCIPOV
PRACE S <TERM_...> FUNCIAMI");
}
if(*cp=='I') {
pop_up(7, 5, 73, 7, 0xe402); /* uloz vyrez Init*/
term_box_fill(7,5,73,7,6<<12|7<<8|0x8000,1,219);
term_printf(8,7,7<<12|0<<8|0x8000,
"if(!term_load(stdout))
error( \"pop: Unable to load tcap entry.\\n\");");
term_printf(9,7,7<<12|0<<8|0x8000,
"term_video_on(); /* enable terminal shadow image */ ");
term_printf(10,7,7<<12|0<<8|0x8000,
"term_colour(0x17);
/* set screen colors (white on blue) */ ");
term_printf(11,7,7<<12|0<<8|0x8000,
"term_clear(_CLS_SCRH);
/* set screen buffer to a known state */");
term_printf(12,7,7<<12|0<<8|0x8000,
"set_option(stdin,
get_option(stdin) & ~(EDIT|ECHO));");
}
if(*cp=='O') {
pop_down(); /* obnov vyrez - (zrus) Init */
term_box_fill(15,5,20,4,7<<12|7<<8|0x8000,1,219);
}
if(*cp=='P') {
term_box_fill(15,28,50,3,7<<12|7<<8|0x8000,1,219);
term_printf(16,29,7<<12|0<<8|0x8000,
"term_box_fill(15,5,20,4,7<<12|7<<8|0x8000,1,219);");
}
if(*cp=='T') {
term_box_fill(20,5,16,4,5<<12|7<<8|0x8000,1,219);
term_printf(21,6,7<<12|0<<8|0x8000,"Tu som v okne");
}
if(*cp=='V') {
term_box_fill(20,23,55,4,5<<12|7<<8|0x8000,1,219);
term_printf(21,25,7<<12|0<<8|0x8000,
"term_box_fill(20,5,16,4,5<<12|7<<8|0x8000,1,219);");
term_printf(22,25,7<<12|0<<8|0x8000,
"term_printf(21,6,7<<12|0<<8|0x8000,
\"Tu som v okne\");");
}
}
}
pop_down(); /* obnov vyrez pod menu */
if(!strcmp(tcap_entry.term_name, "qnx"))
fput("\033R", 2, stdout);
/* restore original screen colours */
term_clear(_CLS_SCRH);
return 0;
}
void submenu1(char ch, int row)
{
char *cp;
pop_up(row, 13, 20, 8, 0xe402);
cp=*b_menu;
term_type(row, 15, &ch, 1, 0xe402);
for(;;){
cp=term_lmenu(row+1, 15, b_menu, cp, 0xe400, NULL, 1);
if((*cp=='Q')||(cp==NULL)) break;
else submenu2(*cp, row+*cp-'A'+2);
}
pop_down();
}
void submenu2(char ch, int row)
{
char *cp;
pop_up(row, 18, 20, 8, 0xf102);
cp=*c_menu;
term_type(row, 20, &ch, 1, 0xf102);
for(;;){
cp=term_lmenu(row+1, 20, c_menu, cp, 0xf100, NULL, 1);
if((*cp=='Q')||(cp==NULL)) break;
}
pop_down();
}
void pop_up(int y, int x, int width, int length,
unsigned attr)
{
int i;
struct screen_save *s;
char *p;
struct state_entry vstate;
unsigned offset;
if(top_screen>=MAX_SCREENS)
error("pop: Not enough screen buffers.\n");
if(!bytes){
bytes=term_save_image(0, 0, NULL, 0);
memset(screens, 0, sizeof(screens));
}
offset=width*bytes;
term_get_state(&vstate);
if((screens[top_screen].data=calloc(offset,length))==NULL)
error( "pop: Out of memory\n" );
s=&screens[top_screen];
s->cursor_x=vstate.state_cursor_x;
s->cursor_y=vstate.state_cursor_y;
s->x=x;
s->length=length;
s->y=y;
s->width=width;
p=s->data;
for(i=y; i<LENGTH+Y; p="s-" s="&screens[top_screen];" buffer.\n?); screen unused restore to Attempt error(?pop: if(screens[top_screen].data="=NULL)" screens\n?); many too Restored if(--top_screen<0) offset; length, width, y, x, unsigned *p; char *s; screen_save struct i; int { pop_down() void } ?); ? _BOX_FRAME, attr, term_box_fill(y, ++top_screen; p+="offset;" width); p, term_save_image(i, * window the of line each save ++i){>data;
x=s->x;
y=s->y;
length=s->length;
width=s->width;
offset=width*bytes;
for(i=y; i<Y+LENGTH; restore x, } p+="offset;" width); p, * window the of line each ++i){ term_cur(s- term_restore_image(i,>cursor_y, s->cursor_x);
free(s->data);
s->data=NULL;
}
EsTo
Komentáre