Předchozí kapitola

Obsah

Konec

Následující kapitola

 

7. UKAZATELE, POLE A ŘETĚZCE

7.1. Ukazatele
7.2.
Pole
7.3.
Aritmetika ukazatelů
7.4.
Řetězce
7.5.
Vícerozměrná pole, ukazatele na ukazatele
7.6.
Ukazatele na ukazatele a pole ukazatelů
7.7.
Ukazatele na funkce.
7.8.
Argumenty příkazového řádku


Ukazatele mohou být používány v souvislosti se statickými daty, například s proměnnými. Mnohem elegantnější použití poskytují v souvislosti s poli (a zejména s poli znaků - řetězci), neboť jak zakrátko uvidíme, (zjednodušeně řečeno) ukazatele a pole jedno jsou. Zcela nezastupitelné místo mají ukazatele v souvislosti s dynamickými datovými strukturami.

Protože zmíněné použití ukazatelů je příliš rozsáhlé pro jednu kapitolu, popíšeme jej v kapitolách dvou. Téma této kapitoly napovídá její název. Následující kapitola obsahuje podrobný přehled vstupu a výstupu. V další kapitole se budeme věnovat dynamickým datovým strukturám.

7.1. Ukazatelezpet

S ukazateli jsme se stručně seznámili již v kapitole věnované základním datovým typům. Proto jistě neuškodí krátká rekapitulace. Ukazatel reprezentuje adresu objektu. Nese sebou současně informaci o datovém typu, který se na této adrese nachází. Deklaraci ukazatele a jeho použití ukazuje příklad:

int x, y, *px, *p2x;
px = &x;         /* px nyní ukazuje na x */
*px = 5;         /* totéž co x = 5;      */
y = *px + 1;     /*          y = x + 1;  */
*px += 1;        /*          x += 1;     */
(*px)++;         /*          x++;  závorky jsou nutné      */
p2x = px;        /* p2x ukazuje na totéž, co px, t.j. na x */
*p2x = *p2x + y; /*          x = x + y;  */

Mějme na paměti, že při deklaraci ukazatele můžeme použít i modifikaci const. Pak se jedná o konstantní ukazatel, ukazatel na konstantu, nebo obojí, tedy konstantní ukazatel na konstantu. Podívejme se na několik příkladů:

int i;
int *pi;             /* pi je neinicializovaný ukazatel na typ int   */
int * const cp = &i; /* konstantní ukazatel na int                   */
const int ci = 7;    /* celočíselná konstanta                        */
const int *pci;/* neinicializovaný ukazatel na celočíselnou konstantu*/
const int * const cpc = &ci;     /* konstantní ukazatel na konstantu */

Překladače ANSI C pochopitelně kontrolují program a v případě pokusu o porušení konstantnosti ukazatele, hodnoty nebo obojího (podle deklarace) ohlásí chybu.

7.2. Polezpet

Pole je kolekce proměnných stejného typu, které mohou být označovány společným jménem. K prvkům pole přistupujeme prostřednictvím identifikátoru pole a indexu1. Pole v C obsazuje spojitou oblast operační paměti tak, že první prvek pole je umístěn na nejnižší přidělené adrese a poslední na nejvyšší přidělené adrese. Pole je v C výhradně jednorozměrné.

Povšimněme si skutečnosti, že na prvky pole neklademe žádné omezení. To nám umožňuje zavést vícerozměrná pole jako pole polí.

Definice pole je jednoduchá:

typ jméno[rozsah];

kde

typ určuje typ prvků pole (bázový typ)
jméno představuje identifikátor pole
rozsah je kladný počet prvků pole (celočíselný konstantní výraz, který musí být vyčíslitelný za překladu)

Na tomto místě zdůrazněme význam položky rozsah. Znamená počet prvků pole. V souvislosti se skutečností, že pole začíná prvkem s indexem nula to znamená, že poslední prvek pole má index rozsah-1.

