Programovací jazyk C <br> <font size="+0">Doplňkové skriptum</font>

Programovací jazyk C
Doplňkové skriptum

Stanislav Kračmar,  Jiří Vogel

1995

Předmluva

Obsáhlé monografie věnované programovacímu jazyku C si kladou za cíl pouze popsat syntaxi jazyka, nebo naučit čtenáře programovat v tomto jazyku. Tato útlá skripta si kladou za ambiciózní cíl obě tyto věci.

Vznikla po odpřednášení několika jednosemestrálních doporučených kurzů: Programovací jazyk C. Při sestavování těchto přednášek jsme vycházeli hlavně z publikace [3], jejíž autoři jsou zároveň tvůrci jazyka C. Další publikace, ze kterých jsme vycházeli, jsou uvedeny v seznamu literatury. Publikace [1], [2] a [4], [5] pojednávají o standardu ANSI C. Publikace [3] o původní K&R verzi jazyka C, [6] pojednává o základech operačního systému UNIX ve dvaceti pokračováních v časopisu Bajt.

Skripta jsme se snažili psát maximálně stručně a důraz jsme kladli na výklad syntaxe jazyka, který jsme doplňovali většinou jednoduchými příklady. Číslované příklady byly odladěny (použili jsme produkty firmy Borland turbo c++, Borland c++), takže syntaktické ani algoritmické chyby by se v nich neměly vyskytovat.

První kapitola tvoří úvod do jazyka C. Na příkladech jsou zde vysvětleny základní pojmy. Tuto kapitolu by si měl každý čtenář důkladně promyslet a příklady odzkoušet.

Ve druhé kapitole jsou definovány odvozené datové typy jako např. pole, struktury, výčtové typy a uniony a jejich souvislost s velmi důležitými datovými typy - ukazateli.

Kapitola třetí pojednává o vstupech a výstupech, čtvrtá je věnována dynamickému rozdělení paměti, což je důležitá a ne právě jednoduchá partie jazyka C a programování vůbec.

Pátá kapitola pojednává o direktivách s kterými pracuje předpřekladač, který je standardní součástí jazyka C a o podmíněném překladu.

Kapitola šestá je věnována operačnímu systému UNIX a důležitému dialektu jazyka C často označovaného K&R, ve kterém je napsána velká část UNIXu.

Kapitolu sedmou tvoří seznam knihovních funkcí zahrnutých do standardu ANSI C.

Za jednotlivými kapitolami nejsou umístěny příklady, skripta jsou určena jako doplňková pro výběrový předmět, ve kterém se příklady zadávají.

Závěrem bychom chtěli upozornit, že ten, kdo se chce seznámit s konkrétními možnostmi konkrétní implementace jazyka C, tj. např. grafickými a matematickými knihovnami, možnostmi optimalizace programu apod., nechť tato skripta odloží. Kdo se chce obecně seznámit se standardem jazyka C a použitím jazyka při algoritmizaci, ten by snad mohl toto skriptum použít jako užitečnou pomůcku.

Je naší milou povinností poděkovat RNDr. Zuzaně Mojžíšové z oddělení informatiky Obvodního úřadu v Praze 8 za pečlivé přečtení rukopisu a cenné připomínky.



                                                                                                                                                                                                           Autoři

Poznámka k "elektronické" podobě skript

Skripta jsme přepsali do jazyka HTML, abychom studentům usnadnili přístup a abychom zároveň demonstrovali užitecnost práce s tímto tvarem informací. Dnešního dne rušíme zprávu: Contents under Construction , ale za jakékoliv připomínky na našich e-mailových adresách:

kracmar@marian.fsik.cvut.cz
vogel@fsid.cvut.cz

předem dekujeme.



                                                                                                                                                                                                           Autoři

Praha 11. července 1998

Obsah

  1. Úvod do jazyka C
    1. První programy
    2. Lexikalní elementy
      1. Znaky
      2. Celočíselné a racionální konstanty
      3. Znakové konstanty
      4. Řetězce
      5. Identifikátory
      6. Klíčová slova
    3. Typy, operátory, výrazy
      1. Základní datové typy
      2. Definice a deklarace jednoduchých proměnných
      3. Objekty jazyka
      4. Aritmetické operátory a výrazy
      5. Konverze
      6. Relační a logické operátory
      7. Operátory přiřazení, přířazovací výraz
      8. Operátory inkrementace a dekrementace
      9. Operátor čárka
      10. Podmíněný výraz
      11. Priorita operací
    4. Příkazy
      1. Prázdný příkaz
      2. Výrazový příkaz
      3. Složený příkaz - blok
      4. Přiřazovací příkaz
      5. Cykly
        1. Příkaz while
        2. Příkaz do
        3. Příkaz for
        4. Příkaz break
        5. Příkaz continue
      6. Podmíněný příkaz
      7. Přepínač
      8. Příkaz go to
    5. Funkce
      1. Definice a vyvolání funkce
      2. Rekurzivní volání funkce
      3. Návratová hodnota funkce main()
      4. Funkce s proměnným počtem parametrů

  2. Datové typy
    1. Globální a lokální proměnné
      1. Globální proměnné
      2. Lokální proměnné
    2. Ukazatelé
      1. Operátory reference a dereference
      2. Definice proměnných typu ukazatel
      3. Ukazatel na typ void
      4. Aritmetické operace s ukazateli
      5. Ukazatelé a řetězové konstanty
    3. Ukazatelé a funkce
      1. Výstupní parametry funkcí
      2. Funkce jako parametr funkcí
    4. Pole
      1. Definice pole, prvky pole
      2. Inicializace pole
      3. Ukazatele a pole
      4. Pole jako parametr funkce
    5. Operátor typedef
    6. Výčtový typ
    7. Struktury
      1. Definice struktury, položky struktury
      2. Struktury a pole
      3. Struktury a funkce
    8. Uniony

  3. Vstup a výstup
    1. Standardní vstup a vstup a výstup
      1. Vstup a výstup znaků
      2. Formatovaný výstup - funkce printf()
      3. Formátovaný vstup - funkce scanf()
      4. Vstup a výstup řádek
    2. Vstup ze souboru a výstup do souboru
      1. Otevření a uzavření souboru
      2. Základní operace s otevřeným souborem
        1. Vstup znaků ze souboru a výstup znaků do souboru
        2. Formátovaný vstup a výstup
        3. Vstup řádek ze souboru a výstup řádek do souboru
      3. Práce s binárními soubory
      4. Parametry funkce main()

  4. Dynamická alokace paměti
    1. Dynamicky definované pole
      1. Dynamicky definované jednorozměrné pole
      2. Dynamicky definovaná vícerozměrná pole
    2. Dynamicky definované datové struktury
      1. Spojový seznam
      2. Binární strom

  5. Poznámky k předpřekladači
    1. Direktiva #define
      1. Direktiva #define bez parametrů
      2. Direktiva #define s parametry
    2. Operátory # a ##
    3. Podmíněný překlad

  6. Vazba na systám UNIX
    1. Některé základní příkazy operačního systému UNIX
    2. Překlad zdrojových souborů
    3. Hlavní rozdíly mezi jazykem ANSI a K&R
    4. Editor vi
    5. Vstupy a výstupy nízké úrovně
      1. Logická čísla
      2. Vstup a výstup nízké úrovně
      3. Použití příkazů open(), create(), close(), unlink()
      4. Přímý přístup příkazy seek() a lseek()

  7. Knihovny funkcí standardu ANSI
    1. Rozpoznávání skupin znaků
    2. Konverzní funkce
    3. Souborově orientované funkce
    4. Práce s řetězci a bloky v paměti
    5. Matematické funkce
    6. Práce s dynamickou pamětí
    7. Práce s datem a časem
    8. Práce s proměnným počtem parametrů
    9. Řízení procesů a některé další funkce
  8. Literatura

Kapitola 1
Úvod do jazyka C

V této kapitole se seznámíme se základními pojmy programovacího jazyka C. Většinu těchto pojmů budeme ilustrovat na jednoduchých programech (nebo částech programů) zapsaných v tomto jazyce. Téměř každá učebnice jazyka C začíná programem, který je analogií následujícího programu.

1.  První programy

Začneme jednoduchým programem, který na monitoru počítače zobrazí text

Muj prvni program. Program v jazyce C bude vypadat takto:


Příklad 1. by 1 11:

#include <stdio.h>
main()
{
  printf("Muj prvni program.");
}

V programovacím jazyce C (narozdíl od mnoha jiných programovacích jazyků např. FORTRAN, COBOL) jsou rozlišována velká a malá písmena. Jména main, printf musí být zapsána takto a nikoliv např. MAIN nebo Printf.

Program v jazyce C je tvořen posloupností definic funkcí a definic tzv. globálních objektů (o těch se zmíníme později, viz ). Každá funkce je identifikována svým jménem (identifikátorem). Definici nové funkce lze vytvářet voláním jiných funkcí. Některé funkce nemusíme sami programovat, ale máme je k dispozici v knihovnách. Součástí ANSI normy jazyka C je i specifikace standardních knihoven, těmi se budeme zabývat v poslední kapitole těchto skript. Tzv. podprogramy (vlastní procedury) v jazyce C jsou považovány za zvláštní funkce, které nevracejí žádnou hodnotu jako výsledek; přesněji vrací hodnotu typu void.

Právě jedna z funkcí programu se jmenuje main. Po spuštění programu se začíná plnit první instrukce této funkce. Funkce main je tedy obdoba tzv. hlavního programu známého z jiných programovacích jazyků.

Uvedený program se skládá z definice právě jedné funkce (která se tudíž musí jmenovat main), v programu není definován žádný globální objekt. Definice každé funkce je tvořena hlavičkou funkce a tělem funkce. Hlavička funkce main má tvar main(), nemá tedy žádné parametry a nemá ani explicitně určen typ výsledku. Jak uvidíme později, implicitně se doplní typ hodnoty funkce na celá čísla. Tělo funkce tvoří blok { printf("Muj prvni program.");} , který obsahuje vyvolání jedné knihovní funkce printf, což je standardní knihovní funkce jazyka C pro formátovaný výstup. Jeden nebo více jejích parametrů se zapisují do okrouhlých závorek (). Funkce printf opíše na standardní výstup (např. na obrazovku) řetězec znaků tvořící v našem případě jediný argument funkce. Řetězec (řetězcový literál) je posloupnost znaků uzavřená mezi uvozovky: "Muj prvni program.". Tiskne se ovšem řetězec bez uvozovek, tedy


Muj prvni program.



Řádky programu začínající znakem # obsahují příkazy (direktivy) pro tzv. předpřekladač (preprocesor). Předpřekladač je součást překladače, která upravuje zdrojový text, vynechává např. komentáře, zajišťuje vložení hlavičkových souborů, rozvoj maker atd. Výsledkem práce předpřekladače je opět textový soubor. První řádek našeho programu, příkaz #include <stdio.h> zajistí, aby na toto místo v programu byl vložen (textový) hlavičkový soubor stdio.h (standardní input/output) obsahující popis standardních funkcí pro vstup a výstup, tedy mj. i popis funkce printf. Dalším příkazem, který budeme od začátku často využívat, je direktiva #define. Např.


#define MAX 5 


zajistí, že od místa výskytu tohoto příkazu až do konce programu se každý identifikátor MAX nahradí konstantou 5. Takto zavedená hodnota MAX se nazývá symbolická konstanta. Systematicky se příkazy pro předpřekladač budeme zabývat v kapitole .


Pro úpravu programu platí v jazyce C jen málo omezení. Pravidla zápisu programu např. v jazyce FORTRAN určují striktně formu programu. Volnost zápisu v jazyce C může být využita více nebo méně šťastným způsobem. Následující dvě verze programu 1.1 jsou z hlediska překladače zcela v pořádku a dosahují stejného výsledku jako původní program, nejsou však příliš přehledné. Při zápisu dalších programů budeme proto uplatňovat jisté stylistické konvence, které většina programátorů v jazyce C dodržuje.


Příklad 1. by 1 11:

#include <stdio.h>
main(){printf("Muj prvni program.");}


Příklad 1. by 1 11:

#include <stdio.h>
main
(
)
{
printf("Muj prvni program.");
}


Programy 1.1 až 1.3 se liší úpravou, tj. rozmístěním tzv. bílých znaků. Bílé znaky jsou znaky, které textový editor použitý při prohlížení a editaci textového souboru reprezentujícího program nezobrazí, ale interpretuje je např. jako vynechání prázdného místa, přechod na další řádek, tabulátor apod.; to jsou tzv. bílé znaky na úrovni zdrojového programu.

Kdybychom např. program 1.1 spustili dvakrát po sobě, vytisklo by se


Muj prvni program.Muj prvni program.

Kdybychom chtěli, aby se tento text vypisoval při opětovném spuštění programu vždy z nové řádky, doplnili bychom do řetězce dvojznak \n, které mají význam přechodu na nový řádek na výstupu. Dvojznak \n tedy umožňuje vkládaní určitého bílého znaku do výstupního, programem zpracovávaného textového souboru. Vyvolání funkce  printf by tedy v tomto případě vypadalo takto:  printf("\nMuj prvni program.");

V programech 1.1 byla funkce  printf používaná pro zobrazení řetězce znaků. Funkci  printf je možné použít i pro výstup hodnot výrazů a proměnných.


Příklad 1. by 1 11:

#include <stdio.h>
#define M 10
#define N 20
main()
{ int sum;

  sum=M+N;
  printf("\nTisk hodnoty celociselne konstanty: %d",100);
  printf("\nCislo M=%d",M);
  printf("\nCislo N=%d",N);
  printf("\n\nSoucet cisel M a N je %d",sum);
  printf("\nDruha mocnina tohoto souctu je %d\n",sum*sum);
}

Příkazy #define jsou zavedeny symbolické konstanty M a N. Předpřekladač nahradí identifikátory M a N v textu programu konstantami 10 a 20, k této náhradě nedojde uvnitř řetězců. První řádka těla funkce je tzv. definice. Tak jako v mnoha jiných programovacích jazycích i v jazyce  C musí být proměnné před použitím definovány. Proměnná je místo v paměti, do kterého mohou být zapisována data (hodnoty) určitého typu. V jazyku C na proměnné zpravidla odkazujeme pomocí identifikátorů, které představují jména těchto míst v paměti. V našem programu je definicí vyhrazeno určité místo v paměti pro ukládání hodnoty typu int tj. celočíselné hodnoty a toto místo je označeno identifikátorem sum. Stručněji v této situaci budeme říkat, že proměnná  sum je definována jako typ int.

Označení int je příklad tzv. klíčového slova. Klíčová slova jsou vlastně vyhrazené identifikátory, které slouží pro označení standardních typů dat, některých příkazů jazyka C apod.

Druhá řádka těla funkce, která zajistí, že se sečtou hodnoty 10 a 20 a výsledná hodnota se uloží na místo v paměti označené jako  sum, je příkladem přiřazovacího příkazu. Výraz na pravé straně symbolu   =  se vyhodnotí a výsledná hodnota se přiřadí proměnné zapsané na jeho levé straně. Středník je zde součástí zápisu přiřazovacího příkazu, nelze ho tedy vynechat ani za posledním přiřazením. V tomto smyslu není oddělovacím znakem od ostatních příkazů jako v některých jiných programovacích jazycích.

Příkazy vyvolání knihovní funkce  printf obsahují dva parametry: znakový řetězec a aritmetický výraz, jehož hodnota se bude tisknout zároveň se znakovým řetězcem. Znak d bezprostředně následující za znakem % určuje, že např. hodnota 30 proměnné sum při vyvolání knihovní funkce printf se vytiskne jako dekadické celé číslo. Také zde je středník součástí vyvolání knihovní funkce.


Výstup programu bude následující:


Tisk hodnoty celociselne konstanty: 100
Cislo M=10
Cislo N=20

Soucet cisel M a N je 30
Druha mocnina tohoto souctu je 900


V tomto programu lze najít všech šest druhů symbolů, které v jazyce C ze syntaktického hlediska rozeznáváme: identifikátory, klíčová slova, konstanty, řetězce, operátory a oddělovače.


identifikátory      main   printf   sum
klíčová slova       int
konstanty           100
řetězce             "\n\nSoucet cisel M a N je %d"
operátory           + *
oddělovače          { } ( ) ; " ,
Pro umístění bílých znaků platí následující pravidlo: Klíčová slova, identifikátory a konstanty, pokud se skládají z více znaků, nesmějí být rozděleny vložením bílého znaku. Totéž platí pro operátory, které se skládají z více znaků, jež se zapisují bezprostředně za sebou. Do textu řetězce také nesmí být vkládány bílé znaky kromě mezery, která řetězec nerozděluje, ale stává se jeho součástí.

K dobrým programátorským zvykům patří vkládat do programů komentáře. Usnadňují čtení programů při jejich ladění a případných pozdějších úpravách. Následující program ilustruje používání komentářů na příkladu předchozího programu.


Příklad 1. by 1 11:

/*  Ukazka pouziti komentare:
**  Tento  program  vypocita soucet  dvou  celociselnych
**  konstant  a  priradi  vysledek  promenne  'sum' typu
**  integer. Vysledna hodnota bude zobrazena na monitoru
**  pocitace spolu se svou druhou mocninou a doplnujicim
**  textem.                                                    */
#include <stdio.h>
#define M 10
#define N 20
main()
{ int sum;                  /* definice promenne 'sum'         */
  sum=M+N;                  /* prirazeni souctu promenne 'sum' */
/**** nasleduje zobrazeni hodnot '100','M','N','sum' ***********/
  printf("\nTisk hodnoty celociselne konstanty: %d",100);
  printf("\nCislo M=%d",M);
  printf("\nCislo N=%d",N);
  printf("\n\nSoucet cisel M a N je %d",sum);

/**** a druhe mocniny 'sum'                          ***********/
  printf("\nDruha mocnina tohoto souctu je %d\n",sum*sum);
  return;   /* ukonceni main() - zde lze tento prikaz vynechat */
}

Začátek komentáře je označen dvojicí znaků /*, konec je označen stejnými dvěma znaky, zapsanými v obráceném pořadí, tedy */. Text komentáře je překladačem ignorován. Podle ANSI normy jazyka C komentáře nemohou být vnořené (vhnízděné). (Řada existujících implementací tuto možnost ale programátorovi nabízí.) Do komentáře je povoleno zapisovat bílé znaky bez omezení, tyto bílé znaky ale nesmí rozdělovat dvojici znaků /* resp. */, která otevírá resp. uzavírá komentář.

Pro umístění komentáře platí stejná omezení jako pro vkládání bílých znaků do programu v jazyce C. Komentář nelze např. vkládat do řetězců, protože takto umístěný "komentář" by překladačem nebyl ignorován. Příkazem return se budeme zabývat v části 1.5.1.


V následujícím výkladu budeme obvykle komentáře používat k podrobnému glosování jednotlivých programů.


Příklad 1. by 1 11:

/*      Tento program vypocita soucet prvnich MAX prirozenych  **
**      cisel a soucet druhych mocnin techto cisel.            */
#include <stdio.h>
#define MAX 10
main()
{ int i,s1,s2;        /* definice promennych 'i' 's1' a 's2'    */
  i=1; s1=0; s2=0;    /* prirazeni pocatecnich hodnot promennym */

  while(i<=MAX)       /* cyklus pokracuje, pokud i<=MAX         */
  {
        s1=s1+i;      /* soucet prirozenych cisel 1 az MAX      */
        s2=s2+i*i;    /* soucet jejich druhych mocnin           */
        i=i+1;
  }
  /* tisk vysledku: */
  printf("\n\n\nSoucet prvnich %d prirozenych cisel je %d.",MAX,s1);
  printf("\nSoucet jejich druhych mocnin je %d.",s2);
}


Příkaz while je jeden z příkazů cyklu jazyka C. Plnění příkazu while začíná vyhodnocením podmínky, uzavřené v okrouhlých závorkách za tímto klíčovým slovem. Jestliže je výsledek nenulový - což odpovídá v jazyce C kladné pravdivostní hodnotě - potom je splněn jediný příkaz, který následuje za okrouhlými závorkami obsahujícími podmínku příkazu while. Několik příkazů je možné sloučit do jediného složeného příkazu pomocí složených závorek jako v uvedeném programu. Celý proces se opakuje a začíná opětným vyhodnocením podmínky příkazu while. Cyklus se provádí do té doby, dokud výsledek vyhodnocení podmínky je nenulový. Když je podmínka vyhodnocena jako nulová hodnota - což odpovídá záporné pravdivostní hodnotě - potom se cyklus přeruší a program pokračuje plněním následujícího příkazu. Poznamenejme ještě, že v uvedeném programu definice proměnných a přiřazení počátečních hodnot

                     int i,s1,s2;
                     i=1; s1=0; s2=0;

může být nahrazeno tzv. definicí s inicializací

                     int i=1,s1=0,s2=0;

V následujícím programu se seznámíme s funkcemi určenými pro vstup a výstup znaků.


Příklad 1. by 1 11:

/*
** Tento program kopíruje posloupnost znaku ze standardniho vstupu
** na standardni vystup. Jako ukoncovaci znak je pouzit znak '@'.
*/
#include <stdio.h>
#define ZNAK '@'
main()
{ int c;                                 /* definice promenne 'c' */
  /* cyklus pokracujici dokud neni z klavesnice precten znak ZNAK */
  while((c=getchar())!=ZNAK)
     putchar(c);
  return;
}


Funkce getchar() je určená pro čtení znaku ze standardního vstupního zařízení tj. zpravidla z klávesnice. Funkce nemá parametry, návratová je typu int, funkce vrací celočíselný kód přečteného znaku. Funkce putchar() je určena pro výstup jednoho znaku na standardní výstupní zařízení. Funkce má jeden parametr typu int - kód tisknutého znaku. Dvojznak != je relační operátor "nerovná se". Funkcí getchar() jsou tedy postupně čteny znaky z klávesnice a porovnávány se znakem zadaným instrukcí #define.

2.  Lexikální elementy

2.1  Znaky

Znaky hrají důležitou roli ve standardním jazyku C. Program v jazyku C je zapsán v jednom nebo v několika zdrojových souborech. Tyto zdrojové soubory jsou textové soubory zapsané pomocí znaků, které můžeme přečíst, jestliže zdrojové soubory zobrazíme na monitoru nebo vytiskneme na tiskárně. Znaky jsou definovány tzv. znakovou sadou, která každému znaku přiřazuje i určité celé nezáporné číslo - kód.

Nulová hodnota kódu je v každé znakové sadě vyhrazena pro nulový znak (null character, null).

Minimální znaková sada, ve které lze zapsat libovolný program v jazyku C (minimal C character set) obsahuje následující znaky, které mají grafickou podobu (tištitelné znaky):

444444444444               444444444444444 4444444444444  A B C D E F G H I J K L M
 N O P Q R S T U V W X Y Z
malá písmena:  a b c d e f g h i j k l m
 n o p q r s t u v w x y z
číslice:  0 1 2 3 4 5 6 7 8 9
podtržítko:  _
pomocné znaky:  ! " # % & ' ( ) * , - . / :
 ; < = > ? [ ] \ ^ { } ~ |

Minimální znaková sada dále obsahuje kódy pro některé další (netištitelné) znaky:


444444444444               444444444444444 4444444444444  Význam:
space  vynechání mezery (space)
BEL  zvukové znamení (alert)
BS  posun kurzoru vlevo (backspace)
FF  odstránkování (form feed)
NL  nová řádka (newline)
CR  návrat kurzoru na začátek řádky (carriage return)
HT  horizontální tabelátor
VT  vertikální tabelátor
null  znak, jehož kódová hodnota je v každé znakové sadě 0

Tyto netištitelné znaky (tedy znaky, které nemají grafickou reprezentaci) a dále čtyři následující tištitelné znaky

                  '    "     ?      \

se zapisují pomocí znaku \ změnovými posloupnostmi (tzv. escape sekvence). Změnovou posloupnost \n jsme už používali v programech 1.1 až 1.3 pro zápis znaku NL. Některé často používané změnové posloupnosti jsou uvedené v následující tabulce:

444444444                                           4444444 4444444444444  Změnová  Označení   Změnová
znaku:  posloupnost:  znaku:   posloupnost:
     

'  \'  FF  \f
"  \"  NL  \n
?  \?  CR  \r
\  \\  HT  \t
BEL  \a  VT  \v
BS  \b  null  \0

Další možností, jak vyjádřit libovolný znak, jsou tzv. numerické změnové posloupnosti tj. zápis

                \D       nebo       \xH      nebo     \XH

kde D je oktalový (osmičkový) a H je hexadecimální (šestnáctkový) kód znaku v dané znakové sadě, která je nejčastěji zadána ASCII tabulkou. D obsahuje 1 až 3 významové cifry, H obsahuje 1 až 2 významové cifry. Viz příklad v odst. .

2.2  Celočíselné a racionální konstanty

Celočíselné konstanty jsou celá čísla, která mohou být zapsána v desítkové (decimální), osmičkové (oktalové) nebo šestnáctkové (hexadecimální) soustavě.

Desítková celočíselná konstanta je posloupností desítkových číslic nezačínající číslicí 0 (s výjimkou čísla nula). Zápisy

              4563       58      10        0

