KURZ JAZYKA C - 1. DÍL

Pavel Čížek

Vážení čtenáři, vítám vás u prvního dílu kurzu programování v jazyce C. Dovolte mi pár slov na úvod. Ačkoli takovýto seriál musí být názorný, chtěl bych po nezbytném úvodu do jazyka vyložit jeho základy tak, aby co nejdříve bylo možné demonstrovat různé rysy tohoto jazyka na příkladech operačního systému Amigy a tím rozšířit náš seriál o postupné pronikání do základů systému, a to se zaměřením na OS 2.0 a výše (starší systémy ovšem úplně neopomineme, ani nemůžeme).

Ještě malé upozornění - v předchozím článku naleznete informaci o tom, jak získat do začátků nějaké to "Céčko". Na poslední chvíli tam byla doplněna informace, kterak získat za cenu asi 200 Kč speciální verzi komerčního Céčka DICE 3.0!
Programovací jazyk C vznikl jako systémový jazyk pro implementaci OS UNIX na přelomu 60. a 70. let, později vznikla norma ANSI, která na UNIX již přímo vázána není. Jazyk C je tedy od prvopočátku zaměřen na systémové programování a k němu je využíván i na počítačích Amiga. To proto, že se jedná o vyšší programovací jazyk, ale na druhé straně jej lze přeložit do velmi efektivního strojového kódu (je to v podstatě dáno výběrem konstrukcí jazyka - nepřipouští "nevhodné" konstrukce). Nástupcem Céčka je C++, kterým se však zabývat nebudeme. Nicméně některé rysy pro něj charakteristické (objektově orientované programování) jsou využity v novějších verzích Amiga OS.

Struktura programu v jazyce C
Základní jednotkou v C je funkce - vše se skládá z funkcí (i funkce samotné). Ne všechny musíme vytvářet sami - některé již existují v tzv. knihovnách funkcí (u Amigy jich máme navíc stovky v ROM). Funkce nevracející hodnotu se nazývají procedury. C však není čistě funkcionální jazyk, existují v něm globální datové objekty, jejichž stav určuje stav výpočtu. Program je pak sada definic těchto datových objektů a funkcí, přičemž jedna z funkcí se jmenuje main() - ta představuje hlavní program. Céčko používá blokovou strukturu, např. tělo každé funkce tvoří blok. V těle funkce lze používat globální datové objekty, ale navíc i lokální datové objekty - to jsou datové objekty definované uvnitř bloku a pouze uvnitř bloku jsou použitelné.
Různé funkce a globální objekty mohou být obsaženy v různých souborech, není třeba vše napsat do jednoho souboru (to by bylo u větších programů dost nepřehledné). Jeden soubor s funkcemi se nazývá modul, ten, který obsahuje funkci main(), je hlavní. Ještě než přikročíme k prvnímu příkladu, chtěl bych se zmínit o tom, co s vaším programem provede kompilátor. Nejprve jsou zdrojové texty zpracovány tzv. preprocesorem (dostaneme se k němu za chvíli), který pouze upraví text podle vašich pokynů. Pak přijde na řadu kompilátor, který z textu vytvoří objektový soubor, což je vlastně váš program převedený do "strojáku", ale neobsahuje funkce z knihoven a jiných modulů programu, pouze odkazy na ně. Nakonec přijde na řadu linker, který spojí vše dohromady (vaše moduly, funkce z knihoven, ...).

