Hlavní navigace

Matematika v příkazové řádce IX - utilita calc (4)

22. 3. 2006
Doba čtení: 13 minut

Sdílet

V deváté části seriálu věnovaného matematicky zaměřeným aplikacím provozovaným zejména z příkazového řádku či z textového terminálu dokončíme popis aplikace calc. Ukážeme si práci s uživatelsky definovanými objekty, formátováním výstupu a také vstupně/výstupními operacemi, včetně manipulace s obsahem souborů.

Obsah

1. Objekty v calcu
2. Funkce určené pro práci s objekty
3. Příkaz print a formátovaný výstup pomocí funkce printf()
4. Vstupně/výs­tupní operace
5. Funkce určené pro práci se soubory
6. Další vlastnosti calcu
7. Obsah dalšího pokračování tohoto seriálu

1. Objekty v calcu

V předchozím pokračování tohoto seriálu jsme si ukázali práci s užitečnými datovými typy seznam (list) a matice (matrix). Dalším datovým typem je objekt (object), který v calcu vlastně nahrazuje a v jednom směru i rozšiřuje známé céčkové struktury, i když pojmenování tohoto strukturovaného datového typu poněkud mylně navozuje dojem plně objektově orientovaného jazyka. Objekty v calcu se od struktur známých z programovacího jazyka C liší především v tom, že je pro ně možné deklarovat „operátorové“ funkce, které po své aplikaci (zavolání) na uživatelsky definované objekty provádějí základní operace jako je sčítání, odečítání, násobení, dělení, porovnání, inicializaci, tisk hodnoty objektu atd. Pokud jsou tyto funkce vytvořeny (nadeklarovány), použijí se pro provádění všech základních operací.

Vzhledem ke způsobu své implementace však není možné calcovské objekty použít ve smyslu objektově orientovaného programování, tj. neposkytují v plné míře vlastnosti dědičnosti (pouze skládání), zapouzdření ani polymorfismu. Ve své nejjednodušší podobě je nový typ objektu (v OOP by se de facto jednalo o třídu) definován příkazem obj, který má následující podobu:

obj jméno_typu_objektu { datové_položky_objektu }; 

Takto vytvořený objektový typ je považovaný za globální, tj. po první deklaraci jména typu objektu by toto jméno nemělo být v běžícím skriptu již nikdy předeklarováno. V případě, že objekt obsahuje více datových položek, je nutné je od sebe oddělit čárkami. Pokud je typ objektu výše uvedeným příkazem definován, je možné na globální úrovni vytvořit proměnnou tohoto typu (tj. instanci třídy) deklarací:

obj jméno_typu_objektu jméno_proměnné 

Na lokální úrovni, tj. uvnitř funkcí, je situace komplikovanější, protože proměnné je zapotřebí nejprve vytvořit, tj. použít příkaz global či local, a teprve poté použít výše uvedený příkaz obj pro změnu typu proměnné na objektový typ. Typickým příkladem užitečného typu objektu nahrazujícího strukturu může být objekt reprezentující úsečku ležící v rovině. Úsečku je možné reprezentovat mnoha různými (rafinovanými) způsoby, my však použijeme přímočarý způsob, při kterém se uchovávají souřadnice obou krajních vrcholů úsečky. Nový datový typ se vytvoří příkazem:

obj line2d {x1, y1, x2, y2}; 

Všimněte si, že pokud jsou datovými složkami objektu primitivní datové typy, tj. přirozená, reálná i komplexní čísla, pravdivostní hodnoty a řetězce, není u nich zapotřebí deklarovat jejich typ; ten se za běhu programu odvodí přímo z hodnoty uložené do datové složky. Instance objektu typu line2d se vytvoří velmi jednoduše:

obj line2d a; 

Při tomto způsobu vytvoření (globální) objektové proměnné a jsou do všech jejích datových složek implicitně přiřazeny nulové hodnoty. K datovým položkám instance objektu se přistupuje, podobně jako v mnoha dalších programovacích jazycích, pomocí operátoru tečky, který je vložen mezi název instance objektu a název jeho datové položky:

a.x1=1;
a.y1=2;
a.x2=3;
a.y2=4; 

O výsledku přiřazení se můžeme jednoduše přesvědčit zadáním příkazu print s názvem objektové proměnné:

print a;
obj line2d {1, 2, 3, 4} 

