Úvod do programovania v jazyku C

Doc. Ing. Pavel Horovčák, CSc., Prof. RNDr. Igor Podlubný, CSc.

Táto on-line príručka vznikla v rámci riešenia výskumného projektu KEGA č. 112/97 "uniWWWerzita"

  1. Úvod
  2. Preprocesor jazyka C
  3. Premenné
  4. Lexikálne prvky jazyka
  5. Riadiace štruktúry
  6. Štandardný vstup a výstup
  7. Reťazce
  8. Súbory
  9. Smerníky
  10. Datové štruktúry
  11. Triedenie
  12. Terminálové funkcie (QNX)
  13. Doporučená literatúra

9 Smerníky

Charakteristickým rysom jazyka C je možnosť komplexného využívania smerníkov (pointrov) a smerníkovej aritmetiky. S použitím smerníkov sa stretávame pri spracovaní

Správne pochopenie a používanie smerníkov je na jednej strane silnou stránkou a prednosťou jazyka C, kým na druhej strane ich nesprávne použitie sa mení v rukách užívateľa na nebezpečnú zbraň, čo môže v lepšom prípade mať za následok prístup k "nesprávnyn" údajom, v horšom prípade i haváriu úlohy. "Voľnosť" užívateľa pri práci so smerníkmi je podmienená jeho vedomosťami a disciplínou, pretože systém niektoré okolnosti nekontroluje (ako ukážeme v  ďalšom na konkrétnom príklade).

Smerník je deklarovaný pomocou operátora * (* označuje operátor dereferencie, resp. indirekcie, ktorý sprístupňuje obsah uložený na danej adrese) v tvare

    typ *meno;
kde     typ   - ľubovoľný typ jazyka C
        meno - identifikátor premennej typu smerník

Smerník teda obsahuje dve základné informácie o objekte, na ktorý ukazuje:

Príklad p9.1 ilustruje použitie smerníka.

/*    p9_1.c ilustracia pouzitia smernika (pointra)   */
main()
{
int index,*pt1,*pt2;

  index = 39;    /* any numerical value */
  pt1 = &index;    /* the address of index */
  pt2 = pt1;
  printf("1. hodnota %d %d %d\n",index,*pt1,*pt2);
  *pt1 = 13;      /* this changes the value of index */
  printf("2. hodnota %d %d %d\n",index,*pt1,*pt2);
}

9.1 Smerník a premenná

V príklade

     int i, *ip;
      i=3;
      ip=&i;

je i premenná typu int, ip je premenná typu smerník na int. Adresa premennej i je uložená do premennej ip, ktorá v dôsledku toho ukazuje na hodnotu 3, uloženú v i. Adresa i nesúvisí s hodnotou 3, ako to ukazuje príkaz

   printf("Hodnota i = %d  Adresa i = %x\n",*ip,ip);

výsledkom ktorého je

Hodnota i = 3  Adresa i = fff4   (hexa)

Smerník môže byť inicializovaný už pri deklarácii, v našom prípade

      int i=3, *ip=&i;

Majme

      int a,b,*c;

Vzájomnú súvislosť operátorov * a & ilustrujú následujúce príkazy

     c=&a;    /* c obsahuje adresu a */
      b=*c; /* b obsahuje obsah adresy, uloženej v c */

čo je to isté ako

   b=*&a;

a teda

   b=a;

Smerník môže mať priradenú i konštantnú hodnotu v tvare c = NULL; alebo c = (data_type *) MEM_LOC;

V prvom prípade sa využíva skutočnosť, že smerník nemôže mať platnú hodnotu 0. Priradením symbolickej konštanty NULL (ktorá obsahuje 0) sa indikuje nejaká chybová podmienka. Predpokladajme napríklad, že funkcia má vrátiť smerník na polohu podreťazca v rámci daného reťazca. Ak sa takýto podreťazec v danom reťazci nenachádza, smerník môže na indikáciu tejto okolnosti vrátiť NULL.

V druhom prípade ide o priradenie absolútnej adresy s využitím typovej konverzie na typ, aký má definovaný smerník. Majme napr.

   #define MEM_LOC B000

(začiatok videopamäte na PC, decimálne 720896L) a chceme inicializovať smerník typu char:

      char *c;
      c = (char *) MEM_LOC;
resp. stručnejšie

      char *c = (char *) MEM_LOC;