představují správné příklady desítkových konstant. (Znaménko není v jazyku C součástí syntaxe konstanty, ale je aplikací unárního operátoru + nebo - na tuto konstantu. Pro zjednodušení výkladu budeme ale celočíselnou konstantou rozumět nadále i posloupnost cifer opatřenou znaménkem, např. tedy i zápisy: -68  +9525 apod. Do zápisu není povoleno vkládat čárku nebo bílý znak.

Oktalová konstanta je zapsána jako posloupnost oktalových cifer (0 až 7) začínající cifrou 0. Příkladem oktalových konstant mohou být zápisy:

             05671     0255     0000   01

Hexadecimální konstanta se zapisuje jako posloupnost hexadecimálních cifer začínající 0x nebo 0X. Hexadecimální cifry zahrnují číslice 0 až 9 a písmena A až F (nebo a až f). Písmena reprezentují hodnoty 10 až 15. Příkladem hexadecimálních konstant mohou být zápisy:

             0XFF   0x48     0x1f   0XfF

Racionální konstanty zapisujeme v desetinném nebo v semilogaritmickém tvaru. V zápisu používáme desetinnou tečku a exponent označujeme písmenem e nebo E (tak jako je to obvyklé i u jiných programovacích jazyků.)



Příklady zápisu číselných konstant:

4444444444444                                          ¯ 4444444444444  Matematický zápis  Zápis v C  Matematický zápis   Zápis v C
-500  -500  10-7  1E-7
2,345  2.345  6·108  6e8
-0,5  -.5  (23)8  023
6,25 103  6.25E3  (FA)16  0XFA

2.3  Znakové konstanty

Znaková konstanta je právě jeden znak zapsaný mezi apostrofy, např.:


       'X'    '\''     '\n'    '\x0a'   '\0'
Hodnota znakové konstanty je kód příslušného znaku v používané znakové sadě. Pokud se vám zdá, že v předchozích příkladech jsou někde mezi apostrofy zapsány dva nebo více znaků, podívejte se, jak jsou znaky definovány a jak se zapisují.



Příklad 1. by 1 11: Následující zápisy mají stejnou hodnotu rovnou deseti:

     '\012'   '\12'    10     '\xa'     0xa

a tyto zápisy jsou chybné:

     '\0012'    '\0xa'     xa

2.4  Řetězce

Řetězec je v jazyku C posloupnost znaků uzavřená mezi uvozovkami.

Např. řetězcová konstanta "xyz" je posloupnost znakových konstant uložených v po sobě jdoucích bytech paměti, za kterými následuje byte obsahující nulovou hodnotu:

                 'x', 'y', 'z', '\0'

Vyskytuje-li se v řetězci znak uvozovka, musí jej předcházet znak zpětné lomítko. Tisk textu:


Tisk v palcich (")

se zobrazí např. příkazem: printf("Tisk v palcich (\")");

ANSI C umožňuje automatické zřetězení řetězců:

"Dlouhy retezec lze "

"takto rozdelit"

Toto lze např. s výhodou použít při rozdělení konverze v příkazu printf():

printf("Tento text se bude"

" tisknout na jedne radce");

2.5  Identifikátory

Identifikátor (nebo též jméno) je posloupnost maximálně 31 písmen, číslic a podtržítek začínající písmenem nebo podtržítkem. Identifikátory používáme k označení objektů jazyka (proměnné, pole symbolické konstanty apod.). V jazyku ANSI C se rozlišují velká a malá písmena, tedy

Nejneobhospodarovatelnejsi je jiným identifikátorem než

nejneobhospodarovatelnejsi

Zápis je ovšem nevhodným užitím identifikátorů nejen pro využití rozdílu velkého a malého písmene, ale i pro délku identifikátorů. Identifikátory mají být pokud možno krátké a hledáme-li optimum mezi stručností a mnemotechnikou měli bychom se snažit o délku šesti, maximálně osmi znaků. Je vhodné používat jednoho druhu písma, obvykle malých písmen.

Příklady:

 proud_1        proud_2        x633         zz         znak_a

2.6  Klíčová slova.

Pojem klíčového slova jsme probrali v části 1.1. V jazyce ANSI C je 32 následujících klíčových slov tj. jmen, které mají pro překladač speciální význam:

auto            double            int            struct
break           else              long           switch
case            enum              register       typedef
char            extern            return         union
const           float             short          unsigned
continue        for               signed         void
default         goto              sizeof         volatile
do              if                static         while
Tato slova jsou v ANSI C vyhrazenými identifikátory, které se nesmějí používat k označení objektů. ANSI norma zavádí nová klíčová slova const, enum, signed, void, volatile.

3.  Typy, operátory, výrazy

V tomto článku vysvětlíme podrobněji definice a deklarace typu pro jednoduchou proměnnou a vytváření výrazů pomocí aritmetických a logických operací definovaných v jazyku.

3.1  Základní datové typy

V jazyku ANSI C jsou definovány následující standardní typy:

char   - obvykle jedna slabika (byte) pro uchování jednoho
         znaku.
int    - celé číslo, (obvykle dvě slabiky)
float  - racionální číslo (obvykle čtyři slabiky)
double - racionální číslo dvojnásobné přesnosti
Slovo öbvykle" v uvedených příkladech se vztahuje na většinu implementací. Konkrétní rozsahy pro jednotlivé datové typy jsou uvedeny v hlavičkových souborech limits.h a float.h, které jsou součástí každé implementace.

Kromě toho existují v jazyce C modifikátory short tj. "krátký", long tj. "dlouhý", signed tj. se znaménkem a unsigned tj. bez znaménka, kterými lze standardní typy modifikovat. Ne všechny kombinace jsou ovšem povoleny. Pokud je nějaká kombinace povolená, potom lze zapisovat klíčová slova v libovolném pořadí. K modifikátorům patří i klíčové slovo const. Identifikátoru označenému v definici tímto modifikátorem se zároveň přiřadí hodnota; identifikátor se stává tzv. symbolickou konstantou.


Příklady:

 const float PI=3.141593;          /* definice symbolicke konstanty */
 const L=3;            /* definice celociselne symbolicke konstanty */
 const int K=235;      /* definice celociselne symbolicke konstanty */
 short i=0;            /* definice zkracene celociselne promenne    */
 short int j=1;        /* definice zkracene celociselne promenne    */
 long int m=12345678;  /* definice dlouhe celociselne promenne      */
 unsigned int n;       /* definice nezaporné celociselne promenne   */
 unsigned nm;          /* definice nezaporne celociselne promenne   */

V následující tabulce jsou uvedeny povolené kombinace klíčových slov pro základní datové typy s typovými modifikátory a jejich zkrácené ekvivalentní vyjádření:

signed char    (většinou totéž co char, závisí na implementaci!)
unsigned char
int = signed = signed int
short = short int = signed short = signed short int
unsigned short = unsigned short int
long = long int = signed long = signed long int
unsigned long = unsigned long int
float
double
long double

Z uvedené tabulky je patrné, že některá klíčová slova se pokládají za implicitní ( int, signed). Konkrétní rozsahy pro jednotlivé datové typy jsou rovněž uvedeny v hlavičkových souborech limits.h a float.h. Uveďme proto pouze, že typ short nemůže být delší a typ long nemůže být kratší než typ int.

3.2  Definice a deklarace jednoduchých proměnných

Pod pojmem definice se míní příkaz, kterým se zavádí nová proměnná, tj. příkaz, kterým se v paměti počítače vyhradí určité místo pro určitý datový typ a toto místo je nazváno určitým jménem (identifikátorem).

Deklarace je naopak příkaz, který pouze informuje překladač o typu určité proměnné, která musí být definována na jiném místě programu, viz čl. 2.1.

Jednoduchá proměnná je proměnná, která v každém okamžiku nabývá hodnoty jedné číselné nebo znakové konstanty.

V následujícím příkladu jsou definovány některé jednoduché proměnné:


Příklad 1. by 1 11:

            int i;
            unsigned long int number_a,number_b;
            char znak1,znak2;
            double eps_1=1.0e-5,eps_2=1.0e-10;
            unsigned char lomitko='/',procenta='%';

Ve čtvrtém a pátém řádku je zapsána tzv. definice s inicializací.


3.3  Objekty jazyka

Kdybychom chtěli zcela přesně definovat další datové struktury, pole (množiny prvků téhož typu hodnot), struktury (množiny prvků různých typů hodnot), uniony atd., výklad by byl sice přesný, ale ztratil by na přehlednosti. Budeme zatím pracovat pouze s jednoduchými proměnnými Se všemi dalšími objekty se podrobně seznámíme ve druhé kapitole.

3.4  Aritmetické operátory a výrazy

Programovací jazyk C podporuje obvyklé aritmetické operace: sčítání +, odčítání -, násobení *, dělení /. Kromě toho obsahuje i operátor dělení modulo %, jehož výsledkem je zbytek po dělení dvou celočíselných hodnot. Právě vyjmenované operátory patří k binárním operátorům, které se aplikují na dva operandy (argumenty). Pokud oba operandy dělení / jsou celočíselné, potom se provede tzv. celočíselné dělení, tzn. že se čísla vydělí a od výsledku se odřízne desetinná část.

Součástí jazyka C jsou i unární operátory - a unární operátor +, přičemž poslední z nich nebyl k dispozici v normě K&R a byl zaveden až v ANSI normě jazyka C. Příklady aritmetických výrazů jsou zapsány na následující řádce:

  24*den            -1024            60*(60*hodiny+minuty)+sekundy
V prvním příkladu se násobí celočíselná konstanta 24 hodnotou proměnné den. Ve druhém příkladu se používá unární operátor - pro vyjádření záporné celočíselné hodnoty.

V následující tabulce je uvedena priorita a asociativita vyhodnocování aritmetických operací.

Třída operátorů:     Operátor:               Asociativita:
unární               +   -                   zprava doleva
multiplikativní      *   /   %               zleva doprava
aditivní             +   -                   zleva doprava
V tabulce jsou operace uspořádány podle priority od nejvyšší k nejnižší. Multiplikativní operátory se tedy např. provádějí dříve než aditivní. U operací se stejnou prioritou rozhoduje o pořadí operací tzv. asociativita.

Asociativita zleva doprava znamená, že z hlediska syntaxe jazyka C je výraz 1+2+3 ekvivalentní výrazu (1+2)+3. Je třeba upozornit, že ani přesně vyznačené pořadí operací není pro kompilátor závazné.

Příklad 1. by 1 11: Hodnoty celočíselných aritmetických výrazů - předpokládáme, že proměnné a, b jsou celočíselné:

  a     b    a/b     (a/b)*b     a%b      (a/b)*b+a%b
  13    5     2       10          3         13
 -13    5    -2      -10         -3        -13
  13   -5    -2       10          3         13
 -13   -5     2      -10         -3        -13

3.5  Konverze

Při vyhodnocování aritmetického výrazu dochází k implicitním konverzím (změnám) typu. Nejprve proběhnou následující celočíselné implicitní konverze:


Po těchto změnách dochází k následujícím konverzím v tomto pořadí:


V ANSI C tedy narozdíl od K&R C tedy operandy typu float nemusí být nutně implicitně konvertovány na typ double.


Hierarchii konverzí datových typů je možné schematicky znázornit takto:



int Ž unsigned int Žlong int Ž unsigned long int Ž


float Ž double Ž long double

3.6  Relační a logické operátory

Relační operátory jsou

< <= >= > 


které mají stejnou prioritu, nižší než dříve uvedené aritmetické operátory. Za nimi, pokud se týká priority, následují operátory rovnosti   a nerovnosti  != .

Logické operátory jsou logický součin (tzv. konjunkce)  && , logický součet (tzv. disjunkce)  || a negace ! . Výrazy se vyhodnocují zleva doprava do okamžiku, kdy je z logického hlediska zřejmý výsledek. Výraz tvaru y!=0 && z/y je v jazyku C správný narozdíl od mnohých jiných jazyků, nulová hodnota proměnné y zde nemá za následek případnou havárii programu. Na tomto místě znovu upozorňujeme, že logické hodnoty pravda, resp. nepravda jsou reprezentovány v jazyku C celočíselnými hodnotami různými od nuly, resp. nulou.

Logické operátory po bitech není možné aplikovat na typy float a double. Mají vyšší prioritu než operace disjunkce a konjunkce. Jsou to operátory:

   &       operátor logického součinu po bitech
   |       operátor logického součtu po bitech
   ^       operátor neekvivalence po bitech
   <<      operátore posunu vlevo
   >>      operátor posunu vpravo
   ~       unární operátor jednotkového doplňku

Příklady:

a) n & 0177

nuluje vše s výjimkou posledních sedmi bitů, protože oktalová konstanta 0177 v binárním zápisu má např. tvar 000 000 001 111 111

b) x << 3

posune hodnoty jednotlivých bitů o tři místa vlevo, což znamená, že se hodnota vynásobí osmi. Poslední tři bity zprava se doplní nulami.

c) x >> 3

posun o tři místa vpravo (tj. u typu unsigned dělení osmi, u typu signed je tato operace implementačně závislá).

d) n & ~0177

vynuluje posledních sedm bitů.

3.7  Operátory přiřazení, přiřazovací výraz

Přiřazovací operátor umožňuje přiřazení určité nové hodnoty některé proměnné programu. Nejjednodušší forma přiřazovacího výrazu je:


proměnná výraz


Operátor způsobí, že se vyhodnotí výraz na pravé straně a výsledná hodnota se přiřadí proměnné na straně levé. Pokud nejsou oba operandy téhož typu je typ výrazu na pravé straně změněn (konvertován) na typ proměnné na levé straně. Priorita operátoru  a jeho asociativita je dána tabulkou v odstavci . Nízká priorita tohoto operátoru zaručuje, že přiřazení bude provedeno, až po vyhodnocení pravé strany.

Přiřazení


proměnná výraz


je opět výraz (přiřazovací výraz), který má hodnotu pravé strany po případné konverzi této hodnoty na typ proměnné, která stojí na levé straně. Přiřazovací výraz je tedy možné používat v kterémkoliv místě programu, kde syntaktická pravidla jazyka C výraz připouštějí, např. jako součást zápisu některých příkazů. Následující příklad je syntakticky správným zápisem přiřazovacího výrazu v jazyce C.


Příklad 1. by 1 11:

                           w=(a=b+c)*(x=y*z)

Jak jsme se zmínili, při vyhodnocení přiřazovacího výrazu se přiřazuje (v podstatě jako vedlejší efekt) nějaká (nová) hodnota určité proměnné.

Přitom dochází k implicitní konverzi hodnoty pravé strany na typ proměnné stojící na levé straně. Například, je-li na pravé straně přiřazovacího výrazu hodnota typu int a na levé straně proměnná typu double, tj. schematicky

                           double=int

dojde před přiřazením k automatické konverzi hodnoty int na typ double. Při této konverzi hodnoty na vyšší typ ("vyšší" ve smyslu hierarchického uspořádání zavedeného v odstavci ) nedochází k problémům, tyto operace jsou jednoznačně definovány a nejsou implementačně závislé.

Pokud je ale konvertována hodnota na "nižší typ", může dojít k různým (implementačně závislým) jevům. Např. při přiřazení typu

                           int=double
je-li hodnota typu double mimo rozsah typu int, pak výsledek této operace není definován a závisí na implementaci. Leží-li tato hodnota v rozsahu typu int, pak se proměnné na levé straně přiřadí hodnota, získaná z původní hodnoty odříznutím desetinné části.


V jazyce C je možné použít i tzv. vícenásobná přiřazení.

Příklad 1. by 1 11:

 výraz:                                  x=y=z=p+q
 je interpretován jako:                  x=(y=(z=p+q))

Kromě uvedeného přiřazovacího operátoru existují v jazyce C ještě tzv. složené přiřazovací operátory. Přiřazení, ve kterých se opakuje levá strana na pravé straně např.: sum=sum+4 je možno zapsat s použitím operátoru += takto:

sum+=4

Obdobně lze použít i operátory  op , kde  op  je jeden z následujících operátorů:

  -      *      /      %     <<     >>      &     ^     |
Výraz
proměnná op výraz

je ekvivalentní s výrazem
proměnná proměnná op (výraz)


Všimněte si v uvedeném zápisu dvojice závorek. Výraz prom_1*=koef+10

tedy znamená totéž jako prom_1=prom_1*(koef+10)

a ne např.: prom_1=prom_1*koef+10


V části jsou uvedeny priorita a asociativita těchto operátorů.

Příklad 1. by 1 11: Hodnoty následujících tří výrazů jsou stejné:

     d/=c+=b*=a        d/=(c+=(b*=a))       d=d/(c=c+(b=b*a))

3.8  Operátory inkrementace a dekrementace

V programovacím jazyce C existují dva speciální unární operátory:

inkrement   ++

dekrement   -

Oba operátory lze použít jako předpony i jako přípony s následujícím odlišným významem:

++proměnná

- hodnota proměnné je nejprve zvětšena o jedničku a teprve potom je tato nová hodnota vrácena jako hodnota aritmetického výrazu


proměnná++

- nejprve je vrácena původní hodnota aritmetického výrazu a teprve potom je hodnota proměnné zvětšena o jedničku.

Příklady:

   stručný zápis:            zápis pomocí přiřazovacího výrazu:
   i++                       i=i+1
   i--                       i=i-1
   ++i                       i=i+1
   --i                       i=i-1

3.9  Operátor čárka

Syntaxe operátoru čárka je:


výraz_1,výraz_2


Význam operátoru: výraz_1 je vyhodnocen a výsledek tohoto vyhodnocení je zapomenut. Pak se vyhodnotí výraz_2, a to je závěrečný výsledek.

Příklad: Nechť i má hodnotu 7 a j má hodnotu 5, pak výraz (3+i-,i-j) bude mít hodnotu 1. Vedlejším efektem je přiřazení hodnoty 6 proměnné i.

3.10  Podmíněný výraz

Chceme-li vyhodnotit výraz v závislosti na nějaké logické podmínce, použijeme tzv. podmíněný výraz, který v C se zapisuje pomocí tzv. ternárního operátoru ? : .

Chceme-li např. vypočítat hodnotu sin(x) / x pro x š 0 a hodnotu 1 pro x = 0 , píšeme

x ? sin(x)/x : 1

Chceme-li realizovat výpočet maxima ze dvou zadaných hodnot, můžeme psát

a > b ? a : b

Zápis se obvykle zpřehledňuje nepovinnými závorkami, např.

(a > b) ? a : b

3.11  Priorita operací

V tomto článku shrneme priority a asociativitu probraných operací, které uvedeme v přehledné tabulce. Uvedeme všechny operátory. Některé z nich probrány nebyly a zmíníme se o nich v dalších kapitolách na vhodných místech.

Ve třetím řádku je třeba upozornit, že operátory +, - jsou unární, narozdíl od operátorů +, - v pátém řádku, které jsou binární. Také operátor & ve třetím řádku je jiným operátorem, než operátor logického součinu & v řádku devátém, viz .

Tabulka je uspořádána od operací s nejvyšší prioritou po prioritu nejnižší. Operátory v témže řádku mají tutéž prioritu.



Operátor Asociativita
() [ ] -> . zleva doprava
! ~ ++ -- + - ( typ) * & sizeof zprava doleva
* / % zleva doprava
+ - zleva doprava
<< >> zleva doprava
< <= >= > zleva doprava
= = ! = zleva doprava
& zleva doprava
^ zleva doprava
| zleva doprava
&& zleva doprava
|| zleva doprava
? : zprava doleva
= + = - = * = / = % = >>= <<= & = / = ^= zprava doleva
, zleva doprava
tabular



4.  Příkazy

Příkazy jazyka C jsou prováděny v pořadí, v jakém byly zapsány (sekvenčně). Některé příkazy ale mohou změnit toto přirozené pořadí a mohou přenést řízení do jiné části programu (skoky, příkazy cyklu, volání funkcí, přepínač atd.) Začneme od nejjednodušších příkazů jazyka C.

4.1  Prázdný příkaz

Syntaxe tohoto příkazu je tvořena samotným znakem středníku:

                            ;

Prázdný příkaz je možné označit návěštím a přenést na něj řízení skokem goto, viz .

4.2  Výrazový příkaz

Výrazový příkaz je výraz ukončený středníkem, výrazový příkaz má tedy následující tvar:


výraz;


K výrazovým příkazům patří např. přiřazovací příkaz (viz ), volání funkce (i v případě, že je typu void) apod.

Příklady:

      a=b+c;     i++;      printf("Ahoj!");      5;    x+y;

Poslední dva příklady výrazových příkazů jsou sice ze syntaktického hlediska správně, ale nemá smysl je používat.

4.3  Složený příkaz - blok

Skupinu příkazů lze pomocí složených závorek sloučit do tzv. složeného příkazu:

                  {a=b+c; i++; printf("Ahoj!");}

Kromě toho je možné umístit na začátek této skupiny příkazů definice proměnných. Vzniká tak konstrukce nazývaná blok.

                  {int a,b,c; a=b+c; i++; printf("Ahoj!");}

Protože středník je v jazyce C ukončovacím znakem některých příkazů a nikoliv oddělovacím znakem jako v některých jiných programovacích jazycích, je třeba ho použít v uvedeném příkladu i za posledním příkazem bloku. Za uzavírací závorkou bloku } se středník nedává, pokud ho tam umístíme, bude chápán jako prázdný příkaz. Syntakticky správně je i konstrukce prázdného složeného příkazu { } .

Jedním z příkazů bloku může být opět blok, mluvíme pak o bloku vnořeném a bloku nadřazeném. Proměnné, které jsou v bloku definovány, jsou v něm lokální - je možné je tedy využívat pouze v rámci tohoto bloku a z jiných částí programu nejsou viditelné. Pokud se identifikátor lokální proměnné bloku kryje s identifikátorem proměnné v nadřazeném bloku (nebo s identifikátorem globální proměnné, viz ) bude identifikátor z nadřazeného bloku (nebo identifikátor globální proměnné) zastíněn.

Příklad 1. by 1 11: Zastínění proměnné lokální proměnnou vnořeného bloku:

#include<stdio.h>
void main()
{
 int i=10; {int i=1; i++;} printf("\n i=%d",i);
}

Uvedený program tedy vytiskne hodnotu i=10, protože tělo funkce main() tvoří nadřazený blok a proměnná i tohoto bloku je vlivem zastínění nedostupná ve vnořeném bloku.

4.4  Přiřazovací příkaz

Přiřazovací příkaz má následující formu:


přiřazovací_výraz;


tj. přiřazovací výraz ukončený středníkem. Přiřazovací výraz byl rozebrán v 3.7.

V jazyce C je možné, jak už víme, použít i tzv. vícenásobná přiřazení.

Příklad 1. by 1 11: Vícenásobné přiřazení a konverze typu.

               int iii;
               float fff;
               fff=iii=3.14;

Po provedení tohoto příkazu získá proměnná iii hodnotu typu integer rovnou 3 (dochází k odříznutí desetinné části), proměnná fff hodnotu typu float rovnou 3.0.

Příklad 1. by 1 11: Promyslete si následující fragment programu:

/*    ...                                                    */
      double a,b,c,d;
      a=3;b=4;c=5;d=34;
/*    ...                                                    */
      d/=c+=b*=a;
/* Tento prikaz ma stejny vyznam jako     d/=(c+=(b*=a));
**                 a tedy jako prikaz     d=d/(c=c+(b=b*a));
**                 (vysledek je d=2.)
      ...                                                    */

4.5  Cykly

V části jsme se setkali s cyklem while. Zabývejme se nyní podrobněji jednotlivými typy cyklů:

4.5.1 Příkaz while

Syntaxe příkazu cyklu while je


while(výraz) příkaz


Podmínkou opakování je výraz, je číselného typu, typu char nebo je to ukazatel. V případě, že je výraz nulový (nepravdivý), příkaz tvořící tělo cyklu se neprovede a provádění cyklu se ukončí. Je-li výraz nenulový (pravdivý), provede se příkaz tvořící tělo cyklu, opět se vyhodnotí výraz atd. Vyhodnocení podmínky se tedy provádí před každým krokem cyklu.

Příklad 1. by 1 11: Program vypisuje dekadické, oktalové a hexadecimální kódy znaků zadávaných z klávesnice. Ukončovací znak je @.

/********************  Znaky a jejich kody  ************************/
#include <stdio.h>
main()
{
  int c;
  printf("\nZadavej znaky, konec = znak '@'\n");
  while((c=getch()) != '@') /* cteni znaku c, jeho porovnani s '@' */
    printf("\n %c: %d %o %x", c, c, c, c);
  return;
}

Funkce getch() má podobný význam jako funkce getchar(); znak se ale čte ihned po stisknutí příslušné klávesy, nikoliv jako u funkce getchar() až po stisknutí ENTER. Výraz c=getch() musí být v závorce, protože priorita operátoru != je vyšší než operátoru =, viz 3.11.

4.5.2 Příkaz do

Tento příkaz cyklu má tvar


do příkaz while(výraz);


Pro podmínku opakování výraz platí totéž co pro podmínku cyklu while. Na rozdíl od cyklu while se testování podmínky výraz provádí až po provedení těla cyklu. Tělo cyklu do se tedy provede alespoň jednou.

4.5.3 Příkaz for

Tvar příkazu for je následující:


for(výraz_1;výraz_2;výraz_3) příkaz


Tato konstrukce má stejný význam jako následující konstrukce s příkazem while:


výraz_1; while(výraz_2) příkaz výraz_3;


V příkazu for je ale možné vynechat kterýkoliv z výrazů výraz_1 výraz_2, výraz_3, počet středníků však se však nezmění. V případě, že se vynechá výraz_2, je považován za nenulový, tj. za pravdivý. Následující příkaz znamená tedy nekonečnou smyčku:

         for(;;){
         ...
         }

4.5.4 Příkaz break

Příkaz break se používá k ukončení cyklů while, do, for. Pokud je více cyklů do sebe vložených, ukončuje jen nejvnitřnější z cyklů, ve kterých se nalézá. Příkaz break se uplatní zejména tam, kde není výhodné testovat podmínku opakování cyklu na začátku nebo na konci každé smyčky.

Příklad 1. by 1 11: Program opisuje znaky z klávesnice na monitor, po čtyřech po sobě jdoucích znacích * se program ukončí.

#include<stdio.h>
#include<conio.h>
void main()
{   int a,b,c,d;
    clrscr(); putchar('\n');  /* Smazani obrazovky, odradkovani.*/
    putchar(a=getch());       /* Cteni a zapis prvnich 3 znaků. */
    putchar(b=getch());
    putchar(c=getch());
/* Nekonecnou smycku 'while(1)' je mozne nahradit 'for(;;)'     */
    while(1){
    putchar(d=getch());               /* Precteni dalsiho znaku */
    if(a=='*'&&b=='*'&&c=='*'&&d=='*')
        break;                        /* Ukončení cyklu.        */
    a=b;b=c;c=d;
    }
}

Funkce main() je popsána jako funkce vracející typ void; příkaz return pak není nutný.

4.5.5 Příkaz continue

Uveďme si nejprve fragment programu, ve kterém se používá příkaz continue pro zjednodušení zápisu programu:

Příklad 1. by 1 11: Ukončení probíhajícího kroku cyklu for příkazem continue.

/*  ...                          */
   for(i=0;i<20;i=i+1){
     if(pole[i]==0) continue;
     pole[i]=(pole[i]+1.)/pole[i];  /*  pole[i] je indexovana promenna
             v datove strukture pole, ktera je podrobne probirana napr.
             ve 2. kapitole                                          */
   }
/*  ...                          */

Příkaz continue ukončí právě probíhající krok cyklu; cyklus pokračuje bezprostředně dalším krokem. V uvedeném příkladu je ukončen krok cyklu, ve kterém nastane pole[i]=0.

4.6  Podmíněný příkaz

Podmíněný příkaz if-else má v jazyce C následující tvary: Na závěr tohoto odstavce si uveďme o něco složitější příklad na použití příkazů cyklu, příkazu break a podmíněného příkazu. Uvedeme zde text celého programu, i když jeho části týkající se práce se soubory budou objasněny až ve třetí kapitole. Čtenář se zde může soustředit na příkazy řízení toku, tj. na příkazy while, if-else, break atd.; v ostatních částech programu se může zatím spolehnout na komentáře a vrátit se k tomuto programu později.

Příklad 1. by 1 11: Program upraví soubor prog1.c obsahující program v jazyce C tak, že vypustí všechny komentáře a výsledný text zapíše do souboru prog2.c. Program neuvažuje případ, kdy se dvojice znaků /*, */ vyskytuje uvnitř řetězce.

#include<stdio.h>
main()
{ /******* otevreni souboru prog1.c, prog2.c ************************/
  FILE *fr,*fw;
  int a,PZL=0,PZH=0;
  if((fr=fopen("prog1.c","r"))==NULL) printf("Chyba otevreni prog1.c");
  if((fw=fopen("prog2.c","w"))==NULL) printf("Chyba otevreni prog2.c");

  while((a=getc(fr))!=EOF){/*  getc() funkce pro precteni znaku ze
                               souboru, EOF znak ukoncujici soubor */
    if(PZL==0 && a!='/')
      putc(a,fw);          /*  putc() funkce pro zapis znaku do
                               souboru                             */
    else if(PZL==0 && a=='/')
      PZL=1;
    else if(PZL==1 && a=='*'){           /* ZACAL KOMENTAR!   */
         PZH=0;
         while((a=getc(fr))!=EOF){
           if(PZH==0 && a!='*');
           else if(PZH==0 && a=='*')
             PZH=1;
           else if(PZH==1 && a=='/'){    /* SKONCIL KOMENTAR! */
             PZL=0;break;
           }
           else if(a!='*') PZH=0; /* pripad: PZH==1 && a!='/' */
         }
    }
    else{          /* zde muze nastat pouze: PZL==1 && a!='*' */
       putc('/',fw);
       if(a!='/') { putc(a,fw); PZL=0;
       }
    }
  }
  fclose(fr);fclose(fw);return;
}

Proměnné PZL resp. PZH se zde používají k indikaci faktu, že předchozí načtený znak ze souboru bylo lomítko (PZL=1) resp. že předchozí načtený znak byla hvězdička (PZH=1). V opačných případech jsou PZL resp. PZH nulové.

4.7  Přepínač

Pro větvení výpočtu podle hodnoty celočíselného nebo znakového výrazu slouží tzv. přepínač. Tvar tohoto příkazu je následující:



switch(výraz) {

case hodnota_1: příkaz_1

case hodnota_2: příkaz_2

...

case hodnota_n: příkaz_n

default: příkaz

}


Příklad 1. by 1 11: Program určí známku na základě bodového hodnocení takto: 0-8 bodů: neúspěšný pokus; 9-12 bodů: dobře; 13-15 bodů: velmi dobře; 16-18 bodů: výborně. Program se ukončí, jestliže počet bodů bude zadán jako záporné číslo.

#include<stdio.h>
main()
{
int b=1, hod=0;
printf("\nZadej bodove ohodnoceni (0 - 18),"
       " konec = zaporny pocet bodu \n\n");
do {
scanf("%d",&b);
             hod=0;
  if (b>=0)  hod=4;
  if (b>=9)  hod=3;
  if (b>=13) hod=2;
  if (b>=16) hod=1;
  if (b>18)  hod=0;

  switch(hod) {
    case 1 :printf("Vyborne\n\n"); break;
    case 2 :printf("Velmi dobre\n\n"); break;
    case 3 :printf("Dobre\n\n"); break;
    case 4 :printf("Neuspesny pokus\n\n"); break;
    default :printf("Mimo rozsah stupnice\n\n");
  }
} while (b>=0);
return(0);
}


Poznámka: Standardní funkce scanf() je určena pro vstup hodnot ze standardního vstupního souboru, viz odst. 3.1.3. Čtení a přiřazení hodnoty jednoduché proměnné b je nutné v této funkci realizovat operátorem &, vysvětlení čtenář najde v čl. 2.3.1.


4.8  Příkaz goto

