Úvod

Tento kurz vznikl jako závěrečná bakalářská práce na elektrotechnické fakultě Západočeské univerzity.

Kurz si klade za cíl seznámit úplného začátečníka se základními pravidly a návyky pro úspěšný začátek jeho programátorské budoucnosti. Výuka bude provedena v jazyce C. Je pravda, že pro úplné začátky bývá spíše doporučován jazyk PASCAL, ten je dle mého soudu názornější a "čitelnější", ale zase tímto odpadnou nepříjemnosti spojené s přechodem z již známého na jiný jazyk, které mohou někomu činit potíže.

V úvodu se zde budeme zabývat základy programování a dále pak základy psaní programů v jazyce C. Pokud jste v jazyce C jste nováčky, ale máte již zkušenosti s programováním můžete bez obav první kapitolu přeskočit.

 

Základy programování

Program je jednoznačný popis řešení problému tak, aby byl srozumitelný pro počítač. Podle správně napsaného programu by měl být počítač schopen řešit zadaný problém bez chyb.

Všechny zde uvedené zásady platí při psaní programu v jakémkoliv jazyce.

Moudré pořekadlo praví: "V každém programu je alespoň jedna chyba." Proto je potřeba dělat vše, aby program obsahoval chyb co nejméně..

Některé praktické poznatky v programování:

Chceme-li se vyhnout při vytváření většího programu zbytečným problémům je dobré dodržovat následující posloupnost kroků:

1) Přesná specifikace problému

2) Návrh postupu řešení a volba algoritmu

4) Kódování

5) Ladění a testování

6) Dokumentace

7) Údržba programu

Pokud to s programováním myslíte vážně jediný způsob jak se to naučit je zkoušet psát vlastní programy, nikdo se ještě nenaučil dobře programovat čtením knížek nebo posloucháním přednášek. Pro začátek je vhodné zkoušet modifikovat již hotové kódy. Dalším velice šikovným cvičením je zkoušet řešit jeden problém několika různými způsoby a hledat ten nejlepší.

 

Základní prvky jazyka

V souborech s koncovkou c (cpp) je uložen vlastní kód programů a nazýváme je zdrojové.

Soubory s koncovkou h (.hpp) jsou tzv. hlavičkové soubory, které obsahují popis standardních funkcí. Aby bylo možné tyto funkce správně používat je nutné příslušný soubor s funkcemi vložit do zdrojového souboru příkazem #include <jméno souboru>.

Například:

#include <stdio.h>

Toto vám umožní používat všechny standardní vstupní a výstupní funkce.

Ve zdrojovém souboru by neměl chybět popis co obsahuje a kdo a kdy ho napsal například:

/***************************************************************
                    ASCII.c  v 1.0
                    vypis ASCII tabulky
                    J.Novák   7.2002
***************************************************************/

 

V jazyce 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 vyhrazenými identifikátory, žádný jiný identifikátor nemůže mít ve fázi překladu stejné znění jako klíčové slovo.

 

Identifikátory jsou názvy proměnných, konstant a funkcí. Jazyk C (C ++) rozlišuje velká a malá písmena říkáme že je case sensitive. Maximální délka je 31 znaků, mohou to být písmena číslice a podtržítka, musí však začínat písmenem nebo podtržítkem.

Tedy například:

int prom;
int PROM;
int Prom;

jsou tři různé proměnné typu int.

V žádném případě nedoporučuji tuto vlastnost využívat pro rozlišování několika identifikátorů, protože tím akorát znepřehledníte program a i když si dáte pozor nakonec se vám určitě podaří jednotlivá označení zaměnit.

Běžně se identifikátory používají asi takto (není to pravidlo)

i j k - indexy, parametry cyklů
c ch  - znaky
m n   - čítače
f r   - reálná čísla
p_    - ukazatel(např: p_pom)
s     - řetězec

Je dobrý zvyk nazývat proměnné podle toho co představují:

int   n_prom;
float f_prom;
char  c_prom;

Tim se zpřehlední program a zlepší se čitelnost kódu za cenu několika stisknutých kláves navíc.

 

Každý kousek programu, jehož funkce není srozumitelná na první pohled je vhodné opatřit komentářem. Slouží nejen k tomu, aby se v programu vyznal někdo jiný, ale také pomůže porozumět kódu pokud se k nějakému programu vrátíte po delší době a již si přesně nepamatujete jeho funkci.

Text komentáře se vkládá mezi znaky /* text */ nebo za dvojici znaků //. V textu mohou být použity také znaky z horní poloviny ASCII tabulky tedy také český popis.

Komentář v C může vypadat například takto:

     /* toto je komentar */
nebo /* toto je komentář */
 
/*
Takovýto dlouhý komentář může posloužit například k oddělení nějakého
logického bloku příkazů nebo popisu použití funkce následující za tímto
popisem.
*/

Jazyk C++ umožňuje ještě použití zdvojeného lomítka:

// komentář až do konce řádku - před ním může být program,
// ale vše za ním až do konce řádku je ignorováno.
 

Struktura programu

Nejprve klasický jednoduchý příklad na kterém si ukážeme co musí program obsahovat aby jej bylo možné přeložit, spustit a dal o sobě vědět na výstupním zařízení.

#include <stdio.h>
 
void main()
{
  printf("Ahoj světe");
}

Funkce main() je pro jazyk C specifická. Musí ji obsahovat každý program napsaný v tomto jazyce a představuje hlavní program. Ve chvíli kdy je ukončeno vykonávání funkce main, skončí také program.

příklad

Je dobré dodržovat nepsaná pravidla pro určitou formu zápisu programu, které velice usnadňují orientaci v napsaném kódu. Na následujícím velice jednoduchém příkladu je vidět jak je důležité psát přehledně..

Prohlédněte si následující program, je to příklad dobře formátovaného kódu. Na první pohled je zřejmé co to dělá a každý by měl být schopen velmi rychle pochopit funkci programu.

Překladač jazyka C ignoruje všechny dodatečné mezery a všechny volné řádky a tím vám dává značnou svobodu ve formátování vašeho programu a jejich použití pro zpřehlednění zápisu programu.

#include <stdio.h>
int main() /* začátek hlavního programu */
{
  printf("Vhodný způsob členění ");
  printf("umožňuje ");
  printf("porozumět programu.\n");
 
  printf("Nevhodný způsob ");
  printf("činí program ");
  printf("nečitelným.\n");
 
  return(0);
}

Pokud se podíváte na další příklad bude vám trvat podstatně déle pochopit co vlastně program dělá. Po prozkoumání zjistíte, že je totožný s předcházejícím příkladem. Jestliže takovýmto způsobem napíšete nějaký složitější program nevyznáte se ve vlastnoručně vytvořeném kódu ani sami a co teprve někdo jiný . Překladači je jedno jaký styl formátování použijete, ale záleží na vás jak snadné bude najít případné chyby.

#include <stdio.h>
int main() /* začátek hlavního
programu */{printf("Vhodný způsob členění ");printf("umožňuje ")
;printf("porozumět programu.\n");
printf("Nevhodný způsob ");printf("činí program ");
printf("nečitelným.\n");return(0);}
 

Jednoduché datové typy