Ještě jedna důležitá vlastnost C. Překladač nekontroluje rozsah použitého indexu. Pokud se o tuto skutečnost nepostaráme sami, můžeme číst nesmyslné hodnoty při odkazu na neexistující prvky pole.

Proti některým jiným vyšším programovacím jazykům na nás klade C vyšší nároky. Pokud se ovšem zamyslíme, zmíněná kontrola mezí se stejně použije pouze při ladění, hotový program by možnost nedodržení mezí neměl obsahovat.

Ukažme si několik definic polí, v nichž budeme postupně ukazovat možnosti definice proměnných typu pole:

#define N 10
int a[N];

a je deklarováno jako pole o N prvcích, každý typu int. Na jednotlivé prvky se můžeme odkazovat indexy 0 až 9, t.j.

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

paměťový prostor přidělený poli a můžeme zjistit výpočtem:

počet obsazených byte = N * sizeof(int)

Proč musíme potřebný paměťový prostor počítat právě tímto způsobem? Pouhé sizeof(a) vrátí velikost paměti pro proměnnou typu ukazatel. sizeof(*a) vrátí velikost prvku pole, tedy velikost int.

C sice netestuje indexy polí, ale umožňuje nám současně s definicí pole provést 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 jednoduchý. Před středník, jímž by definice končila, vložíme do složených závorek seznam hodnot, jimiž chceme inicializovat prvky pole. Hodnoty pochopitelně musí být odpovídajícího typu. Navíc jich nemusí být stejný počet, jako je dimenze pole. Může jich být méně. Přiřazení totiž probíhá následovně: první hodnota ze seznamu je umístěna do prvního prvku pole, druhá hodnota do druhého prvku pole, ... . Pokud je seznam vyčerpán dříve, než je přiřazena hodnota poslednímu prvku pole, zůstávají odpovídající (závěrečné) prvky pole neinicializovány.

Při inicializaci popsané výše jsme museli uvádět dimenzi pole. Tuto práci ovšem můžeme přenechat překladači. Ten poli vymezí tolik místa, kolik odpovídá inicializaci, jako například:

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

Abychom předchozí větu uvedli na správnou míru, musíme si říci, jak chápe C identifikátor pole. Identifikátor pole (symbolicky id) představuje konstantní ukazatel na první prvek pole, tedy na id[0]. Někdy také hovoříme, že id představuje bázovou adresu pole. Dříve popsané vlastnosti C nám zaručí, že nezměníme hodnotu konstantního ukazatele id (viz následující Aritmetika ukazatelů).

7.3. Aritmetika ukazatelůzpet

Název tohoto odstavce zřejmě vyvolá zděšení u většiny pascalistů. Jaký to má smysl, provádět aritmetické operace s ukazateli? Jak hned uvidíme, význam spočívá ve zvýšení přehlednosti2, zrychlení chodu programu i v možnostech, které dostáváme k dispozici.

Jak víme, ukazatel ukazuje na hodnotu nějakého typu. Nese sebou informaci o tomto typu. Tato informace pochopitelně představuje počet bajtů nutný pro uchování hodnoty typu. A při aritmetice ukazatelů smysluplně používáme obě části zmíněné informace, adresu i velikost položky.

Aritmetika ukazatelů je omezena na tři základní operace, sčítání, odčítání3a 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 ukazatel4, nebo jím může být celé číslo.

Pokud jsou oba operandy ukazatele, 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. Představíme-li si prvky s vyšším indexem nad prvky s vyšším indexem. Tak je ale pole v C definováno. Tato představa je zcela dokonalá, pokud si představíme ukazatel doprostřed nějakého pole.

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

int i, *pi, a[N];

nepřiřazujme pro jednoduchost již žádné konkrétní hodnoty. Co můžeme nyní s jistotou říci. Jen to, co víme o umístění prvků pole5.

adresa prvku a[0] je &a[0] <-->6 a <--> 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, *pi, a[100];

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