Jazyk C obsahuje i příkaz skoku goto. Vedle strukturovaných příkazů skoku např. zmíněných break, continue je goto příkladem nestrukturovaného příkazu skoku. Použití příkazu goto je omezeno tělem jedné funkce, není ale třeba řídit se blokovou strukturou, je možné nejen z bloku vyskočit ale i skočit dovnitř bloku. Každý program obsahující goto je možné přepsat i bez něho, obecně lze doporučit pouze velice umírněné používání tohoto příkazu. Jednou z mála situací, ve kterých se zdá být použití goto snad smysluplné, je jeho použití pro ukončení provádění několika do sebe vnořených cyklů. Program v následujícím příkladu přečte jeden znak ze standardního vstupu a určí, zda je tento znak obsažen ve dvourozměrném poli abc typu char inicializovaném při spuštění programu. Proměnnou abc si lze představit jako tabulku typu 4x4 zadaných znaků. Program zde používáme pro ilustraci příkazu goto, poli se budeme systematicky zabývat ve druhé kapitole, kde se také ještě k tomuto příkladu vrátíme.

Příklad 1. by 1 11:

#include<stdio.h>
#include<conio.h>
#define M 4
#define N 4
main(){
/********   definice dvourozměrného pole s inicializací  ******/
char abc[][N]={{'1','2','*','4'},
                {'a','b','c','d'},
                {'$','%','#','@'},
                {'+','-','^','/'} }, znak;
  int i,j,nalezen;
  printf("\nZadej znak:  "); znak=getchar();
  for(nalezen=0,i=0;i<M;i++)       /* pouziti operatoru carka */
    for(j=0;j<N;j++)
      if(abc[i][j]==znak){nalezen=1;goto konec;}
  konec:                      /* toto je navesti prikazu goto */
  clrscr();                              /* smazani obrazovky */
  /* pouziti podmineneho vyrazu v ridicim retezci fce printf: */
  printf("\nZadany znak %s nalezen",(nalezen?"byl":"nebyl"));
  printf("\n\nStisknete libovolnou klavesu.");
  getch();
}

V programu je v cyklu for použit operátor čárka, viz odst. 3.9.

5.  Funkce

Funkce je základní programovou jednotkou v jazyce C. Každý program obsahuje alespoň jednu funkci - main(). Definice libovolné funkce má podobnou strukturu jako definice funkce main().

5.1  Definice a vyvolání funkce

Základní rysy definice funkce a její vyvolání si ukážeme na jednoduchém příkladu. V tomto odstavci rozebereme případ, kdy parametry funkce nemění ve funkci hodnotu, představují tedy vstupní hodnoty a výstup je zprostředkován návratovou hodnotou funkce.

Příklad 1. by 1 11: Je zadána výška a poloměr válce. Sestavte funkci pro výpočet jeho objemu. Funkci použijte v hlavním programu pro výpočet objemů válců pro zadanou posloupnost výšek a poloměrů. Výpočet ukončete, bude-li jeden ze vstupních údajů záporný.


#include<stdio.h>
#define PI 3.141592
                                     /* nasleduje definice funkce  */
double objem_valce(double v,double r)/* hlavicka fce, neni zde ';' */
{ double objem;                             /* zacatek tela funkce */
  objem=PI*r*r*v;                          /* vypocet objemu valce */
  return(objem);  /* urceni navratove hodnoty funkce objem_valce() */
}                                             /* konec tela funkce */
main()
{ double v,r;
  while(1){
     printf("\n\n\nZadej vysku a polomer valce\n");
     scanf("%lf%lf",&v,&r);
     if(v<0.||r<0.) return;
     printf("\nv=%f    r=%f        objem=%f",v,r,objem_valce(v,r));
  }
}



Definice funkce nesmí být součástí definice jiné funkce narozdíl např. od programovacího jazyku Pascal, nesmí být tedy zapsána v těle žádné funkce, tedy např. ani funkce main(). Definice funkce v jazyce C se skládá z hlavičky funkce a z těla funkce. V hlavičce funkce je uveden typ návratové hodnoty funkce, identifikátor funkce a typy parametrů.

Příkaz return výraz;  return(výraz); použitý ve funkci objem_valce() definuje návratovou hodnotu funkce jako hodnotu výraz, ukončí funkci objem_valce() a předá řízení programové jednotce, která objem_valce() vyvolala tj. funkci main(). Přitom dochází k případné konverzi hodnoty výraz na typ návratové hodnoty funkce.

Hlavička funkce je v našem příkladu zapsána v souladu s doporučením novější normy ANSI, která ale zároveň připouští i zápis podle starší normy K&R.

ANSI                                       K&R

double objem_valce(double v,double r)      double objem_valce(v,r)
{ ... }                                    double v,r;
                                           { ... }

Obě konstrukce jsou naprosto ekvivalentní.


Programové jednotky odpovídající funkcím main() a objem_valce() mohou být zapsány v libovolném pořadí. V případě, že by funkce main byla zapsána jako první, nebyl by ve funkci main znám typ návratové hodnoty objem_valce(). V tomto případě je vhodné informovat překladač o typu návratové hodnoty, i o typu parametrů tj. deklarovat funkci uvedením tzv. úplného funkčního prototypu podle normy ANSI.

#include<stdio.h>
#define PI 3.141592
/* uplny funkcni prototyp (ANSI), (pozor, je zde ';') */
double objem_valce(double v,double r);
main()
{ double v,r;
  while(1){
     printf("\n\n\nZadej vysku a polomer valce\n");
     scanf("%lf%lf",&v,&r);
     if(v<0.||r<0.) return;
     printf("\nv=%f    r=%f       objem=%f",v,r,objem_valce(v,r));
  }
}
                                      /* nasleduje definice funkce */
double objem_valce(double v,double r) /* hlavicka funkce           */
{ double objem;                       /* zacatek tela funkce       */
  objem=PI*r*r*v; /* vypocet objemu valce */
  return(objem);  /* urceni navratove hodnoty funkce objem_valce() */
}                                          /* konec tela funkce    */
Funkční prototyp může být zapsán i bez identifikátorů parametrů. Oba zmíněné způsoby jsou z hlediska překladače ekvivalentní. Namísto
                  double objem_valce(double v,double r);

tedy můžeme zapsat:

                  double objem_valce(double, double);

Namísto funkčního prototypu lze použít i starší způsob zápisu - uvést definici funkce podle K&R normy, (ANSI norma tento způsob také připouští):

                  double objem_valce();

Nevýhoda zde spočívá v tom, že překladač nezná typ parametrů. Pokusíme-li se např. vyvolat funkci s parametry typu int

                  objem_valce(1,1);

dojde k chybě, protože se neprovede konverze parametrů na typ double. V případě že byl uveden funkční prototyp, typ parametrů je znám a konverze na typ double se provedou. Používání funkčního prototypu se tedy jeví jako výhodnější a doporučujeme ho. V jazyku C++ je tento způsob povinný.

Poznámka: Ze syntaktického hlediska není chyba, jestliže funkční prototyp podle ANSI normy nebo deklaraci podle normy K&R neuvedeme. V tomto případě se uplatní implicitní konverze typů hodnot a jestliže nesouhlasí se skutečnými typy, nelze předem určit, co proběhne. Norma ANSI se zde jeví jako nebezpečně tolerantní, v jazyku C++ je proto vynechání funkčního prototypu považováno za syntaktickou chybu. Zdá se nám proto, že uvedení funkčních prototypů všech používaných funkcí na začátku programu je dobrým programátorským zvykem. Ze stejného důvodu je vhodné umístit příkazy #include, které vkládají do programu hlavičkové soubory, na začátek programu.

5.2  Rekurzivní volání funkce

Funkce může být definována rekurzivně, tj. funkce může volat sama sebe (většinou s jinou hodnotou parametru). V následujícím příkladu je rekurzivní volání použito pro výpočet faktoriálu.

Příklad 1. by 1 11:

#include <stdio.h>
#include <stdlib.h>                       /* Obsahuje popis exit() */
long double faktorial(int i)
{ if(i<0){
    printf("\n\nChybny argument funkce faktorial!");
    exit(1);/* Ukonceni programu, (jinak vznikne nekonecna rekurze)*/
  }
  return( i ? i*faktorial(i-1) : 1. );
}

main()
{ int n;
  printf("\nVypocet faktorialu:");
  printf("\nProgram se ukonci zadanim zaporneho cisla.\n\n");
  do{
    printf("\nZadej cele cislo:\t");
    scanf("%d",&n);
    printf("Faktorial tohoto cisla je %Lf", faktorial(n));
    }while(n>=0);   /* Funkce main() muze byt pouzita i pro variantu
                       funkce faktorial, ktera program neukonci    */
}


Je užitečné si uvědomit, že algoritmy využívající rekurzivní volání funkce kladou zpravidla větší nároky na vnitřní paměť, než algoritmy, které dosahují stejného cíle bez rekurzivního volání. Na druhé straně lze rekurzivní volání funkce v některých případech použít pro jednodušší vyjádření určitého algoritmu. Následující program je určen pro nalezení největšího společného dělitele dvou celých kladných čísel čísel.


Příklad 1. by 1 11:

#include <stdio.h>
long int spolecny_delitel(long int i, long int j)
{
  long int k;
  if(i<=0||j<=0) {printf("Nektere z cisel neni kladne.");return;}
  if(i<j){
    k=i;i=j;j=k;
  }
  return( i%j ? spolecny_delitel(j,i%j): j );
}

main()
{
  long int i,j;

  printf("\n\nNejvetsi spolecny delitel dvou celych kladnych cisel.");
  printf("\nUkonceni programu - jedno z cisel neni kladne.");

  while(1){
   printf("\n\nZadej dve cela kladna cisla:  ");
   scanf("%ld%ld",&i,&j);
   printf("Nejvetsi spolecny delitel cisel %ld a %ld je cislo %ld",
          i,j,spolecny_delitel(i,j));
  }
}


5.3  Návratová hodnota funkce main()

Není-li hlavní funkce typu void, je možné použitím příkazu return(výraz) funkci main() přiřadit návratovou hodnotu. Přístup k této návratové hodnotě, definované po ukončení programu, závisí na operačním systému, pod kterým byl program spuštěn. Např. v operačním systému DOS je tato hodnota dostupná příkazem ERRORLEVEL. Návratovou hodnotu funkce main() lze tedy využít při práci s dávkovými soubory.



Příklad 1. by 1 11:

/*
**   program odpoved.c
**   Program definuje navratovou hodnotu v zavislosti
**   na zadanem znaku z klavesnice
*/
#include<stdio.h>
#include<conio.h>
#define ZNAK1 'T' /* TEX */
#define znak1 't'
#define ZNAK2 'W' /* WORD */
#define znak2 'w'
#define ZNAK3 'C' /* TC++ */
#define znak3 'c'

main()
{
 int c;
 c=getch();
 if(c==ZNAK1||c==znak1) return(1);
 if(c==ZNAK2||c==znak2) return(2);
 if(c==ZNAK3||c==znak3) return(3);
 return(4);
}

Program je možné použít v dávkovém souboru menu.bat.

@echo off
echo ************************************************************
echo **              TEX ............ T                        **
echo **              WORD ........... W                        **
echo **              TurboC++ ....... C                        **
echo ************************************************************
odpoved.exe
IF ERRORLEVEL 4 GOTO NC
IF ERRORLEVEL 3 GOTO C
IF ERRORLEVEL 2 GOTO WORD
IF ERRORLEVEL 1 GOTO TEX
REM   Zaciname nejvyssi hodnotou protoze prikaz 'ERRORLEVEL i'
REM   je povazovan za pravdivy, je-li kod, se kterym byl ukoncen
REM   program vetsi nez hodnota 'i'.
:NC
echo **              Provede se BATCH pro NORTON               **
REM        Tady se umisti prikaz pro spusteni NC
goto end
:C
echo **              Provede se BATCH pro TurboC++             **
REM        Tady se umisti prikazy pro spusteni TC++
goto end
:WORD
echo **              Provede se BATCH pro WORD                 **
REM        Tady se umisti prikazy pro spusteni Windows a Wordu
goto end
:TEX
echo **              Provede se BATCH pro TEX                  **
REM        Tady se umisti prikazy pro spusteni TEXu
goto end
:end
echo ************************************************************
echo **              KONEC                                     **
echo ************************************************************

5.4  Funkce s proměnným počtem parametrů

V programovacím jazyce C existuje možnost vytvářet funkce s proměnným počtem argumentů. Příkladem funkcí s proměnným počtem argumentů jsou standardní knihovní funkce printf() a scanf(). Fakt, že funkce má proměnný počet parametrů se vyjadřuje třemi tečkami '...'.
  funkční prototyp :                  definice funkce:
  double max(int n, ...);             double max(int n, ...)
                                      {
                                            ....
                                      }

Definice funkce musí obsahovat alespoň jeden pojmenovaný argument určitého typu. V našem příkladu je n počet následujících parametrů.

Pro usnadnění práce s funkcemi o proměnném počtu argumentů byly mezi standardní knihovní funkce zařazeny funkce va_start(), va_arg(), va_end(). Funkční prototypy těchto funkcí jsou uloženy ve standardním hlavičkovém souboru <stdarg.h>, viz rovněž kapitolu 7.

V následujícím příkladu je definována funkce max() s proměnným počtem parametrů pro výpočet maxima konečné posloupnosti racionálních čísel.

Příklad 1. by 1 11:

/*
**     Funkce s promennym poctem parametru - max(n,...)
**     n - pocet prvku konecne posloupnosti,
**     dalsi parametry jsou typu double.
*/

#include <stdio.h>
#include <stdarg.h> /* obsahuje funkcni prototypy funkci va_start(),
                       va_arg(), va_end() a definici typu va_list   */
double max(int n, ...);     /* to je funkcni prototyp funkce max()  */
/*  '...'   ve funkcnim prototypu znamena neurceny pocet parametru  */
main()
{  double a=20., b=-10., c=30., d=-20., e=10.;
                                           /* vyvolani funkce max() */
   printf("\n\n\nMaximum z cisel %f  %f\n"
         "je cislo %f",a,b,max(2,a,b));

                  /* vyvolani funkce max() s jinym poctem parametru */
   printf("\n\n\nMaximum z cisel %f  %f  %f  %f  %f\n"
         "je cislo %f",a,b,c,d,e,max(5,a,b,c,d,e));
}
double max(int n, ...)
{
  int i; double amax;
  va_list seznam;
  /* datovy typ va_list je obsazen v hlavickovem souboru stdarg.h;
     'seznam' je promenna, do ktere se ulozi seznam parametru
     funkce max()                                                   */
  double x;                      /* jeden z parametru funkce max()  */

  va_start(seznam, n);
  /* Seznam parametru funkce max() byl ulozen do promenne 'seznam',
     precetla se promenna n znamenajici pocet parametru
     funkce max(), ktere nasleduji za n.                            */

  x=va_arg(seznam,double);            /* Precetl se dalsi parametr
            typu double ze seznamu parametru 'seznam' funkce max(). */

  for(i=0, amax=x; i<n ;i++){            /* pouziti operatoru carka */
     x=va_arg(seznam,double);/* cteni dalsiho parametru typu double */
    if(x>amax) amax=x;
  }
  va_end(seznam);            /* ukonceni cteni ze seznamu argumentu */
  return(amax);
}

Obsah

Kapitola 2
Datové typy

V první kapitole jsme probrali základní datové typy jazyka C - datové typy char, int, float, double. Zmínili jsme se stručně i o datových modifikátorech short, long a const. V této kapitole se budeme nejprve zabývat oblastí platnosti identifikátorů, globálními a lokálními proměnnými a dále typy ukazatel, pole, výčtový typ, struktura a union.

1.  Globální a lokální proměnné

Program v jazyce má obecně následující strukturu:


globální definice a deklarace

definice funkcí

globální definice a deklarace

definice funkcí

atd.

Definice funkcí mohou obsahovat lokální definice a deklarace.

1.1  Globální proměnné

Definice proměnné je chápána kompilátorem jako definice globální proměnné, jestliže je v programu umístěna mimo funkce. Globální proměnná je přístupná ze všech funkcí, od místa definice globální proměnné do konce souboru, pokud není zastíněna lokální proměnnou.

Globální deklarace jsou deklarace proměnných, které jsou definovány v jiných souborech. Tyto deklarace jsou specifikovány použitím klíčového slova extern, proto se nazývají externí deklarace. Z hlediska každé funkce, která využívá globální proměnnou, je možné ji považovat za externí.

Příklad 2. by 1 12: Použití globálních proměnných v jiných souborech. Soubory HLAVNI.C a FUNKCE.C mohou být kompilovány odděleně.

/* Soubor HLAVNI.C ***/
#include <stdio.h>
int gl;           /* definice globalni promenne gl */
main()
{ gl=10;
  printf("\nHodnota funkce f pro x=%d je rovna %d",5,f(5));
  return;
}

/* Soubor FUNKCE.C ***/
int f(x)
{ extern int gl;   /* deklarace externi promenne */
  return(gl+x);
}
Uvedený program vytiskne:


Hodnota funkce f pro x=5 je rovna 15

Poznámka: Pokud by globální proměnnou gl v souboru FUNKCE.C využívalo více funkcí, bylo by možné umístit externí deklaraci na začátek souboru mimo funkce, tj. použít globální deklaraci extern.

Kdybychom chtěli zamezit použití globální proměnné v jiných souborech než byla definována, je možné použít v její definici klíčové slovo static. Inicializace globálních proměnných se provádí právě jednou. V případě, že není globální proměnná inicializována, má implicitní hodnotu 0.

O možnosti zastínění proměnné lokální proměnnou jsme se zmínili v části 4.3.

Poznámka: Samostatný překlad jednotlivých funkcí programu, popřípadě různých souborů funkcí, lze obejít příkazem #include:

/* Soubor HLAVNI.C ***/
#include <stdio.h>
#include "FUNKCE.C" /* eventuálně včetně cesty k souboru */
int gl;             /* definice globalni promenne gl */
main()
...

1.2  Lokální proměnné

Lokálními proměnnými jsme se zabývali v části 4.3. Je-li proměnná definována uvnitř nějaké funkce (a tedy i uvnitř nějakého bloku), potom je chápána jako lokální v tomto bloku, tj. je možné ji využívat pouze v rámci tohoto bloku a z jiných částí programu není viditelná.

Implicitní paměťová třída lokálních proměnných je třída auto, tzn. že není-li řečeno jinak, při opuštění příslušného bloku (např. při ukončení funkce) proměnná zaniká a vyhrazený paměťový prostor je uvolněn pro další použití. Těmto proměnným se v jazyku C říká automatické. Odtud jsou odvozeny termíny: automatické pole, automatická struktura atd.

Pokud v definici lokální proměnné použijeme klíčové slovo static a explicitně ji tedy deklarujeme jako proměnnou této paměťové třídy, nezaniká hodnota proměnné při opuštění příslušného bloku nebo ukončení funkce, paměť je těmto proměnným vyhrazena trvale. Těmto proměnným se v jazyku C říká statické. Obdobně hovoříme o statických polích, statických strukturách apod.

Inicializace statických proměnných proběhne vždy pouze jednou, přesto že např. funkce, kde je statická proměnná definována, může být vyvolána vícekrát. Pokud nepoužijeme explicitní inicializaci, jsou statické proměnné inicializovány implicitně nulou. Automatickým proměnným musí být hodnota přiřazena, protože jinak je jejich hodnota při vstupu do bloku nedefinována. Inicializace automatických proměnných se provádí pokaždé při vstupu do bloku. ANSI norma jazyku C narozdíl od normy K&R připouští rovněž inicializaci automatických polí, podrobněji se budeme touto otázkou zabývat v odst. . (Automatická pole, která se v původním jazyku K&R nesměla inicializovat, měla však počáteční hodnotu implicitně 0).

Příklad 2. by 1 12: Použití statické lokální proměnné k určení, kolikrát byla funkce vyvolána.

#include <stdio.h>
main()
{ pocitadlo(); pocitadlo(); pocitadlo();
}
pocitadlo()
{ static int i=0;
  printf("\nKolikrat byla funkce vyvolana?   %d-krat! ",++i); return;
}

Jestliže je využívána některá lokální proměnná velice často (např. proměnná cyklu), je možné ji definovat s použitím klíčového slova register. Překladač se v tomto případě pokusí vygenerovat kód, kde by se pro příslušnou proměnnou využíval přímo některý registr procesoru, což má za následek urychlení práce s touto proměnnou.

2.  Ukazatelé

Ukazatel (směrník, spoj, pointer) je proměnná obsahující adresu jiné proměnné. Slovo adresa zde nemusí bezprostředně znamenat např. adresu paměti procesoru řady 80x86, může znamenat např. pouze její offsetovou část. Skutečný vztah mezi těmito významy slova adresa je dán konkrétní implementací jazyka C. U produktů Turbo C++ a Borland C++ firmy Borland závisí i na zvoleném tzv. paměťovém modelu.

Ukazatelé jsou mocným nástrojem jazyka C, kterým lze vytvořit velice efektivně fungující programy. Při jejich nepozorném použití lze však snadno získat i velice neočekávané výsledky.

2.1  Operátory reference a dereference

Předpokládejme, že i je proměnná typu integer a že p proměnná typu ukazatel. Operátor reference & umožňuje získat adresu proměnné i. Příkaz přiřazení
                        p=&i;

tedy přiřadí adresu proměnné i proměnné p. V této situaci budeme říkat, že p ukazuje na proměnnou i. Operátor reference nelze použít na aritmetický výraz, následující zápisy jsou chybné:

                       p=&5;       p=&(i+1);


Operátor dereference * naopak umožňuje získat hodnotu, která je uložena na určité adrese. Jestliže j je další proměnná typu int, pak příkaz

                       j=*p;

bude znamenat totéž co

                       j=i;

Operátor dereference rovněž umožňuje uložit hodnotu na určitou adresu:

                        j=3;
                       *p=j;

Hodnota proměnné j byla přiřazena na adresu obsaženou v proměnné p. Při tomto typu přiřazení je ale nutné, aby proměnná p byla ukazatel na typ int. Definicemi ukazatelů se budeme zabývat v dalším odstavci.

2.2  Definice proměnných typu ukazatel

Hodnotami typu ukazatel jsou adresy objektů. Každý ukazatel je spjat s typem objektu, na který může ukazovat. Význam tohoto spojení vysvětlíme v části věnované aritmetickým operacím s ukazateli, viz . Pro datový typ ukazatel není v jazyce C zavedeno klíčové slovo. Zápis
                       int *p;

znamená, že proměnná p byla definována jako proměnná typu ukazatel na typ integer. Mnemotechnický význam tohoto zápisu je to, že *p je typu int, dereferenční operátor aplikovaný na proměnnou p tedy dává hodnotu typu int, tzn. že p ukazuje na na hodnotu typu int.

Poznámka: Uvědomte si rozdíl mezi zápisy následujícího typu:

const char *ptr; /* ukazatel na konstantni objekt */

char *const ptr; /* konstantni ukazatel na objekt */

Příklady:

char const *jmeno="Jan";
jmeno[0]='D'; /* spravne prirazeni, pointer 'jmeno' ukazuje
                 nyni na retezec "Dan"                            */
jmeno="Jana"; /* nespravne prirazeni, pointer je konstantni       */

const char *objekt="OBJEKT1";
objekt="OBJEKT2";/* spravne prirazeni, byla zmenena hodnota
                    ukazatele; hodnotou je adresa retezce OBJEKT2 */
objekt[6]='1';   /* nespravne prirazeni, retezec, na ktery
                    ukazuje pointer 'objekt' nelze menit          */

Při definování proměnných typu ukazatel lze rovněž využít definici s inicializací, o které jsme se zmínili v 1. kapitole.


       int i, j, *p_i, *p_j;           int i, j, *p_i=&i, *p_j=&j;
       p_i=&i;
       p_j=&j;


Oba uvedené zápisy mají stejný význam: Proměnné i, j byly definovány jako proměnné typu int a proměnné p_i, p_j jako ukazatelé na typ int. Proměnným (ukazatelům) p_i, p_j byly po řadě přiřazeny adresy proměnných i, j.

2.3  Ukazatel na typ void

Někdy je výhodné, aby ukazatel nebyl svázán s jedním datovým typem. V tomto případě se používá definice ukazatele na typ void. Použití této definice budeme ilustrovat následujícím školním příkladem:
main()
{
  int i=3,j=10;
  float f=3.5,g=10.5;
  void *p;             /* Definice ukazatele na typ void */

  printf("\ni=%2d,     f=%5.1f",i,f);

/*Nasleduje prirazeni 'i=j;' vyuziva se neprimy pristup k promenne i */

  p=&i;         /*Pri urcení hodnoty pointeru neni pretypovani nutne */
  *(int *)p=j;  /*Pretypovani (int *) je nutne, prirazujeme hodnotu
                  typu integer na adresu, kam ukazuje pointer p.     */

/*Nasleduje prirazeni 'f=g;' vyuziva se neprimy pristup k promenne f */

  p=&f;         /*Pri urcení hodnoty pointeru neni pretypovani nutne */
  *(float *)p=g;/*Pretypovani (float *) je nutne, prirazujeme hodnotu
                  typu float na adresu, kam ukazuje pointer p.       */


  printf("\ni=%2d,     f=%5.1f",i,f);
}

Poznámka:

2.4  Aritmetické operace s ukazateli

Pro ukazatele jsou definovány některé aritmetické operace. V následujících odstavcích předpokládáme, že p je ukazatel na konkrétní datový typ, tj. že není definován jako ukazatel na typ void, a že n je celé číslo.

2.4.1 Součet a rozdíl ukazatele a celého čísla

Výraz
p+n
je adresový výraz, který má hodnotu adresy n-tého prvku "za" prvkem, na který právě ukazuje p. K ukazateli p se tedy nepřičítá hodnota n, ale násobek této hodnoty a velikosti typu, na který p ukazuje.

Obdobně výraz
p-n
je adresový výraz, který má hodnotu adresy n-tého prvku "před" prvkem, na který právě ukazuje p.

Poznámka: Slova "za" a "před" zde samozřejmě mají své obvyklé názorné významy jen pro kladné hodnoty n. Čtenář jistě chápe, jaké hodnoty budou mít uvedené adresové výrazy pro záporné hodnoty n.

2.4.2 Porovnávání ukazatelů

Pro porovnání velikostí ukazatelů stejného typu p1, p2 můžeme použít relační operátory
             <      <=      >       >=      ==      !=

Výrazy typu p1<p2 mají smysl pouze tehdy, jestliže p1, p2 ukazují na stejný úsek paměti, tj. např. na stejné pole. Výraz  p1<p2 je nenulový jestliže hodnota p1 je menší než hodnota p2, v opačném případě má výraz hodnotu 0.

2.4.3 Odečítání ukazatelů

Výraz
p1-p2
má smysl v případě, že p1, p2 ukazují na stejné pole dat. V tomto případě slouží ke zjištění počtu položek pole, které jsou uloženy mezi položkami, na které ukazují p1 a p2.

2.4.4 Operátor sizeof a aritmetické operace s ukazateli

Klíčové slovo sizeof označuje operátor, který je určen k získání velikosti zkoumaného datového typu ve slabikách (bytech).

Příklady: Předpokládejme, že a je proměnná typu int a p_d je ukazatel na typ double. Jako argument operátoru sizeof lze použít proměnnou nebo klíčové slovo označující datový typ:

     sizeof(a)      sizeof(p_d)       sizeof(*p_d)     sizeof(int)

Např. první z uvedených výrazů znamená počet slabik (bytů) nutných k uložení hodnoty proměnné a, tj. hodnoty typu int, druhý výraz určuje počet slabik, nutných k uložení ukazatele na typ double. Třetí výraz je příkladem často využívané možnosti jak zjistit, kolik slabik je nutných pro uložení objektu, jehož adresa je zadaná určitým ukazatelem.

Pomocí operátoru sizeof lze výsledky některých aritmetických operací s ukazateli vyjádřit takto:

          p+n                    (char *)p+n*sizeof(*p)
          p-n                    (char *)p-n*sizeof(*p)
          p1-p2                  ((char *)p1-(char *)p2)/sizeof(*p)

2.5  Ukazatelé a řetězcové konstanty

Řetězcová konstanta reprezentuje konstantní ukazatel, tj. adresu místa v paměti, které bylo alokováno pro její obsah.

Příklad 2. by 1 12:

      char *p;                      char *p="AHOJ!";
      p="AHOJ!";

Pointer p tedy nyní ukazuje na začátek bloku paměti alokovaného pro řetězec ÄHOJ!", a je tedy možné ho použít pro přístup k této části paměti:

Příklad 2. by 1 12:

      příkaz:                                      výstup:
      printf("%s","AHOJ!");                        AHOJ!
      printf("%s",p);                              AHOJ!
      for(i=0;i<2;i++)printf("%c%c",p[3],p[2]);    JOJO