Tady je třeba připomenout, že také znak je číslo. (je to jeho ASCII hodnota, čehož se někdy také využívá) Celočíselné proměnné existují ve dvou variantách a to se znaménkem (signed), nebo bez znaménka (unsigned).

Zde jsou jednoduché datové typy, které poskytuje C(C++):

celočíselné:
  unsigned char       8 bitů                  0 až 255
  char                8 bitů               -128 až 127
  unsigned int       16 bitů                  0 až 6 5535
  int                16 bitů            -32 768 až 32 767
  short int (short)  16 bitů            -32 768 až 32 767
  unsigned long      32 bitů                  0 až 4 294 967 295
  long int (long)    32 bitů     -2 147 483 648 až 2 147 483 647
 
desetiné:
  float              32 bitů        3.4(10-38  ) až 3.4(10+38)
  double             64 bitů        1.7(10-308 ) až 1.7(10+308)
  long double        80 bitů        3.4(10-4932) až 1.1(10+4932)
 

Jednoduchý způsob jak zjistit ASCII hodnotu znaku ukazuje tento příklad.

Každou proměnou je před jejím použitím potřeba charakterizovat, to znamená, že je nutné ji pojmenovat a určit její typ.

Proměnné deklarujeme takto :

int   i, j;  // 2 proměnné typu int
char  c, ch; // 2 proměnné typu char
float f;     // 1 proměnná typu float
 
příklad celočíselných proměnných
příklad reálných proměnných

Rozsah platnosti proměnných:

 
int i;   // globální proměnná
main()   // funkce main - zde začíná program
{        // začátek bloku
  int j;   // lokální proměnná
}        // konec bloku 
 

Globální proměnná - takto definované proměnné mají platnost od místa definice do konce souboru.

Lokální proměnná - takto definované proměnné mají platnost pouze uvnitř funkce ve které jsou definovány.

příklad
 

Záporné konstanty jsou uvozeny znakem - ( -56, -2, -5.74 ...)

Hexadecimální konstanty jsou uvozeny 0x (0x0a, 0xFF, 0x1C, 0x5d ...)

Znakové konstanty tzv. "escape sekvence":

Sekvence              Hodnota                Význam 
 
  \n                   0x0A               Nová řádka (LF)
  \r                   0x0D               Návrat na začátek řádky (CR)
  \f                   0x0C               Nová stránka (FF)
  \t                   0x09               Tabulátor (HT)
  \b                   0x08               Posun doleva (SP)
  \a                   0x07               Písknutí (BELL)
  \\                   0x5C               Zpětné lomítko
  \'                   0x2C               Apostrof 
  \0                   0x00               Nulový znak (NUL)
  \"                   0x--               Uvozovky  
příklad konstant

Jsou uzavřeny v uvozovkách a mohou obsahovat české znaky (podobně jako komentáře)na například.:

"Toto je ukázka" 
 
"Toto je o trochu delší řetězec."
 
"Toto je "            "velice"              "dlouhý řetězec."
 
"Toto je "                         "velice"
"dlouhý řetězec, který se nevejde na řádku"
 

Takže pokud se řetězec nevejde na jeden řádek, ukončíte ho uvozovkou a pokračujete opět uvozovkou na dalším řádku.

 

Výrazy

Logické výrazy jsou výrazy, jejichž výsledek je 1, pokud jsou pravdivé a 0 pokud jsou nepravdivé.

Používá se v nich těchto operátorů:

==    rovnost
!=    nerovnost
&&    logický součin (a zároveň)
||    logický součet (nebo)
!     negace         (not)
 
<     menší
<=    menší, nebo se rovná
>     větší
>=    větší, nebo se rovná
 
warning.gif Pozor! přiřazení = není totéž jako porovnání ==.

Logické výrazy se v používají všude tam kde syntaxe vyžaduje logickou hodnotu pravda - nepravda. Tedy například rozhodovací podmínky a cykly:

if(podminka) {
  provadene_prikazy;  // provede se pokud je splnena podminka
}
else {
  provadene_prikazy; // provede se pri nesplneni podminky
}

 

Pokud blok prováděných příkazů obsahuje pouze jeden příkaz není potřeba ho uzavírat do závorek:

if(podminka)
  provadeny_prikaz;
else
  provadeny_prikaz;
 

Logický výraz můžete použít takto:

if(podminka_1)
  provadene_prikazy;
else if(podminka_2)
       provadene_prikazy;
else
  provadene_prikazy;
 

Nebo takto:

if(podminka_1 && podminka_2) // pokud jsou splněny obě podmínky
  provadene_prikazy;
else if(podminka_2 || podminka_3) // alespoň jedna z podmínek
  provadene_prikazy;

 

Pokud je podmínka složena z více výrazů provádí se jejich vyhodnocování postupně odpředu dozadu. Zkrácené vyhodnocování výrazů znamená, že pokud mají podmínky platit současně, pak skončí jejich vyhodnocování v okamžiku kdy je jeden z výrazů nepravdivý a výsledkem celé složené podmínky je logická hodnota nepravda. Naopak pokud musí platit aspoň jeden výraz, je výsledek logická hodnota pravda jakmile bude jeden z výrazů vyhodnocen jako pravdivý a další už se netestují. Zkrácené vyhodnocování výrazů urychluje program a umožňuje například velice elegantní řešení vyloučení dělení nulou:

if(delitel != 0 && delenec / delitel < konstanta)
  // prováděné příkazy

V tomto případě nenastane chyba, protože první výraz (delitel != 0) ukončí vyhodnocování dřív, než k ní může dojít.

příklad 

Příkazy

Je nutné si uvědomit, že výraz ukončený středníkem se stává příkazem:

 
i = 2  výraz s přiřazením
i = 2; příkaz

 

Terminologie:

Česky                Anglicky                  zápis
výraz             expression          i * 2 + 3
l-hodnota         l-value             j
přiřazení         assigment           j = i * 2 + 3
příkaz             statement           j = i * 2 + 3;

Pokud výraz nalevo od rovnítka není l-hodnota, při překladu se vyskytne chyba !

Příklad:

j = 5;
d = 'y';           // Char, d = "y"; by byla chyba !!!
f = f + 3.14 * i;
i = j = k = l = 2; // Několikanásobné přiřazení
i + 2 = 7;         // je chyba - není l-hodnota

 

Podmíněný příkaz slouží k tomu, aby bylo možné provést část programu pouze při splnění určité podmínky.

1. Příkaz if()

Příkaz if je nejednodušším reprezentantem podmíněného příkazu, zde je syntaxe:

if (podmínka) příkaz;

Pokud následuje složený příkaz, je ho nutné uzavřít do složených závorek.

if (podmínka) {
  vykonávané_příkazy;
}

Příkazy jsou vykonávány pokud je splněna podmínka.

příklad

2. Příkaz if()-else

Příkaz if-else rozšiřuje schopnosti příkazu if. Umožňuje vícenásobné větvení programu tím, že pokud není splněna podmínka, provede část následující po příkazu else. Dalším použitím příkazu if-else po příkazu else je možná dosáhnout několikanásobného větvení.

if (podmínka) příkaz1; else příkaz2;

V případě vytváření složitějších struktur příkazů doporučuji požívat složené závorky i tam kde nejsou nutné. Nic tím nezkazíte a zjednoduší se tím pochopení funkce.