pi = a; /* pi 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) <--> pi[i] <--> *(pi+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 pi:

pi = 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: ++pi nebo pi++, 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í!

/****************************************************************/
/* 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. Řetězcezpet

Řetězec je (jednorozměrné) pole znaků ukončené speciálním znakem ve funkci zarážky. Na tuto skutečnost musíme myslet při definici velikosti pole. Pro zarážku musíme rezervovat jeden znak. Nyní se zamysleme nad zarážkou. Ta je součástí řetězce (bez ní by řetězec byl jen pole znaků). Musí tedy být stejného typu, jako ostatní prvky řetězce - char. A při pohledu na ASCII tabulku nám zůstane jediná použitelná hodnota - znak s kódem nula: '\0'.

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

char s[SIZE];

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

Jednak jako na proměnnou typu řetězec (pole znaků, zarážku přidáváme někdy sami) délky SIZE, jehož jednotlivé znaky jsou přístupné pomocí indexů s[0]s[SIZE-1].

Nebo jako na konstantní ukazatel na znak, t.j. na první prvek pole s, s[0].

Řetězcové konstanty píšeme mezi dvojici úvozovek, úvozovky v řetězcové konstantě musíme uvodit speciálním znakem \ - opačné lomítko. Tak to jsme si připomněli obsah jedné z úvodních kapitol. 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 pozdrav[] = "hello";

je ekvivalentní zápisu

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

V obou případech se délka (dimenze) 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 nepoužívá.

Jestliže jsme pracovali s polem znaků, proč to nyní nezkusit s ukazatelem na znak. Následující definice je velmi podobná těm předcházejícím:

char *ps = "retezec";

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

Jednoduchý program popisovanou situaci ozřejmí (v něm jsme pochopitelně neměnili hodnotu konstantního 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

*/


Nejdůležitějším bodem programu je přiřazení

new1 = "hello",

v němž pouze měníme hodnotu ukazatele new1. Nedochází při něm k žádnému kopírování řetězců. To by se musel změnit obsah obou dalších řetězců text i new2, vždyť původně byly umístěny na stejné adrese.

Podobné věci, které můžeme dělat s ukazateli, můžeme dělat i s konstantními řetězci, například:

"ahoj světe" + 5

představuje ukazatel na

"světe"

Prostě jsme použili aritmetiku ukazatelů a zejména fakt, že hodnota řetězcové konstanty je stejná jako jakéhokoliv ukazatele na řetězec (a nejen na řetězec) - ukazatel na první položku.

Nadále si tedy pamatujme přibližně toto. Při práci s řetězci nesmíme zapomínat na skutečnost, že jsou reprezentovány ukazateli. Pouhou změnou či přiřazením ukazatele se samotný řetězec nezmění. Proto s těmito ukazateli nemůžeme provádět operace tak, jak jsme zvyklí třeba v Turbo Pascalu.

Na závěr odstavce věnovaného popisu práce s řetězci si uveďme modifikaci programu, který kopíroval obsah jednoho celočíselného pole do jiného. Dvě varianty funkce pro kopírování zdrojového řetězce do cílového jsou použity 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
*/

Podívejme se na obě kopírovací funkce. Nemusíme jim předávat počet znaků. Zarážka má skvělou vlastnost. Díky ní poznáme konec pole. Současně však vzniká nebezpečí, že cílový řetězec nebude mít dostatek prostoru pro zapsání znaků z řetězce zdrojového.

Řetězce zřejmě budeme v našich programech používat velmi často. Jistě by nám nebylo milé, kdybychom museli psát pro každou operaci nad řetězci svou vlastní funkci. Jednak by to vyžadovalo jisté úsilí, jednak bychom museli vymýšlet jednoznačné a současně mnemonické identifikátory. Tuto práci naštěstí dělat nemusíme. Zmíněné funkce jsou součástí standardní knihovny funkcí a jejich prototypy jsou obsaženy v hlavičkovém souboru string.h.