Smerník však nemôže ukazovať na konštanty, výrazy, resp. registrové premenné, t.j. na objekty, ktoré nie sú prístupné cez adresy pamäte. Avšak aj formálne správne použitie smerníka môže viesť k chybnému výsledku, ako môžeme vidieť v príklade

     float x,y;
      char *c;
      c=&x;
      y=*c;

Uvedená postupnosť nezabezpečí priradenie x do y v dôsledku rôznych typov (a teda aj dĺžok) x,y (float) a c (char). Pri presune dochádza k zmene rozsahu presúvaných hodnôt, čo ale systém nestráži, ale musí si to zabezpečiť programátor.

Smerníková aritmetika disponuje 4 aritmetickými operátormi +, -, ++, --. Ak p je smerník na daný typ, potom výraz p + 1 ukazuje na adresu bezprostredne následujúcu po p (vzdialenú práve o dĺžku typu p). Pri aritmetických operáciách so smerníkmi sa zohľadňuje dĺžka typu, na ktorý smerník ukazuje. Rozdiel medzi vzdialenosťami objektov v pamäti a ich skutočnou vzdialenosťou v bytoch ilustruje práklad 8.2. Majme napr.

   float *fp;

pričom nech adresa fp je napr. 65524 a jeho dĺžka 4 byte. Potom výraz p + 1 ukazuje na následujúcu premennú typu float, t.j. na adresu 65528.

Smerníky je možné odčítať, nie však sčítať. Je však možné pripočítať i odpočítať celočíselnú konštantu, napr.:

   fp + i, resp. fp - i
/*   p9_2.c   smernikova aritmetika 1   */

main()
{
  char c,*cp1,*cp2;
  int i,*ip1,*ip2;

  cp1=&c;
  cp2=cp1+1;
  ip1=&i;
  ip2=ip1+2;
  printf("ip1 ip2: %x %x\n",ip1,ip2);
  printf("Vzdialenost objektov %d %d\n",cp2-cp1,ip2-ip1);
  printf("Vzdialenost v byte  %d %d\n",
   (int)cp2-(int)cp1,(int)ip2-(int)ip1);
}

9.2 Smerník a pole

Pole je množina prvkov rovnakého typu, ktorá je označená spoločným názvom. Pole je uložené v operačnej pamäti spojite, pričom prvý prvok (index 0) má najnižšiu a posledný prvok najvyššiu adresu. Medzi poľami a smerníkmi existuje úzka súvislosť. Práca s poľom a jeho prvkami formou používania názvu poľa a indexov je jednoduchšia, názornejšia a možno povedať apriori akceptovateľná. Používanie smerníkov vedie k rýchlejšiemu behu programu, umožňuje dynamickú alokáciu pamäte a môže redukovať celkové požiadavky na pamäť.

Meno poľa je v skutočnosti symbolická konštanta, ktorej hodnota je smerník na umiestneni prvého prvku poľa. Teda pre pole data[] identifikátor data znamená to isté ako &data[0], data + i to isté ako &data[i], čo môžeme zapísať v tvare

   data + i == &data[i]

Aplikáciou operátora indirekcie * na obe strany výrazu dostaneme

   *(data+i) == data[i]

Ak sa i zvyšuje o 1, referencované miesto pamäte sa zvyšuje o počet bytov (dĺžku), daný veľkosťou daného typu.

Jazyk C netestuje hranice polí, čo umožňuje zápis i čítanie mimo rozsahu poľa, pravda, so všetkými z toho vyplývajúcimi dôsledkami. Za kontrolu dodržania rozsahu poľa je preto zodpovedný programátor.

Použitie smerníka a poľa si ilustrujeme časťou programu

     int *p, data[10];
      p=&data[0];

potom *p = data[0] a my môžeme namiesto data[0] v ľubovoľnom výraze používať *p, resp. pre prvok data[i] *(p + i), lebo

       p + i  == &data[i]
     *(p + i) == data[i]

Z uvedeného by sa mohlo zdať, že identifikátory data a p sú ekvivalentné. Je tu však jeden podstatný rozdiel, ktorý spočíva v tom, že data je symbolická smerníková konątanta, zatiaľ čo p je smerníková premenná. Preto operácie ako p++, p = data sú prípustné, zatiaľ čo data++, resp. data = p nie sú povolené. Majme napr.

     float array[100];
      fp = &array[0];

potom fp++ ukazuje na array[1]. Podobne fp = fp + 10 bude ukazovať na array[10]. Predpokladajme, že

      fp1 = &array[100];

Potom fp-- ukazuje na array[99], resp. fp1 = fp1 - 10 alebo fp1 -= 10 bude ukazovať na array[90].