if (podmínka) příkaz1;
else {                     // zavorka neni nutna, ale usnadni pochopeni
  if (podmínka) příkaz1;
  else příkaz2;
}
příklad

3. Příkaz switch

Příkaz switch, (česky přepínač) slouží k vícenásobnému větvení programu.

Syntaxe je následující :

switch (výraz) {
  case hodnota_1 :
    prikazy_pro_hodnotu_1;
    break;                       // vyskočení za znak }
  case hodnota_2 :
    prikazy_pro_hodnotu_2;
    break;                       // vyskočení za znak }
  default :                      // obdoba else, nepovinný
    prikazy_pro_ostatni_hodnoty;
    break;                       // vyskočení za znak }
}

 

Příkaz funguje tak, že výraz uvedený za klíčovým slovem se porovnává se zadanými hodnotami. Pokud žádná nevyhovuje, vykonají se příkazy v části default. Pokud není uvedena možnost default a žádná hodnota nebyla vyhovující nebudou vykonány žádné příkazy. Příkaz break ukončí vyhodnocování jednotlivých možností a skočí na konec bloku přepínače. Příklad :

char c;
c = getchar();
switch(c) {
  case ' ': printf("Mezera \n");
  case 'A': printf("Acko \n");   break;
  case 'B': printf("Bcko \n");   break;
  case 'C': printf("Ccko \n");   break;
  default:  printf("Neco \n");
}
 

Výstup bude :

- pro zadané "A":
"Acko"
- pro zadané "B":
"Bcko"
- pro zadané "C":
"Ccko"
- pro zadané " ":
"Mezera"
"Acko"
- pro cokoli jiného:
"Neco"

Důvodem proč pro zadanou mezeru vrátí program ještě Acko je ten, že u přepínače pro mezeru chybí break. Tím nedojde k ukončení výběru a provádějí se ještě následující příkazy až do jeho nalezení.

příklad

 

Příkazy pro ukončování cyklů

Zde je několik příkazů, které byste mohli (a budete) při programování cyklů potřebovat :

break        ukončí provádění cyklu (skočí za znak '}')
continue     skočí na začátek cyklu (opětovné vyhodnocení podmínky)
příklad 

Cykly s nastaveným počtem opakování

Jsou to cykly s předem známým počtem průchodů. Používaná syntaxe je tato:

for(prom = start; podminka; inkrementace_prom){
  vykonávané_příkazy;
}

Také u takového cyklu lze použít příkazy break - vyskočení, a continue - skočení na začátek, zopakování inkrementace_prom, a nyní příklad :

int i;
for(i = 0; i <= 3; i++){
  printf("%d \n", i);
}

Bude mít výstup :

0
1
2
3

Při malé změně inkrementace:

int i;
for(i = 0; i <= 3; ++i){
  printf("%d \n", i);
}

Dostaneme následující výstup:

1
2
3
příklad

V cyklu nemusí být jenom "++" a může se použít tzv. operátor čárky, umožňující vložit více výrazů v jednom argumentu funkce:

int i, m;
for(i = 0, m = 1; i <= 5; i++, m *= 2)
  printf("Dva na %d. = %d\n", i, m);

Tohle vytiskne tabulku mocnin dvou:

Dva na 0. = 1
Dva na 1. = 2
Dva na 2. = 4
Dva na 3. = 8
Dva na 4. = 16
Dva na 5. = 32
příklad

Je také možné vkládat cykly do sebe.

příklad

Nekonečné cykly

Zde je uvedeno použití několika příkazů pro "nekonečné" cykly. Nekonečný cyklus je možné vytvořit i pomocí příkazu for :

for(;;) {
  vykonavane_prikazy;       // musí obsahovat break pro vyskočení z cyklu
}

 

1. Příkaz do-while()

Příkaz do je přesným příkladem "nekonečného" cyklu, který lze ukončit pouze příkazem break, příkaz continue jen skočí na začátek cyklu a pokračuje dále:

do {
  vykonavane_prikazy;       // musí obsahovat break pro vyskočení z cyklu
} while(1)

Příklad :

int i = 0;
do {
  if(i % 2 == 0) {      // sudá čísla (po dělení dvěma není zbytek)
    printf("%d je sude \n", i);
    i ++;
    continue;
  }
  printf("%d je liche \n", i);
  if(i == 5)
    break;
  i ++;
} while(1)

Bude mít výstup :

0 je sude
1 je liche
2 je sude
3 je liche
4 je sude
5 je liche
příklad 

2. Příkaz while()

Příkaz while má jednu podmínku, která pokud je splněná, cyklus probíhá znova a znova. Příkazy break continue na něj mají obvyklé účinky. Častá verze je nekonečný cyklus (v závorce je "1") Základní syntaxe :

while(podm_bezi){
  prikazy; // Příkazy se vykonávají dokud bude podm_bezi = 1
}

Jednoduchý příklad použití:

char c;
while(c != 'a'){
  printf("\nChces vyskocit z cyklu ?");
  c = getchar(); 
}
printf("\nSbohem ...);
 

Bude mít výstup :

Chces vyskocit z cyklu ?
n
Chces vyskocit z cyklu ?
n
Chces vyskocit z cyklu ?
a
Sbohem ...
příklad

Cykly by měly být každému naprosto jasné, protože se používají téměř všude.

 

Ukazatele

Ukazatel představuje adresu paměťového místa. Jeho hodnota říká, kde je uložen nějaký objekt. Součástí deklarace ukazatele je i informace o typu dat, které jsou na získané adrese očekávány.

int *p_int;  // p_int je ukazatel na integer

Hodnota p_int je adresa v paměti a *p_int je celočíselná hodnota uložená na této adrese.

Obvyklou chybou začátečníků je použití ukazatele bez jeho předchozí inicializace (přidělení paměťového místa). Takový ukazatel může ukazovat na kritické oblasti paměti a jeho použití může vést (v operačních systémech jako je MS-DOS) i k havárii systému.

Pro přidělení paměti se užívá funkce malloc()

My zatím vystačíme bez alokace paměti. Na to potřebujeme nějakým způsobem ke získat adresu existující proměnné. To nám umožní korektně odkazovat na správné paměťové místo.

int i, *p_i;
p_i = &i;
*p_i = 123; 

Po definici proměnné i typu int, a ukazatele p_i na typ int v prvním řádku následuje získání adresy proměnné i a její přiřazení do ukazatele p_i. Poté na místo kam ukazuje uložíme příslušnou hodnotu, v našem případě 123.

příklad 

Aby části programu mohly jak žádat o přidělení dynamické paměti, tak již nepotřebnou paměť vracet, musí existovat alespoň základní programová podpora, kterou dostáváme spolu s překladačem. Souhrnně se programová podpora pro dynamickou alokaci paměti nazývá správce haldy. Deklarace funkcí správce haldy jsou umístěny v stdlib.h, případně ve alloc.h. Tyto soubory se připojí standardním způsobem.

void malloc(size);

představuje požadavek o přidělení souvislého bloku paměti o velikosti size. Je-li úspěšný, dostáváme ukazatel typu void na jeho začátek, jinak NULL.

Příklad:

int *p_i;
 
if ((p_i = (int *) malloc(sizeof(int) == NULL) {
  printf("Neni volna pamet.\n");
  exit(1);
}

Tento úryvek programu se pokusí přidělit potřebné množství paměti (sizeof(int)) pro ukazatel p_i a pokud bude úspěšný přiřadí mu adresu takto získané paměti. Současně provede změnu typu na typ int (int *). V případě neúspěchu vrátí hodnotu NULL, vypíše hlášení o chybě a ukončí program.

free((void *)p_i); 

je naopak vrácení dříve alokované paměti na kterou ukazuje p_i.

Po uvolnění paměti je vhodné okamžitě provést

p_i = NULL;

a zabránit tím v přístupu do paměti, která již programu nepatří, a znemožnit tak vznik případných chyb.

příklad
 

Jaký má smysl, provádět aritmetické operace s ukazateli? Význam spočívá ve zvýšení přehlednosti, zrychlení chodu programu i v možnostech, které dostáváme k dispozici.

Aritmetika ukazatelů je omezena na tři základní operace, sčítání, odčítání a porovnání. Podívejme se nejprve na první dvě operace.

Sčítání i odčítání jsou binární operace. Protože se zabýváme aritmetikou ukazatelů, bude jedním z operandů vždy ukazatel. Druhým operandem pak bude buď opět ukazatel, nebo jím může být celé číslo.

Pokud jsou oba operandy ukazatele stejného typu, které ukazují do různých částí stejného úseku paměti, je výsledkem jejich rozdílu počet položek, které se mezi adresami na něž ukazatele ukazují nacházejí. Pokud k ukazateli přičítám, respektive odčítám celé číslo, je výsledkem ukazatel ukazující o příslušný počet prvků výše, respektive níže.

Podívejme se tedy na ukazatele a pole. Mějme následující deklaraci,

int i, *p_i, a[N];
 

adresa prvku a[0] je &a[0] je totéž jako a nebo a + 0.

Poslední výraz již představuje součet ukazatele s celočíselnou hodnotou. Ta musí být nula, aby se opravdu jednalo o ukazatel na první prvek pole.

Nyní nás jistě nepřekvapí, že následující výrazy uvedené na stejném řádku

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

představují jiné zápisy téhož výrazu. V prvním řádku jde o ukazatel na i+1. prvek, ve druhém řádku pak hodnotu tohoto prvku.

Pro konkrétní N rovno 100 dostáváme,

int i, *p_i, a[100]; 

a následující přiřazení je přiřazení hodnoty jednoho ukazatele ukazateli druhému,

pi = a;     /* p_i nyní ukazuje na začátek pole a, na 1. prvek */ 

výrazy uvedené níže jsou po tomto přiřazení zaměnitelné (mají stejný význam, představují hodnotu prvku pole a s indexem i),

a[I]          *(a+I)          p_i[i]         *(p_i+i). 

Budeme-li nyní potřebovat ukazovat na prostřední prvek pole a, snadno tak můžeme učinit s pomocí našeho pomocného ukazatele p_i:

p_i = a + 49;    /* p nyní ukazuje doprostřed pole a, na 50. prvek */ 

S tímto ukazatelem se pak můžeme posunout na následující prvek pole třeba pomocí inkrementace, neboť i u ní překladač ví, o kolik bajtů má posunout (zvětšit) adresu, aby ukazoval na následující prvek: ++p_i nebo p_i++, ale ne ++a ani a++, neboť a je konstantní ukazatel (je pevně spojen se začátkem pole).

Část věnovanou polím a aritmetice ukazatelů ukončeme krátkým programem. V něm si zejména povšimneme skutečnosti, že formální argumenty funkcí nejsou poli, nýbrž ukazateli na typ. Navíc jejich změna neovlivní změnu argumentů skutečných (to by ostatně překladač nedovolil - jsou přece konstantní). Proto není problémem těmto funkcím předat ukazatele jak na začátek pole, tak na libovolný jiný jeho prvek. Opatrnost pak musíme zachovat co se předávaného počtu prvků týče. C neprovádí kontrolu mezí!

#define N 6
 
#include <stdio.h>
 
void copy_array1(int *a, int *b, int n)
/* a - vstupni pole, b - vystupni pole, n - pocet prvku */
{
  register int i = 0;
 
  for (; i < n; i++)
  b[i] = a[i];
}
 
void copy_array2(int *a, int *b, int n)
/* a - vstupni pole, b - vystupni pole, n - pocet prvku */
{
  while (n-- > 0)
  *b++ = *a++;
}
 
void print_array(int *p, int n)
/* vytiskne celociselne pole o n prvcich */
/* zacne a skonci na novem radku */
{
  puts("");
  while (n-- > 0)
  printf("\t%d", *p++);
  puts("");
}
 
void main()
{
  int pole1[] = {1, 2, 3, 4, 5, 6, 7, 8, 9},
      pole2[N], dim1;
 
  dim1 = sizeof(pole1) / sizeof(int);
  print_array(pole1, dim1);
  copy_array1(pole1, pole2, N);
  print_array(pole2, N);
  copy_array2(pole1 + 3, pole2, N);
  print_array(pole2, N);
}

Výstup vypadá následovně::

1 2 3 4 5 6 7 8 9
1 2 3 4 5 6
4 5 6 7 8 9

Program na funkcích kopírujících prvky jednoho pole do druhého ukazuje přístup k prvkům pole pomocí indexu i pomocí ukazatele, tedy pointerovou aritmetiku.

Pomocí ukazatelů lze také vytvářet vázané seznamy. Nebudeme se jimi blíže zabývat, pouze si uvedeme nějaké příklady.

přidání na začátek sezmamu
vložení doprostřed seznamu
přidání na konec seznamu

Pole

Pole je kolekce proměnných stejného typu, které jsou označovány společným jménem. K samotným prvkům pole přistupujeme pomocí identifikátoru pole a indexu prvku. Vytvořením pole obsazujete souvislou oblast operační paměti tak, že první prvek pole je na nejnižší přidělené adrese a poslední na nejvyšší přidělené adrese.

Definice pole je jednoduchá:

typ jméno[rozsah];

Kde typ určuje typ prvků, jméno představuje identifikátor pole a rozsah je požadovaný počet prvků pole.

warning.gif Je potřeba upozornit, že pole začíná prvkem s indexem nula a poslední prvek má index o jedna menší než rozsah. Protože překladač nekontroluje rozsah použitého indexu při přístupu k prvkům musíme si jeho hodnotu ohlídat sami. Jinak se může stát, že budeme číst nesmyslné hodnoty při odkazu na neexistující prvky pole.

Ukažme si nyní několik definic polí:

#define N 10
int a[N]; 

a je pole o N prvcích, každý typu int. Počet prvků pole je 10 a odkazovat se na ně můžeme indexy 0 až 9 takto:

a[0], a[1], ..., a[N-1]

Nebo například:

int c[] = { 3, 4, 5}; 

Tímto vytvoříme jednorozměrné pole c a jeho prvky přednastavíme na hodnoty zadané v závorce.

příklad pole čísel
příklad pole řetězců

Jazyk C umožňuje deklarovat pouze jednorozměrné pole. Avšak jeho prvky mohou být libovolného typu, tedy také jednorozměrné pole. Tím dostaneme pole jednorozměrných polí, tedy matici. Budeme-li uvedeným způsobem postupovat dále, vytvoříme datovou strukturu prakticky libovolné dimenze, omezené pouze množstvím paměti.

Definice matice (dvourozměrného pole) vypadá takto:

typ jmeno[3][5]; 

Kde typ určuje datový typ položek pole, jmeno představuje identifikátor pole a [3][5] určuje rozsah jednotlivých vektorů (3 řádky a 5sloupců).

 
příklad 

Existují také dynamická pole jejichž velikost lze měnit během chodu programu a tim šetřit paměť. Jejich vytváření a použití si ukážeme na příkladu.

příklad na dynamické pole

 

Řetězce

Řetězce jsou speciálním případem jednorozměrného pole prvků typu znak zakončeného znakem \0. Na toto musíme myslet při definici velikosti pole a nezapomenout pro zarážku přidat jeden znak

Pro práci s řetězci slouží standardní knihovna string.h a její připojení provedeme příkazem:

#include <string.h>

Podívejme se blíže na definici pole, které použijeme pro řetězec:

char str[SIZE];

Na identifikátor s se můžeme dívat ze dvou pohledů:

1) Jako na proměnnou str typu řetězec (pole znaků) délky SIZE, jehož jednotlivé znaky jsou přístupné pomocí indexů str[0]str[SIZE-1].

2) Jako na konstantní ukazatel na znak, t.j. na první prvek pole str, str[0].

Jak již bylo uvedeno dříve řetězcové konstanty píšeme mezi dvojici uvozovek, pokud chceme aby řetězcová konstanta obsahovala uvozovky musíme je uvodit speciálním znakem \ - opačné lomítko. Pokračujme ještě chvíli v jednoduchých ukázkách:

"abc" je konstanta typu řetězec délky 3+1 znak (zarážka), tedy 3 znaky, 4 bajty paměti (konstantní pole 4 znaků).

"a" je rovněž řetězcovou konstantou, má délku 1+1 znak

'a' je znaková konstanta (neplést s řetězcem!).

 

Nyní si spojíme znalosti řetězcových konstant a polí. Například zápis:

char str[] = "retezec";

je ekvivalentní zápisu

char str[] = {'r','e','t','e','z','e','c','\0'};

V obou případech se délka pole použije z inicializační části. Druhý příklad je zejména zajímavý nutností explicitního uvedení zarážky. Jinak lze říci, že se tento způsob zadání řetěce příliš nepoužívá.

Mnohem častěji se používá definice pomocí ukazatele na znak:

char *p_str = "retezec";

Je podobná, přesto se velice liší p_str je ukazatel na znak, jemuž je přiřazena adresa řetězcové konstanty "retezec". Tato konstanta je tedy umístěna v paměti, ale proměnná p_str na ni ukazuje jen do okamžiku, než její hodnotu změníme. Pak ovšem ztratíme adresu konstantního řetězce "retezec".

Jednoduchý příklad pro vyjasnění situace:

#include <stdio.h>
void main()
{
  char text[] = "world", *new1 = text, *new2 = text;
  printf("%s\t%s\t%s\n", text, new1, new2);
  new1 = "hello";
  printf("%s\t%s\t%s\n", text, new1, new2);
  printf("%s\n", "hello world" + 2);
}

Bude mít výstup:

world world world
 
world hello world
 
llo world

Klíčovým místem programu je přiřazení:

new1 = "hello";

Tímto pouze měníme hodnotu ukazatele new1. Nedochází při něm k žádnému kopírování řetězců, to by změnilo také obsah obou dalších řetězců text i new2, které původně byly umístěny na stejné adrese.

příklad 
 

Struktury

Dosud jsme vystačili se základními datovými typy. Realita, kterou se v programech často pokoušíme popsat tuto jednoduchost většinou postrádá. Často se setkáváme se skutečnostmi, k jejichž popisu potřebujeme více souvisejících údajů. Existuje konstrukce, která nám toto dovolí. Směřujeme k definici struktury. Její syntaktický předpis je následující:

struct [jmeno typu struktury] {
  typ jmeno promene[, jmeno promene, ...];
  typ jmeno promene[, jmeno promene, ...];
...
} [jmeno struktury];

Konstrukci uvádí klíčové slovo struct. Následuje nepovinné pojmenování jmeno typu struktury, které obvykle nepoužíváme. Následuje blok definic položek struktury. Položky jsou odděleny středníkem. Jsou popsány identifikátorem typu typ, následovaným jedním, nebo více identifikátory prvků struktury jmeno promene. Ty jsou navzájem odděleny čárkami. Na konci je uvedeno jméno proměnné právě definovaného typu struktury jmeno struktury.

Podívejme se na příklady.

struct {
  int den;
  int mesic;
  int rok;
} d1;

Struktura není pojmenovaná a nedá se proto dále využít, je možné pouze používat proměnnou d1.

struct datum {
  int den;
  int mesic;
  int rok;
} d1;
 
 
struct datum d2;

Pozor častou chybou je:

datum d2;  /*schazi klicove slovo struct */

Struktura je pojmenovaná a proto se dá dále využít pro další proměnné.

typedef struct {
    int den;
    int mesic;
    int rok;
  } DATUM;
 
DATUM d1, d2;

Můžeme definovat typ DATUM a ten pak dále využít pro deklaraci proměnných.

Pro přístup k prvkům struktury používáme tečku. Tu umístíme mezi identifikátory proměnné typu struktura a identifikátor položky, s níž chceme pracovat.

d1.den = 1;

V případě, kdy máme ukazatel na strukturu, použijeme pro přístup k položkám místo hvězdičky a nezbytných závorek raději operátor ->.

p_struct -> polozka = 1;
(*p_struct).polozka = 1;

Oba zápisy znamenají totéž.

Dále se podívejme na přiřazení hodnoty strukturované proměnné při její definici.

DATUM d1 = {31, 12, 2002};

Pro základní použití struktur již máme dostatečné informace. Asi jsme schopni odhadnout, že k prvku struktury, který je rovněž strukturou přistupujeme pomocí další tečky.

 

Funkce

V této kapitole si rozebereme základní stavební kámen jazyka C - funkci. Z předchozího textu víme, že každý C program obsahuje alespoň jednu funkci - main().

Funkce v sobě zahrnuje takové příkazy, které se v programu často opakují a proto se vyplatí je vyčlenit, pojmenovat a volat. Takto byla funkce chápána zejména v dobách, kdy byla paměť počítačů malá a muselo se s ní zacházet velmi hospodárně. i dnes můžeme používat funkce ze stejných důvodů. Představují základní jednotku, která řeší nějaký problém. Pokud je problém příliš složitý, volá na pomoc jinou (jiné) funkce. Z toho plyne, že by funkce neměla být příliš rozsáhlá. Pokud tomu tak není, nejenže se stává nepřehlednou, ale je i obtížně modifikovatelnou.

Rozlišujme standardní funkce a uživatelské funkce.

Standardní funkce jsou definovány normou jazyka a výrobce překladače je dodává jako součást programového balíku tvořícího překladač a jeho podpůrné programy a soubory. Tyto standardní funkce zpravidla dostáváme jako součást standardních knihoven (proto se jim někdy říká knihovní funkce) a jejich deklarace je popsána v hlavičkových souborech. Nemáme k dispozici jejich zdrojový kód.

Uživatelské funkce jsou ty funkce, které jsme napsali a máme jejich zdrojové texty. Pokud jsme profesionály, vyplatí se nám naše funkce precizně dokumentovat a archivovat. Když už jsme je jednou napsali a odladili, můžemeje příště již s důvěrou používat.

Po obecném úvodu, v němž jsme si shrnuli význam funkcí, se podívejme, jak se prakticky s funkcemi pracuje. Začněme klasicky, uvedením syntaktického zápisu deklarace funkce:

typ jméno([seznam argumentů]); 

Kde typ představuje typ návratové hodnoty funkce, jméno je identifikátor, který funkci dáváme () je povinná dvojice omezující deklaraci argumentů (v tomto případě prázdnou).

Seznam argumentů je nepovinný - funkce nemusí mít žádné argumenty, může mít jeden nebo více argumentů.

Deklarace funkce popisuje vstupy a výstupy, které funkce poskytuje. Funkce nemá provádět akce s jinými daty, než která jí předáme jako argumenty. Současně výstupy z funkce mají probíhat jen jako její návratová hodnota a nebo prostřednictvím argumentů funkce.

Definice funkce, na rozdíl od její deklarace, nemá za závorkou ukončující seznam argumentů středník, ale blok který se nazývá tělo funkce. V úvodu může, jako jakýkoliv jiný blok, obsahovat definice proměnných (jsou v rámci bloku lokální). Pak následuje posloupnost příkazů, které definují chování (vlastnosti) funkce.

Pokud uvedeme pouze definici funkce, na kterou se později v souboru odvoláváme, slouží tato definice současně jako deklarace.

Pojem datový typ známe. Chceme-li získat návratovou hodnotu funkce, musíme určit, jakého datového typu tato hodnota je. Na typ funkce nejsou kladena žádná omezení. Pokud nás návratová hodnota funkce nezajímá, tak ji prostě nepoužijeme. Pokud ovšem chceme deklarovat, že funkce nevrací žádnou hodnotu, pak použijeme klíčové slovo void, které je určeno právě pro tento účel.

Kromě určení datového typu musíme také určit hodnotu, která bude návratovou. Je to snadné, tato hodnota musí být výsledkem výrazu uvedeného jako argument příkazu return. Typ return výrazu se pochopitelně musí shodovat, nebo být alespoň slučitelný, s deklarovaným typem návratové hodnoty funkce.Výraz, následující za return, může být uzavřen v závorkách, ale nejsou nutné:

return výraz_vhodného_typu; 

Spojeno s definicí funkce, může funkce celočíselně vracející druhou mocninu svého celočíselného argumentu vypadat takto:

int isqr(int i)
{
  return (i * i);
} 
 

Příkaz return se v těle funkce nemusí vyskytovat pouze jedenkrát. Pokud to odpovídá větvení ve funkci, může se vyskytovat na konci každé větve. Rozhodně však příkaz return ukončí činnost funkce, umístí návratovou hodnotu na specifikované místo a předá řízení programu bezprostředně za místem, z něhož byla funkce volána. Pokud je použít ve funkci main(), ukončuje celý program.

Deklarace všech formálních argumentů funkcí musí obsahovat datový typ. Není možné uvést společný typ pro více následujících identifikátorů. Korespondence mezi skutečnými a formálními argumenty odpovídá jejich pořadí při volání a v definici.

Podstatný je způsob předávání argumentů funkci. Hodnoty skutečných argumentů jsou předány do zásobníku. Formální argumenty se odkazují na kopie skutečných argumentů v zásobníku. Proto se změna hodnoty formálního argumentu se nepromítne do změny hodnoty argumentu skutečného! Tomuto způsobu předávání argumentů se říká předávání hodnotou.

Problém vzniká v okamžiku, kdy potřebujeme, aby funkce vrátila více něž jednu hodnotu. Řešení pomocí globální proměnné není vhodné, protože má vedlejší účinky. Řešení je předávání nikoli hodnot skutečných argumentů, ale jejich adres. Tím vzniká možnost změny hodnot skutečných argumentů. Mluvíme pak o volání adresou.

Ukažme si jednoduchý program, který volá různé funkce jak hodnotou, tak adresou.

#include <stdio.h>
 
int nacti(int *a, int *b)
{
  printf("\nzadej dve cela cisla:");
  return scanf("%d %d", a, b);
}
 
float dej_podil(int i, int j)
{
  return ((float) i / (float) j);
}
 
void main(void)
{
  int c1, c2;
 
  if (nacti(&c1, &c2) == 2)
  printf("podil je : %f\n", dej_podil(c1, c2));
}

Funkce nacti() musí vracet dvě načtené hodnoty, předává proto argumenty adresou. Funkce dej_podil() naopak skutečné argumenty změnit nesmí, proto jsou jí předávány hodnotou. Návratový výraz je v ní uzavřen v závorkách, nejsou nutné, zpřehlední však zápis.

Další příklady na volání funkce:

příklad
řazení hodnot hledáním maxima
řazení hodnot "probubláváním"

Pokud je potřeba předávat pomocí parametrů funkci proměnnou typu pole provádí se to výhradně odkazem.

Ještě na závěr této kapitoly si uvedeme jednu vlastnost funkce main(). Pokud spouštíme program s použitím parametrů, potom jsou tyto parametry použity právě funkcí main jako její argumenty.

příklad

Vstup a výstup

Každý program zpracovává nějaká vstupní data a sděluje nám výsledky touto činností získané. Pokud by tomu tak nebylo, neměli bychom zřejmě důvod takový program vůbec aktivovat.

Vstup a výstup probíhá z různých vstupně výstupních zařízení. Jejich nejjednodušší rozdělení je znaková a bloková (blokově orientovaná). Znakové vstupní zařízení typicky představuje klávesnice, výstupní pak monitor či tiskárna. Blokové vstupně/výstupní zařízení je velmi často pružný či pevný disk. Rozdíl mezi nimi spočívá zvláště v možnostech přístupu k datům. Z klávesnice čteme sekvenčně znak po znaku (sekvenční přístup), zatímco u diskového souboru můžeme libovolně přistupovat ke zvolené části dat (používaný termín popisující tuto činnost je náhodný přístup).

Vstup a výstup budeme často zkráceně zapisovat I/O.

Ještě než se pustíme do výkladu, definujme si některé základní pojmy.

Řádek textu je posloupnost znaků ukončená symbolem (symboly) přechodu na nový řádek. Zdůrazněme, že v uvedené definici není žádná zmínka o délce řádku. Tuto skutečnost je třeba mít na paměti.

Soubor je posloupnost znaků (bajtů) ukončená nějakou speciální kombinací, která do obsahu souboru nepatří - konec souboru. Tuto hodnotu označujeme symbolicky EOF. Textový soubor obsahuje řádky textu. Binární soubor obsahuje hodnoty v tomtéž tvaru, v jakém jsou uloženy v paměti počítače. Binární soubor obvykle nemá smysl vypisovat na terminál.

Každý program v jazyce C má standardně otevřen standardní vstup stdin, standardní výstup stdout a standardní chybový výstup stderr. Ty jsou obvykle napojeny na klávesnici a terminál.

Standardní vstup a výstup používá vyrovnávací paměť obsahující jeden textový řádek. Při volání funkcí standardního vstupu/výstupu musíme použít hlavičkový soubor stdio.h.

Standardní vstup a výstup znaků představuje zcela základní možnost I/O.

int getchar(void); 

přečte ze standardního vstupu jeden znak, který vrátí jako svou návratovou hodnotu. V případě chyby vrátí hodnotu EOF. Funkce

int putchar(int c); 

má zcela opačnou úlohu, znak, který je jejím argumentem zapíše na standardní výstup. Zapsaná hodnota je současně návratovou hodnotou, nenastane-li chyba. Pak vrací EOF.

Zdůrazněme podstatný fakt, typ hodnoty, kterou čteme/zapisujeme je int, nikoliv char, jak bychom v první chvíli očekávali. Tuto záležitost nám objasní opětné přečtení definice souboru z úvodu této kapitoly. Soubor obsahuje znaky. To odpovídá naší představě. Ale konec souboru je představován hodnotou, která do souboru nepatří. Tato hodnota ovšem musí být odlišná od ostatních znaků. A proto je typu int.

Čtení znaků ze standardního vstupu a jejich zápis na standardní výstup ukazuje program, který přečte z klávesnice znak a potom ho hned vypíše..

#include <stdio.h>
 
void main()
{
  int c;
 
  c = getchar();
  putchar(c);
}

Při zadávání většího počtu hodnot můžeme použít cyklus ukončený předem zvoleným znakem jak ukazuje nasledující příklad.

Každý vstup je také vhodné ošetřit proti chybnému zadání, aby nedocházelo ke zhroucení programu. Následující príklad ukazuje ošetření číselného vstupu.

příklad
 

Pro formátovaný vstup je v stdio.h funkce

scanf();

Pro výstup je zde funkce

printf();

Základní použití :

scanf("%d", &i);

Takto zavolaná funkce přečte z klávesnice celé číslo (%d) a uloží ho do proměnné i.

printf("I je %d \n", i);

Vypíše na výstup řetězec například: I je 12 a odřádkuje.

Následující část programu přečte z klávesnice 2 čísla a vytiskne je.

int i, j;
 
scanf("%d%d", &i, &j); // Načte 2 čísla, může být zapsáno i odděleně
printf("%d + %d = %d", i, j, i + j);

Pokud zadáme například čísla 4 a 7 bude mít volání funkce printf výstup: 4 + 7 = 11. To znamená že se posloupnost %d nahradí příslušným číslem a ostatní text mezi zůstane stejný.

Je to již zmíněná posloupnost podle které překladač pozná jaký formát má výstup mít.

Posloupnosti začínají znakem % (pokud chcete zobrazit znak %, musíte použít zdvojení %%) a určující formát vstupu/výstupu.

Řetězec                                 Data
 
%c                                 Znak - raději putchar()
%d                                 Signed Int
%ld                                Signed Long
%u                                 Unsigned Int
%lu                                Unsigned Long
%f                                 float
%Lf                                long double (L musí být velké !)
%lf                                double (někdy nejde u printf())
%x                                 Hexadecimální malými pís. (1a2c)
%X                                 Hexadecimální velkými pís. (1A2C)
%o                                 Osmičkové číslo
%s                                 Řetězec

Příklady :

1) printf("soucet je %d.", i + j);
vypíše : soucet je 11.
 