Při vytváření instance objektu je možné provést přiřazení hodnot do jeho datových složek – ve své podstatě se vlastně jedná o velmi jednoduchou náhradu konstruktorů s odlišnou syntaxí jejich volání (to se v C++ a Javě zajistí pomocí operátoru new s příslušnými parametry). Přiřazení hodnot do datových složek se provádí tím způsobem, že se ihned za uvedením jména proměnné zapíše znak přiřazení a poté ve složených závorkách hodnoty, které se mají do datových složek přiřadit. Pořadí hodnot přitom odpovídá pořadí datových složek uvedeném při deklaraci typu objektu. Vzhledem k tomu, že je tento příkaz volán dynamicky (tj. až k němu dojde řízení programu), mohou být v hodnotách použity i nekonstantní výrazy:

obj line2d b={4,3,2,1};
print b;
obj line2d {4, 3, 2, 1}

x=10
y=20
obj line2d c={x+y, x-y, 10*3, 10/y}
print c;
obj line2d {30, -10, 30, 0.5} 

Kromě „céčkovky“ pojaté inicializace objektů je v calcu dostupná i vylepšená obdoba konstruktorů. Ta spočívá v tom, že se vytvoří funkce, jejíž název je shodný s názvem objektového typu. Tato funkce musí vracet hodnotu daného objektového typu, přičemž počet parametrů této funkce ani způsob jejich použití není předem předepsán, proto je možné ve funkci provádět i velmi komplikované výpočty. My si ukážeme velmi jednoduchou obdobu konstruktoru, který akceptuje dva parametry, které se chápou jako první vrchol úsečky, druhý vrchol je automaticky umístěn symetricky vůči počátku souřadné soustavy. Všimněte si zejména způsobu vytvoření lokální proměnné objektového typu, kdy je zapotřebí nejdříve proměnnou vytvořit a teprve poté změnit její typ:

define line2d(x,y)
{
    local line;
    obj line2d line;
    line.x1=x;
    line.y1=y;
    line.x2=-x;
    line.y2=-y;
    return line;
}
 

2. Funkce určené pro práci s objekty

Calc při práci s objekty poskytuje velmi příjemnou a užitečnou možnost „přetížení“ některých základních operátorů a mimo to také změnu chování příkazu print při jeho aplikaci na objektový typ. Přetížení se provádí velmi jednoduchým způsobem, který je mimo jiné použitý i v programovacím jazyku Python (i když syntaxe se liší): stačí definovat funkci, jejíž jméno začíná stejnými znaky, jako jméno daného objektového typu, po tomto jménu následuje znak podtržítka „_“ a posléze jméno odpovídající operátoru, který má být přetížen; toto jméno je možné nalézt v níže uvedené tabulce, spolu s významem operátoru pro neobjektové, tj. především numerické a v některých případech i pro řetězcové hodnoty.

Podobně jako u normálních funkcí, i u funkcí odpovídajících přetíženým operátorům je možné (a dokonce nutné) definovat parametry. To, kolik parametrů bude daná funkce mít, odpovídá typu přetíženého operátoru. Ukažme si, jakým způsobem je možné přetížit například operátor sčítání (zde nebudeme brát v úvahu, že sčítání úseček způsobem složka po složce nemá matematický smysl). Po definici funkce line2d_add() je možné tuto funkci použít pro „součet“ dvou úseček pomocí operátoru „+“, tedy nikoli přímo voláním této funkce (i když i tento způsob zápisu je samozřejmě možný):

define line2d_add(a,b)
{
    local x;
    obj line2d x;
    x.x1=a.x1+b.x1;
    x.y1=a.y1+b.y1;
    x.x2=a.x2+b.x2;
    x.y2=a.y2+b.y2;
    return x;
}

line2d_add(a,b) defined

print a;
        obj line2d {1, 2, 3, 4}
print b;
        obj line2d {4, 3, 2, 1}
print a+b;
        obj line2d {5, 5, 5, 5} 

V následující tabulce jsou vypsány funkce, po jejichž přetížení se mohou aplikovat příslušné operátory na daný objektový typ. Místo trojice znaků ??? (otazníky) je v reálných programech použito jméno objektového typu. Kromě jména funkce je zapotřebí znát i počet parametrů, který odpovídá počtu operandů. Syntaktické umístění parametrů i operandů je shodné, tj. první operand odpovídá prvnímu parametru atd. Pokud není některý z operátorů přetížen, snaží se interpreter při jeho výskytu ve zdrojovém textu programu použít smysluplné alternativy, například provádět sčítání po jednotlivých položkách atd.