Na smerníky (ak sa vzťahujú na prvky toho istého poľa) je možné aplikovať aj 4 relačné operátory (<, <=, >, >= ) a testovať ich rovnosť, resp. nerovnosť ( ==, != ). Použitie týchto operátorov ilustruje následujúci príklad pre nájdenie najväčšieho prvku v poli data.

/*   p9_3.c   smernikova aritmetika 2 najst maximalny prvok pola   */
#include <STDIO.H>

int max(int *p,int n);
main()
{
  int i,data[]={1,0,3,4,7,9,6,8,5,2};

  i=max(data,10);
  printf("Index  najvacsieho prvku pola 'data': %d\n",i);
  printf("Hodnota najvacsieho prvku pola 'data': %d\n",data[i]);
}

int max(int *p,int n)
{
  int *scan=p, *loc=p; /* *scan - pracovny smernik,
           *loc - maximum */
  while(scan-p < n){
   if(*scan > *loc)
      loc=scan;
   scan++;
   }
  return(loc-p);
}

Funkcia max(p,n) dostane smerník na pole p a jeho dĺžku n. Nájdenie najväčšieho prvku je realizované porovnávaním hodnôt poľa s hodnotou najväčšieho prvku (na začiatku je to hodnota 0 - prvku). Keď sa nájde väčšia hodnota, jej index sa uloží do smerníka loc. Na začiatku hľadania obidva smerníky scan i loc ukazujú na začiatok poľa, potom smerník scan postupne ukazuje na ďalšie prvky poľa, až kým scan - p = n, kedy sa príkaz while ukončí. V každom kroku prehľadávania sa porovnáva hodnota prvku, na ktorý ukazuje scan, s priebežným maximom, na ktoré ukazuje loc. Ak nastane *scan > *loc, loc sa nastaví na scan,. Takto po skončení cyklu while bude loc ukazovať na najväčší prvok poľa data. Poloha, resp. index najväčšieho prvku je vrátená ako posun od začiatku poľa v tvare loc - p. Následujúci obrázok ilustruje pohyb smerníkov po poli.

Je dôležité uvedomiť si, že deklarácia int data[100]; deklaruje pole a alokuje miesto pre jeho prvky, zatiaľ čo deklarácia int *p; deklaruje premennú typu smerník na int a rezervuje miesto iba pre túto premennú, nie pre prvky poľa. V takom prípade je potrebné priradiť

   p = data;

a potom môžeme pracovať s prvkami poľa jedným z nasledujúcich spôsobov

   data[i], *(data+i), p[i], *(p+i).

Ako príklad uvedieme časť programu pre súčet prvkov poľa:

      #define N 40
      int data[N],i,sum=0;
      for (i=0;i<N;) PRE < sum+="data[i++];">

Posledný príkaz môže mať aj tvar

      sum += *(data + i++);

resp. v prevedení so smerníkom

   int data[N], *p=data, *q=data+N, sum=0;
      for(;p<Q;++P) PRE < +="*p;" sum>

alebo v ešte kompaktnejšom tvare

   for (;p < q;) sum += *p++;

pričom *p++ znamená pripočítať hodnotu, na ktorú ukazuje *p a potom zvýšiť p tak, aby ukazoval na ďalšiu hodnotu. Všimnime si, že nemôžeme použiť

      sum += *data++;

9.3 Smerník a reťazec

Reťazec je jednorozmerné pole znakov, t.j. prvkov typu char, ktoré je deklarované v tvare

      char s[N];

Konvencia jazyka C určuje, že reťazec je štandardne ukončený binárnou nulou - znakom '\0', čím sa reťazec líši od ostatných polí a preto je uvádzaný ako samostatný typ. Konštanty typu reťazec zapisujeme v tvare "abc". Dĺžka tohoto reťazca je 4 (počíta sa aj miesto pre posledný znak binárna nula). Hodnotou konštanty typu reťazec je adresa v pamäti, na ktorej je reťazec uložený. Porovnajme príkazy printf ("%s","abc"); a printf ("%x", "abc"); Výstupom prvého príkazu budú všetky znaky reťazca (bez koncového '\0'), t.j. abc. Výstupom druhého príkazu bude adresa uloženia reťazca v pamäti v hexa tvare, napr. d3. Pretože sa jedná o smerníkovú konštantu, sú povolené rôzne konštrukcie, napr. "abc"+1:

     printf ("%s","abc"+1);
      printf ("%x","abc"+1);

