Predchozi kapitola

Obsah

Konec

Nasledujici kapitola

7. UKAZATELE, POLE A RETEZCE

7.1. Ukazatele
7.2.
Pole
7.3.
Aritmetika ukazatelu
7.4.
Retezce
7.5.
Vicerozmerna pole, ukazatele na ukazatele
7.6.
Ukazatele na ukazatele a pole ukazatelu
7.7.
Ukazatele na funkce.
7.8.
Argumenty prikazoveho radku


Ukazatele mohou byt pouzivany v souvislosti se statickymi daty, napriklad s promennymi. Mnohem elegantnejsi pouziti poskytuji v souvislosti s poli (a zejmena s poli znaku - retezci), nebot jak zakratko uvidime, (zjednodusene receno) ukazatele a pole jedno jsou. Zcela nezastupitelne misto maji ukazatele v souvislosti s dynamickymi datovymi strukturami.

Protoze zminene pouziti ukazatelu je prilis rozsahle pro jednu kapitolu, popiseme jej v kapitolach dvou. Tema teto kapitoly napovida jeji nazev. Nasledujici kapitola obsahuje podrobny prehled vstupu a vystupu. V dalsi kapitole se budeme venovat dynamickym datovym strukturam.

7.1. Ukazatelezpet

S ukazateli jsme se strucne seznamili jiz v kapitole venovane zakladnim datovym typum. Proto jiste neuskodi kratka rekapitulace. Ukazatel reprezentuje adresu objektu. Nese sebou soucasne informaci o datovem typu, ktery se na teto adrese nachazi. Deklaraci ukazatele a jeho pouziti ukazuje priklad:

int x, y, *px, *p2x;
px = &x;         /* px nyni ukazuje na x */
*px = 5;         /* totez co x = 5;      */
y = *px + 1;     /*          y = x + 1;  */
*px += 1;        /*          x += 1;     */
(*px)++;         /*          x++;  zavorky jsou nutne      */
p2x = px;        /* p2x ukazuje na totez, co px, t.j. na x */
*p2x = *p2x + y; /*          x = x + y;  */

Mejme na pameti, ze pri deklaraci ukazatele muzeme pouzit i modifikaci const. Pak se jedna o konstantni ukazatel, ukazatel na konstantu, nebo oboji, tedy konstantni ukazatel na konstantu. Podivejme se na nekolik prikladu:

int i;
int *pi;             /* pi je neinicializovany ukazatel na typ int   */
int * const cp = &i; /* konstantni ukazatel na int                   */
const int ci = 7;    /* celociselna konstanta                        */
const int *pci;/* neinicializovany ukazatel na celociselnou konstantu*/
const int * const cpc = &ci;     /* konstantni ukazatel na konstantu */

Prekladace ANSI C pochopitelne kontroluji program a v pripade pokusu o poruseni konstantnosti ukazatele, hodnoty nebo obojiho (podle deklarace) ohlasi chybu.

7.2. Polezpet

Pole je kolekce promennych stejneho typu, ktere mohou byt oznacovany spolecnym jmenem. K prvkum pole pristupujeme prostrednictvim identifikatoru pole a indexu1. Pole v C obsazuje spojitou oblast operacni pameti tak, ze prvni prvek pole je umisten na nejnizsi pridelene adrese a posledni na nejvyssi pridelene adrese. Pole je v C vyhradne jednorozmerne.

Povsimneme si skutecnosti, ze na prvky pole neklademe zadne omezeni. To nam umoznuje zavest vicerozmerna pole jako pole poli.

Definice pole je jednoducha:

typ jmeno[rozsah];

kde

typ urcuje typ prvku pole (bazovy typ)
jmeno predstavuje identifikator pole
rozsah je kladny pocet prvku pole (celociselny konstantni vyraz, ktery musi byt vycislitelny za prekladu)

Na tomto miste zdurazneme vyznam polozky rozsah. Znamena pocet prvku pole. V souvislosti se skutecnosti, ze pole zacina prvkem s indexem nula to znamena, ze posledni prvek pole ma index rozsah-1.

Jeste jedna dulezita vlastnost C. Prekladac nekontroluje rozsah pouziteho indexu. Pokud se o tuto skutecnost nepostarame sami, muzeme cist nesmyslne hodnoty pri odkazu na neexistujici prvky pole.

Proti nekterym jinym vyssim programovacim jazykum na nas klade C vyssi naroky. Pokud se ovsem zamyslime, zminena kontrola mezi se stejne pouzije pouze pri ladeni, hotovy program by moznost nedodrzeni mezi nemel obsahovat.

