AMIGA REVIEW online
  Uvodná stránka     Software     Hry     Obaly     Download     Amiga na PC     Amiga Forever  

Kurz jazyka C - 9. díl

Pavel Čížek

Vítám Vás u dalšího (pravděpodobně předposledního) pokračování našeho povídání o jazyce C. Dnes zdárně dokončíme to, co jsme minule načali.

Minule jsme vytvořili základ funkce RozeberVyraz, která měla zadaný řetězec rozložit na jednotlivé elementy. Zatím jsme se stále omezovali na výrazy obsahující pouze sčítání a násobení (s případnými závorkami vyznačujícími prioritu). Část, kterou jsme vytvořili, prošla zadaný řetězec a rozdělila jej na jednotlivé elementy - sčítance v 1. úrovni, popř. součinitele v 2. úrovni. Neukázali jsme si však, jak tyto jednotlivé elementy dále zpracovat jedná se totiž opět o samostatné výrazy (např. v závorce můžete mít opět libovolný výraz obsahující sčítání a násobení). To nás čeká právě teď.
Jak jste si jistě všimli, typ výrazu byl vždy určen již při jeho vytváření. Nyní tedy stačí projít vytvořené elementy (= parametry nadřazené operace dané parametrem vyraz, například jednotlivé sčítance). Podle typu elementu budeme pokračovat v jeho zpracování různým způsobem. Zatím uvažované typy zahrnují TE_NASOBENI (násobení), TE_ZAVORKY (výraz v závorkách) a TE_CISLO (číslo v dekadickém zápisu). Vše je opět realizováno pomocí for-cyklu, nebol potřebujeme projít všechny vytvořené elementy. Ukazatel na první parametr = element výrazu jsme uložili do položky parametr struktury vyraz. Stačí tedy použít již nepotřebný ukazatel aktualni, přiřadit mu na počátku ukazatel na první uzel seznamu parametrů a po té vše projít (ukazatel na následující uzel v seznamu je v položce dalsi). Jak víme, poslední uzel seznamu jsme si označili tím, že položka dalsi obsahuje NULL. Při volbě akce pro daný element nám opět pomůže příkaz pro selekci switch opět vybíráme jednu z možností dle typu výrazu (později mohou přibýt různé funkce apod.). Volba je prováděna na základě položky typ ve struktuře Element. Popisovaná část funkce RozeberVyraz je uvedena na jiném místě této dvoustrany.
V případě, že se jedná o číslo (TE_CISLO), stačí ono číslo převést z dekadického zápisu v části řetězce s výrazem na číslo v plovoucí desetinné čárce. Pozice v řetězci je stejně jako u všech dosud nerozebraných výrazů určena položkami zacatek a konec ve struktuře Element. Stačí tedy použít knihovní funkci sscanf (totéž jako scanf, ale načítá formátovaný vstup z řetězce místo z klávesnice). Číslo si ukládáme do položky cislo.
Pokud náš sčítanec byl součin (TE_NASOBENl), je třeba jej rozdělit na jednotlivé součinitele - to můžeme provést pomocí funkce RozeberVyraz (tj. pomocí rekurentního volání - funkce volá sama sebe). Stačí zadat, že se jedná o operaci vyšší prioritou (tedy násobení), kterou jsme minule značili UROVEN_2. Pokud rozbor neuspěje (chyba ve výrazu nebo nedostatek paměti), opouštíme opět funkci a vracíme FALSE = neúspěch; v opačném případě je vše v pořádku.
Poslední zatím uvažovanou variantou jsou závorky (TE_ZAVORKY). V tomto případě řetězec určený položkami zacatek a konec výraz i s krajními závorkami. Tyto závorky ovšem nejsou součástí výrazu, kterému vyznačují prioritu, a je tedy třeba je vynechat. V podstatě nejde o nic jiného než posunout ukazatel zacatek o jeden znak dále a ukazatel konec o znak vpřed. Pak opět stačí zavolat rekurentně naši funkci RozeberVyraz, tentokrát však na nižší úrovni (UROVEN_1), nebol závorky mohou obsahovat i sčítání. Všimněte si, že typ měníme z TE_ZAVORKY na TE_SCITANI tím, že je pro výraz v závorce vytvořen samostatný element, je priorita již vyjádřena a není třeba brát závorky jako zvláštní operátor, vnitřek stačí posčítat.
A to je vše, celá funkce Rozeber Vyraz. Jak vidíte, stačí nám vlastně umět provést rozbor na jedné úrovni (např. rozdělení na sčítance či součinitele) a zbytek zajistí rekurze. Pokud se na celou funkci podíváte, zjistíte, že se v ní na 3 místech opakuje v podstatě tatáž posloupnost úkonů; jedná se o vytvoření další struktury pro parametr = element. V takovém případě je samozřejmě vhodnější vytvořit funkci, která by danou činnost vykonávala, a tuto funkci pouze na příslušných místech zavolat. Ušetříme tím mnoho místa i námahy. Stačí zjistit, které údaje jsou pro vytvoření potřeba a ty použít jako parametry funkce pro vytvoření nového elementu; návratová hodnota je jasná- adresa nově vytvořené struktury nebo NULL v případě neúspěchu. Vše si můžete prohlédnout v zdrojovém kódu programu, který jako obvykle naleznete na coverdisku.