Prvý príkaz vytlačí reťazec, ktorý začína na adrese o 1 väčšej ako predtým, t.j. výstupom bude bc. Druhý príkaz vytlačí adresu o 1 väčšiu, ako je adresa "abc", napr. d4.

Všimnime si rozdiel medzi medzi konštantou "a" typu reťazec a konštantou 'a' typu char. Konštanta "a" je reťazec, musí byť preto zakončená znakom '\0' a je to preto pole 2 znakov (má dĺžku 2).

Externé a statické premenné typu reťazec môžu byť inicializované konštantou typu reťazec, napr.

   char s[] = "Mama";
(dĺžka poľa sa dopočíta automaticky), čo je ekvivalentné príkazu

      char s[] = {'M','a','m','a','\0'};
Všimnite si rozdiel

      char *ps = "Mama";

kde ps je smerník na znak (char) s počiatočnou hodnotou adresy reťazca "Mama".

/*   p9_4.c   smernik a retazec   */
main()
{
char strg[40],*there,one,two;
int *pt,list[100],index;

  strcpy(strg,"To je retazec znakov (character string).");

  one = strg[0];     /* one and two are identical */
  two = *strg;
  printf("Vystup 1: %c %c\n",one,two);

  one = strg[8];    /* one and two are indentical */
  two = *(strg+8);
  printf("Vystup 2: %c %c\n",one,two);
  there = strg+10;  /* strg+10 is identical to strg[10] */
  printf("Vystup 3: %c\n",strg[10]);
  printf("Vystup 4: %c\n",*there);

  for (index = 0;index < 100;index++)
   list[index] = index + 100;
  pt = list + 27;
  printf("Vystup 5: %d\n",list[27]);
  printf("Vystup 6: %d\n",*pt);
}
Príklad zápisu funkcie pre určenie dĺžky reťazca (viď aj kap. 6.1):

      unsigned int strlen(register char *s)
      {
         register unsigned int n=0;
         while (*s++) ++n;
         return(n);
      }

Majme deklaráciu

     char c, *p, s[10];
      s[0]='a'; s[1]='b'; s[2]='c'; s[3]='\0';
      printf("%s %c %s",s,*(s+1),s+1);

Výstupom posledného príkazu bude abc b bc, lebo *(s+1) je to isté ako s[1], a s++ znamená začať tlač od s[1] po koncový znak.

Priradenie po znakoch je účelné nahradiť zápisom

strcpy(s,"abc");

Spracujme tento reťazec príkazmi

      for(p=s; *p; ++*p++);
      printf("%s\n",s);

"Čudne vyzerajúci" výraz ++*p++ je povolený výraz a znamená vlastne postupnosť dvoch príkazov

     *p = *p +1;
      p = p + 1;

takže výstupom bude reťazec bcd (najprv sa zvýši o 1 obsah, na ktorý ukazuje smerník, t.j. a sa zmení na b, potom sa zvýši o 1 hodnota smerníka).

Príklad p9_5 ukazuje použitie funkcie pre zrušenie všetkých výskytov znaku c v reťazci s so súčasným určením počtu týchto výskytov.

/*   p9_5.c   smernik a retazec  rusenie znaku   */
#include <STDIO.H>
#include <STRING.H>

int rusznak(char *s, char c);
main()
{
char str[60],*ps,c='e';

  strcpy(str,"Priklad pre zrusenie vyskytov znakov v retazci.");

  printf("Text pred zrusenim znakov '%c':\n%s\n",c,str);
  printf("Pocet znakov '%c' v texte: %d\n",c,rusznak(str,c));
  printf("Text po zruseni znakov '%c':\n%s\n",c,str);
}

int rusznak(char *s, char c)
{
  char *p=s;
  int count=0;
  while(*s){
   if(*s!=c) *p++=*s;
   else count++;
   s++;
   }
  *p='\0';
  return(count);
}

Ako je vidieť, funkcia rusznak pracuje len so smerníkmi s a p. Smerník s sa pohybuje nad starým obsahom reťazca, smerník p nad novým. Smerník p sa zvyšuje vtedy, keď sa znak starého poľa nerovná znaku c. V opačnom prípade sa zvyšuje hodnota počítadla výskytu znakov v reťazci count. Nakoniec je reťazec ukončený znakom '\0'. Deklarácia (a tým priestor) pre samotný reťazec musí byť uvedená tam, kde dochádza k volaniu funkcie rusznak, v našom prípade v hlavnom programe. Upozorňujeme na potrebu dostatočnej rezervy v tých prípadoch, kedy dochádza pôsobením funkcií (tak štandardných ako aj vlastných) k predĺženiu reťazca.