Následující řádky dávají přehled o nejpoužívanějších řetězcových funkcích. Protože jsme se řetězcům věnovali dostatečně, uvedeme pouze deklarace těchto funkcí s jejich velmi stručným popisem.

int strcmp(const char *s1, const char *s2);
lexikograficky porovnává řetězce, vrací hodnoty

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

int strncmp(const char *s1, const char *s2, unsigned int n);
jako předchozí s tím, že porovnává nejvýše n znaků

unsigned int strlen(const char *s);
vrátí počet významných znaků řetězce (bez zarážky)

char *strcpy(char *dest, const char *src);
nakopíruje src do dest

char *strncpy(char *dest, const char *src, unsigned int n);
jako předchozí, ale nejvýše n znaků (je-li jich právě n, nepřidá zarážku)

char *strcat(char *s1, const char *s2);
s2 přikopíruje za s1

char *strncat(char *s1, const char *s2, unsigned int n);
jako předchozí, ale nejvýše n znaků

char *strchr(const char *s, int c);
vyhledá první výskyt (zleva) znaku c v řetězci s

char *strrchr(const char *s, int c);
vyhledá první výskyt (zprava) znaku c v řetězci s

char *strstr(const char *str, const char *substr);
vyhledá první výskyt (zleva) podřetězce substr v řetězci str

7.5. Vícerozměrná pole, ukazatele na ukazatelezpet

Po delší vsuvce, která ukazovala podrobněji vlastnosti znakových polí - řetězců, se dostáváme k problematice vícerozměrných polí. Nejčastěji nám zřejmě půjde o matice.

Jazyk C umožňuje deklarovat pole pouze jednorozměrné. Jeho prvky ovšem mohou být libovolného typu. Mohou tedy být opět (například) jednorozměrnými poli. To však již dostáváme vektor vektorů, tedy matici. Budeme-li uvedeným způsobem postupovat dále, vytvoříme datovou strukturu prakticky libovolné dimenze. Pak již je třeba mít jen dostatek paměti7.

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

type jmeno[5][7];

kde

typ určuje datový typ položek pole
jmeno představuje identifikátor pole
[5][7] určuje rozsah jednotlivých vektorů, 5 řádků a 7 sloupců

Pamatujme si zásadu, že poslední index se mění nejrychleji (tak je vícerozměrné pole umístěno v paměti).

S vícerozměrným polem můžeme pracovat stejně, jako s jednorozměrným. To se přirozeně týká i možnosti inicializace. Jen musíme jednotlivé "prvky" - vektory, uzavírat do složených závorek. Tento princip je ovšem stejný (což se opakujeme), jako pro vektor. Proto si přímo uvěďme ukázkový program, převzatý od klasiků - K&R.

Naším úkolem je pro zadaný den, měsíc a rok zjistit pořadové číslo dne v roce. Ve výpisu je uvedena i funkce, mající opačný význam. Pro den v roce a rok (co když je přestupný) určí měsíc a den. Řešení je velmi elegantní.

