Ú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

3 Premenné

3.1 Identifikátory premenných

     Identifikátory označujú jednotlivé objekty jazyka C - konštanty, premenné, funkcie, typy, selektory položiek štruktúrovaných typov, parametre funkcií a pod. Identifikátor je postupnosť písmen a číslic (a niektorých špeciálnych znakov, napr. '_'), ktorá sa začína písmenom, resp znakom '_'. Viacslovný identifikátor nie je prípustný a preto sa do jedného celku zvykne spájať práve uvedeným znakom '_', ktorý zvyšuje čitateľnosť zdrojového textu programu (napr. to_je_premenna). Prípustné sú iba znaky ASCII kódu. Dĺžka identifikátora je daná typom kompilátora, ANSI norma doporučuje maximálne 31 znakov. Jazyk C rozlišuje veľké a malé písmená, preto identifikátory Suma a suma sú rozdielne identifikátory (pozor na to !).
Ako identifikátory nie je možné používať tzv. kľúčové slová jazyka C:

        auto        enum        short
        break       extern      sizeof
        case        float       static
        char        for         struct
        continue    goto        switch
        default     if          typedef
        do          int         union
        double      long        unsigned
        else        register    void
        entry       return      while

     ANSI norma vynecháva z tohoto zoznamu kľúčové slovo "entry" a doplňuje kľúčové slová "const", "signed" a "volatile". V  literatúre sa niekedy doporučuje pre odlíšenie používať pre konštanty a  užívateľom definované typy veľké písmená, pre všetky ostatné objekty malé písmená (premenné, funkcie).

3.2 Deklarácia premenných

Obecný tvar deklarácie premenných v jazyku C má tvar:

  popis typu   deklarované objekty  [=počiatočné hodnoty]

Popis typu môže pozostávať z dvoch častí:

  1. definícia pamäťovej triedy
  2. definícia typu premennej

Pamäťové triedy

Definícia pamäťovej triedy (storage class) určuje požadovaný, resp. doporučený spôsob uloženia premennej. Jazyk C rozlišuje štyri možnosti: extern, auto, register, static.


Trieda extern

Ako sme už spomenuli v úvode, jazyk C má blokovú štruktúru. To znamená, že deklarácia uvedená vo vnútri bloku je lokálna v tomto bloku a zvonku (mimo bloku) nie je prístupná. Premenné deklarované mimo funkcie sú globálne a  prístupné zo všetkých funkcií. Lokálna deklarácia "zatieňuje" globálnu deklaráciu, t.j. má pred ňou prednosť, ako to ilustruje aj nasledujúci príklad:


/*  priklad pr3_1.c prekrývanie (tienenie) deklaráci */

int z = 1;       /* globálna premenná  */
void main(void)
{
  int z =2;      /* lokálna premenná   */

  printf("z=%d\n",z);
}


Program vytlačí hodnotu z=2.

Pre referencovanie globálnych premenných v jednotlivých funkciách či moduloch (modulom rozumieme súbor funkcií a deklarácií globálnych objektov, spravidla súvisiacich) sa využíva pamäťová trieda extern. Použitie tejto triedy v tvare, ktorý umožňuje aj separátnu kompiláciu, ilustruje príklad:


/* priklad pr3_2.c použitie externej deklarácie */

int z;            /* globálna premenná  */

int fncia(void);
void main(void)
{
  extern int z;   /* referencia glob. premennej */

  z=0;
  printf("z=%d\n",fncia());
}

int fncia(void)
{
  extern int z;   /* referencia glob. premennej */

  return(z+1);
}


Program vytlačí hodnotu z=1.


Trieda auto

     Všetky lokálne premenné sú implicitne považované za tzv. automatické premenné, t.j. premenné pamäťovej triedy auto. To znamená, že pamäťovú triedu auto nie je potrebné pri deklarácii premenných vôbec uvádzať. Premenné tejto pamäťovej triedy vznikajú automaticky v okamihu potreby, teda pri vstupe do bloku, v ktorom sú deklarované. Mimo tohoto bloku neexistujú. V okamihu opustenia daného bloku uvedené premenné automaticky zanikajú. Automatické premenné sú realizované prostredníctvom zásobníka (stack) pri aktivácii bloku, pri výstupe z bloku sa zásobník uvoľňuje. Automatické premenné základných typov (char, int, float, double) je možné inicializovať (priradiť počiatočné hodnoty). U iných typov premenných, resp. u základných, ak sme inicializáciu nepredpísali, hodnota premennej v okamihu vzniku nie je definovaná a musí byť programovo ošetrená. Preto príklad:


/* priklad pr3_3.c inicializácia premennej v bloku */

void main(void)
{
  int z;

  {
    int z=1;
  }
  printf("z=%d\n",z);
}


môže vytlačiť ľubovoľnú hodnotu (premenná z je inicializovaná len v rámci vnútorného bloku, v rámci funkcie main jej hodnota nie je definovaná).


Trieda register

Používa sa pri veľmi častom využívaní niektorej automatickej premennej za účelom urýchlenia výpočtu. Použitie tejto triedy umožní priradiť danej premennej určitý register procesora. Ak takýto register nie je k dispozícii, premenná je spracovávaná ako automatická.


Trieda static

     Používa sa v prípadoch, keď potrebujeme uchovať hodnotu premennej aj po opustení bloku. Statická premenná je lokálna v bloku, v ktorom je deklarovaná s tým, že má trvale pridelenú pamäť (nie je realizovaná pomocou zásobníka, ale ako pamäťová bunka). Inicializáciu statickej premennej vykonáva prekladač počas kompilácie zdrojového textu programu. Program teda začne pracovať s nastavenou počiatočnou hodnotou a k  inicializácii sa už viacej nevracia. Ak inicializáciu nepredpíšeme, vykoná prekladač implicitné vynulovanie premenných (premenné typu char znakom '\0', premenné typu int hodnotou 0, premenné typu float hodnotou 0.0 a premenné typu double hodnotou 0.0L).
Pretože statické premenné zaberajú trvalé miesto v pamäti, je potrebné ich použitie dobre zvážiť a podľa možnosti obmedziť na minimum.

3.3 Typy premenných

Definícia typu premenných môže obsahovať:

  1. základný preddefinovaný typ (char, int, float, double)
  2. odvodený typ (smerník, pole, struct, union)
  3. vymenovaný typ (enum)
  4. užívateľom definovaný typ (typedef)
  5. prázdny typ (void)

     Základné typy môžu byť modifikované klasifikátorom (short, long, unsigned), ktorý vyjadruje použitú podmnožinu alebo rozšírenie daného typu.


Základné typy
  -typ int:

     Používa sa pre špecifikáciu celých čísel. Oborom hodnôt typu int je súvislá množina celých číse v intervale -2**(N-2) až 2**(N-2), kde N je počet bitov slova pre celé číslo. Obvykle sa pre celé čísla využíva 16 bitové slovo, čomu zodpovedá rozsah -32768 až 32767. Rozsah pre daný kompilátor je uvedený v tvare symbolických konštánt INT_MIN, INT_MAX (hlavička limits.h).
     Pre zadávanie celých čísel v programe je možné využiť dekadickú, oktálovú i  hexadecimálnu sústavu. Dekadické číslo je zadané v bežnom tvare, napríklad:

  int dek=1500;

Oktálové čísla sa začínajú číslicou 0 (môže nasledovať 0 až 7), napríklad:

  int okt=010;      (dekadicky 8)
  int okt1=0377;    (dekadicky 255)

Hexadecimálne čísla sa začínajú dvojicou 0x, resp. 0X (môže nasledovať 0 až F), napríklad:

  int hex=0xA;      (dekadicky 10)

Typy short int, long int majú rovnaké vlastnosti ako základný typ int. Môžu mať iný rozsah oboru hodnôt, ktorý je uvedený v tvare symbolických konštánt SHRT_MIN, SHRT_MAX pre typ short a LONG_MIN, LONG_MAX pre typ long. Použitím typu unsigned int sa presúva "int" rozsah do kladných hodnôt 0 až 65535.


  -typ char:

Používa sa pre špecifikáciu znakových premenných. Obvyklá dĺžka typu je 1 byt (8 bitov), z čoho vyplýva dekadický rozsah 0 až 255. Umožňuje pracovať so všetkými znakmi ASCII tabuľky. Zadávanie znakových konštánt sa zapisuje pomocou apostrofov, napr. char z='a'; Existuje niekoľko tzv. zmenových postupností s týmto významom a spôsobom zápisu:

        \a      alert            zvuk (beep)
        \b      backspace        posuv o 1 znak späť
        \f      formfeed         nová strana
        \n      newline          nový riadok
        \r      carriage return  návrat vozíka - nový riadok
        \t      hor. tabelator   tabelátor
        \v      vert. tabelator  tabelátor zvislý
        \\      backslash        zobrazí opačné lomítko
        \'      single quote     zobrazí apostrof
        \"      double quote     zobrazí uvodzovky
        \?      question mark    zobrazí otáznik
        \ddd                     bitový obrazec