Ukazme si nekolik definic poli, v nichz budeme postupne ukazovat moznosti definice promennych typu pole:

#define N 10
int a[N];

a je deklarovano jako pole o N prvcich, kazdy typu int. Na jednotlive prvky se muzeme odkazovat indexy 0 az 9, t.j.

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

pametovy prostor prideleny poli a muzeme zjistit vypoctem:

pocet obsazenych byte = N * sizeof(int)

Proc musime potrebny pametovy prostor pocitat prave timto zpusobem? Pouhe sizeof(a) vrati velikost pameti pro promennou typu ukazatel. sizeof(*a) vrati velikost prvku pole, tedy velikost int.

C sice netestuje indexy poli, ale umoznuje nam soucasne s definici pole provest i jeho inicializaci:

b[0] b[1] b[2] b[3] b[4]
static double b[5] = { 1.2, 3.4, -1.2, 123.0, 4.0 };

Princip je jednoduchy. Pred strednik, jimz by definice koncila, vlozime do slozenych zavorek seznam hodnot, jimiz chceme inicializovat prvky pole. Hodnoty pochopitelne musi byt odpovidajiciho typu. Navic jich nemusi byt stejny pocet, jako je dimenze pole. Muze jich byt mene. Prirazeni totiz probiha nasledovne: prvni hodnota ze seznamu je umistena do prvniho prvku pole, druha hodnota do druheho prvku pole, ... . Pokud je seznam vycerpan drive, nez je prirazena hodnota poslednimu prvku pole, zustavaji odpovidajici (zaverecne) prvky pole neinicializovany.

Pri inicializaci popsane vyse jsme museli uvadet dimenzi pole. Tuto praci ovsem muzeme prenechat prekladaci. Ten poli vymezi tolik mista, kolik odpovida inicializaci, jako napriklad:

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

Abychom predchozi vetu uvedli na spravnou miru, musime si rici, jak chape C identifikator pole. Identifikator pole (symbolicky id) predstavuje konstantni ukazatel na prvni prvek pole, tedy na id[0]. Nekdy take hovorime, ze id predstavuje bazovou adresu pole. Drive popsane vlastnosti C nam zaruci, ze nezmenime hodnotu konstantniho ukazatele id (viz nasledujici Aritmetika ukazatelu).

7.3. Aritmetika ukazateluzpet

Nazev tohoto odstavce zrejme vyvola zdeseni u vetsiny pascalistu. Jaky to ma smysl, provadet aritmeticke operace s ukazateli? Jak hned uvidime, vyznam spociva ve zvyseni prehlednosti2, zrychleni chodu programu i v moznostech, ktere dostavame k dispozici.

Jak vime, ukazatel ukazuje na hodnotu nejakeho typu. Nese sebou informaci o tomto typu. Tato informace pochopitelne predstavuje pocet bajtu nutny pro uchovani hodnoty typu. A pri aritmetice ukazatelu smysluplne pouzivame obe casti zminene informace, adresu i velikost polozky.

Aritmetika ukazatelu je omezena na tri zakladni operace, scitani, odcitani3a porovnani. Podivejme se nejprve na prvni dve operace.

Scitani i odcitani jsou binarni operace. Protoze se zabyvame aritmetikou ukazatelu, bude jednim z operandu vzdy ukazatel. Druhym operandem pak bude bud opet ukazatel4, nebo jim muze byt cele cislo.

Pokud jsou oba operandy ukazatele, je vysledkem jejich rozdilu pocet polozek, ktere se mezi adresami na nez ukazatele ukazuji nachazeji. Pokud k ukazateli pricitam, respektive odcitam cele cislo, je vysledkem ukazatel ukazujici o prislusny pocet prvku vyse, respektive nize. Predstavime-li si prvky s vyssim indexem nad prvky s vyssim indexem. Tak je ale pole v C definovano. Tato predstava je zcela dokonala, pokud si predstavime ukazatel doprostred nejakeho pole.

Podivejme se tedy na ukazatele a pole. Mejme nasledujici deklaraci,

int i, *pi, a[N];

neprirazujme pro jednoduchost jiz zadne konkretni hodnoty. Co muzeme nyni s jistotou rici. Jen to, co vime o umisteni prvku pole5.

adresa prvku a[0] je &a[0] <-->6 a <--> a + 0.

Posledni vyraz jiz predstavuje soucet ukazatele s celociselnou hodnotou. Ta musi byt nula, aby se opravdu jednalo o ukazatel na prvni prvek pole.