Jméno funkce Počet operandů Význam
???_add 2 součet obou operandů
???_sub 2 rozdíl
???_mul 2 součin
???_div 2 podíl
???_quo 2 celočíselné dělení
???_mod 2 zbytek po dělení
 
???_sgn 1 znaménko, výsledek může nabývat hodnot –1 (záporná hodnota), 0 (nulová hodnota) nebo 1 (kladná hodnota)
???_cmp 2 porovnání, výsledek může nabývat hodnot 0 (rovnost) nebo 1 (nerovnost)
???_rel 2 relace dvou objektů, výsledek může nabývat hodnot –1 (první menší), 0 (rovnost) nebo 1 (první větší)
???_and 2 logická operace and (logický součin)
???_or 2 logická operace or (logický součet)
???_not 1 logická negace
 
???_neg 1 negace resp. záporná hodnota
???_inv 1 převrácená hodnota (inverzní hodnota vůči operaci násobení)
???_inc 1 inkrementace (měl by se vrátit její výsledek)
???_dec 1 dekrementace (měl by se vrátit její vysledek)
???_int 1 celočíselná část hodnoty objektu
???_frac 1 desetinná část hodnoty objektu
 
???_abs 2 absolutní hodnota objektu v rozsahu maximální zadané chyby
???_norm 1 druhá mocnina absolutní hodnoty objektu
???_conj 1 komplexně sdružená hodnota
???_pow 2 celočíselná mocnina
???_square 1 druhá mocnina
???_sqrt 2 druhá odmocnina
???_fact 1 faktoriál
???_scale 2 vynásobení hodnotou 2n
???_shift 2 bitový posun doleva (druhý parametr kladný) či doprava (druhý parametr záporný)
???_round 2 zaokrouhlení na zadaný počet desetinných míst
???_bround 2 zaokrouhlení na zadaný počet dvojkových (binárních) míst
 
???_print 1 funkce pro tisk hodnoty objektu, standardně vytiskne hodnoty všech složek
???_one 1 měla by se vrátit identita vůči operaci násobení, tj. hodnota, jejíž vynásobení s jiným objektem vrátí tento objekt v nezměněné podobě
???_test 1 logický test vracející hodnotu 0 nebo 1

3. Příkaz print a formátovaný výstup pomocí funkce printf()

Velmi důležitým a užitečným příkazem je příkaz print, který slouží k tisku numerických, řetězcových i objektových hodnot na standardní výstup. Předností tohoto příkazu je jednoduchá syntaxe bez nutnosti zadávání formátovacích řetězců a dalších modifikátorů (ty jsou použity v dále popsané funkci printf()). Po příkazu print je možné zadat libovolný (dokonce i nulový) počet výrazů oddělených znakem čárky nebo dvojtečky. Výrazy jsou jeden po druhém vyhodnoceny a jejich výsledek je zformátován a poslán na standardní výstup. Pokud jsou výrazy odděleny znakem čárky, je mezi jejich hodnoty vložena mezera, pokud jsou však odděleny dvojtečkou, hodnoty jsou zapsány těsně vedle sebe. Dvojtečka má i další význam: pokud je zadána za posledním výrazem, neprovede se po výpisu hodnot všech výrazů odřádkování. Následuje ukázka několika použití tohoto příkazu:

print 1,2;
1 2
print 1:2;
12
print 1,2:;
1 2; print ; 

Příkaz print je možné použít i pro objektové hodnoty, seznamy a matice. Pokud je pro daný objektový typ („třídu“) přetížen příkaz print pomocí funkce ???_print(), je použita právě tato funkce. V opačném případě se i na objektový typ použije standardní příkaz print, který vypíše obsah všech datových položek objektu. V následujícím příkladu je ukázáno, jakým způsobem je možné přetížit příkaz print pro objektový datový typ line2d tak, aby názorně vypsal pozice obou vrcholů úsečky:

define line2d_print(x)
{
    print "Prvni bod: [":x.x1:",":x.y1:"]";
    print "Druhy bod: [":x.x2:",":x.y2:"]";
}

line2d_print(x) defined

print a;
Prvni bod: [1,2]
Druhy bod: [3,4]

print b;
Prvni bod: [4,3]
Druhy bod: [2,1]

print c;
Prvni bod: [30,-10]
Druhy bod: [30,0.5] 