2) printf("Pracovali na 100 %%.");
vypíše : Pracovali na 100 % (pro výpis znaku % je nutné napsat %%)
 
3) printf("soucet je %d soucin je %d\n", i + j, i * j);
vypíše : soucet je 11 soucin je 28 (a odřádkuje)
 
4) printf("\007Ha ! Ivan !");
vypíše : Ha ! Ivan ! (a pískne \007 = \a)
 
5) printf("Toto je \"BackSlash\" : \\");
vypíše : Toto je "BackSlash" : \
 
6) printf("Znak %c má hodnotu %d (%X)", c, c, c);
vypíše : Znak A má hodnotu 65 (41H)
 
7) printf("Znak %c má hodnotu %d (%X)", '*', '*', '*');
vypíše : Znak * má hodnotu 42 (2AH)
 
8) printf("Je přesně %2d:%2d hodin.", hodiny, minuty);
vypíše : Je přesně 1:12 hodin.

(2 v řídící posloupnosti znamená, že číslo bude tištěno na 2 znaky)

 
9) printf("Toto je reálné číslo : \'%4.2f\' ", 2.8356);
vypíše : Toto je reálné číslo : '2.83' 

(4.2 znamená, že reálné číslo bude tištěno na 4znaky, z nich 2 budou za desetinnou tečkou + desetinná tečka se taky počítá)

 
10) printf("Toto je \' %s \' ", "STRING");
vypíše : Toto je ' STRING '
 