Pomocné funkce
Zbývá doplnit pomocné funkce používali jsme totiž PreskokZavorky a PreskokCisla. Druhou jmenovanou funkci jsme vytvořili již dříve, není proto třeba se jí zabývat. Popišme si, jak přeskočit závorku.
Je to velmi jednoduché. Na počátku deklarujeme proměnnou zavorky, která bude obsahovat počet otevřených závorek. Pokud ukazatel uk umístíme za levou závorku a počet závorek nastavíme na 1, stačí procházet řetězec znak po znaku a měnit počet závorek; pokud narazíme na (, zvětšíme počet závorek, najdeme-li ), počet otevřených závorek naopak snížíme. Jakmile bude počet závorek roven nule, víme, že jsme načetli uzavírací (pravou) závorku k závorce, na niž původně ukazatel uk ukazoval. Stačí vrátit ukazatel na znak za závorkou.
Všimněte si, že testovaná podmínka má tvar *uk && zavorky. Takto ji lze zapsat proto, že nenulová hodnota (tj. nějaké otevřené závorky) reprezentují TRUE (pravda), nulová hodnota (všechny závorky uzavřeny) reprezentuje FALSE (pravda). Podobná situace je u *uk = znak, na nějž ukazuje uk (nulový znak totiž označuje konec řetězce).

Uvolnění paměti
Rozbor výrazu tak, jak jsme si jej popsali, jsme mimo jiné prováděli proto, aby bylo možno rychle a efektivně opakovaně vyhodnocovat výraz. Pokud už daný výraz nepotřebujeme (rozložený na elementy), je nutno ho odstranit. Při vytváření jsme totiž alokovali pro každou strukturu Element paměť = vyžádali jsme ji od operačního systému pro svoje účely. Pokud bychom ji po použití neuvolnili, nebude ji moci nikdo dále používat. A protože struktura Element má velikost 30 bytů a u složitějšího výrazu těchto elementů může být například více než 100, záhy by paměť začala docházet (na jeden výraz by se spotřebovalo více 3 KB). Pokud se nemýlím, podobnou věc v podstatě dělá program Maple - po nějaké době práce prostě dojde paměť a musíte ho ukončit (pak už ji uvolní) a pak znovu spustit.
Uvolnění paměti provedeme rekurentně vytvoříme funkci UvolniVyraz (viz výpis na jiné části této dvoustrany), která nejprve uvolni paměť daného elementu. To provede tak, že nejprve uvolní paměť, v níž jsou struktury odpovídající jednotlivým parametrům, a pak paměť obsahující vlastní výraz. Podívejme se na tuto funkci podrobněji.
K uvolnění paměti slouží funkce free, které stačí zadat adresu, kterou nám vrátila funkce malloc či calloc. Jediným parametrem funkce je ukazatel na uvolňovanou strukturu Element (vyraz). Část pro uvolnění paměti jednotlivých parametrů je velice podobná výše popisované části pro rozebrání již oddělených parametrů ve funkci RozeberVyraz. I zde máme ukazatel na Element, který na počátku nastavíme na první parametr výrazu (vyraz->parametr) a pomocí for-cyklu procházíme jednotlivé parametry. Ukončovací podmínka má tvar pomelem - to stačí, nebol jsme si poslední parametr označili tak, že do položky dalsi jsme uložili NULL. A stejně jako u čísel, kde nulová hodnota znamená FALSE (nepravda) a nenulová TRUE (pravda), se i ukazatel obsahující NULL chápe jako FALSE a ukazatel obsahující cokoliv jiného (adresu) jako TRUE.
Hlavní částí těla cyklu je podmínka. Pokud je totiž typ parametru číslo (TE_CISLO), tak tento parametr již žádné další svoje parametry nemá a stačí uvolnit jen paměť odpovídající struktuře Element (free(pomelem)). V opačném případě je nutno zavolat rekurentně funkci UvolniVyraz pro právě uvolňovaný parametr. Všimněte si jedné drobnosti. Při procházení parametrů ve funkci RozeberVyraz jsme prostě použili ve for-cyklu pro nalezení dalšího parametru příkaz pomelem = pomelem->dalsi. Zde tomu tak není - obsah položky dalsi se ukládá do pomocné proměnné pomelem2 a po uvolnění se paměti se okopíruje do pomelem. Je tomu tak proto, že v momentě, kdy uvolníme paměť ji už nemůžeme používat. Ačkoli to není zcela korektní, mohli byste teoreticky i po uvolnění z tohoto místa v paměti číst, jenže mezi tím si již volnou paměť mohl zabrat jiný program, naplnit ji svými údaji a z vašeho hlediska budou v daném místě paměti nesmysly. Proto je třeba nejprve načíst ukazatel na další parametr (dokud paměť ještě "vlastníme") a po té ji teprve uvolnit.
Jakmile uvolníme paměť použitou pro parametry daného výrazu, zbývá už pouze uvolnit paměť použitou pro strukturu Element popisující daný výraz. Opět používáme funkci free()