Konverze %s je určená pro vstup a výstup řetězců, %c pro vstup a výstup znaků. Ke třetímu z uvedených příkladů je třeba dodat, že překladač interpretuje indexový výraz p[3] jako *(p+3). Podrobněji se budeme zabývat indexovými výrazy v části .

3.  Ukazatelé a funkce

3.1  Výstupní parametry funkcí

V první kapitole jsme se zabývali případem, kdy parametry funkce nemění během vyvolání funkce svoji hodnotu, tj. parametry funkce jsou jejími vstupními parametry a výstup je realizován návratovou hodnotou funkce. Důležitou vlastností ukazatelů je to, že umožňují použít část parametrů funkce jako parametry výstupní, tzn. umožňují trvale změnit hodnotu skutečného parametru funkce.


V programovacím jazyku C jsou parametry funkcí volány hodnotou. Volání odkazem v C sice neexistuje, 1 ale s pomocí ukazatelů lze dosáhnout stejného efektu.

Volání parametrů funkce hodnotou spočívá v tom, že při vyvolání funkce se v tzv. zásobníku (část vnitřní paměti - stack) vytvoří lokální kopie pro uložení parametrů. Případné změny parametrů se týkají pouze těchto lokálních proměnných, které zaniknou s ukončením funkce.

Chceme-li aby došlo k trvalé změně hodnoty proměnné, použijeme jako parametr funkce adresu této proměnné. V zásobníku se vytvoří lokální kopie pro uložení této adresy. Tato lokální proměnná sice zaniká s ukončením příslušné funkce, ale pomocí adresy kterou obsahuje, můžeme nepřímým přístupem změnit hodnotu příslušné proměnné.

Ukažme si tento postup na jednoduchém příkladu.

Příklad 2. by 1 12: Funkce suma() sečte dvě čísla a výsledek předá funkci main() prostřednictvím jednoho ze svých parametrů.

suma(double a, double b, double *p_c)
{
  *p_c=a+b;/* Soucet se ulozi na adresu, kterou zadava parametr p_c */
           /* Nepřímý přístup k proměnné c pomocí její adresy       */
  return;
}
Jsou-li a,b,c proměnné typu double definované v hlavní funkci main(), pak vyvolání funkce suma() v hlavním programu může vypadat takto:
                         suma(a,b,&c);


Zapišme nyní kompletní program:

#include <stdio.h>
suma(double a, double b, double *c);           /* Funkcni prototyp  */
/* Funkcni prototyp lze i takto:   suma(double,double,double *)     */
main()
{
   double a,b,c;
   printf("\nZadej cisla a, b :  ");
   scanf("%lf %lf",&a,&b);      /* Cteni hodnot 2 cisel double      */
   suma(a,b,&c);                /* Vypocet souctu techto cisel      */
                                /* 3. parametr je adresa promenne c */

   printf("\n%f +%f =%f",a,b,c);                   /* Tisk vysledku */
   return;
}

/*  Definice funkce suma(), parametr p_c je ukazatel na typ double  */
suma(double a, double b, double *p_c)
{
 *p_c=a+b; /* Soucet se ulozi na adresu, kterou zadava parametr p_c */
           /* Nepřímý přístup k proměnné c pomocí její adresy       */
  return;
}

3.2  Funkce jako parametr funkcí

Často se využívá možnosti definovat proměnnou jako ukazatel na funkci vracející nějaký typ. Např.
                  double (*f)();

definuje proměnnou f jako ukazatel na funkci vracející typ double. Podrobněji je možné např. vymezit proměnnou g jako ukazatel na funkci vracející typ double, která má jeden parametr typu double.

                  double (*g)(double);

Máme-li nyní definovánu nějakou funkci tohoto typu, např. funkci sin() , (její funkční prototyp je obsažen v hlavičkovém souboru math.h), je možné provést přiřazení:

                  f=sin;

Překladač jazyka C zachází s identifikátorem funkce jako s ukazatelem na funkci, zmíněné přiřazení tedy bude znamenat, že pointry f, sin budou ukazovat na tutéž funkci. Odkaz na funkci sin() lze nyní zapsat jedním ze dvou následujících způsobů:

                 (*f)(x)                     f(x)

První způsob byl zaveden K&R normou, ANSI norma připouští obě možnosti.

Přiřazení adresy funkce proměnné typu ukazatel na funkci může být provedeno při vyvolání funkce a náhradě formálního parametru skutečným parametrem. Identifikátory funkcí je možné použít jako skutečné parametry funkce.

Zapišme např. funkci která vytiskne tabulku hodnot funkce f, v intervalu  [a,b]  s krokem h.

tabel(double a, double b, double h, double (*f)(double x))
{ double x;
  for(x=a;x<=b;x+=h){
  printf("%15.5f \t %15.5f \n",x,f(x));
                                   /* misto f(x) lze pouzit (*f)(x) */
  }
  return;
}

Poslední parametr funkce je definován jako ukazatel na funkci vracející typ double. Předpokládejme, že máme např. definovanou funkci polynom():

double polynom(double x)
{ return(x*x*x+x+1.);
}

Vyvolání funkce tabel v hlavním programu by tedy mohlo vypadat např. takto:

                         tabel(0.,2.,0.1,polynom);

Zapišme nyní celý program:

Příklad 2. by 1 12:

#include <stdio.h>
#include <conio.h>      /* Prototyp fce clrscr() - smazani monitoru */
double polynom(double x);       /* Prototypy funkci polynom, tabel: */
tabel(double d, double h, double k, double (*f)(double x));
/* Funkcni prototypy lze zapsat i s nepojmenovanymi parametry takto:
           double polynom(double);
               tabel(double, double, double, double (*)(double));   */
main()
{ tabel(0.,2.,0.1,polynom); return;
}
tabel(double a, double b, double h, double (*f)(double x))
{ double x;
  clrscr();
  for(x=a;x<=b;x+=h){
   printf("%15.5f \t %15.5f \n",x,f(x));
                                 /* misto f(x) lze pouzit (*f)(x)   */
  } return;
}
double polynom(double x)
{ return(x*x*x+x+1.);
}

Materiál posledních dvou odstavců je použit v následujícím příkladu programu určeném pro řešení rovnice  f(x) = 0  metodou půlení intervalů na intervalu  [a,b]. Předpokládáme samozřejmě, že funkce  f  je spojitá na intervalu  [a,b].  Algoritmus metody půlení intervalů bude naprogramován v podobě funkce pul(). Tato funkce určí řešení rovnice se zadanou přesností v případě, že platí f(a)f(b) Ł 0. Funkce pul() má následující funkční prototyp:

int pul(double *p_a, double *p_b, int *p_max, double eps, double *p_x,
        double (*f)(double x))

Proměnné p_a, p_b jsou ukazatelé na proměnné, které na vstupu obsahují krajní body zadaného intervalu  [a,b] , na výstupu krajní body intervalu, ve kterém byl v průběhu výpočtu lokalizován kořen rovnice. Proměnná p_max je ukazatel na proměnnou zadávající na vstupu maximální počet iterací, na výstupu číslo iterace, při které byla splněna požadovaná přesnost eps. Proměnná f je ukazatel na funkci, pro kterou se řeší rovnice   f(x) = 0. Funkce pul() má návratovou hodnotu typu int, která je definována podle toho, jak byl ukončen výpočet kořene rovnice:

 pul=-1: nevhodné zadání
 pul=1 : nalezeno přesné řešení, uloží se na adresu zadanou
         proměnnou p_x , pocet provedenych iterací se uloží
         na adresu p_max
 pul=2 : nalezeno řešení se zadanou přesností,
         řešení leží v intervalu s krajními body na adresách p_a, p_b,
         počet provedených iterací je na adrese p_max
 pul=3 : přesnost nedosažena, vypočet ukončen po provedení max kroků
Funkce pul() je vyvolána v hlavním programu příkazem
             pul(&a,&b,&max,eps,&x,g);

kde g je funkce zadaná samostatnou programovou jednotkou:

double g(double x)
{ return(x*x*x+11.);
}

Celý program je tedy rozčleněn do tří programových jednotek main(),pul(), g():

Příklad 2. by 1 12:

#include <stdio.h>
#include <math.h>

double g(double x);
int pul(double *p_a, double *p_b, int *p_max, double eps,
    double *p_x, double (*f)(double x));

/* lze pouzit i nasledujici funkcni prototypy s anonymnimi parametry:
double g(double);
int pul(double *, double *, int *, double,
    double *, double (*)(double));
********************************************************************/
main()
{
  double a,b,x,eps;  int max,ier;

  printf("\n\n\nMetoda puleni intervalu:\n");
  printf("\nZadej krajni body intervalu, presnost,"
         " maximalni pocet kroku");
  printf("\na=");scanf("%lf",&a);    printf("b=");scanf("%lf",&b);
  printf("eps=");scanf("%lf",&eps);  printf("max=");scanf("%d",&max);

  switch(pul(&a,&b,&max,eps,&x,g)){
    case 1: printf("\nPresne reseni x=%f nalezeno po %d krocich",
                   x,max); break;
    case 2: printf("\nReseni nalezeno v intervalu (%f,%f)"
                  "\nPresnost splnena po %d krocich",a,b,max); break;
    case 3: printf("\nReseni nalezeno v intervalu (%f,%f)"
                   "\nPresnost nesplnena po %d krocich",a,b,max);
                   break;
    default:printf("\nChybne vstupni udaje!"); break;
  }
  return;
}
/********************************************************************
**  Funkce pul() : reseni rovnice metodou puleni intervalu
**  Navratove hodnoty funkce pul:
**  pul=-1: NEVHODNE ZADANI
**  pul=1 : PRESNE RESENI, reseni na adrese p_x,
**                           pocet provedenych kroku na adrese p_max
**  pul=2 : RESENI SE ZADANOU PRESNOSTI,
**          reseni lezi v intervalu s krajnimi body na adresach
**          p_a ,p_b pocet provedenych kroku na adrese p_max
**
**  pul=3 : PRESNOST NESPLNENA vypocet ukoncen po provedeni max kroku
*********************************************************************/

int pul(double *p_a, double *p_b, int *p_max, double eps, double *p_x,
        double (*f)(double x))  /* lze i takto: double (*f)(double) */
{
   double s,c; int i,max;
   s=f(*p_a)*f(*p_b);
   if(s > 0.) return(-1);              /* Chybne zadani pul=-1  */
   if(s == 0.){                        /* Pripad: Presne reseni */
     *p_x=( f(*p_a) ? *p_b : *p_a);   /* Koren ulozen na adresu p_x */
     *p_max=0;  /* Pocet provedenych iteraci ulozen na adresu p_max */
     return(1);                          /* Navratova hodnota pul=1 */
   }
   max=*p_max;
   for(i=1;i<=max;i++){
     if(fabs(*p_b-*p_a)<eps) {
        *p_max=i; return(2);}            /* Reseni s presnosti eps  */
     *p_x=(*p_a + *p_b)/2.;              /* Novy krok metodou puleni*/
     if(f(*p_x)==0) {*p_max=i; return(1);}         /* Presne reseni */
     if(f(*p_a)*f(*p_x)< 0.)              /* Vyber noveho intervalu */
              *p_b=*p_x;
     else
              *p_a=*p_x;
   }
   return(3);
}
/*******************************************************************/
double g(double x)
{ return(x*x*x+11.);
}

4.  Pole

Každá proměnná, kterou jsme dosud v našich programech používali, nabývala v každém okamžiku pouze jedné hodnoty určitého typu. V této kapitole začínáme studovat tzv. složené (agregované) datové typy. Proměnné složených typů označují zpravidla skupinu určitých hodnot. Do této třídy patří pole, struktury a uniony.

Pole je datový typ používaný pro ukládání skupiny hodnot stejného typu. Prvky jednoho pole jsou rozlišeny tzv. indexem.

4.1  Definice pole, prvky pole

Pole je nutné definovat v programovacím jazyku C jako jiné proměnné. Definice má následující tvar:


typ_pole identifikátor_pole[počet_prvků];


typ_pole může označovat libovolný ze základních datových typů, se kterými jsme se doposud setkali kromě typu void. Později ukážeme, že typ_pole může označovat i složené datové typy. Pro zápis identifikátoru_pole platí stejná pravidla jako pro zápis identifikátorů proměnných jiných typů. počet_prvků je konstantní výraz, určující počet prvků pole. Přístup k jednotlivým prvkům lze získat výrazem  identifikátor_pole[index], kde celočíselný výraz  index nabývá hodnot


0, 1, ..., počet_prvků-1


Např. definicí

             int a[5];

je v paměti alokováno místo pro 5 hodnot typu int, přístup k nim je zajištěn indexovanými výrazy

             a[0], a[1], a[2], a[3], a[4]

Zmíněné hodnoty (v našem případě typu int) jsou v paměti ukládány za sebou.

Je třeba si uvědomit, že překladač jazyka C neprovádí kontrolu mezí. Přiřazení

             a[5]=0;

je syntakticky správné a provede se i když s případnými katastrofálními následky, neboť bude přepsána část paměti, která nebyla alokována pro pole a.

Následující jednoduchý program ilustruje definici a použití jednorozměrného pole.

Příklad 2. by 1 12

/*
**  Program precte 5 cisel ze standardniho vstupu
**  a v obracenem poradi je vytiskne.
*/

#include <stdio.h>
#define POCET 5

main()
{
  int i;         /* promenna cyklu */
  int a[POCET];  /* definice pole typu typu int */

  printf("\n\n\n Zadej %d cisel\n",POCET);

  for(i=0;i<POCET;i++)   /* cteni vstupnich dat v cyklu */
     scanf("%d",&a[i]);  /* adresa prvku pole :  &a[i]  */

  for(i=POCET-1;i>=0;i--)
         printf("\n%d",a[i]);
}

Jedna z obvyklých úloh souvisejících s jednorozměrným polem je jeho uspořádání. Uvedený program provádí uspořádání konečné posloupnosti v neklesajícím pořádku tzv. bublinkovým tříděním.

Příklad 2. by 1 12:

/*   Program precte konecnou posloupnost racionalnich cisel
**   Usporada posloupnost v neklesajicim poradku jednoduchym
**   bublinkovym tridenim. Prvky usporadane posloupnosti
**   vytiskne s jejich indexy.
*/
#include <stdio.h>
#define MAX 100
main()
{ int pocet;
  int i,j;
  double a[MAX],pomoc;

  printf("\n\n\n\nZadej pocet prvku posloupnosti:  ");
  scanf("%d",&pocet);
  if(pocet>MAX)
              printf("\nTo je prilis mnoho, maximum je %d\n",MAX);

  else                    /* pocet prvku byl programem akceptovan */
  {
       for(i=0;i<pocet;i++) {         /* cteni prvku posloupnosti */
           printf("a[%3d] : ",i+1);
           scanf("%lf",&a[i]);
       }
                /* algoritmus jednoducheho bublinkoveho trideni : */
       for(i=pocet-1;i>0;i--)
          for(j=0;j<=i-1;j++)
             if(a[j]>a[j+1])
             { pomoc=a[j]; a[j]=a[j+1]; a[j+1]=pomoc;
             }
       for(i=0;i<pocet;i++) {     /* tisk usporadane posloupnosti */
           printf("\na[%3d] : ",i+1);
           printf("%f",a[i]);
       }
  }                                                 /* konec else */
}



Programovací jazyk C poskytuje také možnost použití vícerozměrných polí. Dvourozměrné pole je v jazyce C chápáno v podstatě jako "jednorozměrné pole jednorozměrných polí" atd. Definice i přístup k jednotlivým prvkům se tedy příliš neliší od jednorozměrného případu, včetně katastrofálních následků při chybné volbě indexu.

V následujícím příkladu použijeme dvourozměrné obdélníkové pole pro určení euklidovské normy obdélníkové matice.


Příklad 2. by 1 12: Program přečte obdélníkovou matici A = (ai,j), i = 1,m, j = 1,n ze standardního vstupu a spočítá její euklidovskou normu: ||A||E = Ö{ĺi = 1mĺj = 1n ai,j2}.

#include <stdio.h>
#include <math.h>  /* math.h obsahuje funkcni prototyp sqrt()      */
#define MAX 10

main(){
  int m,n,i,j;
  double a[MAX][MAX];              /* Definice dvourozmerneho pole */
  double sum;

  /* cteni rozmeru matice */
  printf("\n\n\nZadej pocet radek a pocet sloupcu matice: ");
  scanf("%d%d",&m,&n);
  if(m>MAX||n>MAX){
    printf("\nMaximum pro oba rozmery je %d",MAX); return;
  }
  /* cteni matice */
  for(i=0;i<m;i++)
    for(j=0;j<n;j++)
     { printf("a[%2d,%2d]: ",i+1,j+1);
       scanf("%lf",&a[i][j]);   /* Cteni prvku dvourozmerneho pole */
     }

  /* vypocet euklidovske normy matice */
  for(sum=0,i=0;i<m;i++)
    for(j=0;j<n;j++)
      sum+=a[i][j]*a[i][j];   /* Scitani druhych mocnin prvku pole */

  /* Tisk vysledku; sqrt() je funkce pro vypocet druhe odmocniny   */
  printf("\nEuklidovska norma matice = %f",sqrt(sum)); return;
}



4.2  Inicializace pole

Zároveň s definicí pole je možné v jazyce C provést jeho inicializaci. Např. jednorozměrné pole obsahující 7 prvků typu int může být inicializováno takto:
                int pole[7]={0,1,2,3,4,5,6};

Hodnota pole[0] je po takové inicializaci rovna 0, pole[1] se rovná 1 atd.

Počet prvků pole není nutné uvádět explicitně, překladač ho určí podle počtu hodnot uvedených ve složených závorkách. Definice stejného pole by tedy mohla vypadat i takto:

                int pole[]={0,1,2,3,4,5,6};

Poznámka:

Pole typu char je možné inicializovat některým z následujících způsobů:

                char norma[]={'A','N','S','I',' ','C'};
                char norma[]={"ANSI C"};

Při druhém způsobu inicializace, bude pole norma[] vypadat takto

                char norma[]={'A','N','S','I',' ','C','\0'};

tj. poslednímu prvku pole je překladačem přiřazen ukončovací znak řetězců.

Obdobným způsobem lze inicializovat i vícerozměrné pole. Následující dva způsoby inicializace obdélníkového pole tabulka jsou ekvivalentní:

          int tabulka[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

V definici je možné vynechat "počet řádek", "počet sloupců" vynechán být nesmí.

          int tabulka[][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};

Obdobně lze definovat vícerozměrná pole typu char. Např. v části 4.8 jsme použili definici s inicializací dvourozměrného pole abc o velikosti 4x4 :

       #define N 4
       ...
       char abc[][N]={{'1','2','*','4'},
                      {'a','b','c','d'},
                      {'$','%','#','@'},
                      {'+','-','^','/'}};

Kdybychom chtěli inicializovat pole obsahující tyto znaky pomocí řetězců, museli bychom zvětšit délku "řádků" pole o jednu, každá "řádka" bude ukončena znakem '\0':

       #define N 5
       ...
       char abc[][N]={{"1234"},{"abcd"},{"$%#@"},{"+-^/"}};

4.3  Ukazatelé a pole

Souvislost mezi ukazateli a poli je dána tím, že identifikátor určitého jednorozměrného pole je překladačem interpretován jako konstantní ukazatel na první prvek tohoto pole, tedy na prvek s indexem 0. Předpokládejme např., že a je následující pole typu double:
                        double a[10];

Identifikátor a je tedy konstantní ukazatel na typ double. Z toho co jsme řekli vyplývá, že následující dvojice výrazů mají stejné hodnoty:

                  a               &a[0]
                 *a                a[0]
                 *(a+i)            a[i]

Definujeme-li teď např. ukazatel p jako ukazatel na typ double

                  double *p;

pak po přiřazení p=a; se následující výrazy v jednotlivých řádkách rovnají:

   a               &a[0]             p           &p[0]
  *a                a[0]            *p            p[0]
  *(a+i)            a[i]            *(p+i)        p[i]

Ukazatel p je možné měnit (narozdíl od a). Po přiřazení p++; dostaneme:

   a               &a[0]             p-1          &p[-1]
  *a                a[0]            *(p-1)         p[-1]
  *(a+1)            a[1]            *p             p[0]
  *(a+i)            a[i]            *(p+i-1)       p[i-1]

Pro dvourozměrné pole, definované zápisem

                double b[10][20];

je b ukazatel na ukazatel na typ double, tj. b je ukazatel na jednorozměrná pole typu double (o délce 20 prvků), b[i] je ukazatel na typ double, tj. b[i] je pole typu double (o délce 20 prvků), b[i][j] je typu double a platí:

       b                 &b[0]
      *b                  b[0]            &b[0][0]
     **b                 *b[0]             b[0][0]
     *(b+i)               b[i]            &b[i][0]
     *((*b+i)+j)        *(b[i]+j)          b[i][j]

Jako příklad můžeme použít program z části 4.1, zapsaný tentokrát pomocí pointerů.

Příklad 2. by 1 12

/*
**  Program precte 5 cisel ze standardniho vstupu
**  a v obracenem poradi je vytiskne.
*/

#include <stdio.h>
#define POCET 5

main()
{
  int a[POCET];/* definice pole typu typu int */
  int *p;      /* ukazatel použitý pro přístup k jednotlivým prvkům */

  printf("\n\n\n Zadej %d cisel\n",POCET);

  for(p=a;p<&a[POCET];p++)   /* cteni vstupnich dat v cyklu */
     scanf("%d",p);       /* p je adresa prvku pole: &a[i]  */

  for(p=&a[POCET-1];p>=a;p--)
         printf("\n%d",*p);
}

4.4  Pole jako parametr funkce

Budeme se zabývat následující modelovou úlohou: Jsou zadána pole a, b stejného typu. Sestavte program pro výpočet součtu a+b. Součet polí a+b budeme definovat jako pole c, jehož každý prvek se rovná součtu stejnolehlých prvků polí a a b, tj. prvků polí a a b, které mají stejné indexy jako příslušný prvek pole c. Budeme se zabývat touto úlohou postupně pro jednorozměrná, dvourozměrná a trojrozměrná pole. (Rozměrností pole zde, tak jak je obvyklé, rozumíme počet indexů.) Proceduru pro sčítání polí zapíšeme jako funkci secti() s parametry a, b a c volanou funkcí main(). V následujícím programu, rozčleněném do dvou souborů je popsaná úloha zpracovaná pro jednorozměrná pole.

Příklad 2. by 1 12:

/*******************     soubor 1d_pole.c         ******************/
#include <stdio.h>
#include <conio.h>
#define MAX 30
/*  funkcni prototypy funkci precti(), tiskni() a secti():         */
precti(int, int [], const char *);
tiskni(int, int [], const char *);
secti(int, int [], int [], int []);
main()
{
  int n;
  int a[MAX], b[MAX], c[MAX];
  clrscr();
  printf("\n Zadej pocet prvku n vektoru a,b (n <= %d)  : ",MAX);
  scanf("%d",&n);
  /*  cteni a kontrolni tisk vektoru a,b: */
  precti(n,a,"a"); tiskni(n,a,"a"); precti(n,b,"b"); tiskni(n,b,"b");
  /*  vypocet a tisk vektoru c: */
  secti(n,a,b,c);                       /* VYVOLANI FUNKCE secti() */
  tiskni(n,c,"c"); return;
}

Všimněte si, že ve funkčních prototypech není třeba udávat počet prvků jednorozměrných polí. Všimněte si také rozdílu mezi vyvoláním funkce suma(), určené pro součet hodnot dvou jednoduchých proměnných typu double, viz 3.1, a funkce secti():

           suma(a,b,&c);               secti(n,a,b,c);

Zápis &c ve vyvolání funkce suma() znamená adresu proměnné c, zápis c ve vyvolání funkce secti() znamená adresu prvního prvku pole c.

Definice funkcí precti(), secti(), tiskni() obsahuje další soubor. Také v hlavičkách definic jednotlivých funkcí není třeba udávat počet prvků jednorozměrných polí.

/*********************   soubor secti_1d.c   ***********************/
#include <stdio.h>
precti(int n, int a[], const char *s)
{
  int i;
  printf("\n");
  for(i=0;i<n;i++){
    printf("%s[%2d] : ",s,i); scanf("%d",&a[i]);
  }
  return;
}
tiskni(int n, int a[], const char *s)
{
  int i;
  printf("\nTisk vektoru %s :\n",s);
  for(i=0;i<n;i++)
    printf( (i%10==9 ? "  %4d\n":"  %4d"), a[i]);
 return;
}
secti(int n, int a[], int b[], int c[])
{
  int i;
  for(i=0;i<n;i++)
    c[i]=a[i]+b[i];
 return;
}
V následujícím příkladu je analogická úloha řešena pro dvourozměrná pole. Pro správnou funkci programu může být při definici parametrů - dvourozměrných polí - ve funkčních prototypech a v hlavičkách definic jednotlivých funkcí vynechán pouze první rozměr pole, druhý musí být uveden.

Příklad 2. by 1 12:

/*******************     soubor 2d_pole.c         ******************/
#include <stdio.h>
#include <conio.h>
#define M_MAX 30
#define N_MAX 40
/*  funkcni prototypy funkci precti(), tiskni() a secti():         */
precti(int, int, int [][N_MAX], const char *);
secti(int, int, int [][N_MAX], int [][N_MAX], int [][N_MAX]);
tiskni(int, int, int [][N_MAX], const char *);
main()
{
  int m,n;
  int a[M_MAX][N_MAX], b[M_MAX][N_MAX], c[M_MAX][N_MAX];
  clrscr();
  printf("\n Zadej pocet radek m poli  a,b (m <= %d)  : ",M_MAX);
  scanf("%d",&m);
  printf("\n Zadej pocet sloupcu n poli  a,b (n <= %d)  : ",N_MAX);
  scanf("%d",&n);
  precti(m,n,a,"a"); tiskni(m,n,a,"a");
  precti(m,n,b,"b"); tiskni(m,n,b,"b");
  secti(m,n,a,b,c);
  tiskni(m,n,c,"c");
  return;
}

/*********************   soubor secti_2d.c   ***********************/
#include <stdio.h>
#define N_MAX 40
precti(int m, int n, int a[][N_MAX], const char *s)
{
  int i,j;
  printf("\n");
  for(i=0;i<m;i++)
   for(j=0;j<n;j++){
    printf("%s[%2d][%2d] : ",s,i,j); scanf("%d",&a[i][j]);
  }
  return;
}
tiskni(int m, int n, int a[][N_MAX], const char *s)
{
  int i,j;
  printf("\nTisk matice %s :\n",s);
  for(i=0;i<m;i++)
    for(j=0;j<n;j++)
     printf( (j%10==9||j==n-1 ? "  %4d\n":"  %4d"), a[i][j]);
 return;
}
secti(int m, int n, int a[][N_MAX], int b[][N_MAX], int c[][N_MAX])
{
  int i,j;
  for(i=0;i<m;i++)
    for(j=0;j<n;j++)
    c[i][j]=a[i][j]+b[i][j];
 return;
}
Nevýhoda tohoto řešení spočívá v tom, že funkce precti(), secti(), tiskni() nejsou univerzální v tom smyslu, že konstantu N_MAX je nutné upravit podle funkce main(). V následující variantě programu je tento nedostatek odstraněn.

Příklad 2. by 1 12:

/*******************     soubor 2dpole.c         *******************/
#include <stdio.h>
#include <conio.h>
#define M_MAX 30
#define N_MAX 40
/*  funkcni prototypu funkci precti(), tiskni() a secti():         */
precti(int, int, int *[], const char *);
secti(int, int, int *[], int *[], int *[]);
tiskni(int, int, int *[], const char *);

main()
{
  int m,n,i;
  int a[M_MAX][N_MAX], b[M_MAX][N_MAX], c[M_MAX][N_MAX];
 /* definice pomocnych poli pointeru zajistujicich pristup k a,b,c */
  int *ua[M_MAX], *ub[M_MAX], *uc[M_MAX];

  clrscr();
  printf("\n Zadej pocet radek m poli  a,b (m <= %d)  : ",M_MAX);
  scanf("%d",&m);
  printf("\n Zadej pocet sloupcu n poli  a,b (n <= %d)  : ",N_MAX);
  scanf("%d",&n);

  /* vytvoreni pristupu k prvkum pole pomoci ukazatelu             */
  for(i=0;i<M_MAX;i++){
       ua[i]=&a[i][0]; ub[i]=&b[i][0]; uc[i]=&c[i][0];
  }
/*******************************************************************/

  precti(m,n,ua,"a");   tiskni(m,n,ua,"a");
  precti(m,n,ub,"b");   tiskni(m,n,ub,"b");
  secti(m,n,ua,ub,uc); tiskni(m,n,uc,"c"); return;
}

Analogické změny jako ve funkčních prototypech uděláme i v hlavičkách definic funkcí precti(), secti(), tiskni(). Těla těchto funkcí zůstanou nezměněna.

/*********************   soubor secti2d.c   ************************/
#include <stdio.h>
precti(int m, int n, int *a[], const char *s)
{ ...}
tiskni(int m, int n, int *a[], const char *s)
{ ...}
secti(int m, int n, int *a[], int *b[], int *c[])
{ ...}
Uveďme si ještě stručně obdobné řešení pro trojrozměrná pole. Namísto označení analogické M_MAX, N_MAX použijeme v trojrozměrné situaci symbolické konstanty M, N, R.

Příklad 2. by 1 12:

/*******************     soubor 3dpole.c         *******************/
#include <stdio.h>
#include <conio.h>
#define M 10
#define N 10
#define R 10
/*  funkcni prototypy funkci precti(), tiskni() a secti():         */
precti(int, int, int, int **[], const char *);
tiskni(int, int, int, int **[], const char *);
secti(int, int, int, int **[], int **[], int **[]);

main()
{
  int m,n,r,i,j;
  int a[M][N][R], b[M][N][R], c[M][N][R];
  /*** pomocna jedno- a dvourozmerna pole pointeru                 */
  int *ua[M][N],  *ub[M][N],  *uc[M][N];
  int **uua[M],   **uub[M],   **uuc[M];
  /*** nasleduje cteni m,n,r                                       */
   ...
  /*** vytvoreni pristupu k prvkum pole pomoci ukazatelu           */
  for(i=0;i<M;i++){
   for(j=0;j<N;j++){
    ua[i][j]=&a[i][j][0]; ub[i][j]=&b[i][j][0]; uc[i][j]=&c[i][j][0];
   }
  }
  for(i=0;i<M;i++){
    uua[i]=&ua[i][0]; uub[i]=&ub[i][0]; uuc[i]=&uc[i][0];
  }
/*******************************************************************/

  precti(m,n,r,uua,"a"); tiskni(m,n,r,uua,"a");
  precti(m,n,r,uub,"b"); tiskni(m,n,r,uub,"b");
  secti(m,n,r,uua,uub,uuc); tiskni(m,n,r,uuc,"c"); return;
}

/*********************   soubor secti3d.c   ************************/
#include <stdio.h>
precti(int m, int n, int r, int **a[], const char *s)
{...}
tiskni(int m, int n, int r, int **a[], const char *s)
{...}
secti(int m, int n, int r, int **a[], int **b[], int **c[])
{...}

5.  Operátor typedef

Operátor typedef je určen pro vytvoření nového označení určitého datového typu. Používá se pro zvýšení přehlednosti programu. Při definicích jednoduchých datových typů se příliš nepoužívá, v definicích ukazatelů, polí a dalších odvozených datových typů je jeho použití daleko častější.

Příklad 2. by 1 12:

typedef int ROCNIK;    typedef int *P_INT;      typedef int POLE[10];
ROCNIK a,b,c;          P_INT p1,p2,p3;          POLE x,y,z;

typedef double (*FUN)(double, int);
FUN f1,f2;

V prvním příkladu bylo vytvořeno nové jméno ROCNIK pro označení datového typu typu int a pomocí tohoto jména byly definovány proměnné a,b,c. Ve druhém případě byly proměnné p1,p2,p3 definovány jako ukazatelé na typ int pomocí nově vytvořeného označení P_INT tohoto typu. Ve třetím případě byly definovány proměnné x,y,z jako jednorozměrná pole typu int obsahující 10 prvků. V posledním případě byl definován typ ukazatel na funkci vracející hodnotu double s parametry typu double a int. V dalších odstavcích se zmíníme o dalších možnostech použití operátoru typedef.

6.  Výčtový typ

Výčtový typ se často používá pro definici proměnných, které mohou nabývat pouze konečného počtu hodnot.



Příklad 2. by 1 12:

enum den{PONDELI,UTERY,STREDA,CTVRTEK,PATEK,SOBOTA,NEDELE}den1,den2;

Proměnné den1,den2 zde byly definovány jako proměnné výčtového typu den, tj. jako proměnné , které mohou nabývat jedné z uvedených hodnot:

         {PONDELI,UTERY,STREDA,CTVRTEK,PATEK,SOBOTA,NEDELE}

Pokud nebyl vynechán identifikátor výčtového typu, (v našem případě identifikátor den,) je možné použít ho pro definici proměnných v jiném místě programu:

                       enum den den3, den4;

Jinou možností je zavedení nového označení datového typu operátorem typedef:

enum den{PONDELI,UTERY,STREDA,CTVRTEK,PATEK,SOBOTA,NEDELE};
typedef enum den DEN;

Definici proměnných den1, den2, den3, den4 lze pomocí nově zavedeného typu DEN zapsat takto:

                       DEN den1,den2,den3,den4;

Hodnoty uvedené ve výčtu datového typu enum jsou reprezentovány hodnotami typu int. Jednotlivým položkám jsou přiřazena celá čísla počínaje od nuly a zvyšující se o 1. V našem příkladu má tedy PONDELI hodnotu 0 a NEDELE hodnotu 6. Toto implicitní přiřazení může být změněno, jestliže přiřadíme explicitně některé položce seznamu explicitní hodnotu.



Příklad 2. by 1 12:

                 enum krev_skupiny{NULA,A,B,AB,BA=3};

Konstanty AB a BA zde budou označovat stejné hodnoty.

Nově zavedený typ může být použit např. pro definici návratové hodnoty funkce, nebo typu parametru funkce:



Příklad 2. by 1 12:

              DEN zitra(DEN d)
              {   DEN z;
                  z=(DEN)((int)d+1)%7); /* staci ovsem z = (d+1)%7; */
                  return(z);
              }