9.4 Smerník a parametre funkcie

Pri štandardnom volaní parametrov funkcie hodnotou funkcia nemení hodnoty parametrov a vracia len jednu hodnotu (špecifikovanú pri deklarácii funkcie). Ak použijeme parametre typu smerník, hovoríme o parametroch volaných odkazom. Ich použitím sa odovzdáva funkcii adresa skutočného parametra, čo umožňuje vo vnútri funkcie modifikovať príslušný počet parametrov prostredia, odkiaľ bola funkcia vyvolaná (nadradená funkcia, resp. hlavný program). Príklad p9_6 názorne ilustruje takúto modifikáciu.

/*   p9_6.c parametre funkcie  hodnotou a odkazom   */
void fixup(int nuts,int *fruit);
main()
{
  int hruska,mrkva;

  hruska = 100;
  mrkva = 101;
  printf("Pociatocne hodnoty %d %d\n",hruska,mrkva);
  fixup(hruska,&mrkva);
  printf("Hodnoty po prepocte %d %d\n",hruska,mrkva);
}

void fixup(int nuts,int *fruit)
{
  printf("Vstup %d %d\n",nuts,*fruit);
  nuts = 135;
  *fruit = 172;
  printf("Vystup %d %d\n",nuts,*fruit);
}

9.5 Smerník a dynamické prideľovanie pamäte

Každý program pracuje so svojimi dátami a dátovými štruktúrami, ktoré môžu byť statické aj dynamické. Statické existujú po celý čas behu programu, v ktorom sú deklarované príslušnými deklaráciami. Miesto pre nich vyhradí kompilátor už počas prekladu. Dynamické štruktúry vznikajú a zanikajú dynamicky počas činnosti programu.

Pre dynamické prideľovanie, resp. uvoľňovanie pamäte je v rôznych implementáciách jazyka C k dispozícii rôzny počet štandardných funkcií. Na tomto mieste preto uvedieme iba dve základné funkcie - malloc() a free().

Funkcia malloc slúži pre pridelenie (alokáciu) pamäte. Má jeden argument typu unsigned int, ktorý udáva požadovaný rozsah dynamicky pridelenej pamäte. Vracia smerník (adresu) pamäťovej oblasti požadovanej dĺžky. Táto oblasť je pre úlohu rezervovaná až do jej uvoľnenia. Na uvoľnenie slúži funkcia free, ktorá má jeden argument typu smerník na rezervovanú oblasť pamäte. Príklad p9_7 ilustruje dynamické pridelenie pamäte reťazcu a jej uvoľnenie.

/*   p9_7.c   dynamicke pridelovanie pamate   */
#include <STDIO.H>
#include <ALLOC.H>
#include <STRING.H>

main()
{
char str[200];
register char *t;

  strcpy(str,"Ukazka dynamickeho pridelovania pamate.");
  printf("Retazec str: %s\n",str);
  t = (char *)malloc(strlen(str)+1);
  strcpy(t,str);
  printf("Retazec t:  %s\n",t);
  free(t);
}

Do reťazca s priradíme text, potom alokujeme dynamickú pamäť pre tento reťazec (veľkosť pamäte je daná dĺžkou reťazca, t.j. strlen(s) +1). Do takto priradenej pamäte tento reťazec skopírujeme - t. Po vytlačení obsahu dynamicky pridelenej pamäte uvoľníme pamäť príkazom free(t).

9.6 Smerník a funkcie

Použitie smerníka na funkciu umožňuje v jazyku C prenášať identifikátor funkcie ako parameter. Deklarácia smerníka na funkciu má tvar

      typ (*meno)();

kde typ je typ hodnoty, ktorú funkcia vracia

meno je identifikátor typu smerník na funkciu

Všimnime si rozdiel medzi deklaráciami

      int *f1();
      int (*f2)();

Prvá deklarácia určuje, že f1 je funkcia, ktorá vracia hodnotu typu smerník na int. Druhá definuje f2 ako smerník na funkciu, ktorá vracia hodnotu typu int. Posledná dvojica zátvoriek () v oboch prípadoch udáva, že sa jedná o funkciu (abc je premenná, abc() označuje funkciu).

Použitie smerníka na funkciu ukážeme na troch príkladoch. Prvý p9_8 ukazuje výpočet hodnôt zadanej funkcie pomocou funkcie tabel. V príklade sú tabelizované dve funkcie f1(x) = 1 + x; a f2(x) = x2;