Zmenová postupnosť \ddd pozostáva z opačného lomítka, za ktorým následuje jedna, dve alebo tri osmičkové číslice, udávajúce hodnotu požadovaného znaku. Špeciálnym prípadom tejto konštrukcie je \0 (bez ďalších číslic), ktorá reprezentuje prázdny znak (používa sa na označenie ukončenia reťazca - poľa znakov). Ak za opačným lomítkom nenasleduje jeden z uvedených znakov, opačné lomítko sa ignoruje.


  -typ float a  double:

Používa sa pre špecifikáciu reálnych čísel v pohyblivej rádovej čiarke. Konštanty sa zapisujú obvyklým spôsobom s desatinnou bodkou, resp. v semilogaritmickom tvare (mantisa plus exponent), napráklad

  float f=12.76, ff=12.76e-3;

Odvodené typy
  -typ smerník:

Používa sa pre špecifikáciu adresy dátového objektu. Formálne sa označuje znakom * (operátor dereferencie). Smerník obsahuje vždy dve základné informácie o objekte - jeho adresu a jeho dĺžku (vyplýva z typu objektu). V  zápise int *si; označuje si smerník na typ int, *si znamená hodnotu objektu, na ktorú smerník si ukazuje.


  -typ pole:

Používa sa pre špecifikáciu homogénnej dátovej štruktúry daného typu. Pole je v jazyku C jednorozmerné, indexované od 0. Viacrozmerné pole vzniká ako pole jednorozmerných polí , napr.

  float p1[3],p2[2][4];

Názov poľa predstavuje smerník na jeho začiatok, takže zápis *(p1+2) má rovnaký význam ako tradičné p1[2].


  -typ struct:

Používa sa pre špecifikáciu nehomogénnej dátovej štruktúry (obdoba dátového typu záznam z jazyka Pascal), napr. zápis

  struct clen
  {
    char  meno[25];
    int   vek;
    float vaha;
  }

špecifikuje typ štruktúry clen (údaj o mene, veku a hmotnosti). Deklarácia premenných typu struct má tvar

   struct clen pr1, pr2[5], *pr3;

Táto deklarácia vytvára premennú pr1 typu clen, pole štruktúr pr2 s piatimi prvkami a smerník pr3 na štruktúru clen. Štruktúra môže obsahovať aj smerník sama na seba, čo sa využíva pri vytváraní zoznamov (viď kap. 9.1), napr.

  struct clen
  {
    char        meno[25];
    int         vek;
    float       vaha;
    struct clen *dalsi;
  }

  -typ union:

Vzniká analogicky ako štruktúra s tým, že namiesto kľúčového slova struct sa použije kľúčové slovo union. Rozdiel medzi nimi spočíva v spôsobe uloženia položiek struct a union v pamäti. Kým položky objektu struct sú uložené všetky za sebou v pamäti, objektu union (zjednotenie) je pridelená iba pamäť, potrebná pre najdlhšiu položku. Jednotlivé položky unionu sa prekrývajú, preto v unione môže byť v jednom okamihu iba jedna položka. Použitie unionu je pomerne zriedkavé. Syntax ako aj prístup k položkám unionu je analogický ako v prípade štruktúry

  typedef union
  {
    char  meno[25];
    int   vek;
    float vaha;
  } typun;

  typun a, *sa = &a;

  a.vek = 15;
  sa->vaha = 75.5;

Praktické použitie unionu ilustruje príklad pr3_4.c na ukážke výpočtu objemov geometrických telies.


/* pr3_4.c pouzitie unie a pristup k jej zlozkam */
/* program bol kompilovany pod Borland C v3.0*/

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

typedef enum {NIC, GULA, VALEC, KOCKA, KVADER} T;

struct kvader{
   double dlzka;
   double sirka;
   double vyska;
   };
struct valec{
   double polomer;
   double vyska;
   };
