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.
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.
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ů).
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
*/
Ř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]
až 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
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".
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í:
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.
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) */
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:
p
ukazatel na nějaký typ, pak *p
je právě ta hodnota, na kterou ukazuje 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.
Název: | Programování v jazyce C |
Autor: | Petr Šaloun |
Do HTML převedl: | Kristian Wiglasz |
Poslední úprava: | 19.11.1996 |