/*   p9_8.c   smernik a funkcia  tabelacia funkcie   */
#include <STDIO.H>

void tabel(float a, float b, float krok, float (*f)(float c));
float f1(float x);
float f2(float x);

main()
{
  float z=1,a=1,b=4;
  printf("Tabelacia funkcie y = 1 + x\n");
  printf("  x    y\n");
  tabel(b,a,z,f1);
  printf("\nTabelacia funkcie y = x*x\n");
  printf("  x    y\n");
  tabel(a,b,z,f2);
}

void tabel(float a, float b, float krok, float (*f)(float c))
{
  float p;

  if(b<a){p=b;b=a;a=p;};
  for(p=a;p<b;p+=krok)
      printf("%f %f\n", p, f(p));
}

float f1(float x)
{
   return(1.+x);
}

float f2(float x)
{
   return(x*x);
}

Tretí príklad p9_10 je prevzatý z [Richta93, str. 117] a zaoberá sa vynechaním komentára zo zdrojového textu programu v jazyku C. Komentár sa začína dvojicou znakov /* a končí dvojicou */. Program sa preto môže nachádzať v jednom zo štyroch možných stavov:

Na tieto stavy sa viažu rôzne činnosti. V stave mimo sa kopírujú vstupné znaky na výstup - funkcia kopíruj. V stave vnútri sa vstupné znaky na výstup nekopírujú - funkcia nekopíruj. Pri prechode zo stavu mimo do stavu zackom sa nebude znak / kopírovať, lebo nevieme, či nebude súčasťou komentára. Pri prechode zo stavu zackom do stavu mimo (po znaku / nenasledoval znak *) treba toto lomítko doplniť - funkcia lomítko. Tabuľka prechodov medzi jednotlivými stavmi a volanie príslušných funkcií v závislosti na vstupnom znaku potom má tvar:

Stavy sú v programe reprezentované vymenovaným typom STAV, tabuľka dvojrozmerným poľom tabulka typu POLOZKA, ktorá obsahuje smerník na funkciu a identifikátor nového (následujúceho) stavu. Logika činnosti programu:

         stav = mimo
      while(nie je koniec vstupu){
         čítaj znak
         preveď znak na stĺpec tabuľky
         vykonaj funkciu podľa tabuľky
         prejdi do nového stavu podľa tabuľky
         }

Príklad je dobrou ukážkou programu, riadeného dátami. Všimnime si pozorne spôsob zápisu vykonania funkcie podľa tabuľky. Na rozdiel od deklarácie smerníka na funkciu v zátvorkách sú uvedené príslušné parametre funkcie.

Všimnime si tiež rozdiel medzi obidvoma príkladmi. V prvom prípade je použitý priamo identifikátor funkcie bez parametrov, v druhom sa užíva smerník na funkciu s uvedením príslušného parametra. Zapamätajte si, že všetky funkcie, ktoré budú referencované pomocou smerníka na funkciu, musia byť rovnakého typu a mať rovnaký počet parametrov rovnakých typov (viď funkcie f1, f2, resp. kopíruj, nekopíruj a lomítko).

/*   p9_10.c   smernik a funkcia 2      spracovanie komentara   */
#include <STDIO.H>

typedef enum {mimo,zackom,vnutri,konkom} STAV;
typedef struct {
   void (*funkcia)();
   STAV novy_stav;
   } POLOZKA;

void kopiruj(char c);
void nekopiruj(char c);
void lomitko(char c);

main()
{
  POLOZKA tab[4][3] = {
   {{nekopiruj,zackom},{kopiruj,mimo},{kopiruj,mimo}},
   {{lomitko,mimo},{nekopiruj,vnutri},{lomitko,mimo}},
   {{nekopiruj,vnutri},{nekopiruj,konkom},{nekopiruj,vnutri}},
   {{nekopiruj,mimo},{nekopiruj,vnutri},{nekopiruj,vnutri}}
   };
  STAV stav;
  char znak;
  int stlpec;

  stav = mimo;
  while((znak=getchar()) != EOF){
/*   prevod znaku na stlpec tab      */
   switch (znak) {
    case '/' :   stlpec = 0;         break;
    case '*' :   stlpec = 1;         break;
    default :   stlpec = 2;         break;
    };

/*   vykonaj funkciu podla tabulky      */
   (*tab[stav][stlpec].funkcia)(znak);

/*    prechod do noveho stavu         */
   stav = tab[stav][stlpec].novy_stav;
   };
  if (stav == zackom) putchar('/');
}

