KURZ JAZYKA C - 1. DÍLPavel Číž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 */ Vytlačiť článok
Pozn.: články boli naskenované ako text a preto obsahujú aj zopár chýb. Taktiež neručíme za zdrojové kódy (Asm, C, Arexx, AmigaGuide, Html) a odkazy na web. Dúfame, že napriek tomu vám táto databáza dobre poslúži.
Žiadna časť nesmie byť reprodukovaná alebo inak šírená bez písomného povolenia vydavatela © ATLANTIDA Publishing
none
|