Vyhodnocení výrazu
Tak, a teď to hlavní - ukážeme si, jak zjistit hodnotu výrazu, který byl rozebrán na jednotlivé elementy. Znovu můžeme velice efektivně využít rekurze - pokud totiž napíšeme funkci, která na základě parametrů určí hodnotu elementu (např. sečte jednotlivé sčítance), jsme téměř hotovi. Pro jednotlivé parametry dané operace stačí zavolat rekurentně tutéž funkci, která provede vyhodnocení těchto parametrů = menších výrazů a tak stále dokola... Postup se zastaví, narazíme-li na číslo na tom není co rozebírat, jeho hodnotu jsme již zjistili.
Funkce VypoctiVyraz provádí různé věci v závislosti na typu výrazu. Vždy však výsledek uloží do reálné proměnné vysledek, který pak vrátí volající funkci. Dále budeme využívat reálnou proměnnou parametr, která bude sloužit pro uložení hodnoty parametrů.
Pokud je výraz číslo, stačí prostě vzít hodnotu tohoto čísla - tu jsme uložili do položky cislo struktury Element.
Dalším zvláštním případem jsou operace sčítání a násobení. Zvláštní jsou proto, že mají proměnný počet parametrů. Běžné funkce mají totiž zpravidla pevný počet (1 nebo 2) parametry. Vyhodnocení sčítání či násobení probíhá opět v cyklu, kdy procházíme jednotlivé parametry výrazu (první je opět vzat z vyraz-> parametr). Tyto parametry vždy vyhodnotíme pomocí rekurentního volání funkce VypoctiVyraz. Do proměnné vysledek si na počátku uložíme 0 nebo 1 (pro sčítání nebo násobení) a hodnoty jednotlivých parametrů pak této proměnné přičítáme, resp. jimi násobíme (v případě inverzních operací odčítáme a dělíme). Po projití všech parametrů je ve vysledek správná hodnota součtu / součinu.
Poslední případ charakterizovaný příkazem switch je pak vytvořen pro další "klasické" funkce jako je sinus, faktoriál či mocnina. Všimněte si, že závorky nejsou ošetřeny - nahradily jsme je totiž po rozboru operací sčítání.

Kontrola správnosti
Tak toto by mohl být váš domácí úkol zkuste si rozmyslet, jak zkontrolovat správnost výrazu; něco se kontroluje již při rozboru, správnost funkčních hodnot při výpočtu (to teprve uděláme). Každá funkce (od příště) má jistý pevný počet parametrů. Zkuste si rozmyslet, jak by vypadala funkce, která by zkontrolovala správný počet parametrů každé funkce ve výrazu.