Nyni nas jiste neprekvapi, ze nasledujici vyrazy uvedene na stejnem radku

a + i <--> &a[i]
*(a+i) <--> a[i]

predstavuji jine zapisy tehoz vyrazu. V prvnim radku jde o ukazatel na i+1. prvek, ve druhem radku pak hodnotu tohoto prvku.

Pro konkretni N rovno 100 dostavame,

int i, *pi, a[100];

a nasledujici prirazeni je prirazeni hodnoty jednoho ukazatele ukazateli druhemu,

pi = a; /* pi nyni ukazuje na zacatek pole a, na 1. prvek */

vyrazy uvedene nize jsou po tomto prirazeni zamenitelne (maji stejny vyznam, predstavuji hodnotu prvku pole a s indexem i),

a[i] <--> *(a+i) <--> pi[i] <--> *(pi+i).

Budeme-li nyni potrebovat ukazovat na prostredni prvek pole a, snadno tak muzeme ucinit s pomoci naseho pomocneho ukazatele pi:

pi = a + 49; /* p nyni ukazuje doprostred pole a, na 50. prvek */

S timto ukazatelem se pak muzeme posunout na nasledujici prvek pole treba pomoci inkrementace, nebot i u ni prekladac vi, o kolik bajtu ma posunout (zvetsit) adresu, aby ukazoval na nasledujici prvek: ++pi nebo pi++, ale ne ++a ani a++, nebot a je konstantni ukazatel (je pevne spojen se zacatkem pole).

Cast venovanou polim a aritmetice ukazatelu ukonceme kratkym programem. V nem si zejmena povsimneme skutecnosti, ze formalni argumenty funkci nejsou poli, nybrz ukazateli na typ. Navic jejich zmena neovlivni zmenu argumentu skutecnych (to by ostatne prekladac nedovolil - jsou prece konstantni). Proto neni problemem temto funkcim predat ukazatele jak na zacatek pole, tak na libovolny jiny jeho prvek. Opatrnost pak musime zachovat co se predavaneho poctu prvku tyce. C neprovadi kontrolu mezi!

/****************************************************************/
/* soubor CPYARRY.C                                             */
/* na funkcich kopirujicich prvky jednoho pole do druheho       */
/* ukazuje pristup k prvkum pole pomoci indexu i pomoci uka-    */
/* zatele, tedy pointerovou aritmetiku                          */
/****************************************************************/

#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("");
}

int 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);
 return 0;
}

/* vystup vypada nasledovne:

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

*/

7.4. Retezcezpet

Retezec je (jednorozmerne) pole znaku ukoncene specialnim znakem ve funkci zarazky. Na tuto skutecnost musime myslet pri definici velikosti pole. Pro zarazku musime rezervovat jeden znak. Nyni se zamysleme nad zarazkou. Ta je soucasti retezce (bez ni by retezec byl jen pole znaku). Musi tedy byt stejneho typu, jako ostatni prvky retezce - char. A pri pohledu na ASCII tabulku nam zustane jedina pouzitelna hodnota - znak s kodem nula: '\0'.

Podivejme se jiz na definici pole, ktere pouzijeme pro retezec:

char s[SIZE];

Na identifikator s se muzeme divat ze dvou pohledu:

Jednak jako na promennou typu retezec (pole znaku, zarazku pridavame nekdy sami) delky SIZE, jehoz jednotlive znaky jsou pristupne pomoci indexu s[0] az s[SIZE-1].

Nebo jako na konstantni ukazatel na znak, t.j. na prvni prvek pole s, s[0].

Retezcove konstanty piseme mezi dvojici uvozovek, uvozovky v retezcove konstante musime uvodit specialnim znakem \ - opacne lomitko. Tak to jsme si pripomneli obsah jedne z uvodnich kapitol. Pokracujme jeste chvili v jednoduchych ukazkach:

"abc" je konstanta typu retezec delky 3+1 znak (zarazka), tedy 3 znaky, 4 bajty pameti (konstantni pole 4 znaku).
"a" je rovnez retezcovou konstantou, ma delku 1+1 znak
'a' je znakova konstanta (neplest s retezcem!)

Nyni si spojime znalosti retezcovych konstant a poli. Napriklad zapis

char pozdrav[] = "hello";

je ekvivalentni zapisu

char pozdrav[] = {'h','e','l','l','o','\0'};

V obou pripadech se delka (dimenze) pole pouzije z inicializacni casti. Druhy priklad je zejmena zajimavy nutnosti explicitniho uvedeni zarazky. Jinak lze rici, ze se tento zpusob zadani retece nepouziva.