7.  Struktury

Struktura je odvozený datový typ charakteristický tím, že narozdíl od pole může obsahovat datové položky různých typů.

7.1  Definice struktury, položky struktury

Ukážeme si základní rysy práce se strukturami na příkladu: Definujme strukturu clovek obsahující datové položky vek, vyska, vaha:
        struct clovek{int vek; int vyska; double vaha;}c1,c2,c3;

Proměnné c1, c2, c3 zde byly definovány jako proměnné typu struktura clovek. Pokud nebyl vynechán v definici identifikátor struktury, (v našem případě identifikátor clovek,) je možné použít ho pro definici proměnných v jiném místě programu:

                       struct clovek c4, c5, c6;

Jinou možností je zavedení nového označení datového typu operátorem typedef:

       struct clovek{int vek; int vyska; double vaha;};
       typedef struct clovek CLOVEK;

Definici proměnných c1,c2,c3,c4,c5,c6 lze pomocí nově zavedeného typu CLOVEK zapsat takto:

                       CLOVEK c1,c2,c3,c4,c5,c6;

Datový typ CLOVEK lze zavést i zápisem v obráceném pořadí:

       typedef struct clovek CLOVEK;
       struct clovek{int vek; int vyska; double vaha;};

Tyto dva řádky je také možné spojit do jednoho zápisu:

     typedef struct clovek{int vek; int vyska; double vaha;} CLOVEK;

Přístup k jednotlivým položkám struktury je pomocí tečkové notace:

Příklady:

 c1.vek=69;   c1.vyska=182;   c1.vaha=81.4;    c2.vyska=c1.vyska;

Definujeme-li ukazatel na proměnné c1,c2

                    CLOVEK *p_c1=&c1, *p_c2=&c2;

lze získat přístup k jednotlivým položkám struktury c1 pomocí ukazatele na c1 takto:

p_c1->vek=69;p_c1->vyska=182;p_c1->vaha=81.4;p_c2->vyska=p_c1->vyska;
V uvedených zápisech, (kde používáme ukazatel na strukturu) je výhodný pro svoji stručnost operátor -> namísto operátoru . např.:


p_c1->vek=69; je stručnější než zápis (*p_c1).vek=69;



ANSI norma jazyka C, (narozdíl od K&R normy), rovněž umožňuje pracovat se strukturami jako s celkem. Následující příkaz kopíruje obsah struktury c1 do struktury c2:

                                 c2=c1;


ANSI norma jazyka C rovněž umožňuje používání tzv. bitových položek struktur. Položka je popsána jako položka typu unsigned int nebo signed int, za identifikátorem položky udáváme počet bitů, které jí chceme vyhradit. Struktura obsahující údaje o pacientovi by např. mohla obsahovat následující bitovou položku, indikující zda pacient trpí cukrovkou:


unsigned diabetes:1;


Uvedená bitová položka struktury zabírá v paměti 1 bit. Platí následující omezení: Bitová položka struktury nesmí být delší než datový typ int v dané implementaci. Za dvojtečkou tedy mohou následovat čísla 1, 2, ... sizeof(int).


7.2  Struktury a pole


7.2.1 Pole jako položka struktury:

Definujme proměnné p1, p2 jako strukturu pacient obsahující datové položky jmeno, prijmeni, vyska, vaha, teplota, charakterizující zdravotní stav pacienta v nemocnici. Budeme předpokládat, že jméno ani příjmení pacienta neobsahuje více znaků než 30 a že jeho pobyt v nemocnici nebude delší než 100 dní.

Příklad 2. by 1 12:

typedef struct pacient PACIENT;
struct pacient{char jmeno[30]; char prijmeni[30]; int vyska;
               double vaha[101]; double teplota[101];};
PACIENT p1,p2;
Tisk příjmení pacienta p1 a jeho teploty po 14 dnech pobytu v nemocnici lze získat následujícími příkazy:

     i=14;
     printf("%s\t %d.den teplota: %f",p1.prijmeni,i,p1.teplota[i]);
Struktura může jako položku obsahovat jinou strukturu:



Příklad 2. by 1 12:

typedef enum mesic MESIC;
enum mesic {LEDEN=1,UNOR,BREZEN,DUBEN,KVETEN,CERVEN,
                   CERVENEC,SRPEN,ZARI,RIJEN,LISTOPAD,PROSINEC};

typedef struct datum_narozeni DATUM_NAROZENI;
struct datum_narozeni{int den; MESIC mes; int rok;};
typedef struct student STUDENT;
struct student{char jmeno[30]; char prijmeni[30]; DATUM_NAROZENI datum;
               int rocnik;};
STUDENT Petr,Jan,Pavel,Helena,Ivana;

7.2.2 Pole struktur:

Využijme datového typu PACIENT, definovaného v příkladu 2.20 a definujme pole typu PACIENT:
                  PACIENT pacienti[200];

Vytiskněme nyní příjmení 25. pacienta a jeho teplotu po 4 dnech pobytu v nemocnici:

     i=24; j=4;
     printf("%s\t %d.den teplota: %f",pacienti[i].prijmeni,j,
             pacienti[i].teplota[j]);

7.3  Struktury a funkce

K&R norma jazyka C umožňuje, že funkce může vracet ukazatel na strukturu a ukazatel na strukturu také může být parametrem funkce.

Podle ANSI normy jazyka C může být návratovou hodnotou funkce i struktura a struktura také může být předána funkci jako skutečný parametr.

Příklad 2. by 1 12: Funkce pro výpočet vektorového součinu dvou vektorů - návratovou hodnotou funkce je struktura:

typedef struct vector{double s[3];} VECTOR;
VECTOR v_soucin1(VECTOR a, VECTOR b)
{
   VECTOR c;
   c.s[0]=a.s[1]*b.s[2]-a.s[2]*b.s[1];
   c.s[1]=a.s[2]*b.s[0]-a.s[0]*b.s[2];
   c.s[2]=a.s[0]*b.s[1]-a.s[1]*b.s[0]; return(c);
}
#include <stdio.h>
main()
{
  VECTOR a,b,c;
/* ...                */
  c=v_soucin1(a,b);         /* vyvolani v hlavnim programu */
/* ...                */
}

Tento způsob je výhodný pouze v případě, že struktury nejsou příliš veliké. Výsledek funkce je ovšem možné předat parametrem, tak jako v případě jednoduché proměnné:

Příklad 2. by 1 12: Funkce pro výpočet vektorového součinu dvou vektorů - výsledek je předán hlavní funkci parametrem - ukazatelem na strukturu:

typedef struct vector{double s[3];} VECTOR;
void v_soucin2(VECTOR *pa, VECTOR *pb, VECTOR *pc)
{
   pc->s[0]=pa->s[1]*pb->s[2]-pa->s[2]*pb->s[1];
   pc->s[1]=pa->s[2]*pb->s[0]-pa->s[0]*pb->s[2];
   pc->s[2]=pa->s[0]*pb->s[1]-pa->s[1]*pb->s[0]; return;
}
#include <stdio.h>
main()
{
  VECTOR a,b,c;
/* ...                */
  v_soucin2(&a,&b,&c);         /* vyvolani v hlavnim programu */
/* ...                */
}

8.  Uniony

Union (sjednocení) je odvozený datový typ, který podobně jako struktura může obsahovat datové položky různých typů. Rozdíl mezi strukturou a unionem je v přidělení paměti. Položky struktury jsou uloženy za sebou, tj. objektu typu struct se přidělí paměť potřebná pro uložení všech položek, zatímco položky unionu jsou ukládány přes sebe, tj. objektu typu union se přidělí paměť potřebná pro nejdelší položku. Klíčové slovo je union. Při definici unionu se postupuje analogicky jako u struktur, včetně použití operátoru typedef. Analogický je i přístup k položkám unionu pomocí tečkové notace, i nepřímý přístup pomocí ukazatele na union.

Příklad 2. by 1 12: Definice unionu a přístup k jeho položkám:

     /*  ...                                         */
     union znak_int(char zn; int cele) u1,u2;
     u1.zn='*';
     u1.cele=44;  /* znak '*' se premaze hodnotou 44
         ...                                         */

ANSI norma jazyka C umožňuje inicializaci první položky unionu:

union znak_int(char zn; int cele) u='*'; /* u.zn='*' */

Obsah

Kapitola 3
Vstup a výstup

Programovací jazyk C nemá žádné příkazy pro vstupní a výstupní operace, jako např. FORTRAN, kde příkazy READ a WRITE jsou součástí jazyka. V této kapitole budou popsány některé standardní knihovní funkce zajišťující operace vstupu a výstupu. Funkční prototypy standardních knihovních funkcí zajišťujících vstup a výstup jsou obsaženy v hlavičkovém souboru <stdio.h>.

Soubor je v jazyce C chápán jako posloupnost slabik (bytů), která je ukončena určitou speciální kombinací slabik a má své jméno. Ukončovací kombinace - konec souboru, která už do obsahu souboru nepatří, má hodnotu symbolické konstanty EOF typu int, (protože pro ukončení posloupnosti znaků je třeba použít jinou hodnotu než znak). Je definována v hlavičkovém souboru stdin, obvykle je reprezentována hodnotou -1. V terminologii jazyka C (i operačního systému UNIX ) se zmíněná posloupnost slabik nazývá proud dat (stream) .

1.  Standardní vstup a výstup

Jakmile je spuštěn program v jazyce C, automaticky se otevírají tři soubory: standardní vstup, standardní výstup a standardní výstup pro chybová hlášení. Tyto soubory se zpravidla označují jako stdin, stdout, stderr. Obvykle je jako standardní vstup použita klávesnice a jako standardní výstup (i pro chybová hlášení) obrazovka. V tomto odstavci se budeme zabývat funkcemi, které umožňují komunikaci programu se standardním vstupem a výstupem.

1.1  Vstup a výstup znaků

S funkcemi getchar(),putchar() jsme se setkali už v první kapitole. Funkce getchar() je určena pro vstup znaků ze standardního vstupu, funkce putchar() pro výstup znaků na standardní výstup. Funkce mají následující funkční prototypy:
              int getchar(void);
              int putchar(int c);

Funkce getchar() nemá žádný parametr, návratovou hodnotou této funkce je kód znaku, přečtený ze standardního vstupu stdin. (Jestliže byla přečtena hodnota symbolické konstanty EOF vrací funkce tuto hodnotu.)

Funkce putchar má jako argument hodnotu typu int, zadávající kód znaku, který má být zapsán na standardní výstup stdout. Návratová hodnota funkce je kód zapsaného znaku, nebo EOF pokud při výstupu došlo k chybě.

Příklad 3. by 1 13:

/*********************************************************************
* Program kopiruje znaky ze standardniho vstupu na standardni vystup *
* Ukoncovaci "znak" souboru EOF lze zadat kombinaci 2 klaves: Ctrl+Z *
*********************************************************************/
#include <stdio.h>
main()
{ int c;

  /* Testovani konce souboru */
  while((c=getchar())!=EOF)       /* c=getchar() musi byt v zavorce */
  putchar(c);
}

1.2  Formátovaný výstup - funkce printf()

S funkcemi printf() a scanf() jsme se rovněž setkali v první a druhé kapitole. Vyložíme nyní systematicky základní možnosti použití těchto funkcí.

Funkce printf() se používá pro formátovaný výstup dat na standardní výstup stdout. Funkční prototyp má následující tvar:

      int printf(const char *retezec, arg1, arg2, ..., argn);

Parametry arg1, arg2, ... argn jsou obecně výrazy, jejichž hodnoty mají vystoupit v určitém tvaru na stdout. Funkce má proměnný počet argumentů, volání funkce ale musí obsahovat alespoň první argument retezec, tj. formátový řetězec, který obsahuje jednak libovolné znaky, jež se beze změny kopírují na výstup, jednak formátové specifikace (konverze) určující, v jakém tvaru se parametry arg1, arg2, ..., argn zobrazí. Vzájemné přiřazení parametrů a konverzí je provedeno podle pořadí zleva doprava. V případě, že ve formátovém řetězci specifikujeme více konverzí, než kolik jsme zadali skutečných parametrů, vznikne chyba a nelze předpovědět co vystoupí.

Návratová hodnota funkce printf() udává počet slabik, které byly funkcí zapsány do stdout.

Příklad 3. by 1 13:

printf("\n2. mocnina %d. prvku pole A je rovna cislu %f",i,A[i]*A[i]);

Znak '\n' je zde interpretován jako přechod na novou řádku, viz tabulku escape sekvencí v části . Kombinace %d, %f jsou konverze určené pro výstup hodnot po řadě typu int a double, přitom hodnota i se tiskne podle konverze %d a hodnota A[i]*A[i]) podle konverze %f.

Kdyby byl uvedený příkaz printf() umístěn v cyklu

 for(i=0;i<3;i++){...}

vypadal by výstup pro Ai = ii = 0,1,2 takto:

2. mocnina 0. prvku pole A je rovna cislu 0.000000
2. mocnina 1. prvku pole A je rovna cislu 1.000000
2. mocnina 2. prvku pole A je rovna cislu 4.000000
První argument funkce printf() je, jak vyplývá z jejího funkčního prototypu, ukazatel na konstantní řetězec, viz 2.2. V následujícím příkladu je tento ukazatel zadán pomocí identifikátoru pole typu char.


Příklad 3. by 1 13:

#include <stdio.h>
#include <stdio.h>
main()
{ int i;
  char a[][8]={{"\nAhoj,"},{" rad"},{" te"},{" zase"},{" vidim!"}};
  char b[15]={"\nJa tebe taky!"};

  for(i=0;i<5;i++) printf(a[i]);
  printf(b); return;
}

výstup tohoto programu by vypadal takto:

Ahoj, rad te zase vidim!
Ja tebe taky!

Formátová specifikace má obecně následující tvar:

 %[příznaky][šířka][.přesnost][modifikátor]konverze

Závorky [] zde označují nepovinné parametry.

Jednotlivé položky specifikace vysvětlíme v pořadí jejich důležitosti.

1.2.1 Konverze:

Konverze je povinný parametr, je označena jedním znakem, mezi znakem % a označením konverze mohou být umístěné další (nepovinné) parametry. Následující tabulka ukazuje, jaké konverze se používají pro výstup hodnot jednotlivých datových typů, eventuálně v jakém tvaru se zobrazí.
konverze:   typ položky seznamu odpovídající konverzi:
%c          znak; (je-li hodnota typu int, je převedena na typ
            unsigned char)
%d %i       číslo typu signed int, desítkový (dekadický) zápis
%u          číslo typu unsigned int, desítkový zápis
%o          číslo typu unsigned int, osmičkový (oktalový) zápis
%x %X       číslo typu unsigned int, šestnáctkový (hexadecimální)
            zápis, číslice označené a,b,c,d,e,f nebo A,B,C,D,E,F
%f          číslo typu float, double, desetinný tvar
%e %E       číslo typu float, double, semilogaritmický tvar,
            exponent označen podle konverze e nebo E
%g %G       číslo typu float, double, tvar zvolen podle výhodnosti
            zápisu jako desetinný nebo semilogaritmický, exponent
            je podle konverze (v semilogaritmickém tvaru) e nebo E
%s          řetězec (bez ukončovacího znaku '\0')
%p          ukazatel; tiskne se jeho obsah nejčastěji v šestnáctkovém
            zápisu
%n          ukazatel na typ int. Na adresu na kterou ukazuje se za-
            píše počet znaků který byl až dosud tímto voláním zapsán
            na výstup, netiskne se nic,

1.2.2 Modifikátor:

h           modifikuje konverze d,i na typ signed short int
                       konverze u,o,x,X na typ unsigned short int
l           modifikuje konverze d,i na typ signed long int
                       konverze u,o,x,X na typ unsigned long int
                       konverze f,e,E,g,G na typ double
L           modifikuje konverze f,e,E,g,G na typ long double

1.2.3 Šířka:

n           tiskne se alespoň n znaků, mezery se doplňují zprava
            nebo zleva, viz příznaky
0n          tiskne se alespoň n znaků, namísto mezer se doplňují nuly
*           šířka je zadána nepřímo: argument, který "je na řadě"
            obsahuje šířku, (musí být typu int), následuje argument,
            který bude vystupovat

1.2.4 Přesnost:

Přesnost je dekadické číslo, které pro konverze d, i, o, u, x, X znamená minimální počet cifer na výstupu, pro konverze f, e, E, znamená počet cifer za desetinnou tečkou, pro konverze g, G znamená počet významových cifer a pro konverzi s maximální počet znaků. Kromě toho má .* a . následující význam:
.*          přesnost je zadána nepřímo: argument, který "je na řadě",
            obsahuje přesnost,(musí být typu int), následuje argument,
            který bude vystupovat
.           znamená totéž co .0

1.2.5 Příznak:

-           výsledek se zarovná doleva, zprava se doplní mezery
            není-li uveden, výsledek se zarovná doprava a zleva
            se doplní mezery nebo nuly
+           číslo typu signed se vytiskne vždy se znaménkem
            není-li uveden vynechá se znaménko '+' u kladných
            hodnot
mezera      kladné číslo se vytiskne bez znaménka, "+" bude nahra-
            zeno mezerou


Příklady:

 Hodnota:     Konverze:      Výstup:         Poznámka:
 3.141592     %7.3             3.141         mezery se doplní zleva
 3.141592     %-7.3          3.141           mezery se doplní zprava
 369.24       %+11.3E         +3.692E+02
 Ahoj!        %2s            Ahoj!
 Ahoj!        %.2s           Ah

1.3  Formátovaný vstup - funkce scanf()

Funkce scanf() se používá pro formátovaný vstup dat ze standardního vstupu stdin. Funkční prototyp má následující tvar:
      int scanf(const char *retezec, arg1, arg2, ..., argn);

Parametry arg1, arg2, ... argn jsou adresy proměnných, jejichž hodnoty se mají přečíst ze standardního vstupu. (Jak víme, jsou parametry funkcí volány hodnotou. Chceme-li měnit ve funkci hodnoty některých proměnných, musíme použít jejich adresy jako parametry funkce.)

Funkce má proměnný počet argumentů, volání funkce ale musí obsahovat alespoň první argument retezec, tj. formátový řetězec. Tento řetězec obsahuje formátové specifikace (konverze) určující, jak se budou jednotlivé čtené posloupnosti slabik interpretovat. Dále může formátový řetězec obsahovat bílé znaky a ostatní znaky (ASCII znaky různé od % a bílých znaků). Pokud funkce scanf() najde ve formátovém řetězci bílý znak, přečte všechny následující bílé znaky ze vstupu až po první jiný znak. Tyto bílé znaky nejsou přitom transformovány v žádnou vstupní hodnotu. Pokud najde funkce scanf() ostatní znak ve formátovém řetězci očekává, že následující znak z stdin bude s tímto znakem totožný. Tento znak bude přečten a ignorován.

Formátové specifikace mají následující tvar:

            %[šířka][modifikátor]konverze

Význam parametrů modifikátor, konverze je stejný jako ve formátových specifikacích funkce printf(). Parametr šířka určuje počet znaků tzv. vstupního pole. Bude přečteno maximálně tolik znaků, kolik zadává parametr šířka. Pokud narazí funkce scanf() na bílý znak nebo na znak, který nepatří do zápisu čtené hodnoty, ukončí se čtení dříve. Na rozdíl od funkce printf(), kde lze konverze f, e, E, g, G použít pro výstup hodnot typu float i typu double, lze tyto konverze ve funkci scanf() obvykle použít pouze pro vstup hodnot typu float, zatímco pro hodnoty typu double je nutné použít tyto konverze s modifikátorem l, tedy lf, le, lE, lg, lG. Kromě toho mají konverze f, e, E, g, G na vstupu stejný význam, všechny lze je použít k přečtení čísla zapsaného v desetinném i semilogaritmickém tvaru.

Návratová hodnota funkce scanf() je počet přečtených vstupních polí.

Příklad 3. by 1 13:

char c; int i; float r_1; double r_2; char text[9];
                ...
scanf("%c%d%f%lf%8s",&c,&i,&r_1,&r_2,text);

V uvedeném příkladu je čtení řetězce omezeno na jeho prvních 8 znaků, (devátý znak je vyhrazen pro ukončovací znak '\0'). Návratová hodnota funkce by v našem příkladu byla rovna 5.

1.4  Vstup a výstup řádek

Jak jsme uvedli v předchozí části, pokud narazí funkce scanf() na bílý znak nebo na znak, který nepatří do zápisu čtené hodnoty, ukončí se čtení dříve. To poněkud komplikuje čtení řetězců obsahujících v textu mezery. Naproti tomu funkce gets(), puts() pracují s textovými řádkami jako s celky:


       char *gets(char *str);
       int puts(char *str);
Funkce gets() přečte řetězec znaků až do znaku '\n' tj. textovou řádku ze standardního vstupního zařízení a uloží ji do řetězce str. Znak '\n' se neukládá a řetězec je automaticky ukončen znakem '\0'. Návratová hodnota funkce je ukazatel na řetězec str. Pokud je řetězec prázdný, vrací funkce hodnotu symbolické konstantu NULL.

Funkce puts() vytiskne řetězec str a odřádkuje, tj. vypíše znak '\n'. Funkce vrací nezáporné číslo, v případě že operace nemůže z nějakého důvodu proběhnout je návratová hodnota rovna EOF. Příklady na probrané funkce a některé další uvedeme v části .

2.  Vstup ze souboru a výstup do souboru

Kromě standardních, automaticky otevíraných souborů stdin, stdout, stderr, je možné otevřít a používat další soubory. V hlavičkovém souboru stdio.h je pro práci s nimi zaveden datový typ FILE. Identifikátor souboru je typu FILE * tj. ukazatel na typ FILE. Norma ANSI jazyka C rozeznává dva druhy souborů - textový a binární.

V textovém souboru lze knihovními funkcemi vytvářet a rozeznávat textové řádky. To je umožněno tím, že systém při zápisu automaticky některé znaky do souboru doplňuje v souladu s konvencí, která pro textové soubory v daném operačním systému platí. Při čtení textového souboru se tyto znaky naopak automaticky vyřazují. Tento režim práce s textovými soubory zajišťuje, že jejich obsah si lze prohlédnout, vytvořit nebo opravit běžným editorem.

Binární soubor není funkcemi čtení a zápisu nijak ovlivňován. To znamená, že co do binárního souboru zapíšeme, to v něm také přesně bude, a co je v binárním souboru zapsáno, to se také přesně přečte. Výhoda binárních souborů spočívá v tom, že pro uchování stejného množství informace potřebují mnohem méně prostoru než textové soubory a práce s nimi je také daleko rychlejší.

2.1  Otevření a uzavření souboru

Pro otevření a uzavření souboru se používá dvojice knihovních funkcí fopen(), fclose(). Funkce fopen() slouží k otevření existujícího souboru nebo k vytvoření nového souboru:

    FILE *fopen(const char *jmeno, const char *modus);