void kopiruj(char c)
{
   putchar(c);
}

void nekopiruj(char c)
{
}

void lomitko(char c)
{
   putchar('/');
   putchar(c);
}

9.7 Smerník na štruktúru

Štruktúra je súhrn jednej alebo viacerých premenných aj rôznych typov (vrátane ďalších štruktúr), ktoré sú združené pod jedným názvom. To umožňuje pracovať so súvisiacimi dátami ako s celkom. Prístup k zložkám štruktúry sa zadáva názvom štruktúry a názvom zložky, oddelených bodkou (.). Majme napr. štruktúry o dátume a študentovi

   struct datum{
         int den;
         int mesiac;
         int rok;
         }

   struct student{
         char meno[20];
         int rodne_cislo;
         struct datum dnar;
         int rocnik;
         float priemer[5];
         } st;

Štruktúra s menovkou datum spája údaje o dni, mesiaci a roku, štruktúra student rôzne údaje o študentovi, pričom dátum narodenia je reprezentovaný štruktúrou dnar typu datum. Prístup k zložke ročník má tvar

      st.rocnik = 2;
ku zložke rok narodenia

      st.dnar.rok = 1973;

Smerníky na štruktúru sa najčastejšie používajú pre prístup k štandardným štruktúram, definovaným v jazyku C. Prístup k zložkám štruktúry student s využitím smerníka na štruktúru ilustrujú následujúce príkazy:

    struct student *pst;
      ...
      (*pst).rocnik = 2;
      (*pst).dnar.rok = 1973;

Okrúhle zátvorky sú nevyhnutné, lebo operátor bodka (.) má vyššiu prioritu ako operátor indirekcie (*). Pretože používanie smerníka pre prístup k štruktúre v jazyku C je značne frekventované, je k dispozícii špeciálny operátor -> (tvorený dvojicou znakov mínus a väčší), pomocou ktorého môžeme predchádzajúce priradenie vyjadriť v zaužívanom tvare

      pst->rocnik = 2;
      pst->dnar.rok = 1973;

Obidva spôsoby zápisu sú ekvivalentné.

/*   p9_11.c   smernik a struktura   */
#include <STDIO.H>

main()
{
  struct datum{
   int den;
   int mesiac;
   int rok;
   };
  struct student{
   char meno[20];
   int rodne_cislo;
   struct datum dnar;
   int rocnik;
   float priemer[5];
   } st;
  struct student *pst=&st;

   st.rocnik = 2;
   st.dnar.rok = 1975;

   printf("1. vypis rocnik: %d rok narodenia: %d\n",
      st.rocnik, st.dnar.rok);
   printf("2. vypis rocnik: %d rok narodenia: %d\n",
      (*pst).rocnik, (*pst).dnar.rok);
   printf("3. vypis rocnik: %d rok narodenia: %d\n",
      pst->rocnik, pst->dnar.rok);

   (*pst).dnar.den = 13;
   pst->dnar.mesiac = 3;
   printf("4. vypis den: %d a mesiac narodenia: %d\n",
      (*pst).dnar.den, st.dnar.mesiac);
}

9.8 Polia smerníkov

Typické použitie polí smerníkov je spojené s triedením rôznych záznamov, polí, štruktúr a pod. Vtedy je výhodné vytvoriť pole smerníkov tak, že každý jeho prvok ukazuje na príslušný objekt (riadok, položka štruktúry) triedenia. Pri triedení môžeme potom ku každému prvku pristupovať prostredníctvom jeho smerníka a prípadné operácie s prvkami nahradiť operáciami s im odpovedajúcimi smerníkmi. Toto riešenie odstraňuje tak zložitú správu pamäte ako aj veľkú réžiu, spojenú s presúvaním celých objektov. Je to podstatne efektívnejší postup.

V databázových systémoch pri ukladaní dát do súborov sa priebežne vytvára pole smerníkov, ktoré sa po utriedení uloží na disk ako tzv. indexový súbor. V praxi takýto súbor neobsahuje adresy, ale úspornejšie indexy [Galan91]. Takýchto indexových súborov môže byť maximálne toľko, koľko položiek štruktúra obsahuje. Indexové súbory môžu byť aj zložitejšie útvary, napr. lineárne zoznamy alebo binárne stromy. Indexové súbory sú vytvárané za účelom zrýchlenia manipulácie a prístupu k jednotlivým údajom.