/****************************************/
/* 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 nemá vstup ani výstup. Představuje prostě definice funkcí a ukázku jejich volání. Hned v úvodu je definováno statické pole, obsahující počty dní v měsících roku. Prvky s indexem nula mají nulovou hodnotu. Díky tomuto posunu vůči standardnímu C indexování má pátý měsíc index pět. Pole je dvourozměrné. Jeho první vektor odpovídá běžnému roku, druhý vektor odpovídá roku přestupnému. Toto řešení je velmi rychlé a jeho paměťové nároky nejsou přemršťené.

Funkce day_of_year() určí pro vstupní year, month a day číslo dne v roce. Funkce obsahuje rozsáhlejší logický výraz, který určí, je-li rok přestupný či nikoliv. Pak se pouze k zadanému dnu v zadaném měsíci postupně přičítají8 počty dnů v měsících předcházejících.

Funkce month_day() má opačnou úlohu. Ze vstupních údajů year a yearday (číslo dne v roce) vypočte měsíc a den. Problémem je, že výstupní hodnoty jsou dvě. Řešením jsou formální argumenty pmonth a pday, předávané adresou9. Postup algoritmu výpočtu je opačný, než v předchozím případě. Postupně se odečítají délky měsíců (počínaje lednem), dokud není zbytek menší, než je počet dnů měsíce. Tento měsíc je pak výsledným měsícem. Zbytek dnem v měsíci. Návratová hodnota funkce nemá prakticky žádný význam. Je prostě "do počtu".

7.6. Ukazatele na ukazatele a pole ukazatelůzpet

Ukazatel je proměnná jako každá jiná. Proč bychom tedy na něj nemohli ukazovat nějakým jiným (dalším) ukazatelem. Ostatně, ve vícerozměrném poli provádíme v podstatě totéž, jen jednotlivé vektory nejsou pojmenované10. Lze říci, že pole ukazatelů je v jazyce C nejen velmi oblíbená, ale i velmi často používaná konstrukce.

Mějme jednoduchou úlohu. Pole řetězců (t.j. ukazatelů na řetězce), které máme setřídit. Výhodou našeho řešení je, že řetězce nebudou měnit svou pozici v paměti. Zaměňovat se budou jen ukazatele na ně.

Naše pole nechť se jmenuje v a nechť má nějaký počet ukazatelů na char.

char *v[10];

Pokud umístíme do jednotlivých ukazatelů pole například ukazatele na načtené řádky textu (nemusí mít stejnou délku), můžeme princip třídění ukázat na záměně dvou sousedních ukazatelů (na řetězce) v poli. Pro větší názornost jsou řetězce na obrázku v nesprávném pořadí:

ukazatele

Důležité je povšimnout si, že text (řetězce) svou pozici v paměti nezměnily. To by bylo poměrně komplikované, uvědomíme-li si, jak probíhá záměna dvou proměnných. Zaměnily se pouze adresy (hodnoty ukazatelů),

Po vysvětlení principu se můžeme podívat na program, který lexikograficky setřídí jména měsíců v roce. Protože standardní knihovny C netřídí česky, nejsou ani jména měsíců česky, ale jen "cesky". Povšimněme si i inicializace vícerozměrného pole a zjištění počtu jeho prvků.

/************************************************/
/* 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;
}

Nevýhoda zobrazení jmen všech měsíců před i po seřazení se projeví na monitorech s nejvýše 25-ti textovými řádky. Nechtěli jsme však program činit méně přehledným, proto jsme takový výstup ponechali.

7.7. Ukazatele na funkce.zpet

Ukazatele na funkce jsme mohli začlenit do předchozí podkapitoly. Jedná se často o ukazatele nebo o pole ukazatelů. Funkce ovšem nejen vrací hodnotu jistého typu, ale mohou mít i různý počet argumentů různého typu. Protože je použití ukazatelů na funkce nejen velmi mocným prostředkem C, nýbrž i prostředkem velmi nebezpečným, rozhodli jsme se popsat ukazatele na funkce v samostatné podkapitole11.

Definujme například ukazatel na funkci, která nemá argumenty a vrací hodnotu zvoleného datového typu:

typ (*jmeno)();

Poznamenejme, že závorky okolo identifikátoru jmeno a úvodní hvězdičky jsou nutné, neboť zápis

typ *jmeno();

představuje funkci vracející ukazatel na typ.

Příkladem použití ukazatelů na funkce může být knihovní funkce qsort(). Její deklarace je následující:

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

kde

base je začátek pole, které chceme setřídit
nelem je počet prvků, které chceme setřídit (rozsah pole)
width je počet bajtů, který zabírá jeden prvek

fcmp je ukazatel na funkci, provádějící porovnání, ta má jako argumenty dva konstantní ukazatele na právě porovnávané prvky (nutno přetypovat)

Prototyp funkce qsort() je v hlavičkovém souboru stdlib.h.

Funkce qsort() je napsána obecně a tak nemá žádné konkrétní informace o typech hodnot, které třídí. Tuto záležitost řeší naše uživatelská funkce, která správně porovná hodnoty. Neboť my víme, co třídíme. Ukázku použití funkce spolu s měřením času a použitím generátoru náhodných čísel nám dává 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)  */