Jestlize jsme pracovali s polem znaku, proc to nyni nezkusit s ukazatelem na znak. Nasledujici definice je velmi podobna tem predchazejicim:

char *ps = "retezec";

presto se lisi dosti podstatne. ps je ukazatel na znak, jemuz je prirazena adresa retezcove konstanty retezec. Tato konstanta je tedy umistena v pameti (ostatne, kde jinde), ale promenna ps na ni ukazuje jen do okamziku, nez jeji hodnotu treba zmenime. Pak ovsem ztratime adresu konstantniho retezce "retezec".

Jednoduchy program popisovanou situaci ozrejmi (v nem jsme pochopitelne nemenili hodnotu konstantniho ukazatele na pole):

/****************************************/
/* soubor str_ptr.c                     */
/* ukazuje jednoduchou praci s retezci  */
/****************************************/

#include <stdio.h>

int 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);
 return 0;
}

/*

world world world

world hello world

llo world

*/


Nejdulezitejsim bodem programu je prirazeni

new1 = "hello",

v nemz pouze menime hodnotu ukazatele new1. Nedochazi pri nem k zadnemu kopirovani retezcu. To by se musel zmenit obsah obou dalsich retezcu text i new2, vzdyt puvodne byly umisteny na stejne adrese.

Podobne veci, ktere muzeme delat s ukazateli, muzeme delat i s konstantnimi retezci, napriklad:

"ahoj svete" + 5

predstavuje ukazatel na

"svete"

Proste jsme pouzili aritmetiku ukazatelu a zejmena fakt, ze hodnota retezcove konstanty je stejna jako jakehokoliv ukazatele na retezec (a nejen na retezec) - ukazatel na prvni polozku.

Nadale si tedy pamatujme priblizne toto. Pri praci s retezci nesmime zapominat na skutecnost, ze jsou reprezentovany ukazateli. Pouhou zmenou ci prirazenim ukazatele se samotny retezec nezmeni. Proto s temito ukazateli nemuzeme provadet operace tak, jak jsme zvykli treba v Turbo Pascalu.

Na zaver odstavce venovaneho popisu prace s retezci si uvedme modifikaci programu, ktery kopiroval obsah jednoho celociselneho pole do jineho. Dve varianty funkce pro kopirovani zdrojoveho retezce do ciloveho jsou pouzity v programu:

/************************************************/
/* soubor STR_CPY.C                             */
/* ukazuje dve mozne implementace kopirovani    */
/* retezcu                                      */
/************************************************/

#include <stdio.h>
#define      SIZE      80

void strcpy1(char *d, char *s)
{
 while ((*d++ = *s++) != '\0') ;
}

void strcpy2(char d[], char s[])
{
 int i = 0;
 while ((d[i] = s[i]) != '\0')
   i++;
}

int main()
{
 char s1[] = "prvni (1.) retezec",
      s2[] = "druhy (2.) retezec",
      d1[SIZE], d2[SIZE],
      *ps = s2;
 strcpy1(d1, s1);
 strcpy2(d2, s2 + 6);
 ps = s2 + 8;
 printf("s1[]:%s\t\ts2[]:%s\nd1[]:%s\t\td2[]:%s\n\t\t\t\t\t *ps:%s\n\n",
      s1, s2, d1, d2, ps);
 return 0;
}

/*
s1[]:prvni (1.) retezec            s2[]:druhy (2.) retezec
d1[]:prvni (1.) retezec                  d2[]:(2.) retezec
                                            *ps:.) retezec
*/

Podivejme se na obe kopirovaci funkce. Nemusime jim predavat pocet znaku. Zarazka ma skvelou vlastnost. Diky ni pozname konec pole. Soucasne vsak vznika nebezpeci, ze cilovy retezec nebude mit dostatek prostoru pro zapsani znaku z retezce zdrojoveho.

Retezce zrejme budeme v nasich programech pouzivat velmi casto. Jiste by nam nebylo mile, kdybychom museli psat pro kazdou operaci nad retezci svou vlastni funkci. Jednak by to vyzadovalo jiste usili, jednak bychom museli vymyslet jednoznacne a soucasne mnemonicke identifikatory. Tuto praci nastesti delat nemusime. Zminene funkce jsou soucasti standardni knihovny funkci a jejich prototypy jsou obsazeny v hlavickovem souboru string.h.