Triedenie záznamov s využitím polí smerníkov ilustrujú príklady p9_12 a p9_13.

/*   p9_12.c   pole smernikov      usporiadanie   */
#include <STDIO.H>
#include <STRING.H>
#include <STDLIB.H>
#define N 6

main()
{
  struct ex1{
   char m[6];
   int vyska;
   int vaha;
   int vzdial;
   } st[N], *pst[N], *t;
  int i,j;

  randomize();
/*   inicializacia struktury a smernikov   */
  for(i=0;i<N;I++){ * } if(pst[i]- for(j="i+1;j<N;j++)" for(i="0;i<N-1;i++)" vysky podla zotriedenie pst[i]="&st[i];" st[i].vzdial="random(200);" st[i].vaha="70+i;" st[i].vyska="180-i;" sprintf(st[i].m,?Meno%1d?,i);>vyska > pst[j]->vyska){
    t=pst[i];
    pst[i]=pst[j];
    pst[j]=t;
    }
  printf("\nUsporiadanie podla vysky\n");
  for(i=0;i<N;I++) vzdial='%d\n",pst[i]-' vaha="%d" vyska="%d" printf(?%s>m,
    pst[i]->vyska,pst[i]->vaha,pst[i]->vzdial);

/*   zotriedenie podla vahy         */
  for(i=0;i<N-1;I++) if(pst[i]- for(j="i+1;j<N;j++)">vaha > pst[j]->vaha){
    t=pst[i];
    pst[i]=pst[j];
    pst[j]=t;
    }
  printf("\nUsporiadanie podla vahy\n");
  for(i=0;i<N;I++) vzdial='%d\n",pst[i]-' vaha="%d" vyska="%d" printf(?%s>m,
    pst[i]->vyska,pst[i]->vaha,pst[i]->vzdial);

/*   zotriedenie podla vzdialenosti      */
  for(i=0;i<N-1;I++) if(pst[i]- for(j="i+1;j<N;j++)">vzdial > pst[j]->vzdial){
    t=pst[i];
    pst[i]=pst[j];
    pst[j]=t;
    }
  printf("\nUsporiadanie podla vzdialenosti\n");
  for(i=0;i<N;I++) vzdial='%d\n",pst[i]-' vaha="%d" vyska="%d" printf(?%s>m,
    pst[i]->vyska,pst[i]->vaha,pst[i]->vzdial);
}

/*      p9_13.c      pole smernikov - indexy           usporiadanie      */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define N 6

main()
{
   struct ex1{
      char m[6];
      int vyska;
      int vaha;
      int v;
      } st[N], *i1[N], *i2[N], *i3[N], *p;
   int i,j;

/*      inicializacia struktury a smernikov      */
   for(i=0;i<N;i++){
     sprintf(st[i].m,"Meno%1d",i);
     st[i].vyska = 180-i;
     st[i].vaha  = 40+5*i;
     st[i].v     = 3*i % 4;
     i1[i]=&st[i];
     i2[i]=&st[i];
     i3[i]=&st[i];
     }

/*      zotriedenie podla vysky */
   for(i=0;i<N-1;i++)
     for(j=i+1;j<N;j++){
       if(i1[i]->vyska > i1[j]->vyska){
       p=i1[i];
       i1[i]=i1[j];
       i1[j]=p;
       }
/*      zotriedenie podla vahy                  */
       if(i2[i]->vaha > i2[j]->vaha){
       p=i2[i];
       i2[i]=i2[j];
       i2[j]=p;
       }
/*      zotriedenie podla vzdialenosti            */
       if(i3[i]->v > i3[j]->v){
       p=i3[i];
       i3[i]=i3[j];
       i3[j]=p;
       }
    }
     printf("\nZotriedene podla vysky      Netriedene hodnoty:\n");
     for(i=0;i<N;i++)
     printf("%s   %d   %d  %d         %s   %d   %d   %d\n",i1[i]->m,
      i1[i]->vyska,i1[i]->vaha,i1[i]->v,
      st[i].m,st[i].vyska,st[i].vaha,st[i].v);
     printf("\nPodla vahy:\n");
     for(i=0;i<N;i++)
     printf("%s   %d   %d  %d\n",i2[i]->m,
      i2[i]->vyska,i2[i]->vaha,i2[i]->v);
     printf("\nPodla vzdialenosti:\n");
     for(i=0;i<N;i++)
     printf("%s   %d   %d  %d\n",i3[i]->m,
      i3[i]->vyska,i3[i]->vaha,i3[i]->v);
}