První parametr jmeno je označení souboru, který se má otevřít nebo vytvořit. Za tento parametr lze dosadit řetězec nebo pole typu char obsahující jméno souboru (eventuálně včetně disku a cesty). Druhý parametr modus určuje mód, ve kterém se má s otevíraným souborem pracovat. V následující tabulce jsou uvedeny možnosti, jak lze druhý parametr zvolit:


         "r"             textový soubor pro čtení
         "w"             textový soubor pro zápis nebo pro přepsání
         "a"             textový soubor pro připojení na konec
         "rb"            binární soubor pro čtení
         "wb"            binární soubor pro zápis nebo pro přepsání
         "ab"            binární soubor pro připojení na konec
         "r+"            textový soubor pro čtení a zápis
         "w+"            textový soubor pro čtení, zápis nebo přepsání
         "a+"            textový soubor pro čtení a zápis na konec
         "rb+"           binární soubor pro čtení a zápis
         "wb+"           binární soubor pro čtení, zápis nebo přepsání
         "ab+"           binární soubor pro čtení a zápis na konec
Pokud lze soubor jmeno otevřít nebo vytvořit v daném módu, vrátí funkce ukazatel na typ FILE, který bude sloužit k identifikaci souboru jmeno při práci s dalšími knihovními funkcemi. Pokud se soubor nepodaří otevřít nebo vytvořit, vrátí funkce fopen() hodnotu symbolické konstanty NULL, tedy pointer, který neukazuje na žádný objekt.

Pro uzavření souboru je určena funkce fclose():

  int fclose(FILE *file);
Pokud se soubor identifikovaný ukazatelem file nepodaří uzavřít, (např. nebyl-li otevřen,) vrací funkce hodnotu symbolické konstanty EOF.

Příklad 3. by 1 13:

Použití funkcí fopen(), fclose():

FILE *soubor;
     ...
soubor=fopen("DATA.TXT","r");
     ...
fclose(soubor);
     ...

V uvedeném příkladu je funkcí fopen() otevřen soubor DATA.TXT jako textový soubor pro čtení. Vyvoláním funkce fclose() je v našem příkladu soubor DATA.TXT uzavřen. (Patří k dobrým programátorským zvykům uzavřít soubor ihned po ukončení práce s ním.)

Operace otevření a uzavření souboru nemusí ovšem z určitých důvodů proběhnout, program by proto měl testovat úspěšnost provedení těchto operací:


Příklad 3. by 1 13:

FILE *soubor;
     ...
soubor=fopen("DATA.TXT","r");
if(soubor==NULL){printf("\nChyba pri otevreni DATA.TXT");return;}
     ...
if(fclose(soubor)==EOF){printf("\nChyba pri uzavreni DATA.TXT");return;}
     ...

2.2  Základní operace s otevřeným souborem

2.2.1 Vstup znaků ze souboru a výstup znaků do souboru

Funkce getc(), putc() určené pro vstup znaků ze souboru a výstup znaků do souboru jsou analogické funkcím getchar() a putchar() pro standardní vstup a výstup znaků, které jsme probrali v části 1.1.

Funkce getc() je tedy určena pro vstup znaků ze souboru, funkce putc() pro výstup znaků do souboru. Uveďme pro srovnání funkční prototypy všech čtyř funkcí:

     int getchar(void);         int getc(FILE *file);
     int putchar(int c);        int putc(int c, FILE *file);

Funkce getc() má jediný parametr typu ukazatel na FILE identifikující soubor, ze kterého má být znak přečten, návratovou hodnotou této funkce je kód znaku, přečtený z určeného souboru. (Jestliže byla přečtena hodnota symbolické konstanty EOF vrací funkce tuto hodnotu.)

Funkce putc má jako první argument hodnotu typu int, zadávající kód znaku, který má být zapsán do souboru, který je identifikován druhým parametrem. Návratová hodnota funkce je kód zapsaného znaku, nebo EOF pokud při výstupu došlo k chybě.

Následující program využívá knihovní funkce fopen(), fclose(), getc() pro určení počtu řádek textového souboru.

Příklad 3. by 1 13:

#include <stdio.h>
#include <ctype.h>
/********************************************************************/
/* Program urci pocet radek a neprazdnych radek textoveho souboru   */
/* (prazdna radka - radka, na ktere jsou zapsany pouze bile znaky)  */
/********************************************************************/
main()
{
  FILE *soubor; int c,zn=0,rd=0,rd_0=0;
/********************************************************************/
/*  zn   - pocet znaku na radce (bile znaky nejsou zapocitany)      */
/*  rd   - pocet radek (radky obsahujici pouze bile znaky nejsou    */
/*                                                   zapocitany)    */
/*  rd_0 - pocet radek (vcetne "prazdnych" radek)                   */
/********************************************************************/
  if((soubor=fopen("TEXT","r"))==NULL){          /* test - otevreni */
       printf("\nChyba pri otevreni TEXT"); return;
  }
  while((c=getc(soubor))!=EOF){             /* test - konec souboru */
    if(c=='\n'){                            /* test - konec radky   */
      rd_0++;
      if(zn>0)rd++;
      zn=0;
    }
    if(isspace(c)==0)zn++;      /* podminka neplati pro "bily" znak */
  }
  rd_0++;
  if(zn>0)rd++;
  printf("\nPocet neprazdnych radek=%d\nPocet vsech radek=%d",rd,rd_0);
  if(fclose(soubor)==EOF)printf("\nChyba pri uzavreni TEXT ");
  return;
}

Uvedený program využívá knihovní funkci (přesněji, jde o předdefinované makro, viz čl. 5.1 a 7.1) isspace() k identifikaci bílých znaků. Proto je na začátku programu vložen hlavičkový soubor ctype.h příkazem #include.

2.2.2 Formátovaný vstup a výstup

Pro formátovaný vstup ze souboru a formátovaný výstup do souboru se používají funkce fscanf(), fprintf() analogické funkcím scanf(), printf() určeným pro formátovaný standardní vstup a výstup:


int scanf(const char *retezec, arg1, arg2, ..., argn);
int fscanf(FILE *file, const char *retezec, arg1,arg2,...,argn);

int printf(const char *retezec, arg1, arg2, ..., argn);
int fprintf(FILE *file, const char *retezec, arg1,arg2,...,argn);
Funkční prototypy funkcí fscanf(), fprintf() obsahují tedy oproti funkcím scanf(), printf() navíc parametr typu ukazatel na FILE identifikující soubor, se kterým se příslušná vstupní nebo výstupní operace provádí. Ostatní parametry mají stejný význam jako u funkcí scanf(), printf(), stejně je také definována návratová hodnota obou funkcí.

2.2.3 Vstup řádek ze souboru a výstup řádek do souboru

Pro vstup řádky ze souboru slouží funkce:


       char *fgets(char *str, int max, FILE *file);
Funkce fgets() přečte řetězec znaků až do znaku '\n' nejvýše však max znaků ze souboru identifikovaného ukazatelem file a uloží jej do řetězce str. Znak '\n' se ukládá a řetězec je automaticky ukončen znakem '\0'. Návratová hodnota funkce je ukazatel na řetězec str. Pokud je řetězec prázdný, vrací funkce hodnotu symbolické konstanty NULL.

Funkce fputs() je určena pro zápis řetězce do souboru:


       int fputs(char *str, FILE file);
Funkce puts() vytiskne řetězec str do souboru identifikovaného ukazatelem file. Řetězec neukončuje znakem '\n' ani znakem '\0'. Funkce vrací nezáporné číslo, v případě, že operace nemůže z nějakého důvodu proběhnout, je návratová hodnota rovna EOF.



Příklad 3. by 1 13: Program překopíruje zadaný textový soubor do souboru, jehož název (eventuálně i s cestou) určí uživatel.

/**********************************************************************/
/* Program kopiruje libovolny textovy soubor do libovolneho jineho    */
/* textoveho  souboru                                                 */
/**********************************************************************/
#include <stdio.h>
main()
{
  char nazev[20]; int c; FILE *file_r,*file_w;
  printf("\n Ktery soubor se ma kopirovat?"
         "\n Zadej nazev existujiciho textoveho souboru!\n");
  gets(nazev);      /* cteni nazvu souboru, ktery budeme kopirovat    */
  file_r=fopen(nazev,"r");
  if(file_r==NULL){printf("\n Chyba pri otevreni souboru pro cteni!");
                 return;
  }
  printf("\n Do ktereho souboru kopirovat?"
         "\n Zadej novy odlisny nazev textoveho souboru!\n");
  gets(nazev);      /* cteni nazvu souboru, do ktereho ulozime kopii  */
  file_w=fopen(nazev,"w");
  if(file_w==NULL){printf("\n Chyba pri otevreni souboru pro zapis!");
                 return;
  }

  /* Kopirovani souboru: */
                                  /* Testovani konce souboru:         */
  while((c=getc(file_r))!=EOF)    /* c=getc() musi byt v zavorce      */
    putc(c,file_w);

  if((fclose(file_r)==EOF)||(fclose(file_w)==EOF))
    printf("\nNektery ze souboru nelze uzavrit!");
  return;
}

Pro kopírování souborů je zde použito funkcí getc(), putc(), tj. soubor je kopírován znak po znaku. Namísto toho je možné také použít funkce fgets(), fputs(), tj. kopírování po řádkách. V uvedeném programu by bylo třeba změnit jen část programu označenou komentářem /* Kopirovani souboru: */ a doplnit definici pole typu char, která nahradí roli proměnné c. Program bude pracovat za předpokladu, že textové řádky neobsahují více znaků než 100:


Příklad 3. by 1 13:

  ...
char radka[101];         /* k popisum doplnime definici pole 'radka' */
  ...
/* Kopirovani souboru: */
  while(fgets(radka,100,file_r)!=NULL)      /* kopirovani po radkach */
    fputs(radka,file_w);
  ...

2.3  Práce s binárními soubory

V tomto odstavci se budeme zabývat knihovními funkcemi, které jsou specifické pro práci s binárními soubory.

Pro čtení dat z binárního souboru se používá funkce fread():

int fread(char *kam, int delka, int pocet, FILE *file);

kde jednotlivé parametry mají následující význam:


kam    -  adresa v paměti, kam se bude ukládat čtený blok dat
délka  -  délka jedné položky v bloku dat
počet  -  počet datových položek
file   -  proměnná typu ukazatel na FILE identifikující binární soubor
Pro zápis dat do binárního souboru se používá funkce fwrite():


int fwrite(char *odkud, int delka, int pocet, FILE *file);
kde význam formálních parametrů delka, pocet, file je stejný jako jako u funkce fread() a parametr odkud je definován takto:

odkud  -  adresa v paměti, odkud se bude brát zapisovaný blok dat
Obě funkce vrací počet úspěšně přečtených resp. zapsaných položek.

Funkce fread(), fwrite() umožňují ukládat čtená data do části vnitřní paměti zadané ukazatelem kam a zapisovat data z části vnitřní paměti zadané ukazatelem odkud.

Následující funkce umožňují určit, ze které části binárního souboru budeme data číst nebo do které části binárního souboru budeme data ukládat. K tomuto účelu se používají funkce fseek() a ftell().

Funkce fseek() se používá pro nové nastavení pozice v souboru:


int fseek(FILE *file, long posun, int odkud);
kde formální parametry mají následující význam:
file  - proměnná typu ukazatel na FILE identifikující binární soubor
posun - počet slabik (byte) od pozice v souboru dané parametrem 'odkud'
odkud - místo odkud se posun provede, parametr může mít jednu ze tří
        hodnot:  SEEK_SET - od začátku souboru
                 SEEK_CUR - od aktuální pozice v souboru
                 SEEK_END - od konce souboru

Návratová hodnota funkce fseek() je nula v případě úspěšného přesunu a nenulová hodnota v opačném případě.

Funkce ftell() se používá pro zjištění aktuální pozice v souboru:


long ftell(FILE *file);
Funkce vrací posunutí měřené ve slabikách (bytes) od začátku souboru.


Příklad 3. by 1 13: Program otevře binární soubor pro čtení, zápis nebo přepsání. Do tohoto souboru zapíše text: Test funkci fseek() a fwrite(). Pak nastaví ukazatel souboru na začátek souboru, obsah souboru přečte a zobrazí na monitoru:

#include <string.h>
#include <stdio.h>
#include <conio.h>
int main(void)
{
   FILE *f;
   char retezec[] = "Test funkci fseek() a fwrite().";
   int i;
   clrscr();
   /* otevreme soubor pro zapis, ze ktereho je mozne i cist */
   f=fopen("S.TXT", "wb+");
   /* zapiseme retezec do tohoto souboru:       */
   fwrite(retezec,strlen(retezec),1,f);
   /* nastavime "ukazovatko" na zacatek souboru */
   fseek(f, 0, SEEK_SET);
   do
   {    /* cteme jednotlive znaky ze souboru    */
      i=fgetc(f);
      /* vytiskneme je:                         */
      putchar(i);
   }while (i != EOF);
   fclose(f);         /* uzavre soubor S.TXT    */
   return 0;
}

Funkce strlen() se používá pro zjištění délky řetězce (bez ukončujícího znaku), viz čl. .


Příklad 3. by 1 13: Je zadán binární soubor obsahující n položek typu long. Program změní pořadí položek, poslední zapíše jako první, předposlední jako druhou atd. Program je zapsán tak, aby bylo možné zpracovávat velké soubory, které nelze načíst celé do vnitřní paměti. Program je testován na souboru obsahujícím čísla 1, 2, ... n, kde n je zadané kladné číslo typu long.

#include <stdio.h>
vymen_poradi(FILE *file);         /* funkcni prototyp vymen_poradi() */

main()
{  FILE *file;  int c,delka;  long l,i,n;
   file=fopen("data","rb");             /* test, zda soubor existuje */
   if(file!=NULL){
      printf("\nSoubor existuje - premazat?\n\t A/N\n"); c=getchar();
      if( c!='A' && c!='a') return;
   }
/******************** Vytvoreni testovaciho souboru ******************/
   file=fopen("data","wb+");/* pokud soubor existoval, premazal se ***/
   if(file==NULL) { printf("Chyba pri otevreni"); return;}
   printf("\nZadej pocet polozek souboru!\n"); scanf("%ld",&n);
   delka=sizeof(long);
   for(i=1;i<=n;i++) fwrite(&i, delka, 1, file);
/********************* Tisk souboru data *****************************/
   fseek(file,0,SEEK_SET);           /* Nastaveni na zacatek souboru */
   for(i=1;i<=n;i++) {fread(&l, delka, 1, file);printf(" %3ld",l);}

   vymen_poradi(file);

/********************* Tisk souboru po zmene usporadani **************/
   fseek(file,0,SEEK_SET);           /* Nastaveni na zacatek souboru */
   printf("\n\n");
   for(i=1;i<=n;i++) {fread(&l, delka, 1, file);printf(" %3ld",l);}
   fclose(file);  /********* Uzavreni souboru ************************/
   return;
}

vymen_poradi(FILE *file)
{ long j, delka=sizeof(long), pom, zac, kon, n;
  fseek(file,0,SEEK_END);
  n=ftell(file)/delka;            /* Kolik polozek soubor obsahuje? */
  for(j=0;;j++){
  /* cteni 1 polozky od zacatku souboru */
     fseek(file,j*delka,SEEK_SET);      fread(&zac,delka,1,file);

  /* cteni 1 polozky od konce souboru */
     fseek(file,-(j+1)*delka,SEEK_END); fread(&kon,delka,1,file);

  /* vymena prvku 'zac' a 'kon' */
     if(j >= n-1-j) break;
     pom=zac; zac=kon; kon=pom;

  /* zapis zamenenych prvku */
     fseek(file,j*delka,SEEK_SET);      fwrite(&zac,delka,1,file);
     fseek(file,-(j+1)*delka,SEEK_END); fwrite(&kon,delka,1,file);
  } return;
}

2.4  Parametry funkce main()

Dosud jsme používali funkci main() bez parametrů, o využití návratové hodnoty funkce main() jsme se zmínili v části 5.3.

Pokud má hlavní funkce parametry, pak se obvykle označují argc, argv. Hlavička funkce main() v tomto případě vypadá takto:

     main(int argc, char *argv[])

Kdyby byl program např. VYPIS.EXE s hlavní funkcí uvedeného typu spuštěn z příkazové řádky instrukcí

       VYPIS TENTO TEXT

potom by parametry funkce main() měly následující hodnoty:

    argc               3           počet řetězců na příkazové řádce
    argv[0]                        řetězec udávající název programu
    argv[1]            TENTO
    argv[2]            TEXT
Z příkazové řádky lze zadávat i hodnoty číselných parametrů. Tyto hodnoty je ovšem nutné z příslušných řetězců argv[i] získat např. pomocí knihovních konverzních funkcí.

Příklad 3. by 1 13: Program sečte dvě čísla typu long zadaná z příkazové řádky. V případě, že uživatel zadá nesprávný počet parametrů, vypíše příklad správného vyvolání programu SECTI.EXE.

/***************** Program SECTI ************************************/
#include <stdlib.h>          /* Obsahuje funkce pro praci s retezci */
#include <stdio.h>
main(int argc, char *argv[])
{
  long L1,L2;
  if(argc!=3){printf("\nVyvolani programu napr.: SECTI 1 2"
                     "\nProgram secte zadana dve cela cisla.");
                      return;
  }
/* standardni funkce atol() konvertuje retezec na long :            */
  L1=atol(argv[1]); L2=atol(argv[2]);  printf("\tSoucet = %ld",L1+L2);
  return;
}
Funkční prototyp použité konverzní funkce atol() čtenář najde v čl. .

Obsah

Kapitola 4
Dynamická alokace paměti

Alokace paměti je přidělení (vyhrazení) části paměti určitému objektu. Nároky na velikost paměti lze pro některé objekty specifikovat v době překladu programu, v tomto případě se používá tzv. statická alokace paměti. V době běhu programu nedochází s takto přidělenou pamětí k žádné manipulaci.

V některých případech nelze nároky na velikost paměti pro některé objekty v době překladu určit, protože tyto nároky závisí na okolnostech, které vzniknou až v době běhu programu. Alokace paměti v době běhu programu se nazývá dynamická alokace paměti. K dynamické alokaci paměti dochází v různých situacích např. při rekurzivním volání funkcí, při alokaci paměti pro lokální proměnné apod.

V této kapitole se budeme zabývat dynamickou alokací paměti v jedné její části označované jako hromada (heap). Pomocí knihovních funkcí (např. funkce malloc()) je možné dynamicky přidělit (alokovat) oblast paměti určité délky. Tato oblast paměti není pojmenována (nemá identifikátor), přístup k této oblasti paměti je zajištěn adresou, která bývá uložena v proměnné typu ukazatel. Paměť v hromadě zůstává přidělena až do jejího uvolnění (např. funkcí free()) nebo do ukončení běhu programu.

Pro práci s pamětí budeme používat funkce malloc() a free():

   void *malloc(unsigned int delka);
   void free(void *p);
Formální parametr delka udává počet slabik (bytes), které chceme alokovat. Funkce vrací ukazatel na typ void, který představuje adresu prvního přiděleného prvku. Ukazatel je vhodné přetypovat na ukazatel na příslušný konkrétní datový typ. V případě, že se nepodaří požadovanou paměť alokovat, vrací funkce hodnotu NULL.

1.  Dynamicky definované pole

Nejprve se budeme zabývat dynamickou alokací paměti pro jednorozměrné pole v další části tohoto odstavce přejdeme k vícerozměrným polím.

1.1  Dynamicky definované jednorozměrné pole


Příklad 4. by 1 14: Alokace paměti pro 10 čísel typu double, přístup k přidělené paměti a uvolnění paměti:

     ...
  double *d;
     ...
  d=(double *)malloc(10*sizeof(double));/* pretypovani na (double *) */
  if(d==NULL){                          /* test zda alokace probehla */
        printf("\nChyba pri alokaci pameti!"); return;
  }
     ...
/* bezne pouziti d jako ukazatele a jako identifikatoru pole: */
  *d=45.7;  d[0]=1.1;   d[9]=-60.;     /* přístup k přidělené paměti */
     ...
  free(d);                      /* uvolneni pameti pro dalsi pouziti */
     ...

Z toho co jsme uvedli v části 4.3 o souvislosti mezi ukazatelem a polem je zřejmé, že v našem příkladu lze proměnnou d používat s indexovým výrazem a že tedy je možné popsaným způsobem dynamicky definovat jednorozměrná pole. Základní operace s dynamicky definovaným polem se provádějí stejně jako s polem definovaným staticky. Uveďme nicméně dvě odlišnosti staticky a dynamicky definovaných polí:

V části 4.4 jsme v příkladu na sčítání jednorozměrných polí používali staticky definované jednorozměrné pole jako skutečný parametr funkce. Dynamicky definované jednorozměrné pole lze rovněž použít jako skutečný parametr funkce. Zapišme příslušný soubor 1d_pole.c s dynamicky definovanými poli.

Příklad 4. by 1 14: Sčítání jednorozměrných polí - dynamické pole jako parametr funkce:

/*******************     soubor 1d_pole.c, dynamicka pole    ********/
#include <stdio.h>
#include <conio.h>
/**  funkcni prototypu funkci precti(), tiskni() a secti() **/
precti(int, int [], const char *);
tiskni(int, int [], const char *);
secti(int, int [], int [], int []);
main()
{
  int n;
  int *a, *b, *c;
  clrscr();
  printf("\n Zadej pocet prvku n vektoru a,b");
  scanf("%d",&n);
/**  dynamická definice poli a,b,c **/
  a=(int *)malloc(n*sizeof(int));
  b=(int *)malloc(n*sizeof(int));
  c=(int *)malloc(n*sizeof(int));

/**  cteni a kontrolni tisk vektoru a,b **/
  precti(n,a,"a"); tiskni(n,a,"a"); precti(n,b,"b"); tiskni(n,b,"b");
/**  vypocet a tisk vektoru c **/
  secti(n,a,b,c);                             /** secteni vektoru **/
  tiskni(n,c,"c"); return;
}
Soubor secti_1d obsahující definice funkcí precti(), secti(), tiskni() by zůstal i pro takto definovaná pole beze změn.

1.2  Dynamicky definovaná vícerozměrná pole


Příklad 4. by 1 14: Dynamická alokace paměti pro dvourozměrné pole obsahující 10x20 prvků typu double.

    ...
    double **a;
/* alokujeme prostor pro 10 pointeru na typ double             */
    a=(double **)malloc(10*sizeof(double*));
/*  a je pointer, ktery ukazuje na 1. z techto deseti pointeru */
/* 10 pametovych mist zatim zadne adresy neobsahuje            */

    for(i=0;i<10;i++)
      a[i]=(double *)malloc(20*sizeof(double));

/*  a[i] je nyni pointer ukazujici na zacatek bloku pameti,
    ve kterem je misto pro 20 cisel typu double               */
    ...

Přístup k jednotlivým prvkům tohoto dynamicky definovaného pole je stejný jako v případě staticky definovaného pole tedy např.

   a[9][19]=10.;  *(*(a+6)+7)=44.;    a[10][1]=0.;    a[0][20]=8.;

první dva příkazy jsou dobře, třetí a čtvrtý jsou chybně, protože přepisují část paměti, která pravděpodobně není vyhrazena pro pole a, což pro běh programu může mít fatální následky.

V uvedeném příkladu se jednalo o tzv. obdélníkové pole, které v každé "řádce" obsahovalo 20 prvků. Je zřejmé, že lze analogicky definovat pole s nestejnou délkou "řádek". Často se např. používá pole řetězců, tj. dvourozměrné pole typu char s nestejnou délkou "řádek".

Zcela analogicky lze alokovat paměť i pro vícerozměrná pole. V části 4.4 jsme v příkladu na sčítání trojrozměrných polí používali staticky definovaná trojrozměrná pole jako skutečné parametry funkce. Dynamicky definované vícerozměrné pole lze rovněž použít jako skutečný parametr funkce. Zapišme příslušný soubor 3dpole.c s dynamicky definovanými poli.

Příklad 4. by 1 14: Dynamická definice trojrozměrného pole, použití tohoto pole jako parametru funkce:

/*******************     soubor 3dpole.c,  dynamicka pole  **********/
#include <stdio.h>
#include <conio.h>
/*******  funkcni prototypu funkci precti(), tiskni() a secti() ****/
precti(int, int, int, int ***, const char *);
tiskni(int, int, int, int ***, const char *);
secti(int, int, int, int ***, int ***, int ***);

main()
{ int m,n,r,i,j, ***a, ***b, ***c;
/*********** nasleduje cteni m,n,r *********/
   .....
/********   dynamicka definice trojrozmernych poli a,b,c   *********/
  a=(int ***)malloc(m*sizeof(int **));
  b=(int ***)malloc(m*sizeof(int **));
  c=(int ***)malloc(m*sizeof(int **));

  for(i=0;i<m;i++){
    a[i]=(int **)malloc(n*sizeof(int *));
    b[i]=(int **)malloc(n*sizeof(int *));
    c[i]=(int **)malloc(n*sizeof(int *));
  }
  for(i=0;i<m;i++){
    for(j=0;j<n;j++){
       a[i][j]=(int *)malloc(r*sizeof(int));
       b[i][j]=(int *)malloc(r*sizeof(int));
       c[i][j]=(int *)malloc(r*sizeof(int));
     }
  }
/*******************************************************************/
  precti(m,n,r,a,"a"); tiskni(m,n,r,a,"a");
  precti(m,n,r,b,"b"); tiskni(m,n,r,b,"b");
  secti(m,n,r,a,b,c); tiskni(m,n,r,c,"c"); return;
}

Funkční prototypy funkcí precti(), secti(), tiskni() lze také zapsat takto:

/*******  funkcni prototypu funkci precti(), tiskni() a secti() ****/
precti(int, int, int, int **[], const char *);
tiskni(int, int, int, int **[], const char *);
secti(int, int, int, int **[], int **[], int **[]);
V souboru secti3d.c obsahující definice funkcí precti(), secti(), tiskni() není třeba dělat žádnou změnu.

2.  Dynamicky definované datové struktury

V této závěrečné části bychom uvedli dva příklady dynamicky definovaných datových struktur: spojový seznam a binární strom. Realizace obou těchto datových struktur je v jazyce C založena na možnosti definovat strukturu obsahující jako svojí položku jeden nebo více ukazatelů na stejný datový typ, jaký sama reprezentuje. Tato položka obsahující ukazatel se nazývá dynamická položka (dynamický prvek) struktury.

Nejjednodušší takovou strukturou je následující struktura typu PRVEK:

Příklad:

   typedef struct prvek{int hodnota; struct prvek *spoj;} PRVEK;
   PRVEK p1,p2;

2.1  Spojový seznam

Spojový seznam vytvořený z prvků typu PRVEK je datová struktura obsahující posloupnost těchto prvků, která má tu vlastnost, že dynamická položka spoj každého prvku kromě posledního ukazuje na prvek následující.

Poznámka: Spojový seznam takového typu bude možné prohledávat pouze v jednou směru. Lze vytvářet i seznamy s možností prohledávání v obou směrech. Základním prvkem seznamů s možností obousměrného prohledávání je struktura obsahující dvě dynamické položky, které v seznamu ukazují na následující i předcházející prvek.

Spojový seznam je příkladem lineární datové struktury. To znamená, že ke každému prvku existuje maximálně jeden prvek následující a maximálně jeden prvek předcházející. Kromě toho je jeden prvek první a jeden poslední.

Paměť pro prvky, ze kterých je spojový seznam vytvořen lze alokovat dynamicky:

   PRVEK *u_p;       /*   u_p je ukazatel na typ PRVEK   */
    ...
   u_p=(PRVEK *)malloc(sizeof(PRVEK));

Přístup k jednotlivým položkám struktury využívá ukazatel, který byl určen funkcí malloc():

           u_p->hodnota           u_p->spoj

Spojový seznam se často využívá v databázových aplikacích. Důvodem je to, že lze snadno realizovat operace typu zařazení prvku do seznamu, vyjmutí prvku ze seznamu, uspořádání seznamu apod. Další výhoda spočívá v tom, že uspořádání seznamu je možné měnit pouze pomocí hodnot ukazatelů.

Při uspořádávání seznamu tedy není nutné přepisovat jednotlivé prvky do jiné části paměti. Prvky mohou obsahovat velký objem dat a tato operace by mohla uspořádávání seznamu značně zpomalit.