Nasledujici radky davaji prehled o nejpouzivanejsich retezcovych funkcich. Protoze jsme se retezcum venovali dostatecne, uvedeme pouze deklarace techto funkci s jejich velmi strucnym popisem.

int strcmp(const char *s1, const char *s2);
lexikograficky porovnava retezce, vraci hodnoty

< 0 je-li s1 < s2
0 s1 == s2
> 0 s1 > s2

int strncmp(const char *s1, const char *s2, unsigned int n);
jako predchozi s tim, ze porovnava nejvyse n znaku

unsigned int strlen(const char *s);
vrati pocet vyznamnych znaku retezce (bez zarazky)

char *strcpy(char *dest, const char *src);
nakopiruje src do dest

char *strncpy(char *dest, const char *src, unsigned int n);
jako predchozi, ale nejvyse n znaku (je-li jich prave n, neprida zarazku)

char *strcat(char *s1, const char *s2);
s2 prikopiruje za s1

char *strncat(char *s1, const char *s2, unsigned int n);
jako predchozi, ale nejvyse n znaku

char *strchr(const char *s, int c);
vyhleda prvni vyskyt (zleva) znaku c v retezci s

char *strrchr(const char *s, int c);
vyhleda prvni vyskyt (zprava) znaku c v retezci s

char *strstr(const char *str, const char *substr);
vyhleda prvni vyskyt (zleva) podretezce substr v retezci str

7.5. Vicerozmerna pole, ukazatele na ukazatelezpet

Po delsi vsuvce, ktera ukazovala podrobneji vlastnosti znakovych poli - retezcu, se dostavame k problematice vicerozmernych poli. Nejcasteji nam zrejme pujde o matice.

Jazyk C umoznuje deklarovat pole pouze jednorozmerne. Jeho prvky ovsem mohou byt libovolneho typu. Mohou tedy byt opet (napriklad) jednorozmernymi poli. To vsak jiz dostavame vektor vektoru, tedy matici. Budeme-li uvedenym zpusobem postupovat dale, vytvorime datovou strukturu prakticky libovolne dimenze. Pak jiz je treba mit jen dostatek pameti7.

Definice matice (dvourozmerneho pole) vypada takto:

type jmeno[5][7];

kde

typ urcuje datovy typ polozek pole
jmeno predstavuje identifikator pole
[5][7] urcuje rozsah jednotlivych vektoru, 5 radku a 7 sloupcu

Pamatujme si zasadu, ze posledni index se meni nejrychleji (tak je vicerozmerne pole umisteno v pameti).

S vicerozmernym polem muzeme pracovat stejne, jako s jednorozmernym. To se prirozene tyka i moznosti inicializace. Jen musime jednotlive "prvky" - vektory, uzavirat do slozenych zavorek. Tento princip je ovsem stejny (coz se opakujeme), jako pro vektor. Proto si primo uvedme ukazkovy program, prevzaty od klasiku - K&R.

Nasim ukolem je pro zadany den, mesic a rok zjistit poradove cislo dne v roce. Ve vypisu je uvedena i funkce, majici opacny vyznam. Pro den v roce a rok (co kdyz je prestupny) urci mesic a den. Reseni je velmi elegantni.

/****************************************/
/* soubor yearday.c                     */
/* urci cislo dne v roce a naopak       */
/* motivace K&R                         */
/****************************************/

static int day_tab[2][13] =
      {{0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31},
       {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}};

int day_of_year(int year, int month, int day)
{
 int i, leap;
 leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
 for (i = 1; i < month; i++)
   day += day_tab[leap][i];
 return day;
}

int month_day(int year, int yearday, int *pmonth, int *pday)
{
 int i, leap;
 leap = year % 4 == 0 && year % 100 != 0 || year % 400 == 0;
 for (i = 1; yearday > day_tab[leap][i]; i++)
   yearday -= day_tab[leap][i];
 *pmonth = i;
 *pday = yearday;
 return i;
}

int main()
{
 int year = 1993, month = 11, day = 12, yearday, m, d;
 yearday = day_of_year(year, month, day);
 month_day(year, yearday, &m, &d);
 return 0;
}

Program nema vstup ani vystup. Predstavuje proste definice funkci a ukazku jejich volani. Hned v uvodu je definovano staticke pole, obsahujici pocty dni v mesicich roku. Prvky s indexem nula maji nulovou hodnotu. Diky tomuto posunu vuci standardnimu C indexovani ma paty mesic index pet. Pole je dvourozmerne. Jeho prvni vektor odpovida beznemu roku, druhy vektor odpovida roku prestupnemu. Toto reseni je velmi rychle a jeho pametove naroky nejsou premrstene.