Časté chyby :

printf("%d", i, j); mnoho argumentů
printf("%d %d", i); málo argumentů
scanf("%d", i); chybí operátor &, tedy má být &i;

Na závěr si uvedeme nějaké příklady.

příklad1
příklad2

Se soubory se pracuje podobně jako s konzolí. Je zde jeden rozdíl - konzole je jen jedna, ale souborů je víc, takže každý soubor se v Céčku otevře do svojí vlastní proměnné :

#include <stdio.h>
 
int main()
{
  FILE *soubor;
 
  soubor = fopen("jméno_souboru", "způsob otevření"); // otevře soubor
  if(soubor == NULL) {
    printf("Nepodařilo se otevřít soubor");
    getchar();    //cekani na stisk klavesy
    return (1);
  }
  fclose(soubor); // uzavře soubor
  getchar();      //cekani na stisk klavesy
  return (0);
}

Jsou zde použity funkce fopen a fclose. Pro otevření souboru slouží funkce fopen, která má parametry jméno souboru a způsob otevření jenž udává jak se soubor otevře. varianty jsou :

"r" - pro čtení
"w" - pro zápis
"a" - pro připojení na konec
"rb" - pro binární čtení
"wb" - pro binární zápis
"ab" - pro binární připojení na konec
 

Pokud se rozhodneme pro čtení, musí soubor existovat, jinak funkce fopen vrátí NULL. Pokud to bude pro zápis a soubor již existuje bude smazán a vytvoří se znovu. Pro připojení se vytvoří nový, nebo se připisuje do existujícího. Teď umíme otevřít soubor, ale jak se dostat k datům v souboru ? Jde to pomocí stejných funkcí jako pro konzoli. Jsou to fgetc, fputc (obdoby getchar a putchar) a fscanf a fprintf. Objasníme si je na příkladu:

int main()
{
  FILE *fr;
  char c;
 
  fr = fopen("text.txt", "r");
  if(fr == NULL) {
    printf("Soubor nejde otevrit !\n");
    getchar();   //cekani na stisk klavesy
    return (1);
  }
  c = fgetc(fr);
  fclose(fr);
  putchar(c);
  getchar();     //cekani na stisk klavesy
  return (0);
}

Tento program otevře soubor text.txt, přečte z něj jeden znak a ten vytiskne. Pokud chcete přečíst celou řádku, dáte to do smyčky, dokud c nebude '\n'. Ale co když narazíme na konec souboru ? V souboru stdio.h je funkce feof(FILE *), která vrátí nulu, pokud nejsme na konci souboru. To by se taky dalo dát do smyčky. Ale je tu ještě jedna možnost a tou je konstanta EOF (End Of File). Ta je obvykle -1 (ale nemusí), z čehož vyplývá že nemůžeme použít char (ten má moc malý rozsah), ale musíme použít int. Následující program vypíše celý soubor na obrazovku :

int main()
{
  FILE *fr;
  int c;
 
  fr = fopen("text.txt", "r");
  if(fr == NULL) {
    printf("Soubor nejde otevrit !\n");
    getchar();     //cekani na stisk klavesy
  return(1);
  }
 
  /* cteni a tisk  souboru text.txt */
  while((c = fgetc(fr)) != EOF)
  putchar(c);
 
  fclose(fr);
  getchar();      //cekani na stisk klavesy
  return (0);
}