První program
A je tu první příklad... Poznamenejme, že vše, co je v programu uvedeno mezi znaky /* a */ je komentář a kompilátor si ho nevšímá. Komentáře nelze vnořovat. Komentář může (na rozdíl od ostatního textu programu kromě textových řetězců) obsahovat znaky národní abecedy, takže budeme komentovat česky (alespoň v tomto seriálu). Upozornění - jednotlivé příkazy programu mohou být odděleny libovolným počtem mezer a konců řádků.
Náš první program prostě opíše text zadaný z klávesnice (vstupu) na obrazovku (výstup). Opis bude ukončen znakem ".".
Následující program nám dobře dokumentuje charakteristické rysy jazyka C.
Obsahuje jednu instrukci preprocesoru "#include <stdio.h>". Ta preprocesoru říká, že místo ní má vložit obsah souboru "stdio.h". Je to textový soubor, kde je napsáno, co jsou námi použité funkce zač. Program dále obsahuje jedinou funkci, tu povinnou, main(). Její tělo je tvořeno blokem; blok vždy začíná `{` a končí znakem "}". V tomto bloku je definován datový objekt - buňka označená slovem "znak", která může obsahovat 1 znak.
Tělo funkce (tj. blok) je poskládáno z volání nějakých funkcí. První z nich je "printf", která vypíše text obsažený v závorkách za jejím jménem - to je parametr této funkce. Je to již předdefinovaná knihovní funkce jazyka C pro formátovaný výstup. Dalším příkazem je "znak = getchar()". "getchar()" je opět standardní funkce, která nemá žádné parametry a vrátí nám znak přečtený ze standardního vstupu (klávesnice). Příkazem "znak = getchar()" pak řekneme kompilátoru, aby tento znak uložil do paměťové buňky označené "znak". Příkaz přiřazení má v Céčku hodnotu, která je rovna tomu, co uložíme do proměnné na levé straně. Céčko nám tak dovoluje rovnou tuto hodnotu porovnat se znakem tečka "." ("!=" znamená v Céčku "není rovno"). Příkaz while(podmínka) znamená "dělej, dokud platí podmínka". V našem případě tedy: prováděj blok za "while", dokud nenarazíme na tečku. Tento nový blok obsahuje jen jednu funkci "putchar(znak)", která vypíše znak "znak" na standardní výstup. Pokud je blok tvořen jediným příkazem, nemusíme označovat jeho začátek a konec. Pokud narazíme na tečku, pokračujeme dále, a protože tam nic dalšího není, program skončí.