Začlenění do programu
Ještě jsme si neukázali, jak toto vše použít - tj. co napsat do funkce main(). Je to docela prosté. Za část, v níž vytváříme řetězec s výrazem, zařadíme následující; nejprve vytvoříme základní strukturu Element pro celý výraz (ukazuje na ni ukazatel vzorec):

vzorec = malloc(sizeof(struct Element));
if (vzorec == NULL)
{
printf("Není dostatek paměti ");
return(20);
}
vzorec->dal.si = NULL;
vzorec->parametr = NULL;
vzorec->typ = TE_SCITANI;
vzorec->inverzni = FALSE;
vzorec->cislo = 0;
vzorec->zacatek = vyraz;
vzorec->konec = vyraz+strlen(vyraz)-1;

V postatě jsme alokovali paměť pro strukturu a pak jsme ji vyplnili údaji. Dalším krokem je provedení rozboru výrazu na jednotlivé části - elementy:

if(!RozeberVyraz(vzorec, UROVEN_1))
{
printf("Není dostatek paměti nebo je chyba ve výrazu ");
return(20);
}

Pak již stačí spustit vlastní výpočet pomocí funkce VypoctiVyraz a vypsat výsledek:

printf("Vysledek: %s = %G ", vyraz, VypoctiVyraz(vzorec));

V závěru bychom měli použitou paměť uvolnit:

UvolniVyraz(vzorec);

Tolik tedy dnešní, předposlední, pokračování našeho seriálu. Příště si povíme, jak naučit naši kalkulačku rozebrat výrazy s funkcemi a také něco o tom, co by se s tímto již vytvořeným jádrem dalo podniknout. Nebude toho málo.

BOOL RozeberVyraz(struct Element *vyraz, int uroven)
/* ... předchází to, co jsme vytvořili již minule */
/* rozbor jednotlivých elementů */
/* projdeme postupně všechny vytvořené parametry */
for( aktualni = vyraz->parametr; aktualni != NULL;
aktualni = aktualni->dalsi )
/* dle typu parametru provedeme další rozbor - u závorek či násobení rozebíráme rekurentně výraz = parametr,
u čísla jej načteme */
switch (aktualni->typ)
{
case TE_CISLO:
sscanf(aktualni->zacatek, "%lg", &aktualni->cislo);
break;
case TE_NÁSOBENI:
if (!RozeberVyraz(aktualni, UROVEN-2))
return(FALSE);
break;
case TE_ZAVORKY:
aktualni->zacatek++;
aktualni->konec-;
aktualni->typ = TE_SCITANI;
if (!RozeberVyraz(aktualni, UROVEN_1))
return(FALSE);
break;
}
/* všechno je v pořádku -> vrátíme TRUE */
return(TRUE);
}

 

/* pomocná funkce pro přeskočení závorky;
vstup: ukazatel na znak = na řetězec), tj. ukazuje na levou zavorku;
výstup: ukazatel na znak (= na řetězec) - bude =ukazovat na znak;
následující za přeskakovanou závorkou */
char *Preskokzavorky(char*uk)
{
/* počitadlo otevřených závorek na počátku je "otevřená" 1 - ta, na niž =ukazuje ukazatel uk */
int zavorek = l;
/* opakovaně zkoumáme znaky řetězce - ukončíme, pokud narazíme na konec řetězce (*uk == ) nebo na uzavírací závorku (zavorek _= 0) průbežně měníme počet otevřených / uzavřených závorek */
for(uk++; *uk && zavorek; uk++)
if (*uk == ()
zavorek++;
else if (*uk == ))
zavorek--;
/* vrátíme ukazatel na znak za závorkou ... ten je v uk */
return(uk);
}

 