union teleso{
   double rozmer;
   struct kvader a;
   struct valec b;
   };
union teleso t, *pt=&t;
T typ;
double objem;
char c;

void vstup(void);
void vypocet(void);

void main(void)
{
  do
  {
    clrscr();
    puts("Vypocet objemu telesa");
    vstup();
    vypocet();
    printf("Pokracovanie (A/N) :");
    c = toupper(getche());
  } while(c == 'A');
  return 0;
}

void vstup(void)
{
  printf("gula...0 kocka...1 kvader...2 valec...3 ");
  printf("\nteleso: ");
  c=getche();
  switch (c)
  {
  case '0' :
      printf("\npolomer gule : ");
      scanf("%lf",&(t.rozmer));
      typ = GULA;
      break;
  case '1' :
      printf("\nstrana kocky : ");
      scanf("%lf",&(pt->rozmer));
      typ = KOCKA;
      break;
  case '2' :
      printf("\ndlzka kvadra : ");
      scanf("%lf",&(t.a.dlzka));
      printf("sirka kvadra : ");
      scanf("%lf",&(t.a.sirka));
      printf("vyska kvadra : ");
      scanf("%lf",&(t.a.vyska));
      typ = KVADER;
      break;
  case '3' :
      printf("\npolomer valca : ");
      scanf("%lf",&(pt->b.polomer));
      printf("2vyska valca : ");
      scanf("%lf",&(t.b.vyska));
      typ = VALEC;
      break;
  default :
      printf(" Nezname teleso \a\n");
      typ = NIC;
      break;
  }
}

void vypocet(void)
{
  if (typ == GULA)
  {
    objem = 4./3*M_PI*pow(t.rozmer,3);
    printf("Objem gule: %10.2f \n",objem);
  }
  else if (typ == KOCKA)
  {
    objem = pow(t.rozmer,3);
    printf("Objem kocky: %10.2f \n",objem);
  }
  else if (typ == KVADER)
  {
    objem = t.a.dlzka*t.a.sirka*t.a.vyska;
    printf("Objem kvadra: %10.2f \n",objem);
  }
  else if (typ == VALEC)
  {
    objem = M_PI*pow(t.b.polomer,2)*t.b.vyska;
    printf("Objem valca: %10.2f \n",objem);
  }
}



  -typ enum (vymenovaný typ):

Používa sa pre špecifikáciu užívateľom vymenovaných hodnôt (obdobne ako v Pascale), ktoré sú reprezentované podmnožinou oboru hodnôt typu int. Jednotlivým položkám vymenovaného typu sú priradzované celé čísla od nuly a  zväčšujúce sa o jedna, pokiaľ nie je predpísané inak, napr.

  enum ZNAK {pismeno, cislica, ine};
  enum ZNAK p1;

bude mať obor hodnôt [0,1,2]. S položkami vymenovanéh typu pracujeme pri priradzovaní i pri inicializácii, napr.

   enum FARBA {biela, modra, cervena} f = biela;
   ...
   f = modra;

  -typ typedef (Užívateľom definovaný typ):

Používa sa pre špecifikáciu užívateľom vytvorených typov, ktoré sa v ďalšom používajú presne tak, ako typy jazyka C. Názvy vytvorených typov sa píšu spravidla (pre odlíšenie) veľkými písmenami, napr.

   typedef char *STRING;

   STRING s;

Premenná s je pokladaná za premennú typu char *.


  -typ void (Prázdny typ):

Používa sa pre špecifikáciu skutočnosti, že prenášaný typ je prázdny, najčastejšie v spojitosti s funkciou, napr. zápis

   void f(void)

označuje funkciu f, ktorá nemá žiadne parametre ani nevracia žiadnu hodnotu.
Môže sa však použiť aj pri smerníkoch - smerník na nezámy (všeobecný typ).


Typové modifikátory

Ľubovoľná premenná určitej pamäťovej triedy a dátového typu môže byť modifikovaná tzv. typovým modifikátorom, ktorým môže byť const a  volatile.


  -modifikátor const:

Udáva, že daná premenná nesmie po jej inicializácii meniť svoju hodnotu. Nie je možné využiť ju pri definovaní hraníc poľa (ako symbolickú konštantu), ale často sa využíva pri definícii formálnych parametrov funkcií, kedy takto označený parameter je spracovaný iba ako vstupný parameter (jeho hodnota sa v rámci funkcie nezmení).


  -modifikátor volatile:

Udáva, že daná premenná môže byť modifikovaná bližšie nešpecifikovanou asynchronnou udalosťou (i mimo programu, napr. prerušením). Kompilátor nemôže urobiť žiadny záver o konštantnosti alebo možnosti zmeny takejto premennej. Použitie takto modifikovaných premenných je veľmi zriedkavé.


Prideľovanie pamäte

Všetky používané dátové štruktúry (premenné) môžeme podľa doby ich trvania rozdeliť na statické a dynamické. Statické vznikajú vyhradením miesta kompilátorom počas prekladu a trvajú po celý čas vykonávania programu. Dynamické vznikajú a zanikajú podľa potreby počas vykonávania programu. Dynamická premenná vzniká alokáciou pamäte, zaniká uvoľnením tejto pamäte. Na to sa využívajú štandardné knižničné funkcie malloc a free. Funkcia malloc má jeden parameter typu unsigned int, ktorý udáva požadované množstvo dynamicky pridelenej pamäte. Vracia smerník na pridelenú pamäť (typu char *, resp. void *), cez ktorý program komunikuje s takto vytvorenou dynamickou premennou. Táto pamäť je až do jej uvoľnenia rezervovaná. Uvoľnenie pamäte sa vykoná pomocou funkcie free, ktorej zadáme smerník na už nepotrebnú pamäť. Nasledujúci príklad ilustruje vytvorenie, naplnenie aj zrušenie takejto dynamickej premennej d:


/*  priklad pr3_5.c práca s dynamickou premennou */

#include <stdio.h>
#include <string.h>
#include <malloc.h>

void main(void)
{
  char sr[50], *s=sr,*d;

  strcpy(s,"retazec");
  printf("Obsah statickej premennej: %s\n",s);
  d=(char*)malloc(strlen(s)+1);
  strcpy(d,s);
  printf("Obsah dynamickej premennej: %s\n",d);
  free(d);
}



Bitové polia

Vymenovaný typ sa používa pre špecifikáciu užívateľom vymenovaných hodnôt (obdobne ako v Pascale), ktoré sú reprezentované podmnožinou oboru hodnôt typu int. Bitové pole sa používa podobne ako vymenovaný typ s tým, že umožnuje úspornejšiu reprezentáciu zložiek vo forme daného počtu bitov, ktoré sa umiestňujú v rámci jedného, resp. niekoľkých celých čísel. Bitové pole môže byť položkou štruktúry. Deklarácia bitového poľa má tvar

  unsigned nazov : počet_bitov


  typedef unsigned int UInt;

  typedef struct twobytes
  {
    UInt a : 4;
    UInt b : 3;
    UInt c : 7;
    UInt d : 2;
  }
  /*sucet musi byt n nasobok 16 (velkost int)*/

Príklad pr3_6.c uvádza možnosť použitia bitového poľa s úsporou dvoch položiek typu int. Prvá položka pohl zaberá 1 bit, položka stav 2 bity (číselne 0 až 3), položka vek 5 bitov (umožňuje zadávať vek 0 až 31). Posledná položka no sa nepoužíva, jej uvedenie nuluje príslušné bity. Ak ju neuvedieme, bity sú nastavené na 1. Všeobecne sa doporučuje túto položku udávať a to kôly prenositeľnosti (niektorý kompilátor môže vyžadovať, aby bol súčet násobkom veľkosti použitého typu).


/*  priklad pr3_6.c použitie bitového poľa */

#include <stdio.h>
#define m 1
#define z 0

struct ziak
{
  unsigned pohl : 1; /* pohlavie */
  unsigned stav : 2; /* slob, žen, rozv, vdov */
  unsigned vek  : 5; /* vek do 32 rokov */
  unsigned no   : 8; /* nepouzite - do poctu (16) */
}

void main(void)
{
  struct ziak x;

  x.pohl = m;
  x.stav = 3;
  x.vek = 12;
  /*...*/
  printf("pohl=%d x=%o\n", x.pohl, x);
}


Taktiež je možné, že nie každý kompilátor podporuje prenášanie bitov, príklad:

  typedef struct fourbytes
  {
    unsigned a : 15;
    unsigned b : 2; /*nesmie byt*/
    unsigned c : 15;
  }