#include <stdio.h> /* instrukce preprocesoru */
main() /* tady začíná hlavní funkce */
{
char znak;
/* místo pro uložení dat = 1 znak */
/* následuje výzva uživateli, aby už začal psát */
/* značka znamená konec řádku, odřádkování */
printf("Tak jsem tady, můžete psát: ");
/* while ( ... ) - dokud platí závorka ... */
/* znak = getchar() - načteme znak ze vstupu */
/* a rovnou otestujeme, zda to není (!=) tečka . */
while ( (znak = getchar()) != . )
{ /* není-li to tečka ... */
putchar(znak);
/* opiš znak uložený v znak na výstup
}
/* a přejdeme opět na podmínku */
}
/* konec main(), a tedy i programu */

Poznamenejme několik důležitých věcí, kterých jste si již určitě všimli a které si dobře zapamatujte. Za prvé, každý příkaz končí středníkem a každá podmínka je v kulatých závorkách ( a ). A za druhé, Céčko rozlišuje velká a malá písmena. To znamená, že když omylem napíšete v našem programu getchar(Znak), dozvíte se, že proměnná Znak není definována (neexistuje), a pokud napíšete putchAr(), linker vám oznámí, že takovou funkci nemůže nikde najít.
Předpokládejme, že chceme navíc spočítat, kolik znaků jsme opsali. Pak bude Céčkovštější si na to vyrobit funkci, která provede kopírování a zároveň spočítá, kolik znaků okopírovala.
Zde jsou v těle funkce kopiruj() deklarovány dvě proměnné - c typu char (= znak) a pocet typu int (= celé číslo). Všimněte si, že vynulování čítače počet lze provést již přímo v deklaraci. Toto vynulování samozřejmě proběhne při každém volání funkce kopíruj().
Aktualizaci čítače provádíme při každém vypsání znaku příkazem pocet++, což zvětší číslo v proměnné pocet o 1. Tento příkaz je v bloku společně s funkcí putchar() provádějící výstup na obrazovku - provede se tedy právě tolikrát, kolikrát jsme opsali znak (celý ten blok je hlídaný příkazem while). V hlavní funkci main() přenášíme počet, který vrátila funkce kopiruj(), přímo jako argument funkce printf a není tedy třeba toto vrácené číslo nikde uchovávat.

Preprocesor
Při vytváření programů je výhodné mít možnost skládání textu z několika částí, zavádět symbolická označení apod. V Céčku nám toto umožňuje preprocesor jazyka a jeho příkazy - bude užitečné se nimi ve stručnosti seznámit, než budeme pokračovat dále. Vše si samozřejmě nakonec ukážeme na příkladu. Příkazy preprocesoru začínají vždy na počátku řádku, a to znakem #. Zde jsou ty nejdůležitější (to, co je v hranatých závorkách, nemusí být použito, je to volitelná možnost):
#define identifikátor[(argument1[,argument2,...])] [ řetězec]
#undef identifikátor
Tento příkaz slouží k několika věcem. Můžete s jeho pomocí definovat symbolickou konstantu např. #define MAX 100. Preprocesor potom projde text programu a každý výskyt textu MAX nahradí textem 100.
Tělo v této definici může být i prázdné, např. #define TEST. Potom TEST existuje, ale nemá žádnou hodnotu. Takový symbol může posloužit jako jednoduchý přepínač.
#define lze použít i k definici maker, např. #define KVADRAT(n) ((n)*(n)). Pokud potom preprocesor narazí v programu na text KVADRAT(3.5), nahradí ho textem ((3.5)*(3.5)). Asi se ptáte, proč je všude tolik závorek. Představte si, že definujeme jen #define KVADRAT(n) (n*n) a v programu máme použito KVADRAT(1+3). Pak bude tento text chybně převeden na (1+3*1+3), což jsme asi nechtěli. Příkaz #undef nám umožní definované symboly a makra zrušit.
#include <soubor> (např. #include <stdio.h>)
#include "soubor" (např. #include "defs.h")
Tento příkaz říká preprocesoru, aby místo řádku s příkazem vložil soubor (text v něm obsažený), který je uveden jako parametr příkazu. To umožní např. načíst definice systémových struktur AmigaDOSu z tzv. include-souborů (už je asi jasné, proč se jim tak říká). Varianta s úhlovými závorkami <...> slouží k vložení systémového souboru, je hledán v nějakém standardně nastaveném adresáři podle aktuální implementace jazyka. Naproti tomu varianta s uvozovkami "..." hledá soubor v aktuálním adresáři uživatele - je tedy vhodná pro vkládání vašich vlastních souborů. Pro tyto vkládané soubory je doporučena koncovka ".h" (= hlavička = angl. header) pro snadnou identifikaci (např. "stdlib.h"). Následující příkazy umožňují podmíněný překlad:
#if omezený_konstantní_výraz
#ifdef identifikátor
#ifndef identifikátor
#else
#endif
Příkazy #if, #ifdef, #ifndef testují jistou podmínku a když je splněna, jsou v textu ponechány řádky za #if až k prvnímu výskytu #else nebo #endif. Pokud splněna není, jsou vynechány. Narazí-li preprocesor nejprve na #endif, pak za tímto příkazem pokračuje dále již normálním způsobem. Jestliže narazí na #else a podmínka u #if byla splněna, pak řádky mezi tímto #else a prvním následujícím #endif vypustí; pokud podmínka u #if nebyla splněna, ponechá je v textu.
Příkaz #ifdef testuje pouze existenci daného symbolu (pokud symbol existuje, je podmínka splněna), #ifndef testuje neexistenci daného symbolu. Jak už víme, lze existenci symbolu ovlivňovat příkazy #define a #undef. Příkaz #if testuje pravdivost za ním následující podmínky, např. #if 2*SIRKA <= DELKA.
Zmiňme se ještě o jednom příkazu, který byl doplněn normou ANSI. Je to příkaz #pragma, který slouží ke sdělení neznámých skutečností kompilátoru a je tedy různý v různých implementacích jazyka. Na Amize se často používá k tomu, aby kompilátor mohl přímo volat funkce systémových knihoven. Pomocí #pragma lze sdělit kompilátoru např. registry procesoru, v nichž mají být parametry, adresu funkce apod.
Na této stránce je slíbený příklad, na němž si všechno předvedeme. Bude počítat obvod a obsah kruhu se zadaným poloměrem. Na tomto příkladu máme možnost vidět použití většiny možností preprocesoru, ale i některé nové prvky. Aby bylo možné si pamatovat zadaný poloměr, žádáme deklarací float r o 1 buňku pro uložení reálného čísla v jednoduché přesnosti (větší přesnost = více desetinných míst). Dále si všimněte nové funkce scanf(), která slouží k (formátovanému) načítání dat do programu. Tato funkce, stejně jako printf() je deklarována v souboru stdio.h, který jsme na počátku vložili do programu pomocí #include .
Na počátku je definována konstanta ODLADOVANI. Pokud existuje, program navíc (díky podmíněnému překladu) bude vypisovat hlášení o tom, co se právě provádí. Příkazy pro tato hlášení lze pak jednoduše vypustit tak, že smažeme definici symbolu ODLADOVANI. Kromě toho jsou zde definovány symbol PI a makro KVADRÁT, které sice nejsou nezbytně nutné, ale zlepšují čitelnost programu (jak jste si určitě všimli). Jak budou tyto symboly při zpracování preprocesorem reprezentovány jsme si již objasnili v předchozím textu.
Tím dnešní část seriálu o jazyce C ukončíme. Doufám, že jsem vás příliš nevyděsil - a pokud ano, nebojte se. V příštím díle se vrhneme už na Céčko samotné bez nějakých dlouhých řečí. Přeji vám, aby se vám podařilo sehnat kompilátor Céčka, který s Vámi bude ochotně spolupracovat a usnadní nám tím další kroky. A nyní, úplně na závěr, bych rád připomenul jednu důležitou poučku (Murphyho zákon) týkající se programování: počítač zásadně dělá to, co mu řekneme, nikoliv to, co chceme, aby dělal. Nashledanou.

#include <stdio.h> /* instrukce preprocesoru */
int kopíruj() /* začátek funkce `kopíruj, vrací celé číslo */
{ /* tj. výsledek činnosti funkce je celé číslo */
char c; /* místo pro uložení 1 znaku */
int pocet = 0; /* místo pro uložení 1 celého čísla - čítač znaků */
/* na počátku je nastaven na 0 */
/* načteme znak ze vstupu */
/* a testujeme, zda to není tečka . */
while((c = getchar()) != .)
{
putchar(c); /* opiš znak na výstup */
pocet++; /* zvětši čítač, tj. počet okopírovaných znaků */
}
return(pocet); /* tomu, kdo nás zavolá, vrátíme pocet znaků */
}

main() /* tady začíná hlavní funkce */
{ /* zavoláme funkci kopiruj() a zobrazíme výsledek */
printf("- Celkový počet znaků byl %d ", kopíruj());
} /* konec main(), a tedy i programu */

 

#include <stdio.h>
/* vložíme soubor s definicemi funkcí pro standardní vstup a výstup */
#define ODLADOVANI /* definice odlaďovací konstanty */
#define PI 3.14159 /* definice symbolické konstanty PI */
#define KVADRAT(x) ((x)*(x))
/* definice makra pro výpočet 2. mocniny */
main() /* počátek hlavní funkce */
{
float r, o; /* žádáme o dvě paměťové buňky pro reálná čísla */
/* tážeme se uživatele na poloměr */
printf(" Zadej poloměr kruhu: ");
/* nová funkce - zde slouží k načtení
reálného čísla do proměnné r */
scanf("%f", &r);

#ifdef ODLADOVANI /* je-li definován symbol ODLADOVANI ... */
if (r < 0) /* testuje se chyba vstupu */
{ /* a oznamuje se uživateli */
printf(" Nelze zadavat zapornou hodnotu ... ");
r = -1 * r; /* místo r si vezmeme číslo opačné */
} /* v odlaďovacím módu vypisujeme, co právě děláme */
printf("Pocitam obvod kruhu ... ");
#endif /* konec části podmíněné existencí symbolu ODLADOVANI = podmíněný překlad */
o = 2 * PI * r; /* výpočet obvodu a vypsání na obrazovku */
printf("Obvod kruhu: %f ", o);

#ifdef ODLADOVANI /* je-li definován symbol ODLADOVANI ... */
/* v odlaďovacím módu vypisujeme, co právě děláme */
printf(" Pocitam obsah kruhu ... ");
#endif
/* výpočet obsahu a vypsání na obrazovku */
/* využíváme přitom definovaná makra */
printf("Obsah kruhu: %f ", PI * KVADRAT(r));
} /* konec programu */


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