Dalsí pro nás zajímavé jsou funkce fscanf a fprintf. Zkusíme program, který načte dvě čísla ze souboru a.txt a uloží je do b.txt :

int main()
{
  FILE *fr, *fw;
  int a, b;
 
  fr = fopen("a.txt", "r");
  fw = fopen("b.txt", "w");   // otevřeme pro zápis
  if(fr == NULL || fw == NULL) {
    printf("Soubor nejde otevrit !\n");
 
    /* před koncem programu musíme uzavřít všechny soubory */
    if(fr != NULL)
      fclose(fr);
    if(fw != NULL)
      fclose(fw);
 
    getchar();
    return (1);
  }
 
  fscanf(fr, "%d %d", &a, &b);
  fprintf(fw, "%d + %d = %d\n", a, b, a + b);
 
  fclose(fr);
  fclose(fw);
 
  getchar();
  return (0);
}

No a nakonec zkusíme ještě program na připojení textu. Uvedeme si program, který přečte všechny čísla v a.txt, vynásobí je dvěma a připojí k b.txt. Téměř celý program je stejný jako v předešlém případě pouze část

  fscanf(fr, "%d %d", &a, &b);
  fprintf(fw, "%d + %d = %d\n", a, b, a + b);

nahradíme kódem

  while(!feof(fr)) {
    fscanf(fr, "%d", &i);
    fprintf(fw, "%d\n", i * 2);
  }

a místo dvou proměnných a, b použijeme pouze jednu i.

Pro pochopení základů práce se soubory by toto mělo stačit, přesto uvedu ještě nějaké příklady:

příklad zápisu do souboru 
příklad čtení ze souboru
 
binární zápis do souboru
textový zápis do souboru
hexadecimální a textový výpis souboru

Operátory a preference

Jsou to unární + a - (kladná a záporná hodnota)

 
 
inkrement    ++ (zvětšení o 1)
dekrement    -- (zmenšení o 1)
 

Použití :

++l_hodnota - inkrementace před použitím
l_hodnota++ - inkrementace po použití

 

Lze použít pouze na proměnné:

45++         chyba nelze inkrementovat konstantu
(i + j)++    chyba nelze inkrementovat výraz

Příklad :

int c, d, e;
 
c = 1;     // do c přiřadí jedničku
c = c + 1; // přičte jedničku - výsledek je dvojka
c ++;      // přičte jedničku, pouze kratší zápis - výsledek je trojka
e = c ++;  // e = c (v éčku je trojka) c ++ (v céčku bude čtyřka)
d = c;     // do déčka se přiřadí čtyřka
e = ++ c;  // c ++ (v céčku bude pětka), do éčka se přiřadí pětka
 
 

Binární operátory pracující se dvěma členy :

Význam                         zápis 
 
 Sčítání                          +
 Odčítání                         -
 Násobení                        *
 Dělení                          /
 Celočís. Dělení                  /
 Dělení modulo(zbytek po dělení) %

Zda bude operace reálná, nebo celočíselná určují použité typy operandů (dělitel/dělenec) int / int - celočíselné dělení (výsledek se zaokrouhlí dolů)

 
float / float - reálné dělení
float / int   - reálné dělení
int / float   - reálné dělení 
 
 

Místo přiřazení :

l-hodnota = l-hodnota operátor výraz

Se velmi často používá zkrácený zápis:

l-hodnota operátor= výraz

Například takto:

+=, -=, *=, /=, %=, <<=, >>=, &=, ^=, |=

Několik příklad :

int i;
i = 1;  // přiřadí do i jedničku
i += i; // přičte jedničku - výsledek je dva
i *= 5; // vynásobí i pětkou - výsledek je deset
další příklady operátorů

Při vytváření složitějších logických výrazů nebo sekvence příkazů je nezbytně nutné znát pořadí vyhodnocování operátorů. Následující tabulka je uspořádána od operací s nejvyšší prioritou po prioritu nejnižší. Operátory ve stejném řádku mají tutéž prioritu a jejich vyhodnocení se provádí zleva nebo zprava tak jak je uvedeno v tabulce.

Je potřeba upozornit, že operátory +, - na třetím řádku jsou unární(kladná, záporná hodnota), narozdíl od operátorů +, - v pátém řádku, které jsou binární(sčítání, odečítání).

Operátor                                pořadí vyhodnocování 
 
()  []  ->  .                                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 

Při jakékoliv pochybnosti je vhodným řešením použití závorek.

 

Ternární operátor je specialita jazyka C, je to jednoduchá rozhodovací struktura, kterou lze použít místo podmínky if(podminka). Má jednoduché použití a syntaxe vypadá takto:

(podminka)? pravda: nepravda;

Znamená totéž jako:

if(podminka)
  pravda;
else
  nepravda;

Příklad použití:

int i, j = 1, k = 2;
 
i = (j > k)? j : k;
 

Podle výsledku podmínky uvedené v závorce dojde k přiřazení odpovídající hodnoty do proměnné i. V našem příkladu je to hodnota k.

příklad
 

Závěr

Tento kurz poskytuje čtenáři základní znalosti programovacích technik jazyka C a upozorní ho na možné nástrahy a řešení problémů. Při jeho tvorbě jsem vycházel z vlastních zkušeností při svých začátcích s jazykem C. Práce proto obsahuje upozornění na některé časté chyby, kterých se začátečníci obvykle dopouštějí, a způsob jejich řešení.

Většina příkladů je napsána tak, aby byly co nejjednodušší na pochopení vysvětlovaného jevu. Zdrojové texty ukázkových programů byly napsány a vyladěny v prostředích Borland C.


Literatura

[1] Herout P. : Učebnice jazyka C. České Budějovice, Kopp, 2001

[2] Kvoch M. : Programování v Turbo Pascalu 7.0. České Budějovice, Kopp, 1993

[3] Nováček V. : diplomová práce-Programovací technika v jazyce C. Plzeň, ZČU, 2002

[4] Šaloun P. : Programování v jazyce C, Web, 1996

[5] http://www.fsid.cvut.cz/ascii/cz/U201/skrc.html