Při práci s ukazateli na ukazatele, a tedy i na funkce, je velmi užitečné zavést nový typ, který definujeme podle našeho záměru. Odkaz na tento nový typ výrazně zpřehlední náš program. Před uvedením další ukázky poznamejme, že odkazy na funkce mohou být i externí. Znamená to, že máme možnost nezávisle na zdrojovém textu definovat funkci s požadovanými vlastnostmi, kterou pak připojíme do výsledného programu. Naše ukázka ovšem používá jen ukazatele na v souboru definované 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 příkazového řádkuzpet

Pokud ještě spouštíme programy z příkazového řádku, asi rovněž víme, že jim můžeme předat argumenty. Často to bývají jména vstupního a výstupního souboru, přepínače ... . Stejná situace je i v případě, kdy spouštíme program z jiného programu.

Aby nám byly argumenty příkazového řádku v programu dostupné, stačí rozšířit definici funkce main o dva argumenty. Situace je podobná, jako u funkce s proměnným počtem argumentů.

První argument funkce main() udává počet argumentů příkazové řádky (1 = jen jméno programu, 2 = jméno programu + jeden argument, ...). Je celočíselného typu.

Druhý argument funkce main() představuje hodnoty těchto argumentů. Jeho typ je pochopitelně pole ukazatelů na řetězce, neboť jimi argumenty příkazové řádky skutečně jsou.

Je dobrým zvykem popsané argumenty funkce main() pojmenovat argc a argv, kde c znamená count a v znamená value.

Program zobrazující argumenty, s nimiž byl spuštěn následuje:

/************************************************/
/* 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 lepší představu se podívejme, jak bude vypadat situace s argumenty příkazového řádku při spuštění příkladu. Přiznejme, že první argument, tedy cmd_ln04, bude do prostředí spuštěného programu rozvinut v úplné podobě. Nicméně věřme, že obrázek je, přes drobnou nepřesnost, užitečný.

Na závěr rozsáhlé kapitoly věnované polím a ukazatelům si zopakujme hlavní zásady pro práci s nimi:


Výsvětlivky:

1 Zanedlouho uvidíme, že i prostřednictvím ukazatele.
2 Pokud ovšem aritmetice ukazatelů porozumíme.
3 Nezapomeňme na unární operace inkrementace a dekrementace. Představují jen jiný zápis přičtení či odečtení jedničky.
4 Týká se pouze operace odčítání dvou ukazatelů.
5 Proměnné mají po deklaraci nedefinovanou hodnotu.
6 Symbol <--> chápejme jako ekvivalenci. Můžeme jej číst například "je totéž".
7 A OS který nám ji umí poskytnout.
8 S formálním argumentem předaným hodnotou si to můžeme dovolit. Skutečný argument zůstane nezměněn.
9 Jinak řečeno, jsou to ukazatele.
10 Přistupujeme k nim pomocí bázové adresy základního pole a indexu. Z tohoto pohledu jsou opravdu nepojmenované.
11 Tuto podkapitolu jsme nemohli zařadit přímo do kapitoly věnované funkcím, neboť v tom místě jsme ještě dostatečně nepoznali ukazatele.


Předchozí kapitola

Obsah

Začátek

Následující kapitola


Název: Programování v jazyce C
Autor: Petr Šaloun
Do HTML převedl: Kristian Wiglasz
Poslední úprava: 19.11.1996