Prosli jsme vyukou jazyka C az do konce. Presto, ze jsme jazyk zvladli, stale nam chybi neco, co ovsem neni primou soucasti jazyka. Schopnost vytvaret a spravovat rozsahlejsi projekty1.
V cem je vlastne problem? C jiz zname. Cely projekt tedy muzeme napsat do jednoho souboru. A je to. Ano, tento pristup je jiste mozny. Nejspis ovsem jen teoreticky. Predstavme si program dlouhy deset, dvacet, nebo sto tisic radku. Nase orientace v tak dlouhem zdrojovem textu by byla velmi nesnadna. Navic je malo provdepodobne, ze by tak dlouhy kod vytvarel jediny clovek2. Pokud jich bude vice, vznikl by problem se sdilenim takoveho souboru. Ted vezmeme v uvahu i specializaci. Nejen programatoru, ale i zdrojoveho kodu.
Podobne uvahy bychom mohli dale rozvijet. Nastesti nezaciname na zelene louce. Programy v C psaly zastupy programatoru pred nami. Teoretici posouvali hranici poznani. V C muzeme uspesne pracovat modularnim zpusobem. Projekt rozdelime vhodnym3 zpusobem na mensi casti. Inspiraci nam mohou byt hlavickove soubory.
Mame-li projekt rozdelen, musime byt schopni definovat zavislosti jednotlivych jeho casti. Co se stane, kdyz opravime v jednom ze souboru hlavicku funkce. Musime znovu prelozit cely projekt, nebo staci prelozit jen jeho cast. Povsimneme si, jakou vyhodu nam rozdeleni projektu muze prinest. Nemusime vzdy prekladat vsechny casti projektu. Staci prelozit jen ty zmenene, dale pak jen soubory, ktere zmena ovlivni.
Pro usnadneni spravy projektu se pouziva program make
.
Program je rizen textovym souborem, definujicim vzajemne
zavislosti souboru projektu, konfiguraci prekladace,
informace o adresarich. Je zrejme, ze moznosti jazyka,
popisujiciho zminene zavislosti musi byt pomerne
znacne. Myslenka sama zcela zapada do konstrukce Unixu (v
nem C vzniklo). Nepsat jeden rozsahly komplexni program, ale
vice malych. make
proste cte ridici
informace a podle stavu, v nemz se prave nachazi,
vyvolava pro vykonani potrebnych akci odpovidajici
programy. Informace jim predava jako argumenty. Vyhodou je
znacna pruznost. A rovnez jista nezavislost na pouzitem
prekladaci. Teoreticky i na operacnim systemu.
Projekt umoznuje nezavisly preklad svych casti.
Nakonec tyto prelozene casti spojime ve vysledny
spustitelny tvar programu. Hacek je v tom, ze casti sice
muzeme prekladat nezavisle, ovsem musime byt schopni
predavat z nich informace, potrebne pri prekladu v
castech jinych. Definujme napriklad ... . Ale radeji
opustme teoreticke uvahy. Vzdyt metologii navrhu a analyzy
je venovano mnoho rozsahlych publikaci. Nebudeme se snazit
jejich obsah shrnout v casti maleho dodatku skripta. Ukazme
si radeji zakladni myslenky na prikladu.
Projekt: Nasim ukolem je umoznit prevod vnitrniho tvaru informaci (vyctove hodnoty) na vnejsi (retezce). Dale je nasim ukolem odladit nektere matematicke funkce a umistit je oddelene. Tim umoznime jejich nezavisle pouziti4.
Po obecnem zadani poznamenejme, ze v praxi pochopitelne vime, co se od nas ocekava. Zvolme si proto konkretni funkce.
Nas projekt je evidentne ryze vyukovy. Je na prvni
pohled zrejme rozdeleni funkci do souboru. Funkci,
souvisejici s prvnim bodem, umistime do jednoho souboru,
funkce pokryvajici bod druhy do druheho souboru. Treti
soubor bude obsahovat funkci main()
. Z nej budeme
cely program ridit.
Protoze ANSI C vyzaduje deklarace pro vsechny pouzite
funkce, vytvorime k prvnimu i druhemu souboru deklarace tech
funkci, ktere budeme chtit pouzivat vne techto souboru5. Schema
naseho projektu vidime na obrazku.
Na obrazku vidime tri zakladni soubory funkce.c
, prj_demo.c
a kalendar.c
a hlavickove soubory kalendar.h
a funkce.h
. Ty deklaruji nejen funkce, ale v
pripade kalendar.h
definuji i nove datove typy
(viz vypis zdrojoveho textu dale). Proto je zaclenujeme jak
do prj_demo.c
, ale i do "jejich"
zdrojovych souboru. Ve zdrojovych souborech jsou jen definice
funkci. Pripadne definice tech objektu (typu,
promennych, funkci, ... ), ktere nechceme dat vne k
dispozici. Do prj_demo.c
musime jeste pripojit stdio.h
,
nebot obsahuje i vstupni a vystupni operace.
Obrazek obsahuje mnohem vice informaci, nez jsme dosud popsali. Vodorovna prerusovana cara oddeluje textove informace, ktere do projektu vstupuji, od jejich prekladu.
Kazdy ze zdrojovych textu *.c
muze byt
samostatne prelozen. Postacuji mu informace z hlavickovych
souboru (viz obrazek). Jejich prekladem jsou *.obj
soubory6.
Spojenim *.obj
souboru a pripojenim kodu ze
standardnich knihoven (v obrazku jsou oznaceny symbolicky),
vytvorime spustitelny tvar programu. Jeho zacatek (vstupni
bod) je jednoznacne urcen. Funkce main()
musi
byt v celem projektu prave jedina. Ve fazi spojovani se
mohou odhalit chyby pri navrhu projektu. Pokud napriklad
definujeme funkci nebo deklarujeme promennou na vice mistech.
To je casta chyba zacatecniku - definuji funkci v
hlavickovem souboru. Diky jeho zacleneni do vice modulu
ziskaji nechtene vice definic teze funkce. Jak se pak ma
zachovat spojovaci program, kdyz narazi na tyz
identifikator ve vice modulech? Jednoznacne, oznami chybu a
ukonci svou cinnost. Vsechny moduly jsou si z tohoto pohledu
rovnocenne.
Vysvetlili jsme si zavislosti v ukazkovem projektu.
Podivejme se nyni na (mirne zkracenou) ukazku make
souboru, vytvoreneho pro tento projekt. Jeho tvorba probehla
automaticky programem prj2mak
z baliku podpurnych
programu BC31.
.AUTODEPEND .PATH.obj = C:\BIN # *Translator Definitions* CC = bcc +PRJ_DEMO.CFG TASM = TASM TLIB = tlib TLINK = tlink LIBPATH = C:\BC\LIB INCLUDEPATH = C:\BC\INCLUDE # *Implicit Rules* .c.obj: $(CC) -c {$< } .cpp.obj: $(CC) -c {$< } # *List Macros* EXE_dependencies = \ funkce.obj \ prj_demo.obj \ kalendar.obj # *Explicit Rules* c:\bin\prj_demo.exe: prj_demo.cfg $(EXE_dependencies) $(TLINK) /v/x/c/P-/L$(LIBPATH) @&&| c0s.obj+ c:\bin\funkce.obj+ c:\bin\prj_demo.obj+ c:\bin\kalendar.obj c:\bin\prj_demo # no map file emu.lib+ maths.lib+ cs.lib | # *Individual File Dependencies* funkce.obj: prj_demo.cfg funkce.c prj_demo.obj: prj_demo.cfg prj_demo.c kalendar.obj: prj_demo.cfg kalendar.c # *Compiler Configuration File* prj_demo.cfg:
prj_demo.mak
copy &&| ... zkraceno -nC:\BIN -I$(INCLUDEPATH) -L$(LIBPATH) -P-.C | prj_demo.cfg
Po podrobnem rozboru projektu se jiz podivame na jeho
zdrojove texty. Zacneme hlavnim z nich. Obsahuje funkci main()
a vola ostatni funkce definovane vne nej. Najdeme v nem dva
cykly, ktere by mely zajistit spravne nacteni vstupnich
hodnot. Ty jsou pak prevedeny ci prepocteny a zobrazeny.
Odpovidajici volani funkci jsou umistena primo ve
funkci printf()
. Jejich vysledky tedy jsou pouzity
jako argumenty formatovaneho vystupu.
/******************************/ /* soubor
PRJ_DEMO.C
*/ /* ukazka maleho projektu */ /* pouziva funkce ze souboru: */ /* FUNKCE.C, KALENDAR.C */ /******************************/ #include <stdio.h> #include "funkce.h" #include "kalendar.h" int main(void) { int i = -1; tDen eden; do { printf("\nZadej den v tydnu jako cislo < 0, 6> :"); scanf("%d", &i); } while (i < 0 || i > 6); eden = (tDen) i; printf("\nZadal jsi: %s (anglicky: %s)\n", eden_sden(eden, eCZ), eden_sden(eden, eBE)); do { printf("\nZadej cele cislo do 20:"); scanf("%d", &i); } while (i < 0 || i > 20); printf("\n%d! = %0.0lf\t fib(%d) = %0.0ld\n", i, fact(i), i, fib(i)); return 0; } /* int main(void) */
Soubor kalendar.c
definuje funkci eden_sden()
.
Zajimava je definice statickeho dvourozmerneho pole
retezcu v tele teto funkce. Touto konstrukci vytvarime
rozhrani pro pole sdny
. Pole pak neni na
globalni urovni viditelne, tedy neni ani dostupne. Je
odstineno definovanymi sluzbami, ktere zajistuje prave
funkce eden_sden()
.
/******************************/ /* soubor
KALENDAR.C
*/ /* vyzaduje soubor KALENDAR.H */ /* definuje: */ /* - eden_sden() */ /******************************/ #include "kalendar.h" const char *eden_sden(tDen d, tLang l) /********************************************************/ /* prevede vyctovy den v tydnu na odpovidajici retezec */ /* ve zvolenem jazyce */ /********************************************************/ { static char *sdny[eBE+1][eNE+1] = {{"pondeli", "utery", "streda", "ctvrtek", "patek", "sobota", "nedele"}, {"Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"}}; return sdny[l][d]; } /* const char *eden_sden(tDen d, tLang l) */
Hlavickovy soubor kalendar.h
obsahuje deklaraci
funkce eden_sden()
. Jsou v nem definovany i dva
vyctove typy a rada vyctovych konstant, nalezejicich k
temto typum.
/************************************/ /* soubor
KALENDAR.H
*/ /* hlavickovy soubor pro KALENDAR.C */ /* deklaruje a definuje: */ /* - eden_sden() */ /* - tDen, tLang */ /************************************/ typedef enum {ePO, eUT, eST, eCT, ePA, eSO, eNE} tDen; /****************************************************/ /* vyctovy typ pro den v tydnu */ /* PONDELI..NEDELE -> ePO..eNE (0..6) */ /****************************************************/ typedef enum {eCZ, eBE} tLang; /****************************/ /* vyctovy typ pro jazyk: */ /* CZ - cestina */ /* BE - British English */ /****************************/ const char *eden_sden(tDen d, tLang l); /********************************************************/ /* prevede vyctovy den v tydnu na odpovidajici retezec */ /* ve zvolenem jazyce */ /********************************************************/
Soubor funkce.c
definuje funkce pro vypocet
faktorialu a Fibonacciho posloupnosti. Prvni z funkci je
zalozena na iteraci. K tomu ma cyklus for
. Druha
funkce je postavena na eleganci rekurse. Jeji jednoduchost je
ovsem draze vykoupena vyrazne nizsi vypocetni rychlosti
a vysokymi naroky na velikost zasobniku. Z tohoto duvodu je
ve funkci main()
omezen vstup na hodnoty do dvaceti.
/****************************/ /* soubor
FUNKCE.C
*/ /* vyzaduje soubor FUNKCE.H */ /* definuje funkce: */ /* - fact() */ /* - fib() */ /****************************/ #include "funkce.h" double fact(int n) /***********************/ /* vypocte n faktorial */ /* pro n < 0 vrati -1 */ /***********************/ { double f = 1.0L; for ( ; n > 0; n--) f *= n; return f; } /* double fact(int n) */ long fib(long n) /******************************/ /* vrati hodnotu n-teho clenu */ /* Fibbonaciho posloupnosti */ /******************************/ { if (n == 1) return 1; else if (n == 2) return 2; else return fib(n - 1) + fib(n - 2); } /* long fib(long n) */
Posledni soubor projektu, funkce.h
, obsahuje
hlavicky (deklarace) funkci fact()
a fib()
.
Pripomenme si, ze deklarace funkci neobsahuji telo funkce.
Maji vsak popsany argumenty a jejich typy, stejne jako
navratovy typ. Jsou ukonceny strednikem7.
/****************************/ /* soubor
FUNKCE.H
*/ /* deklaruje funkce: */ /* - fact() */ /* - fib() */ /****************************/ double fact(int n); /***********************/ /* vypocte n faktorial */ /* pro n < 0 vrati -1 */ /***********************/ long fib(long n); /******************************/ /* vrati hodnotu n-teho clenu */ /* Fibbonaciho posloupnosti */ /******************************/
Vysvetlivky:
1 Pravdou
je, ze tuto schopnost ziskame teprve tehdy, kdyz nejaky
projekt uspesne ukoncime. A radeji ne hned ten prvni.
2 V
realnem projektu je vyznamny i casovy faktor. Kolik kodu
je schopen jedinec zvladnout?
3 Tim
jsme si situaci pekne zjednodusili. Otazkou zustava, co je
to vhodny zpusob. Pro zacatek muzeme napriklad oddelit
uzivatelske rozhrani od zbyleho kodu. Vykonnou cast
programu rozdelime do souboru podle pribuznych skupin
funkci.
4 Dokonce
po jejich prelozeni i bez nutnosti nasledneho pouziti
zdrojoveho textu.
5 V nasem
pripade budeme predavat informace o vsech objektech. Timto
zpusobem se ovsem muzeme obecne chovat modularne. Ze
zdrojoveho souboru zverejnime v hlavickovem souboru pouze
informace, ktere chceme dovolit exportovat.
6 Nebudeme
popisovat detailne vice OS. Pro nazornost nam postaci popis
v prostredi MS-DOSu.
7 Pokud na
strednik zapomeneme, diky zacleneni hlavickoveho souboru
do jinych zdrojovych textu se syntakticka chyba
pravdepodobne projevi na miste, kde ji zpravidla necekame.
V jazyce C proste musime byt pozorni.
Nazev: | Programovani v jazyce C |
Autor: | Petr Saloun |
Do HTML prevedl: | Kristian Wiglasz |
Posledni uprava: | 29.11.1996 |