Příklad 4. by 1 14: Program vytvoří spojový seznam z prvků typu PRVEK. Položky hodnota jednotlivých prvků budou zadány funkcí rand(), tj. budou zadány jako pseudonáhodná čísla typu int. Program vytiskne posloupnost položek hodnota prvků spojového seznamu, potom seznam uspořádá a opět vytiskne posloupnost položek hodnota prvků uspořádaného seznamu.

#include <stdio.h>
#include <stdlib.h>
main()
{
 typedef struct prvek{int hodnota; struct prvek *spoj;} PRVEK;
 int i,pocet;
 PRVEK *p_f,*p_c,*p_e;          /* mnemotechnika: first, current, end */
 PRVEK *p_odk,*pom;             /* pomocne pointery pro usporadani    */
                               /* mnemotechnika: odkud_zacit, pomocny */
 printf("\nZadej pocet prvku!"); scanf("%d",&pocet);
 p_f=(PRVEK *)malloc(sizeof(PRVEK));   /* alokace pameti pro 1. prvek */
 if(p_f==NULL){printf("\nMalo pameti!");return;}      /* test alokace */
 p_c=p_e=p_f;
 p_c->hodnota=rand();                 /* urceni hodnoty prvniho prvku */

 for(i=2;i<=pocet;i++){                   /* zadani zbyvajicich prvku */
   p_e=(PRVEK *)malloc(sizeof(PRVEK));        /* alokace i-teho prvku */
   if(p_e==NULL){printf("\nMalo pameti!");break;}     /* test alokace */
   p_c->spoj=p_e;                    /* pointer spoj sousedniho prvku */
   p_c=p_e; p_e->hodnota=rand();      /* posunuti p_c, zadani hodnoty */
 }
 p_e->spoj=NULL;

 for(p_c=p_f; p_c!=NULL; p_c=p_c->spoj)
   printf(" \n %d",p_c->hodnota);                /* tisk hodnot prvku */


 /* usporadani seznamu ************************************************/
 /* end    posledni prvek usporadane casti seznamu  (v oznaceni p_e)  */
 /* odk    prvni prvek neusporadane casti seznamu  (v oznaceni p_odk) */

   for(p_e=p_f, p_odk=p_f->spoj; p_odk!=NULL; p_odk=p_e->spoj){

     /* test: bude prvek nasledovat za usporadanou cast seznamu ? *****/
     if(p_e->hodnota<=p_odk->hodnota){
     p_e=p_odk;continue;   /* end bude opet novy posledni prvek u.c.s.*/
     }

     /**** prvek odk neni na spravnem miste, hledame pro nej pozici ***/
     p_e->spoj=p_odk->spoj;  /* nejprve prvek odk vyradime ze seznamu */

     /* test: umistit prvek odk pred usporadanou cast seznamu? ********/
     if(p_odk->hodnota <= p_f->hodnota) {p_odk->spoj=p_f; p_f=p_odk;
     }                            /* first bude opet novy prvni prvek */
     else
     /*****  prvek odk patri dovnitr usporadane casti seznamu  ********/
       for(p_c=p_f;p_c!=NULL;p_c=p_c->spoj){
         if(p_c->hodnota<=p_odk->hodnota &&
            p_odk->hodnota <= p_c->spoj->hodnota){
               p_odk->spoj=p_c->spoj; p_c->spoj=p_odk;
               break;  /* prvek byl ulozen na misto, cyklus ukoncen ***/
         }
       }
   } /******************* konec usporadani seznamu ********************/
 printf("\n\n");
 for(p_c=p_f; p_c!=NULL; p_c=p_c->spoj)
 printf(" \n %d",p_c->hodnota);  /******** tisk hodnot prvku **********/

 /******************* zruseni alokace pameti **************************/
 for(p_c=p_f; p_c!=NULL; p_c=pom){ /* pomocna promenna pom se pouziva */
   pom=p_c->spoj; free(p_c);       /* k zapamatovani adresy           */
 }                                 /* nasledujiciho objektu           */
 return;
}
Postup jsme se snažili podrobně komentovat přímo v textu programu. Uvedený program vychází z obvyklého algoritmu bublinkového třídění, viz následující příklad. Postupné výměny prvků analogické výměnám a[j-1], a[j] není třeba v algoritmu uspořádání spojového seznamu provádět. Prvek je možné umístit přímo mezi zvolené prvky změnou hodnot ukazatelů spoj.

Příklad 4. by 1 14:

  for(i=1;i<n;i++){                /* pole indexovano od hodnoty 0 */
    for(j=i;j>=1;j--){
      if(a[j-1]<a[j])
        break;
      else{
          pom=a[j-1]; a[j-1]=a[j]; a[j]=pom;/* vymena a[j], a[j-1] */
      }
    }
  }

2.2  Binární strom

Strom je příkladem nelineární datová struktura. Slovo 'nelineární' zde znamená, že obecně neplatí, že by každý prvek musel mít maximálně jeden předcházející a maximálně jeden následující prvek. Strom je datová struktura, kde každý prvek má nejvýše jeden předcházející prvek ale může mít více prvků následujících.

Budeme se zabývat nejjednodušším případem, kdy následující prvky mohou být nejvýše dva, tj. binárním stromem. Uvažujme proto nyní strukturu se dvěma dynamickými položkami levy, prav pro ukazatele na levého a pravého následníka každého prvku.

typedef struct uzel{int x; struct uzel *levy; struct uzel *prav;} UZEL;
Pro práci s binárními stromy se často užívají rekurzivní funkce. Uvedeme jeden typický příklad - funkci pro tisk hodnot všech prvků zadaného binárního stromu.

Příklad 4. by 1 14: Program vytvoří binární strom, vytiskne hodnoty všech prvků tohoto stromu, a jeho podstromu;

#include <stdio.h>
#include <conio.h>
typedef struct uzel{int x; struct uzel *levy; struct uzel *prav;} UZEL;
inorder(UZEL *);/* funkcni prototyp funkce pro tisk binarniho stromu */
main()
{
  UZEL            V,
              L,      P,
           LL,  LP, PL,  PP;

/**********vytvoreni stromu a zadani hodnot jednotlivych prvku *******/
  V.x=4;     V.levy=&L;       V.prav=&P;
  L.x=2;     L.levy=&LL;      L.prav=&LP;
  P.x=6;     P.levy=&PL;      P.prav=&PP;
  LL.x=1;    LL.levy=LL.prav=NULL;
  LP.x=3;    LP.levy=LP.prav=NULL;
  PL.x=5;    PL.levy=PL.prav=NULL;
  PP.x=7;    PP.levy=PP.prav=NULL;
  clrscr();
  inorder(&V);           /* tisk obsahu celeho binarniho stromu */
  printf("\n\n\n");
  inorder(&L);           /* tisk podstromu s vrcholem 'L'       */
  return;
}

inorder(UZEL *uu)
{
  if(uu!=NULL){
     inorder(uu->levy);             /* 1. rekurzivni odkaz      */
     printf("\n%d",uu->x);          /* provadena operace - tisk */
     inorder(uu->prav);             /* 2. rekurzivni odkaz      */
   }
   return;
}
V první části programu je vytvořen binární strom. První prvek binárního stromu je prvek V za kterým následují levý následník L a pravý následník P. Za prvkem L následují LL a LP, za prvkem P následují PL a PP. Hodnoty těchto prvků (tj. položky x) jsou zadány tak, aby vyjadřovaly pořadí, v jakém se budou hodnoty jednotlivých prvků tisknou funkcí inorder() tj. zleva doprava. Schématicky bychom mohli vyjádřit situaci takto:

    Označení prvků:                 Hodnoty prvků:

             V                            4
           /   \                        /   \
         L       P                    2       6
       /  \     /  \                /  \     /  \
     LL   LP   PL   PP             1    3   5    7
Funkce inorder() je zajímavá tím, že obsahuje dva rekurzivní odkazy. Co se děje po vyvolání funkce inorder()? Vysvětlíme podrobněji alespoň několik prvních akcí, které po vyvolání této funkce proběhnou:

Funkce inorder() je jedním ze tří typů funkcí, které se standardně užívají pro práci s binárními stromy. Podrobný výklad otázek, které se týkají práce s binárními stromy i s dalšími datovými strukturami najde čtenář v [].

Obsah

Kapitola 5
Poznámky k předpřekladači

Předpřekladač jsme zatím používali intuitivně tak, že jsme v příkladech v této publikaci prakticky vždy používali příkazy #include, kterými jsme zaváděli hlavičkové soubory např. #include<stdio.h> nebo naše uživatelské soubory, např. #include"HLAVNI.C" nebo #include"FUNKCE.C". Nyní se v závěru této publikace zmíníme o předpřekladači (obvykle se předpřekladači říká preprocesor) podrobněji.

Činnost předpřekladače se dá shrnout do několika bodů:

1.  Direktiva #define

1.1  Direktiva #define bez parametrů

Tato direktiva je velmi často užívána v jazyku C jako direktiva bez parametrů (viz čl.  příkl. 6.1.) Všimněte si, že nesmíme v definici konstanty zapsat na konci středník. Ten by se totiž zapsal s uvedenou hodnotou kdykoliv, když by předpřekladač narazil na příslušný text, např. DOLNI a to by způsobilo syntaktické chyby. Můžeme samozřejmě psát

#define PI 3.1415926


ale i

#include<math.h>    /* Matematicka knihovna */
#define PI (4 * atan(1.0)) /* atan je arctg */
...
Konstanty, kterým často říkáme makra, jsou-li takto definovány se nerozvinou, jsou-li uzavřeny v uvozovkách, např.
#define MAKRO past
#include<stdio.h>
main()
{printf("Toto je MAKRO");}

Tento program vytiskne text: Toto je MAKRO a nikoliv Toto je past, což je právě ta past.

1.2  Direktiva #define s parametry

Při sestavování programů se často vyskytne případ, kdy mnohokrát používáme nějakou funkci, která je krátká, nebo při řešení diferenciálních rovnic měníme často okrajové nebo počáteční podmínky. Pak s výhodou můžeme použít direktivy s parametry. Můžeme např. psát:
#define na_treti(x) ((x)*(x)*(x))

Zdálo by se, že závorky jsou nadbytečné, není tomu tak. Kdybychom totiž napsali:

#define na_treti(x) x*x*x

pak po volání:

na_treti(a+b)

se rozvine do: a+b*a+b*a+b, což je chybné. Ani vynechání vnějších závorek není vhodné. Např.

#define cti(c) c=getchar()

se po volání:  if(cti(r) == 'b') rozvine do známé chyby:  
if(r=getchar() == 'b')

Na závěr tohoto odstavce ještě upozorníme na případ, že direktiva s parametry může být někdy tak dlouhá, že se nevejde na jeden řádek. Pak ji přerušíme znakem zpětné lomítko a pokračujeme na další řádce. Např. shora uvedené makro na_treti lze zapsat:

#define na_treti(x) ((x)\
                     *(x)*(x))

Posledním, ale důležitým, poučením bude, že mezi makrem a první otevírací okrouhlou závorkou argumentů nesmí být mezera.

2.  Operátory # a ##

Tyto operátory se neužívají tak často, a proto pro úsporu místa je objasníme pouze na dvou příkladech, ze kterých si čtenář význam snadno domyslí:

Příklad 5.1:

#define otevri_soubor(jmeno_souboru) fopen(#jmeno_souboru,"r")
main()
{/* ...*/
 FILE *ms;
 ms=otevri_soubor(muj_soubor);
 /* ...*/}

Předpřekladač "rozepíše" toto makro na:

/*...*/
 FILE *ms;
 ms=fopen("muj_soubor","r");
/*...*/

Příklad 5.2:

#define print_var(num) printf("var" #num " = %d\n",var##num)
/*...*/
int var1,var2,var3;
/*...*/
print_var(3);
/*...*/

Zde předpřekladač rozepíše makro na:

int var1,var2,var3;
/*...*/
printf("var" "3" " = %d\n",var3);
/*...*/

3.  Podmíněný překlad

Při ladění programů pomáhají často profesionálně sestavené ladící programy. Jejich nevýhodou je, že při snaze pokrýt co nejvíce možností jsou tyto programy někdy dost složité a mnoho programátorů proto programy píše tak, že do nich zapisují vlastní ladící tisky, které po odladění pro zvýšení přehlednosti programu ruší, přičemž při troše nepozornosti zruší víc než ladící tisky, z čehož vznikají mnohé nepříjemnosti. V jazycích C a C++ je umožněn velmi praktický tzv. podmíněný překlad, který si na několika krátkých ukázkách vysvětlíme.



Příklad 5.3: Napišme jednoduchou funkci, která bude napsána tak, aby byla přijatelná, jak pro původní jazyk C jak byl definován v [], tak pro ANSI C a C++. Využijme podmíněného překladu ve třech základních variantách.


  1. varianta: Zde běžným způsobem definujeme makro K&R jako nulové. Část podmíněného příkazu mezi #if a #else se provede, je-li makro rovné jedničce. Je-li makro rovné nule, provede se část mezi #else a #endif. Část za #else mimo #endif může být vynechána, dovoluje-li to logika ladícího tisku.
    #define K&R  0
    #define na_treti(x)  ((x)*(x)*(x))
    #if K&R
      f(x)
      int x;
      {return (na_treti(x)-4*x+7);}
    #else
      f(int x)
      { return (na_treti(x)-4*x+7);}
    #endif
    

  2. varianta: Zde bude nedefinováno makro K&R, což realizujeme direktivou #undef (Pozor! Makro je prázdné. Pohov!). Podmíněný překlad se pouze změní tak, že místo příkazu #if bude příkaz #ifdef. Příkazy mezi #ifdef a #else se provedou, je-li makro definované. Zbytek si čtenář domyslí sám jako cvičení.
    #undef K&R
    #define na_treti(x)  ((x)*(x)*(x))
    #ifdef K&R
      f(x)
      int x;
      {return (na_treti(x)-4*x+7);}
    #else
      f(int x)
      { return (na_treti(x)-4*x+7);}
    #endif
    

  3. varianta: Této variantě čtenář jistě porozumí bez velkého přemýšlení a se zkušenostmi z 2. varianty sám.
    #define K&R
    #define na_treti(x)  ((x)*(x)*(x))
    #ifndef K&R
      f(x)
      int x;
      {return (na_treti(x)-4*x+7);}
    #else
      f(int x)
      { return (na_treti(x)-4*x+7);}
    #endif
    

    Obsah

Kapitola 6
Vazba na systém UNIX

V této kapitole se seznámíme se základními příkazy operačního systému UNIX, aby čtenář, který je zvyklý pracovat s operačním systémem DOS, se rychle orientoval i v tomto široce užívaném systému, který je ostatně z velké části napsán přímo v jazyku C. Naučíme se překládat zdrojové programy psané v jazyku C a sestavovat je do pracovních programů. Poté se zmíníme o původním jazyku C, označovaným nyní často jako K&R C podle autorů jazyka Kerrighana a Ritchieho, který je popsán v dnes již klasické knize [], a v kterém byl operační systém z převážné části napsán. Pak se naučíme pracovat s tzv. vi editorem, který je v UNIXu běžně používán. A nakonec se zmíníme o vstupech a  výstupech nízké úrovně readwrite.

1.  Některé základní příkazy operačního systému UNIX

Budeme postupovat tak, že uvedeme jméno příkazu a jeho funkci. Nebudeme zabíhat do detailů, protože to jednak není cílem těchto skript a jednak se příkazy v různých systémech mírně liší. Čtenář si podle této hrubé kostry, upřesní použití sám.

  1. password

    Příkaz mění heslo uživatele. Uživatel je dotázán na staré a nové heslo. Po úspěšném zadání starého, je mu přiděleno nové. Může být odmítnuto, je-li příliš krátké nebo, podle názoru systému, příliš jednoduché.

  2. who

    Zobrazí identifikace přihlášených uživatelů do systému. Příkaz who am I identifikuje přímo uživatele, který se táže.

  3. date

    Zobrazí systémový čas a datum v pořadí: datum čas.

  4. cat

    Příkaz spojuje soubory s jejich následujícím výpisem. Použití:

    cat soubor_1
    

    Příkaz vypíše soubor soubor_1 na obrazovce; příkaz

    cat soubor_1 soubor_2 > soubor_3
    

    spojí soubor_1 a soubor_2 a zapíše do souboru_3. Bylo-li v souboru něco uloženo před použitím tohoto příkazu, je to zničeno.

  5. more Vypisuje soubor po obrazovkách. Použití:
    more soubor
    

    Je možné též použít při příkazu cat např.

    cat soubor_1 soubor_2 > soubor_3|more
    

    Tento příkaz nám umožní pohodlné prohlížení souboru_3, i když je delší než jedna obrazovka.

  6. clear

    Příkaz maže obrazovku.

  7. pwd

    Příkaz vypíše pracovní (tj. právě otevřený) adresář.

  8. cd

    Příkaz změní pracovní adresář. Např.

    cd /home
    

    změní stávající pracovní adresář na adresář /home, je-li to přípustné nebo možné.

  9. ls

    Provádí výpis souborů v pracovním adresáři. Napíšeme-li např.

    ls -l
    

    Realizuje výpis jmen souborů s podrobnější informací. Uvedeme-li parametr -a vypíše se i seznam tzv. tečkovaných souborů, které se normálně nezobrazují.

    Často v systému existuje ještě příkaz l. Pak platí, že pro podrobný výpis se používá příkazu l a pro stručný ls. Na tomto místě také upozorňujeme na znaky * a ?, které mají tentýž význam jako v systému DOS. Např. ls t* vypíše všechny soubory začínající písmenem t. Příkaz ls t??? vypíše všechna jména čtyřpísmenových souborů začínajících písmenem t.

  10. rm

    Příkaz odstraní soubor z adresáře. Např. příkaz

    rm soubor_1
    

    odstraní z pracovního adresáře soubor_1.

  11. mv

    Příkaz přejmenuje soubor. Např. příkaz

    mv soubor_1 soubor_2
    

    přejmenuje soubor_1 na soubor_2. Existoval-li nějaký soubor_2 je jeho původní obsah zničen.

  12. cp

    Příkaz kopíruje soubory.

    cp soubor_1 soubor_2
    

    Provede kopii souboru soubor_1 do souboru soubor_2.

  13. man

    Tento příkaz vyvolá anglicky psaný manuál o systému; u slova man se musí napsat jméno souboru nebo příkazu (což je v UNIXu obvykle totéž), který nás zajímá, např.

    man vi nám poskytne podrobné informace o textovém editoru vi.

    Toto jsou základní příkazy, s kterými čtenář při prvém seznamování se systémem vystačí. Může se mu však stát, že při sebevětší opatrnosti, udělá takovou logickou chybu v programu, že se program tzv. a uživatel musí proces nějak ukončit. K tomu mu mohou posloužit ještě dva příkazy:

  14. ps

    Příkaz zobrazí stav procesu (nebo procesů), které právě v systému probíhají. Např.

    ps -t3
    

    zobrazí stav procesu na terminálu tty3 (uživatel se dozví o typu terminálu např. z příkazu who am I). Další parametry, které může čtenář vyzkoušet jsou: -tp1 (např. pro terminál typu ttyp1), -g zobrazí všechny procesy, -l by měl zobrazit podrobný výpis. Nejdůležitější, co však v daném případě uživatel potřebuje, je číslo zacykleného procesu, které se vždy dozví jak ze stručného, tak podrobného výpisu. Toto číslo procesu použije v dalším příkazu, který zacyklený proces ukončí.

  15. kill

    Zapíšeme-li např. kill -9 4287 ukončíme proces s číslem procesu 4287. Je samozřejmé, že příkaz ps a příkaz kill, musíme v takovém případě použít z jiného terminálu, než na kterém došlo k zacyklení procesu. Proto je dobré příkaz who am I použít okamžitě po přihlášení se do systému.

2.  Překlad zdrojových souborů

V tomto článku se zmíníme o překladu zdrojových souborů a sestavení pracovního programu.

Představme si modelový případ, kdy máme vytvořeny nejprve dva programové soubory radek.c a funkce.c (zdrojové programy v jazyku C mají standardně koncovku c), které chceme přeložit a uložit jako soubory. To můžeme provést příkazem:

cc -c radek.c funkce.c

Vzniknou soubory radek.o a funkce.o

Nyní např. editorem vi připravíme další zdrojový soubor, např. muj_pra.c, který chceme přeložit a sestavit se soubory radek.o a funkce.o do pracovního programu. Napíšeme

cc muj_pra.c radek.o funkce.o

Pracovní program, protože jsme nestanovili jinak, se standardně jmenuje a.out. Chceme-li nazvat program program jinak, např. mp, napíšeme:

cc muj_pra.c radek.o funkce.o -o mp

Program se pak jmenuje mp a tak může být vyvolán. Např. příkaz:

mp < muj_vst >muj_vys

zajistí vyvolání programu, přičemž vstup do programu je ze souboru muj_vst a výstup je do souboru muj_vys. Potřebujeme-li vyvolat např. matematickou knihovnu (v operačních systémech UNIX má obvykle označení lm), píšeme ji až za soubory, které ji při sestavení potřebují, nejlépe úplně na konci příkazu. Kdybychom tedy využívali matematickou knihovnu ve shora uvedeném programu, psali bychom:

cc muj_pra.c radek.o funkce.o -o mp -lm

Nic nám nebrání provést překlad a sestavení všech souborů najednou. Můžeme tedy psát:

cc muj_pra.c radek.c funkce.c -o mp -lm

Je samozřejmé, že můžeme pracovat i s direktivou #include. Zápis již tak jednoduchého příkazu pro překlad se ještě zjednoduší.

3.  Hlavní rozdíly mezi jazykem ANSI a K&R

V tomto článku se zmíníme o rozdílech mezi standardem jazyka a jazykem původním, o kterém se nám nechce ani psát jako o dialektu, i když vznikem normy jazyka tomu tak je. Jen velice stručně z historie.

O hlavních rozdílech mezi ANSI C a K&R C jsme se již zmínili na různých místech v předchozích kapitolách. Nyní je nejprve vyjmenujeme v přehledu podle důležitosti a pak si hlavní rozdíly ukážeme na příkladech. Je třeba si jen uvědomit, že přechod mezi K&R C a ANSI C je díky mnoha implementacím neostrý.

  1. Formální parametry se nedeklarují přímo v závorkách za identifikátorem funkce, ale nejprve se vyjmenují v závorkách seznamu a po ukončení seznamu se deklarují jejich typy. Tyto deklarace jsou uvedeny před složenou závorkou, která určuje tělo (operační část) funkce. Tato skutečnost je dosti podstatná. Je-li to totiž realizováno takto, ztrácí se možnost kontroly a eventuálního přetypování skutečných parametrů. Tato možnost v ANSI C je v případě, že uvádíme prototypy, což (narozdíl od jazyka C++) ovšem nemusíme.
  2. Automatické pole se nesmí inicializovat.
  3. Nelze pracovat se strukturami jako s celkem, tedy máme-li dvě struktury a a b téhož typu, nelze psát a=b.
  4. Struktura nesmí být návratovou hodnotou funkce a struktura nesmí být předána funkci jako skutečný parametr.
  5. Chybí slovní symbol void. To znamená, že není možné dát překladači informaci, že některá funkce nevrací hodnotu, ale je tím, čemu se v jiných programovacích jazycích říká vlastní procedura nebo podprogram.
  6. Chybí slovní symbol const. To znamená, že konstanty se musí definovat direktivou #define.
  7. Výčtový typ (slovní symbol enum) není zaveden.
  8. Někdy nebývají deklarovány standardní konstanty NULLEOF a musíme je definovat sami. V případě NULL to není problém, protože stačí např.
    #define NULL 0
    

    nebo místo NULL používáme číslici 0, ale v případě EOF je třeba zjistit ekvivalent v daném systému, obvykle to bývá -1 nebo 1. Tyto problémy by ale mohly nastat jen u starších verzí jazyka.

  9. Nejsou definovány operátory ### (viz 2).
  10. Není definováno unární + tzn. že +1, respektive +a se chápe jako 0+1 respektive 0+a.
  11. Při konverzích dochází k automatickému převodu float na double.

Příklad 6.1: Sestavme program pro tisk tabulky Fahrenheit - Celsius. Pro přepočet použijte vzorce

C = 5
9
(F-32).
Tabulku tiskněme ve tvaru:

     Stupne Fahrenheita          Stupne Celsia
          dddd                         ddd.dd
          dddd                         ddd.dd
          ...                           ...
kde d je číslice, znaménko minus nebo prázdný znak.


Program napíšeme standardně tak v ANSI C:

/*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
  pro  fahr = 0, 20,..., 300 */
main()
{
 /* Definice konstant pro dolni a horni mez a krok zmeny */
 const int DOLNI=20,HORNI=300,KROK=20;
 float fahr=DOLNI; /* Definice promenne fahr s pocatecnim prirazenim; */
 float cels; /* Definice promenne, ktera bude nabyvat hodnot stupnu
                Celsia
 Tisk zahlavi tabulky */
 printf("\n    Stupne Fahrenheita          Stupne Celsia\n");
 /* Prikaz while, ktery se bude provadet dokud fahr bude mensi nebo
   rovno 300 */
 while(fahr<=HORNI)
 {
  cels=5./9.*(fahr-32);
  printf( "\n         %4.0f                         %5.2f",fahr,cels);
  fahr=fahr+KROK;
 }
}

kdežto v K&R C musí (a v ANSI C může) mít tvar:

 /*Tisk tabulky prevodu teplot ze stupnu Fahrenheit na stupne Celsius
  pro  fahr = 0, 20,..., 300 */
#define DOLNI 20
#define HORNI 300
#define KROK 20
main()
{
 float fahr=DOLNI; /* Definice promenne fahr s pocatecnim
                      prirazenim;*/
 float cels; /* Definice promenne, ktera bude nabyvat hodnot stupnu
                Celsia
 Tisk zahlavi tabulky */
 printf("\n    Stupne Fahrenheita          Stupne Celsia\n");
 /*Prikaz while, ktery se bude provadet dokud fahr bude mensi nebo
   rovno 300*/
 while(fahr<=HORNI)
 {
  cels=5./9.*(fahr-32);
  printf( "\n         %4.0f                         %5.2f",fahr,cels);
  fahr=fahr+KROK;
 }
}

Příklad 6.2: Zapišme program pro výpočet

ň01[(sin(x))/( x)] Simpsonovou metodou v ANSI C K&R C


4.  Editor vi

Editor vi je nejrozšířenějším editorem v systémech UNIX. Tento editor není tak přátelský k uživateli jako mnohé textové editory známé z prostředí užívaných v systému DOS, ale je schopen spolupráce s libovolným terminálem. Seznámíme se s ním jen do té míry, abychom byli schopni zapisovat soubory a provádět jejich opravy a úpravy, i když někdy ne optimálně. Má-li čtenář zájem se s tímto editorem seznámit podrobněji, nalezne informace v uživatelském manuálu příslušného systému UNIX nebo přímo na obrazovce terminálu pomocí příkazu man vi.

Předpokládejme, že chceme vytvořit nový soubor soub_1, pak napíšeme:

vi soub_1

Pokud soubor neexistoval je obrazovka v 1. sloupci vyplněna znaky ~ a kurzor je na prvním sloupci prvního řádku. Nemůžeme zapisovat, pokud nepřejdeme do tzv. vkládacího režimu stiskem tlačítka s písmenem i (insert) nebo a ( append) bez následného stisku tlačítka enter. Od tohoto okamžiku můžeme psát. Vkládání ukončíme stiskem tlačítka esc. Zápis na disk lze realizovat např. stiskem ZZ, nebo stiskem :x a stiskem enter. Chceme-li soubor přejmenovat, např. na sou_2, zapíšeme :x sou_2 a stiskneme enter. Neuvedli jsme si, že jsme-li v tzv. příkazovém režimu (po stisku :) píšeme do spodní části obrazovky a příkazy tam zapsané ukončujeme vždy stiskem enter. Tuto skutečnost již nebudeme dále zdůrazňovat. Nechceme-li soubor uložit na disk a editaci chceme ukončit, stiskneme :q!. Kdybychom si chtěli soubor jen prohlédnout a neudělali bychom v něm žádné změny, stačí ukončit prohlížení zápisem :q.

V textu se standardně můžeme pohybovat, nejsme-li ve vkládacím režimu tlačítky h vlevo, l doprava, k, nahoru a j dolů, při správném nastavení terminálu se můžeme pohybovat též kurzorovými šipkami, což je názornější. Ve vkládacím režimu pohybujeme kurzorem pouze šipkami. Nejsme-li ve vkládacím režimu můžeme nalézt řetězec znaků, např. if(lad) pomocí zápisu /if(lad)enter na následující výskyty řetězce se dostaneme stiskem n (následující) a N ( předcházející).