Kromě příkazu print je možné pro formátovaný výstup použít také funkci printf(), která ideově vychází ze shodně nazvané céčkovské funkce (je velká škoda, že podobná funkce se nenachází například ve standardní knihovně programovacího jazyka Java, takže je nutné použít externí knihovny). Funkce printf() akceptuje jako první parametr formátovací řetězec, za nímž následuje libovolný počet dalších parametrů; tento počet přitom musí odpovídat počtu parametrů udaných ve formátovacím řetězci, kde každému parametru odpovídá sekvence řídicích znaků začínajících znakem „%“ (procento) za nímž následuje udání celkového počtu číslic (znaků), počtu číslic za desetinnou čárkou a specifikátor formátu.

Celkový počet číslic ani počet číslic za desetinnou čárkou není zapotřebí zadávat, v tomto případě se použijí „optimální“ hodnoty vedoucí k co nejstručnějšímu a nejpřehlednějšímu zápisu. Specifikátor formátu je představován nepovinným znakem „l“ následovaného jedním ze znaků „d“, „s“, „c“, „f“, „e“, „r“, „o“, „x“ a „b“. Pomocí těchto znaků se určuje, jakým způsobem bude zadaná numerická hodnota interpretována:

Specifikátor formátu Význam
b celé číslo vypsané ve dvojkové (binární) soustavě
o celé číslo vypsané v osmičkové soustavě
x celé číslo vypsané v šestnáctkové soustavě
r reálné číslo vypsané v kompaktním tvaru (dokonce i v podobě zlomku)
e reálné číslo vypsané v exponenciální podobě
f reálné číslo vypsané v rozvinutém tvaru s desetinnou čárkou
d,s,c výpis odpovídá aktuálně nastavenému režimu, implicitně se jedná o čísla vypsaná v desítkové soustavě

Příklad použití funkce printf() pro primitivní numerické typy:

printf("%x", 255);
0xff
printf("%b", 255);
0b11111111
printf("%o", 255);
0377

printf("%10.5f", 1/3);
~0.33333
printf("%e", 1/3);
~3.33333333333333333333e-1
printf("%r", 1/3);
1/3 

Funkce printf() sice v některých ohledech zaostává za svým céčkovským protějškem (například chybí možnost dodatečně parametrizovat počet číslic na výstupu), zato je možné ji použít i pro strukturované datové typy, včetně seznamů a matic.

4. Vstupně/výstupní operace

Aplikace calc obsahuje několik funkcí, které je možné použít k provádění vstupně/výstupních operací. Repertoár těchto funkcí sice není úplný, to však nemusí být na škodu, protože calc je určen především jako utilita volaná ze shellovských skriptů, takže zpracování vstupů a výstupů je možné provést už v těchto skriptech a calc může provádět pouze operace, ke kterým je primárně určen, tj. zpracování numerických dat. Při programování skriptů je zapotřebí dát pozor na to, že některé dále uvedené funkce mají problémy se zpracováním binárních dat a v některých případech i s různým kódováním dat textových.

Základem všech vstupně/výstupních operací je dvojice funkcí fopen() a fclose(). První zmíněná funkce slouží k otevření souboru pro čtení a/nebo zápis, druhá funkce soubor naopak uzavírá a interně uvolňuje handle přiřazený k souboru. Pro čtení dat ze souboru je možné použít funkce fgetline(), fgetc(), fgets() a fgetstr(). Pokud je zapotřebí načítat numerické hodnoty, je možné postupovat tak, že se nejprve tyto hodnoty načtou do řetězce a posléze se řetězec vyhodnotí funkcí eval() – tato funkce dokonce zpracuje i příkazy a výrazy zapsané v řetězci, proto je nutné ji používat s vědomím rizika zavlečení „trojských koní“. Zápis dat zajistí funkce fputc(), fputs() a zejména funkce fprintf(), která nabízí stejné možnosti formátování výstupu jako funkce printf().

Mezi pomocné obslužné funkce patří fflush() (vyprázdnění bufferu), ferror() (dotaz na chybu vzniklou při poslední operaci se souborem) a feof() (dotaz, zda poslední operace čtení nedosáhla konce souboru). Poslední popisovanou funkcí je files(), která vrací interní identifikátor souboru otevřeného pod zadaným indexem. První tři indexy jsou obsazeny soubory (datovými proudy) standardního vstupu, standardního výstupu a chybového výstupu. Samozřejmě je možné (prostředky shellu) provést přesměrování těchto „souborů“.

