Predchozi kapitola

Obsah

Konec

Priloha B

Priloha A: Tvorba projektu


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.

  1. Prevod vyctoveho typu, urcujiciho den v tydnu na retezec. Rekneme, ze si ukazeme vicejazycnou variantu.
  2. Pro celociselny argument funkce pro vypocet faktorialu a hodnoty clenu Fibonacciho posloupnosti.

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.


Predchozi kapitola

Obsah

Zacatek

Priloha B


Nazev: Programovani v jazyce C
Autor: Petr Saloun
Do HTML prevedl: Kristian Wiglasz
Posledni uprava: 29.11.1996