Nejsme-li ve vkládacím režimu, můžeme vymazat kterýkoliv znak tím, že na něj přemístíme kurzor a stiskneme tlačítko x. Můžeme tak rušit libovolně dlouhý text, chceme-li vymazat právě jeden znak stiskneme r a zapíšeme správný znak, eventuálně R - pak se přejde do přepisovacího režimu až do stisku tlačítka esc. Chceme-li zrušit celou řádku stiskneme dd. Stiskneme-li u, rušíme poslední změnu, což nám při chybě může ušetřit mnoho práce.

Jsme-li někde na řádku a nejsme ve vkládacím režimu, můžeme se rychle dostat na konec řádky stiskem $ a na začátek řádky stiskem | . Stiskneme-li A, dostaneme se na konec řádky a zároveň přejdeme do vkládacího režimu a k téže změně režimu dojde, přejdeme-li na začátek řádky stiskem I. Chceme-li se pohybovat po řádce rychleji než po znacích, umožní nám stisk w posun o slovo dopředu a b posun o slovo zpět. Téhož, ale s ignorováním interpunkce, dosáhneme stisky W nebo B.

Nejsme-li ve vkládacím režimu, můžeme listovat souborem po obrazovkách: dopředu o obrazovku stiskem ctr F a zpět stiskem ctr B. Chceme-li docílit pohyb o půl obrazovky dopředu, stiskneme ctr D a dozadu ctr U.

Před povely lze přidat číslo: např. 5x zruší pět znaků, 3dd vymaže tři řádky, 3w přeskočí dvě slova apod. Mezi číslo a povel posunu je možno vložit písmeno d, např. dW zruší slovo, 5dW zruší pět slov.

Velice užitečný je přesun části textu na jiné místo. Postup:

Při kopírování postupujeme podobně, jen místo příkazu d, použijeme příkazu y (příkaz yw vybere slovo, příkaz y3w vybere tři slova, příkaz 6yy vybere šest řádek apod.). Postup lze stručně popsat takto:

Chceme-li uvolnit řádku pod kurzorem, stiskneme o, chceme-li uvolnit řádku nad kurzorem, stiskneme písmeno O.

Chceme-li vyměnit řetězce znaků, které se mohou vyskytovat od řádků n1 do řádku n2 můžeme psát

:n1,n2 s/hledaný_řetězec/řetězec_nahrazující/

Při takto zapsaném příkazu se provede náhrada pouze prvního výskytu na řádku. Chceme-li nahradit všechny výskyty na řádku, zapíšeme na konec příkazu písmeno g. Tedy příkaz

:20,200 s/muj program/tvuj program/g

provede záměnu všech řetězců muj program řetězcem tvuj program od dvacátého do dvoustého řádku souboru včetně.

Mohli bychom si vysvětlit, jak lze udávat řádky, ale místo toho doporučíme čtenáři, aby si ve svém domovském adresáři zavedl tečkovaný soubor (tyto soubory jsou běžně nevypsatelné příkazy ls nebo l, neudáme-li parametr -a) s názvem .exrc, ve kterém zapíše vi editorem jediný řádek ve tvaru:

 set nu smd

Tím se zajistí, že řádky souborů, které se editují editorem budou číslovány a v pravém dolním rohu obrazovky se bude zobrazovat režim, v kterém se editor právě nalézá. Zkratka nu lze též psát number a akronym smd lze psát showmode, což je pro čtenáře znalého základů angličtiny dostatečně vysvětlující.

5.  Vstupy a výstupy nízké úrovně

V tomto článku se zmíníme o operacích vstupu a výstupu nízké úrovně pomocí nichž je možné implementovat části standardních knihoven. Byly implementovány v původním K&R C, ale jsou k dispozici i v jazycích C v jiných operačních systémech.

5.1  Logická čísla

V operačním systému UNIX se vstup a výstup realizuje čtením a zapisováním do souborů, protože všechna přídavná zařízení, včetně uživatelského terminálu, se považují za soubory. Veškerá komunikace mezi programem a přídavnými zařízeními se tak uskutečňuje jediným rozhraním.

Před čtením nebo zapisováním do souboru je obecně nutné informovat systém tzv. otevřením souboru. Systém prověří, existuje-li takový soubor a máme-li právo přístupu. Je-li všechno v pořádku, předá systém programu kladné celé číslo, které nazveme logickým číslem souboru. Kdykoliv se pak nad souborem provádí nějaká operace, identifikuje se soubor tímto číslem a nikoliv jménem souboru (podobně je tomu ve v jazyku Fortran např. v příkazech read(1,...) write(2,...)).

Když interpret systémových příkazů, kterému se v UNIXu říká shell spouští program, otevře tři soubory s logickými čísly 0, 1 a 2, které se po řadě nazývají standardní vstup, standardní výstup a standardní chybový výstup. Všechny jsou obyčejně spojeny s terminálem. Nehodí-li se uživateli toto pevné spojení na terminál, může přesměrovat vstupy a výstupy pomocí znaků < a > do souboru nebo ze souboru, jak jsme si již ukázali v čl. 2. V tomto případě shell změní přiřazení pro logická čísla 0, resp. 1 na soubory muj_vst, resp. muj_vys. Chybová hlášení se zapisují na terminál.

5.2  Vstup a výstup nízké úrovně

Celý vstup a výstup se realizuje pomocí dvou příkazů (funkcí), které se nazývají readwrite. Prvním argumentem je logické číslo souboru, druhým argumentem je vyrovnávací paměť z které data přicházejí nebo odcházejí. Třetí argument je počet slabik (bytes), které je třeba přenést. Volání mají např. tvar:
cti_n_sl = read(lc,vp,n);
zapis_n_sl = write(lc,vp,n);

Příkazy vrací počet skutečně přenesených slabik. Při čtení se může vrátit menší počet slabik, než se požadovalo. Je-li hodnota funkce read rovna nule, znamená to konec souboru a tt>-1 signalizuje nějakou chybu. Při zápisu se vrací počet požadovaných slabik. Když se hodnota funkce write tomuto počtu nerovná, znamená to chybu.

Počet slabik, které se čtou nebo zapisují může být libovolný. Často užívané hodnoty jsou 1 (pro čtení po znacích (bez vyrovnávací paměti), 512, 1024 apod. (délka fyzických bloků přídavných zařízení).

Napišme nyní dva jednoduché programy.

Příklad 6.3: Zapišme program, který bude kopírovat soubory.


V systému UNIX bude tento program kopírovat "cokoliv kamkoliv", protože vstup a výstup můžeme přesměrovat na libovolný soubor (a zařízení). verbatim #define VELIKOST 512 main() /* Kopirovani vstupu na vystup */

char vvp[VELIKOST]; /* Velikost vyrovnavaci pameti */ int n; while ((n = read(0,vvp,VELIKOST))>0) write(1,vvp,n);

Příklad 6.4: Použijme příkazy readwrite k implementaci funkcí vyšší úrovně m_getchar m_putchar, které jsou totožné se standardními getcharputchar


#include<stdio.h>
#define MASKA 0377 /*Zabezpeci kladnou velikost znaku */
m_getchar() /* Vstup jedineho znaku */
{
 char c; /* c musi byt definovano jako znak, protoze read prijima
            ukazatel na znak */
 return ((read(0,&c,1)>0) ? c & MASKA : EOF); /* Podmineny vyraz,
                                EOF není totiz znak */
}
m_putchar(int c) /* Vystup jedineho znaku */
{
 return(write(1,&c,1));
}
/* Hlavni program pro testovani m_getchar a m_putchar */
main()
{
 int c;
 while((c=m_getchar())!=EOF)
 {
  m_putchar(c);

 }
}

5.3  Použití příkazů open(), create(), close(), unlink()

Chceme-li použít jiné soubory než náhradní vstupní, výstupní a chybový soubor, musíme je otevřít explicitně. Existují dvě volání systému: open (otevření souboru) a creat (vytvoření souboru). Volání open se podobá funkci fopen, kterou jsme si vysvětlili dříve v kapitole 2.1 s tou výjimkou, že namísto ukazatele na soubor vrací logické číslo souboru. Použití může vypadat např. takto:
/*...*/
int lc,vstvys;char[15]
/*...*/
lc=open(jmeno_souboru, vstvys);

Parametr vstvys má hodnotu 0, resp. 1 pro vstup, resp. pro výstup. Nastane-li nějaká chyba, vrací open hodnotu -1, jinak vrací platnou hodnotu logického čísla.

Neexistuje-li soubor, musíme ho vytvořit příkazem creat:

lc=creat(jmeno_souboru,ochrana_souboru);

Podařilo-li se vytvořit soubor se jménem jmeno_souboru, vrací se hodnota logického čísla, jinak se vrací hodnota -1. Existuje-li již soubor, je zkrácen na nulovou délku.

Je-li soubor úplně nový, vytvoří ho creat s režimem ochrany. V UNIXu je se systémem spojených devět bitů informací o ochranách, které řídí přístupová práva pro čtení, zápis a použití souboru pro vlastníka, vlastníkův tým a ostatní. Vhodné je použít trojciferné osmičkové číslo. Např. číslo 0755 (tj. dvojkově: 111 101 101) povoluje vše vlastníku souboru a čtení a použití souboru skupině i ostatním. Číslo 0774 (tj. dvojkově: 111 111 100) povoluje vše vlastníku a skupině a pouze čtení pro ostatní.

Příklad 6.5: Sestavme zjednodušenou verzi příkazu cp, který kopíruje jeden soubor do druhého.


/* Definice m_cp, ktery je zjednodusenou verzi prikazu cp kopirujici
   jeden soubor do druheho. Ve skutecnem prikazu cp je mozne, aby
   druhym argumentem byl adresar */
#define NULA 0
#define VYRPAM 512 /* Vyrovnavaci pamet */
#define OCHRANA 744 /* Vlastnik smi vse; skupina a ostatni jen cist */
main(poc_arg,arg)
int poc_arg; char *arg[];
{
 int f1,f2,n,chyba();
 char vp[VYRPAM];
 if(poc_arg!=3)
  chyba("Pouziti: m_cp odkud kam",NULA);
 if((f1=open(arg[1],0))==-1)
  chyba("m_cp: nelze otevrit %s",arg[1]);
 if((f2=creat(arg[2],OCHRANA))==-1)
  chyba("m_cp: nelze vytvorit %s",arg[2]);
 while((n=read(f1,vp,VYRPAM))>0)
  if(write(f2,vp,n)!=n)
   chyba("m_cp: chyba pri zapisu",NULA);
 exit(0);
}
chyba(s1,s2) /* chybova zprava a ukonceni prikazu */
char *s1,*s2;
{
 printf(s1,s2);
 printf("\n");
 exit(1);
}
Počet souborů, které mohou být v programu současně otevřené, bývá omezený a závisí na konkrétním systému (15 až 20 je standardní). Z toho ovšem vyplývá, že program, který pracuje s mnoha soubory, musí používat logická čísla opakovaně. Funkce close ruší spojení mezi logickým číslem a otevřeným souborem a uvolňuje logické číslo pro spojení s jiným souborem. Při ukončení programu se všechny otevřené soubory automaticky uzavřou.

Zmiňme se ještě o příkazu (funkci) exit, která je v příkladu použita. Argument funkce exit je k dispozici procesu, který daný proces vyvolal, aby program, který tento program vyvolal jako podproces mohl testovat, skončil-li úspěšně nebo s chybou. Podle konvence hodnota 0 signalizuje, že vše je v pořádku a různé nenulové hodnoty signalizují nenormální situace.

Příkaz unlink(jmeno_souboru) vyřadí soubor jmeno_souboru z evidence systému správy souborů.

5.4  Přímý přístup příkazy seek() a lseek()

Vstupní a výstupní operace nad souborem jsou v jazyku C sekvenční, tzn. že každý příkaz read nebo write se provede na tom místě souboru, které je bezprostředně za tím místem, kde se provedla operace předcházející. Do souboru však můžeme zapisovat nebo číst v libovolném pořadí. Umožňuje to příkaz (funkce) lseek. Umožňuje pohyb po souboru bez čtení nebo zápisu. Např. zápis:
lseek(lc,rel_adresa,pocatek);

zajistí, že běžná pozice v souboru s logickým číslem lc se posune na běžnou pozici rel_adresa - často užívaný (a trochu matoucí) anglický název je offset -, která je určena parametrem pocatek. Proměnná rel_adresa je typu long, lcpocatek jsou typu int. Hodnotou pocatek může být 0, resp. 1, resp. 2, které určují, že rel_adresa se vztahuje k začátku, resp. k běžné pozici, resp. ke konci souboru. Příkaz

lseek(lc,0L,2);

vyhledá před zápisem do souboru jeho konec. Kdežto návrat na začátek, tj. převinutí je

lseek(lc,0L,0);

konstantu 0L lze psát i (long)0.

Příklad 6.6: Sestavme jednoduchou funkci, která spočítá počet slabik v libovolném souboru od libovolného místa.


#define VELIKOST 512
get(lc,bp,vp)
int lc;long bp;char *vp;
/*****************************************************************
* Funkce pocita a vraci pocet slabik v souboru cislo lc od bezne *
* pozice bp. Ukazatel vp ukazuje na pocatek vyrovnavaci pameti,  *
* urychlujici celou operaci                                      *
*****************************************************************/
{
 long n=0,m;
 lseek(lc,bp,0); /* Prejdi na pocatek */
 while((m=read(lc,vp,VELIKOST))>0)
  n=n+m;
 return(n);
}
main(int parg, char *arg[])
/****************************************************************
* Hlavni program testujici funkci get. Sectou se vsechny slabiky*
* v souboru zapsanem v arg[1]                                   *
****************************************************************/
{
 int lc;char vyr_pam[VELIKOST];
 if (parg !=2)
  printf("Pouziti: jmeno_programu jmeno_souboru_jehoz_znaky_pocitame\n");
 else
 {
  lc=open(arg[1],0);
  printf("\nPocet znaku v souboru %s je %d\n",arg[1],get(lc,0L,vyr_pam));
 }
}
Ve starších verzích UNIXu se příkaz lseek značí seek. Má tutéž funkci, jen parametr rel_adresa není typu long int, ale int.

Obsah

Kapitola 7
Knihovny funkcí standardu ANSI

V této kapitole uvádíme přehled často používaných knihovních funkcí standardu ANSI C. Funkce jsou rozděleny podle oblasti jejich použití. Vždy je uveden název, stručná charakteristika a úplný funkční prototyp.

1.  Rozpoznávání skupin znaků

Všechny funkce vrací 0 jako FALSE a nenulovou hodnotu - obvykle tentýž znak jako TRUE.


isalnum - znak je alfanumerický ['A'- 'Z', 'a'-'z', '0'-'9']

int isalnum(int);


isalpha - znak je písmeno ['A'- 'Z', 'a'-'z']

int isalpha(int);


iscntrl - znak je řídící [0x01 - 0x1f, 0x7f]

int iscntrl(int);


isdigit - znak je číslice ['0' - '9']

int isdigit(int);


isgraph - znak je viditelný [0x21 - 0x7e]

int isgraph(int);


islower - znak je malé písmeno ['a' - 'z']

int islower(int);


isprint - znak lze vytisknout [0x20 - 0x7e]

int isprint(int);


ispunct - znak je interpunkční znak [0x21 - 0x2f, 0x3a - 0x40, 0x5b - 0x60, 0x7b - 0x7e]

int ispunct(int);


isspace - znak je "bílý" znak [0x09 - 0x0d, 0x20]

int isspace(int);


isupper - znak je velké písmeno [ 'A' - 'Z']

int isupper(int);


isxdigit - znak je hexadecimální číslice ['0' - '9', 'a' - 'z', 'A' - 'Z']

int isxdigit(int);

2.  Konverzní funkce


atof - řetězec na double

double atof(const char *s);


atoi - řetězec na int

int atoi(const char *s);


atol - řetězec na long int

long atol(const char *s);


strtod - řetězec na double

double strtod(const char *s, char **endptr);


strtol - řetězec na long int

long strtol(const char *s, char **endptr,int radix);


strtoul - řetězec na unsigned long int

unsigned long strtol(const char *s, char **endptr,int radix);


tolower - velká písmena na malá, jiné znaky nezměněny

int tolower(int);


toupper - malá písmena na velká, jiné znaky nezměněny

int toupper(int);

3.  Souborově orientované funkce

V některých funkčních prototypech následujících funkcí se používá typ size_t:


size_t - je definován tak, aby byl shodný s typem návratové hodnoty operátoru sizeof, tj. jako některý z typů unsigned

typedef ui-type size_t);


clearerr - nuluje indikaci chyb

void clearerr(FILE *file);


fclose - uzavírá soubor

int fclose(FILE *file);


feof - test konce souboru

int feof(FILE *file);


ferror - zjišťuje chyby při práci se souborem

int ferror(FILE *file);


fflush - zapisuje obsah vyrovnávacího bufferu do paměti

int fflush(FILE *file);


fgetc - čte znak ze souboru

int fgetc(FILE *file);


fgetpos - vrací aktuální pozici ukazatele v souboru

int fgetpos(FILE *file, fpos_t *pos);


fgets - čte řetězec maximální délky n ze souboru

char *fgets(char *s, int n, FILE *file);


fopen - otevření souboru

FILE *fopen(const char *filename, const char *mode);


fprintf - formátovaný zápis do souboru

int fprintf(FILE *file, const char *format, [,arg, ...]);


fputc - zápis znaku do souboru

int fputc(int c, FILE *file);


fputs - výstup řetězce do souboru s odřádkováním

int fputs(const char *s, FILE *file);


fread - čte blok dat ze souboru

size_t fread(void *ptr, size_t size, size_t n, FILE *file);


freopen - sdružuje nový soubor s již otevřeným souborem, výhodné pro přesměrování

FILE *freopen(const char *fname, const char *mode, FILE *file);


fscanf - formátované čtení ze souboru

int fscanf(FILE *file, const char *format, [,address, ...]);


fseek - nastavuje novou pozici ukazatele v souboru

int fseek(FILE *file, long offset, int whence);


fsetpos - nastavuje novou pozici ukazatele v souboru

int fsetpos(FILE *file, const fpos_t *pos);


ftell - vrací aktuální pozici ukazatele v souboru

long ftell(FILE *file);


fwrite - zapisuje blok dat do souboru

size_t fwrite(void *ptr, size_t size, size_t n, FILE *file);


getc - čte znak ze souboru

int getc(FILE *file);


getchar - čte znak z stdin

int getchar(void);


gets - čte řetězec z stdin

char *gets(char *s);


perror - výpis chybového hlášení na stderr

void perror(const char *s);


printf - formátovaný zápis do souboru stdout

int printf(const char *format, [,arg, ...]);


putc - zápis znaku do souboru

int putc(int c, FILE *file);


putchar - zápis znaku do souboru stdout

int putchar(int c);


puts - zápis řetězce do souboru stdout

int puts(const char *s);


remove - ruší soubor specifikovaný jeho jménem

int remove(const char *fname);


rename - přejmenovává soubor

int rename(const char *oldname, const char *newname);


rewind - umísťuje ukazatel souboru na začátek souboru

void rewind(FILE *file);


scanf - formátované čtení ze souboru stdin

int scanf(const char *format, [,address, ...]);


setbuf - přiřazuje souboru vyrovnávací paměť

void setbuf(FILE *file, char *buf);


setvbuf - přiřazuje souboru vyrovnávací paměť

int setvbuf(FILE *file, char *buf, int type, size_t size);


sprintf - formátovaný zápis do řetězce

int sprintf(char *buf, const char *format[,arg,...]);


sscanf - formátované čtení ze řetězce

int sscanf(const char *buf, const char *format[,address,...]);


strerror - vrací ukazatel na řetězec chybového hlášení

char *strerror(int errnum);


tmpfile - otevírá dočasný pracovní soubor v binárním režimu

FILE *tmpfile(void);


tmpnam - vytváří dosud nepoužité jméno souboru

char *tmpnam(char *sptr);


ungetc - vrací znak zpět do vstupního souboru

int ungetc(int c, FILE *file);


vfprintf - zapisuje formátovaný výstup do souboru

int vfprintf(FILE *file, const char *format, va_list arglist);


vprintf - zapisuje formátovaný výstup do souboru stdout

int vprintf(const char *format, va_list arglist);


vsprintf - zapisuje formátovaný výstup do řetězce buf

int vsprintf(char *buf, const char *format, va_list arglist);

4.  Práce s řetězci a bloky v paměti


memchr - hledá v n bytech znak c

void *memchr(const void *s, int c, size_t n);


memcmp - porovnání obsahů pamětí

int memcmp(const void *s1, const void *s2, size_t n);


memcpy - kopírování obsahu paměti

void *memcpy(void *dest, const void *src, size_t n);


memmove - kopíruje blok n slabik byte

void *memmove(void *dest, const void *src, size_t n);


memset - vyplnění obsahu paměti konstantou

void *memset(void *s, int c, size_t n);


strcat - sloučení dvou řetězců

char *strcat(char *dest, const char *scr,);


strchr - hledá první výskyt daného znaku v řetězci

char *strchr(const char *s, int c);


strcmp - porovná dva řetězce

int strcmp(const char *s1, const char *s2);


strcoll - porovná dva řetězce

int strcoll(char *s1, char *s2);


strcpy - kopírování jednoho řetězce do druhého

char *strcpy(char *dest, const char *src);


strerror - vytváří zakázkové chybové hlášení

char *strerror(int errnum);


strlen - zjištění délky řetězce (bez ukončujícího znaku '\0')

size_t strlen(const char *s);


strncat - připojuje nejvýše n znaků k řetězci dest

char *strncat(char *dest, const char *scr, size_t n);


strncmp - porovná nejvýše n znaků jednoho řetězce s částí druhého

int strncmp(const char *s1, const char *s2, size_t n);


strncpy - kopírování nejvýše n znaků řetězce scr do řetězce dest, přičemž v případě potřeby dest ořezává nebo doplňuje znaky '\0'

char *strncpy(char *dest, const char *src, size_t n);


strpbrk - prohledává řetězec s1 a hledá první výskyt libovolného znaku ze řetězce s2

char *strpbrk(const char *s1, const char *s2);


strrchr - prohledává řetězec s1 a hledá poslední výskyt daného znaku

char *strrchr(const char *s, int c);


strspn - vrací délku počátečního úseku s1, který se zcela shoduje s s2

size_t strspn(const char *s1, const char *s2);


strstr - hledá v řetězci s1 výskyt daného podřetězce s2

char *strstr(const char *s1, const char *s2);


strtok - dělí s1 na podřetězce v místech, kde se vyskytují znaky z s2

char *strtok(char *s1, const char *s2);


strxfrm - transformuje úsek řetězce - kopíruje nejvýše n slabik (byte) z s1 do s2

size_t strxfrm(char *s1, char *s2, size_t n );

5.  Matematické funkce


abs - absolutní hodnota celého čísla

int abs(int x);


acos - arkus kosinus

double acos(double x);


asin - arkus sinus

double asin(double x);


atan - arkus tangens

double atan(double x);


atan2 - arkus tangens hodnoty y/x

double atan2(double y, double x);


ceil - zaokrouhluje nahoru

double ceil(double x);


cos - funkce kosinus

double cos(double x);


cosh - funkce hyperbolický kosinus

double cosh(double x);


div - dělí dvě celá čísla, vrací podíl a zbytek ve struktuře div_t

div_t div(int numer, int denom);


exp - exponenciální funkce při základu e

double exp(double x);


fabs - absolutní hodnota reálného čísla

double fabs(double x);


floor - zaokrouhluje dolů

double floor(double x);


fmod - počítá x modulo y , zbytek podílu x/y

double fmod(double x);


frexp - rozděluje číslo double na mantisu a exponent: x = f*2i , kde f Î [1/2,1),

kde f je návratová hodnota funkce a i je uloženo v řetězci pexp

double frexp(double x, int *pexp);


labs - absolutní hodnota long int

long int labs(long int x);


ldexp - hodnota funkce x 2i

double ldexp(double x, int i);


ldiv - dělí dvě čísla long int, vrací podíl a zbytek

ldiv_t ldiv(long int numer, long int denom);


log - přirozený logaritmus x

double log(double x);


log10 - dekadický logaritmus x

double log10(double x);


modf - rozkládá číslo typu double na celočíselnou a zlomkovou část, celočíselná je definována funkcí celá část [x]

double modf(double x, double *i);


pow - počítá xy

double pow(double x, double y);


rand - generuje pseudonáhodné číslo v rozsahu 0 až 215-1

int rand(void);


sin - funkce sinus

double sin(double);


sinh - funkce hyperbolický sinus

double sinh(double);


sqrt - druhá odmocnina z čísla x

double sqrt(double);


srand - inicializuje generátor náhodných čísel

void srand(unsigned seed);


tan - funkce tangens

double tan(double x);


tanh - funkce hyperbolický tangens

double tanh(double x);

6.  Práce s dynamickou pamětí


calloc - alokuje operační paměť pro n položek

void *calloc(size_t n, size_t size);


free - uvolňuje alokovaný blok operační paměti

void free(void *block);


malloc - alokace operační paměti

void *malloc(size_t size);


realloc - změna velikosti paměťového bloku

void *realloc(void *block, size_t size);

7.  Práce s datem a s časem

V některých funkčních prototypech následujících funkcí se používají datové typy clock_t, time_t definované v hlavičkovém souboru time.h.


clock - procesorový čas, který uplynul od začátku programu

clock_t clock(void);


ctime - konvertuje datum a čas na řetězec

char *ctime(const time_t *time);


difftime - počítá rozdíl mezi dvěma časy získanými pomocí time

double difftime(time_t t2, time_t t1);


gmtime - konvertuje datum a čas na typ struct tm

struct tm *gmtime(const time_t *timer);


localtime - provádí korekci času pro časové pásmo

struct tm *localtime(const time_t *timer);


mktime - konvertuje čas z typu struct tm na kalendářový formát

time_t mktime(struct tm *t);


strftime - formátuje čas pro výstup

size_t _cdecl strftime(char *s, size_t max, const char *fmt, const struct tm *t);


time - vrací aktuální počet sekund od 1.1.1970

time_t time(time_t *timer);

8.  Práce s proměnným počtem parametrů


va_arg - nastavení dalšího argumentu z proměnného seznamu argumentů

type va_arg(va_list ap, type);


va_end - nastavení posledního argumentu z proměnného seznamu argumentů

void va_end(va_list ap);


va_start - nastavení prvního argumentu z proměnného seznamu argumentů

void va_start(va_list ap, lastfix);

9.  Řízení procesů a některé další funkce


abort - abnormálně ukončuje program

void abort(void);


assert - otestuje zadanou podmínku a popřípadě přeruší program

void assert(int test);


atexit - registrace funkce, která se provede po skončení main()

int atexit(atexit_t func);


exit - ukončení programu

void exit(int status);


longjmp - provádí nelokální goto

void longjmp(jmp_buf jmpb, int retval);


setjmp - připravuje nelokální goto

int setjmp(jmp_buf jmpb);


localeconv - vrací ukazatel na strukturu aktuálního státu (lokality), informace jak je zapisován čas, datum, ...

struct lconv *localeconv(void);


setlocale - nastavuje lokalitu

char *setlocale(int category, char *locale);


bsearch - binární vyhledávání v poli

void *bsearch(const void *key, const void *base, size_t nelem, size_t width,


int (*fcmp)(const void*, const void*));


signal - specifikuje akce, které obsluhují signály

void (*signal(int sig, void (*func)(int))(int);

Literatura

[1]
Herout, P: Učebnice jazyka C. České Budějovice, KOPP, 1992

[2]
Barclay K.A.: ANSI C. Prentice-Hall, New York, 1990.

[3]
Kernighan, B.W.-Ritchie, D.M.: Programovací jazyk C. Bratislava, Praha, Alfa, SNTL, 1988; slovenský překlad originálu The C Programming Language. Englewood Cliffs, Prentice-Hall, 1978

[4]
Plauger P.J.,Brodie J.: Standard C. Microsoft Press, New York, rok vydání neuveden.

[5]
Richta K.-Brůha, I.: Programovací jazyk C. Praha, Ediční středisko ČVUT, 1991

[6]
Tywoniak J.: Unix pro začátečníky, 1 -10. Bajt, 1992-93

[7]
Virius M.: Programovací jazyky C/C++. Praha, G-Comp, 1992

[8]
Wirth N.: Algorithms + Data Structures = Programs. Prentice-Hall, Englewood Cliffs, New Jersey, 1975. (Slovenský překlad EVT ALFA 1988)


Footnotes:

1 narozdíl např. od C++, které umožňuje skutečné volání odkazem,


File translated from TEX by TTH, version 1.41.