Funkce day_of_year() urci pro vstupni year, month a day cislo dne v roce. Funkce obsahuje rozsahlejsi logicky vyraz, ktery urci, je-li rok prestupny ci nikoliv. Pak se pouze k zadanemu dnu v zadanem mesici postupne pricitaji8 pocty dnu v mesicich predchazejicich.

Funkce month_day() ma opacnou ulohu. Ze vstupnich udaju year a yearday (cislo dne v roce) vypocte mesic a den. Problemem je, ze vystupni hodnoty jsou dve. Resenim jsou formalni argumenty pmonth a pday, predavane adresou9. Postup algoritmu vypoctu je opacny, nez v predchozim pripade. Postupne se odecitaji delky mesicu (pocinaje lednem), dokud neni zbytek mensi, nez je pocet dnu mesice. Tento mesic je pak vyslednym mesicem. Zbytek dnem v mesici. Navratova hodnota funkce nema prakticky zadny vyznam. Je proste "do poctu".

7.6. Ukazatele na ukazatele a pole ukazateluzpet

Ukazatel je promenna jako kazda jina. Proc bychom tedy na nej nemohli ukazovat nejakym jinym (dalsim) ukazatelem. Ostatne, ve vicerozmernem poli provadime v podstate totez, jen jednotlive vektory nejsou pojmenovane10. Lze rici, ze pole ukazatelu je v jazyce C nejen velmi oblibena, ale i velmi casto pouzivana konstrukce.

Mejme jednoduchou ulohu. Pole retezcu (t.j. ukazatelu na retezce), ktere mame setridit. Vyhodou naseho reseni je, ze retezce nebudou menit svou pozici v pameti. Zamenovat se budou jen ukazatele na ne.

Nase pole necht se jmenuje v a necht ma nejaky pocet ukazatelu na char.

char *v[10];

Pokud umistime do jednotlivych ukazatelu pole napriklad ukazatele na nactene radky textu (nemusi mit stejnou delku), muzeme princip trideni ukazat na zamene dvou sousednich ukazatelu (na retezce) v poli. Pro vetsi nazornost jsou retezce na obrazku v nespravnem poradi:

ukazatele

Dulezite je povsimnout si, ze text (retezce) svou pozici v pameti nezmenily. To by bylo pomerne komplikovane, uvedomime-li si, jak probiha zamena dvou promennych. Zamenily se pouze adresy (hodnoty ukazatelu),

Po vysvetleni principu se muzeme podivat na program, ktery lexikograficky setridi jmena mesicu v roce. Protoze standardni knihovny C netridi cesky, nejsou ani jmena mesicu cesky, ale jen "cesky". Povsimneme si i inicializace vicerozmerneho pole a zjisteni poctu jeho prvku.

/************************************************/
/* soubor str_sort.c                            */
/* ukazuje trideni pole ukazatelu na retezce    */
/************************************************/

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

void sort(char **v, int n)
{
 int gap, i, j;
 char *temp;
 for (gap = n/2; gap > 0; gap /= 2)
   for (i = gap; i < n; i++)
     for (j = i - gap; j >= 0; j -= gap)
     {
      if (strcmp(v[j], v[j+gap]) <= 0)
        break;
      temp = v[j]; v[j] = v[j+gap]; v[j+gap] = temp;
     }
}

int main()
{
 char *name[] =
  {
   "chyba", "leden", "unor", "brezen", "duben", "kveten", "cerven",
   "cervenec", "srpen", "zari", "rijen", "listopad", "prosinec"
  };
 int i;
 puts("jmena mesicu v roce (podle poradi):");
 for (i = 0; i < 13; i++)
   printf("%2d. mesic je : %s\n", i, name[i]);
 puts("\na ted po setrideni:");
 sort(name, sizeof(name)/sizeof(char *));
 for (i = 0; i < 13; i++)
   printf("%2d. mesic je : %s\n", i, name[i]);
 return 0;
}

Nevyhoda zobrazeni jmen vsech mesicu pred i po serazeni se projevi na monitorech s nejvyse 25-ti textovymi radky. Nechteli jsme vsak program cinit mene prehlednym, proto jsme takovy vystup ponechali.

7.7. Ukazatele na funkce.zpet