/* následující funkce vypočte hodnotu zadaného výrazu */
double VypoctiVyraz(struct Element *vyraz)
{
struct Element *pomelem;
double vysledek, parametr, parametr2;
/* postupujeme dle typu výrazu; pokud se jedná o sčítání či násobení, je parametrů libovolný počet - ošetříme zvlášť jinak mají funkce vždy jen jeden či dva operátory, speciální případ je číslo - jeho hodnotu máme zapsána přímo ve struktuře Element */
if (vyraz->typ -= TE_CISLO)
vysledek = vyraz->cislo;
else if ( (vyraz->typ == TE_SCITANI) ||
(vyraz->typ == TE NASOBENI))
{
vysledek = (vyraz->typ == TE_SCITANI) ? 0 : 1;
/* postupně projdeme jednotlivé parametry výrazu, zjistíme i jejich hodnotu a aplikujeme na ně příslušnou operaci (přičtení či násobení) */
for( pomelem = vyraz->parametr; pomelem; pomelem = pomelem->dalsi)
{
parametr = VypoctiVyraz(pomelem);
if (pomelem->inverzni)
if (vyraz->typ == TE_SCITANI)
parametr *= -1;
else parametr = 1 / parametr;
if (vyraz->typ == TE SCITANI)
vysledek += parametr;
else vysledek *= parametr;
}
}
else
/* ostatní operátory, které mají 1 parametr */
{
/* zde budou případné další typy výrazů... */
}
/* vrácení získané hodnoty */
return(vysledek);
}

 

/* následující funkce !uvolní pamětpoužitou pro lstruktury popisující zadaný výraz */
void UvolniVyraz(struct Element *vyraz)
{
struct Element *pomelem, *pomelem2;
/* postupně projdeme jednotlivé parametry výrazu a uvolníme jejich pamět*/
for(pomelem = vyraz-> parametr; pomelem; )
{
/* zapamatujeme si dalsi parametr, pak rovnou ,uvolníme pamětjedná-li se o číslo, jinak použijeme rekurentně tuto funkci */
pomelem2 = pomelem->dalsi;
if ( pomelem->typ == TE_CISLO)
free(pomelem);
else
UvolniVyraz(pomelem);
pomelem = pomelem2;
}
/* uvolnění paměti vlastní struktury vyraz */
free(vyraz);
}

Vytlačiť článok


© ATLANTIDA Publishing Všechna práva vyhrazena.
Žádna část nesmí být reprodukována nebo jinak šířena bez písemného svolení vydavatele.



Amiga na Vašem PC rychle, snadno a zdarma!


none

AMIGA REVIEW

57 ( 11-12 / 2000 )
56 ( 9-10 / 2000 )
55 ( 7-8 / 2000 )
54 ( 5-6 / 2000 )
53 ( 3-4 / 2000 )
52 ( 1-2 / 2000 )
 
51 ( 12 / 1999 )
50 ( 11 / 1999 )
49 ( 10 / 1999 )
48 ( 9 / 1999 )
46-47 ( 7-8 / 1999 )
45 ( 6 / 1999 )
44 ( 5 / 1999 )
43 ( 4 / 1999 )
42 ( 3 / 1999 )
41 ( 2 / 1999 )
40 ( 1 / 1999 )
 
39 ( 12 / 1998 )
38 ( 11 / 1998 )
37 ( 10 / 1998 )
36 ( 9 / 1998 )
35 ( x / 1998 )
34 ( x / 1998 )
33 ( 1-2 / 1998 )
 
32 ( 11-12 / 1997 )
31 ( 9-10 / 1997 )
30 ( 7-8 / 1997 )
29 ( 6 / 1997 )
28 ( 5 / 1997 )
27 ( 4 / 1997 )
26 ( 3 / 1997 )
25 ( 2 / 1997 )
24 ( 1 / 1997 )
 
23 ( 12 / 1996 )
22 ( 11 / 1996 )
21 ( 10 / 1996 )
20 ( 9 / 1996 )
18-19 ( 7-8 / 1996 )
17 ( 6 / 1996 )
16 ( 5 / 1996 )
15 ( 4 / 1996 )
14 ( 3 / 1996 )
13 ( 2 / 1996 )
12 ( 1 / 1996 )
 
11 ( 12 / 1995 )
10 ( 11 / 1995 )
9 ( 10 / 1995 )
8 ( 9 / 1995 )
7 ( 7 / 1995 )
6 ( 5 / 1995 )

ATLANTIDA NEWS

5 ( 3 / 1995 )
4 ( 1 / 1995 )
 
3 ( 11 / 1994 )
2 ( 9 / 1994 )
1 ( 7 / 1994 )
0 ( 5 / 1994 )