5. Funkce určené pro práci se soubory

V následující tabulce jsou vypsány ty nejdůležitější funkce, které je možné použít při práci se soubory. Všimněte si podobnosti názvů některých funkcí (a jejich parametrů) s funkcemi dostupnými ve standardní céčkovské knihovně:

Jméno funkce Význam
files tato funkce vrací soubor otevřený pod zadaným číslem, včetně implicitních „souborů“ s čísly 0, 1 a 2
fopen otevření souboru pro čtení, zápis, přepis či přidání na konec souboru
fgetline načtení celého řádku ze souboru, a to včetně případných znaků NUL
fgetc načtení pouze jednoho znaku ze souboru
fgets načtení řádku ze souboru
fgetstr načtení řetězce ze souboru (řetězec je ukončen znakem NUL, nikoli koncem řádku)
fputc zápis jednoho znaku do souboru
fputs zápis řetězce do souboru
fprintf formátovaný výstup do souboru, význam této funkce odpovídá výše popsané funkci printf()
fflush zápis obsahu celého bufferu do souboru s následným vyprázdněním bufferu
ferror tato funkce vrátí nenulovou hodnotu v případě, že některá souborová funkce selhala
feof tato funkce vrátí nenulovou hodnotu v případě, že poslední operace čtení dosáhla konce souboru

6. Další vlastnosti calcu

V šesté kapitole jsou popsány některé další zajímavé vlastnosti calcu, kterým jsme se dostatečně či vůbec nevěnovali v předchozích pokračováních tohoto seriálu. První zajímavou a současně i v praxi užitečnou vlastností, kterou se calc odlišuje od svého ideového předchůdce (tj. jazyka C), je způsob indexování položek vektorů a matic. V céčku jsou datové položky uložené pomocí těchto datových typů indexovány vždy od nuly (místo vektorů a matic se používá pole), to však nemusí ve všech případech vyhovovat, protože například numerický zápis sloupců a řádků matic v matematice většinou začíná od jedničky. Jak jsme si již řekli v předchozím pokračování tohoto seriálu, je v calcu možné vytvořit dvourozměrnou matici pomocí následující deklarace:

mat matice1[4][5]; 

což je ekvivalentní „matematickému“ zápisu (taktéž v calcu podporovanému):

mat matice1[4, 5]; 

Při provedení obou předešlých deklarací došlo k vytvoření matice, jejíž první index může nabývat hodnot 0–3 a druhý index hodnot 0–4. Existuje však i alternativní způsob deklarace matic, ve kterém je možné uvést nejnižší a nejvyšší hodnotu indexu, přičemž obě zadané hodnoty představují uzavřený interval („včetně“). Následuje příklad zápisu tohoto alternativního způsobu deklarace:

mat vektor1[1:3];
mat matice2[1:4, 1:5]; 

Nejprve je vytvořen vektor se třemi složkami přístupnými přes indexy 1, 2 a 3. Posléze je vytvořena dvourozměrná matice, jejíž první index může nabývat hodnot 1–4 a druhý index hodnot 1–5. Samozřejmě není nutné indexy číslovat pouze od jedničky; povolené jsou všechny celočíselné hodnoty, včetně hodnot záporných:

mat vektor2[-100:-1]
mat matice3[-5:5, -5:5] 

Vzhledem k tomu, že deklarace matic a vektorů je prováděna dynamicky za běhu skriptu (což je výrazný rozdíl oproti céčku, kde je deklarace zpracována již při překladu programu), není nutné při nastavování dimenzí používat pouze konstantní výrazy. Povoleny jsou všechny výrazy, které se vyhodnotí na celé číslo, což je ilustrováno na následujících příkladech:

root_podpora

prvniDimenze=10;
druhaDimenze=20;
mat matice4[prvniDimenze*3, druhaDimenze/2]; 

7. Obsah dalšího pokračování tohoto seriálu

V následujícím pokračování tohoto seriálu se začneme věnovat popisu aplikace sc, což je plnohodnotný tabulkový procesor, který pro svůj běh nevyžaduje žádné grafické uživatelské prostředí (GUI), ale vystačí si s pouhým textovým terminálem.

Byl pro vás článek přínosný?

Autor článku

Vystudoval VUT FIT a v současné době pracuje na projektech vytvářených v jazycích Python a Go.