Ukazatele na funkce jsme mohli zaclenit do predchozi podkapitoly. Jedna se casto o ukazatele nebo o pole ukazatelu. Funkce ovsem nejen vraci hodnotu jisteho typu, ale mohou mit i ruzny pocet argumentu ruzneho typu. Protoze je pouziti ukazatelu na funkce nejen velmi mocnym prostredkem C, nybrz i prostredkem velmi nebezpecnym, rozhodli jsme se popsat ukazatele na funkce v samostatne podkapitole11.

Definujme napriklad ukazatel na funkci, ktera nema argumenty a vraci hodnotu zvoleneho datoveho typu:

typ (*jmeno)();

Poznamenejme, ze zavorky okolo identifikatoru jmeno a uvodni hvezdicky jsou nutne, nebot zapis

typ *jmeno();

predstavuje funkci vracejici ukazatel na typ.

Prikladem pouziti ukazatelu na funkce muze byt knihovni funkce qsort(). Jeji deklarace je nasledujici:

void qsort(void *base, size_t nelem, size_t width, int (*fcmp)(const void *, const void *));

kde

base je zacatek pole, ktere chceme setridit
nelem je pocet prvku, ktere chceme setridit (rozsah pole)
width je pocet bajtu, ktery zabira jeden prvek

fcmp je ukazatel na funkci, provadejici porovnani, ta ma jako argumenty dva konstantni ukazatele na prave porovnavane prvky (nutno pretypovat)

Prototyp funkce qsort() je v hlavickovem souboru stdlib.h.

Funkce qsort() je napsana obecne a tak nema zadne konkretni informace o typech hodnot, ktere tridi. Tuto zalezitost resi nase uzivatelska funkce, ktera spravne porovna hodnoty. Nebot my vime, co tridime. Ukazku pouziti funkce spolu s merenim casu a pouzitim generatoru nahodnych cisel nam dava program:

/********************************************************/
/* soubor fn_qsrt.c                                     */
/* ukazuje pouziti qsort() a definici a pretypovani     */
/* uzivatelske srovnavaci funkce                        */
/********************************************************/

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

#define      POCET          1000
#define      RND_START      1234

int float_sort (const float *a, const float *b)
{
 return (*a - *b);      /* <0, ==0, >0      */
} /* int float_sort (const float *a, const float *b) */

void test (float *p, unsigned int pocet)
/* otestuje zda dane pole je setrideno vzestupne     */
{
 int chyba = 0;
 for (; !chyba && --pocet > 0; p++)
   if (*p > *(p+1))
     chyba=1;
 puts ((chyba) ? "\npole neni setrideno\n"
             : "\npole je setrideno\n");
} /* void test (float *p,unsigned int pocet) */

void vypln(float *p, int pocet)
/* vypni pole dane adresou a poctem prvku nahodnymi */
/* cisly pomoci generatoru nahodnych cisel          */
{
 srand(RND_START);
 while (pocet-- > 0)
    *p++ = (float) rand();
} /* void vypln(float *p, int pocet) */

int main (void)
{
 static float pole [POCET];
 clock_t start, end;
 vypln (pole, POCET);
 start = clock();
 qsort(pole, POCET, sizeof(*pole),
       (int (*) (const void *, const void *)) float_sort);
 end = clock();
 printf ("Trideni qsort trvalo %fs", (end - start) / CLK_TCK);
 test (pole, POCET);
 getc (stdin);
 return 0;
}   /* int main (void)  */

Pri praci s ukazateli na ukazatele, a tedy i na funkce, je velmi uzitecne zavest novy typ, ktery definujeme podle naseho zameru. Odkaz na tento novy typ vyrazne zprehledni nas program. Pred uvedenim dalsi ukazky poznamejme, ze odkazy na funkce mohou byt i externi. Znamena to, ze mame moznost nezavisle na zdrojovem textu definovat funkci s pozadovanymi vlastnostmi, kterou pak pripojime do vysledneho programu. Nase ukazka ovsem pouziva jen ukazatele na v souboru definovane funkce.

/********************************/
/* soubor PTR_FN01.C            */
/* pole ukazatelu na funkce     */
/********************************/

#include <stdio.h>
#include <process.h>

typedef void (*menu_fcn) (void); /* definice typu                 */
menu_fcn command[3];             /* pole tri ukazatelu na funkce  */

void fn_1(void)                  /* definice prvni funkce         */
/**************/
{
 puts("funkce cislo 1\n");       /*puts() je kratsi, nez printf() */
} /* void fn_1(void) */

void fn_2(void)                  /* definice druhe funkce         */
/**************/
{
 puts("funkce cislo 2\n");
} /* void fn_2(void) */

void fn_3(void)                  /* definice treti funkce         */
/**************/
{
 puts("funkce cislo 3\nKONEC\n");
 exit(0);                       /* ukonceni chodu programu        */
} /* void fn_3(void) */

void assign_fn(void)            /* prirazeni ukazatelu na fce     */
/******************/            /* do pole ukazatelu              */
{
 command[0] = fn_1;
 command[1] = fn_2;
 command[2] = fn_3;
} /* void assign_fn(void) */

void menu(void)                  /* funkce s nabidkou            */
/*************/
{
 int choice;
 do {
     puts("1\tpolozka\n2\tpolozka\n3\tukonceni\n");
     putchar('>');
     scanf("%d", &choice);
     if (choice >= 1 && choice <= 3)
       command[choice - 1]();
       /* ^ volani funkce pomoci pole ukazatelu, () jsou nutne  */
    } while (1);                  /* nekonecny cyklus           */
} /* void menu(void) */

int main(void)                    /* hlavni funkce programu     */
/************/
{
 assign_fn();     /* volani spravne inicializace pole ukazatelu */
 menu();          /* volani nabidky, obsahuje i volbu ukonceni  */
 return 0;        /* tento radek je vlastne zbytecny, program   */
                  /* ukonci exit() ve treti funkci - fn_3       */
} /* int main(void) */

7.8. Argumenty prikazoveho radkuzpet

Pokud jeste spoustime programy z prikazoveho radku, asi rovnez vime, ze jim muzeme predat argumenty. Casto to byvaji jmena vstupniho a vystupniho souboru, prepinace ... . Stejna situace je i v pripade, kdy spoustime program z jineho programu.

Aby nam byly argumenty prikazoveho radku v programu dostupne, staci rozsirit definici funkce main o dva argumenty. Situace je podobna, jako u funkce s promennym poctem argumentu.

Prvni argument funkce main() udava pocet argumentu prikazove radky (1 = jen jmeno programu, 2 = jmeno programu + jeden argument, ...). Je celociselneho typu.

Druhy argument funkce main() predstavuje hodnoty techto argumentu. Jeho typ je pochopitelne pole ukazatelu na retezce, nebot jimi argumenty prikazove radky skutecne jsou.

Je dobrym zvykem popsane argumenty funkce main() pojmenovat argc a argv, kde c znamena count a v znamena value.

Program zobrazujici argumenty, s nimiz byl spusten nasleduje:

/************************************************/
/* soubor CMD_LN04.C                            */
/* pomoci ukazatelu, dva formaty printf         */
/************************************************/

#include <stdio.h>

int main(int argc, char **argv)
{
 while (argc-- > 0)
   printf((argc > 0) ? "%s " : "%s\n", *argv++);
 return 0;
}

Pro lepsi predstavu se podivejme, jak bude vypadat situace s argumenty prikazoveho radku pri spusteni prikladu. Priznejme, ze prvni argument, tedy cmd_ln04, bude do prostredi spusteneho programu rozvinut v uplne podobe. Nicmene verme, ze obrazek je, pres drobnou nepresnost, uzitecny.

Na zaver rozsahle kapitoly venovane polim a ukazatelum si zopakujme hlavni zasady pro praci s nimi:


Vysvetlivky:

1 Zanedlouho uvidime, ze i prostrednictvim ukazatele.
2 Pokud ovsem aritmetice ukazatelu porozumime.
3 Nezapomenme na unarni operace inkrementace a dekrementace. Predstavuji jen jiny zapis pricteni ci odecteni jednicky.
4 Tyka se pouze operace odcitani dvou ukazatelu.
5 Promenne maji po deklaraci nedefinovanou hodnotu.
6 Symbol <--> chapejme jako ekvivalenci. Muzeme jej cist napriklad "je totez".
7 A OS ktery nam ji umi poskytnout.
8 S formalnim argumentem predanym hodnotou si to muzeme dovolit. Skutecny argument zustane nezmenen.
9 Jinak receno, jsou to ukazatele.
10 Pristupujeme k nim pomoci bazove adresy zakladniho pole a indexu. Z tohoto pohledu jsou opravdu nepojmenovane.
11 Tuto podkapitolu jsme nemohli zaradit primo do kapitoly venovane funkcim, nebot v tom miste jsme jeste dostatecne nepoznali ukazatele.


Predchozi kapitola

Obsah

Zacatek

Nasledujici kapitola


Nazev: Programovani v jazyce C
Autor: Petr Saloun
Do HTML prevedl: Kristian Wiglasz
Posledni uprava: 29.11.1996