Abstraktní datové typy, moduly BI-PA1 Programování a Algoritmizace 1 Miroslav Baĺık, Ladislav Vagner a Josef Vogel Katedra teoretické informatiky a Katedra softwarového inženýrství Fakulta informačních technologíı České vysoké učení technické v Praze xvagner@fit.cvut.cz, vogeljos@fit.cvut.cz 4. ledna 2018
Přehled Datové typy, abstraktní datové typy. Komplexní číslo jako abstraktní datový typ. Moduly, modulární programování. Preprocesor. M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 2/27
Datové typy Klasická kniha prof. Niklause Wirtha: Algorithms + Data structures = Programs Datový typ: množina možných hodnot, množina použitelných operací. Abstraktní datový typ formální popis dat a operací. Příklady abstraktních datových typů: fronta (queue), zásobník (stack), pole (array), tabulka (table, map), seznam (list), strom (tree), množina (set),... M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 3/27
Abstraktní datové typy (ADT) Definice ADT je množina hodnot a množina operací. Tyto množiny jsou definovány exaktně a jsou nezávislé na konkrétní implementaci. Definice ADT: formální definice signatury a axiomy, neformální definice implementace ADT (knihovní funkce). Implementace ADT: definice datového typu (např. typedef), funkce pro vytvoření instance (proměnné) datového typu, funkce, které mají hodnotu(y) ADT jako argument(y) a provádějí nějaké operace s hodnotou(ami) argumentu(ů). M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 4/27
ADT příklad Datový typ boolean: true a false jsou boolské hodnoty. Když x a y jsou boolské hodnoty, tak: 1.! (x) 2. (x & y) 3. (x y) 4. (x == y) 5. (x!= y) jsou boolské hodnoty (může být zavedena priorita, aby se zabránilo zbytečným závorkám). Sémantika: 1.! (true) = false,! (! (x)) = x 2. (x & false) = false, (x & true) = x, (x & y) = (y & x) 3. (x true) = true, (x false) = x, (x y) = (y x) 4. (true == x) = x, (false == x) =! x, (x == y) = (y == x) 5. (x!= y) =! (x == y) M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 5/27
Implementace ADT Datový typ complex: Potřebujeme počítat s komplexními čísly: datový typ complex s reálnou a imaginární částí, operace sčítání, odčítání, násobení, dělení a výpis. Norma jazyka C neposkytuje datový typ pro komplexní čísla. My zavedeme vlastní datový typ TCOMPLEX a funkce provádějící požadované operace. Datový typ TCOMPLEX může být implementován bud jako struktura nebo jako pole (dvou prvků). M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 6/27
ADT komplexní čísla typedef... TCOMPLEX; /* Exaktní implementace bude na následujících slajdech. * Níže uvedený interface zůstává stejný. */ TCOMPLEX cplxadd ( TCOMPLEX a, TCOMPLEX b ); TCOMPLEX cplxsub ( TCOMPLEX a, TCOMPLEX b );... double cplxabs ( TCOMPLEX x ); void cplxprint( TCOMPLEX x ); M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 7/27
ADT komplexní čísla int main ( void ) { TCOMPLEX x = {1,1}, y = {2,2}, z; printf ( "x=" ); cplxprint ( x ); printf ( "\n" ); printf ( "y=" ); cplxprint ( y ); printf ("\n" ); z = cplxadd ( x, y ); printf ( "x+y=" ); cplxprint ( z ); printf ( "\n" ); z = cplxsub ( x, y ); printf ( "x-y=" ); cplxprint ( z ); printf ( "\n" ); printf ( "x+(y-z)=" ); cmplxprint( cmplxadd(x, cmplxsub( y, z ) ) ); printf ( "\n" ); printf ( "abs(x)=%f\n", cplxabs(x) ); return 0; } M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 8/27
ADT komplexní čísla Reprezentace datového typu TCOMPLEX v C: struktura: typedef struct TComplex { double re; double im; } TCOMPLEX; pole: typedef double TCOMPLEX[2]; Která implementace je lepší: z hlediska pamět ových nároků, z pohledu programátora (čistota kódu)? M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 9/27
ADT komplexní čísla TCOMPLEX cplxadd ( TCOMPLEX x, TCOMPLEX y ) { TCOMPLEX res; res.re = x.re + y.re; res.im = x.im + y.im; return res; } TCOMPLEX cplxsub ( TCOMPLEX x, TCOMPLEX y ) { TCOMPLEX res; res.re = x.re - y.re; res.im = x.im - y.im; return res; }... M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 10/27
ADT komplexní čísla double cplxabs ( TCOMPELX x ) { return sqrt ( x.re * x.re + x.im * x.im ); } void cplxprint ( TCOMPLEX x ) { printf ( "(%f,%f)", x.re, x.im ); } Poznámka: komplexní číslo z můžeme vyjádřit také v tzv. goniometrickém tvaru: z = z (cos ϕ + i sin ϕ) = z e iϕ, kde i je imaginární jednotka, z je modul a ϕ je argument (bud ϕ ( π; π nebo ϕ 0; 2π)). Pak by byla implementace typu TCOMPLEX i operací zcela jiná (jaká?). M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 11/27
Moduly Složitější programy jsou rozděleny do několika (u velkých programů mnoha) zdrojových souborů modulů. Modul je část programu, která poskytuje určitou funkcionalitu pro zbytek programu: modul obvykle obvykle zpracovává několik souvisejících úkolů (např. zpracování vstupu, výpočty s komplexními čísly, GUI,... ), moduly jsou obvykle považovány za černé krabičky od zbytku programu jsou odděleny dobře definovaným inteface, Modul se skládá ze dvou částí: specifikace seznam zdrojů poskytovaných modulem (datové typy, funkce, proměnné, v C++ třídy,... ), implementace implementace nabízených zdrojů. M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 12/27
Moduly Různé programovací jazyky mají různou podporu modularity: Pascal jednotky (units), Java třídy a baĺıčky (classes and packages), C/C++ hlavičkové soubory (header files.h) a implementační soubory (implementation files.c/.cpp). Hlavičkové soubory (.h): deklarace funkcí, specifikace datových typů, deklarace globálních sdílených proměnných. Implementační soubory (.c/.cpp): definice funkcí, definice proměnných. M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 13/27
Moduly komplexní čísla Hlavičkový soubor complex.h: typedef struct { double re; double im; } TCOMPLEX; TCOMPLEX cplxadd ( TCOMPLEX a, TCOMPLEX b ); TCOMPLEX cplxsub ( TCOMPLEX a, TCOMPLEX b );... double cplxabs ( TCOMPLEX a ); void cplxprint ( TCOMPLEX a ); M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 14/27
Moduly komplexní čísla Implementační soubor complex.cpp: #include <stdio.h> #include <math.h> #include "complex.h" /* ano, vkládá vlastní hlavičkový soubor complex.h: * udržuje deklaraci a implementaci funkcí konzistentní * čte definice datových typů */ TCOMLPEX cplxadd ( TCOMPLEX a, TCOMPLEX b ) { TCOMPLEX res; res.re = a.re + b.re; res.im = a.im + b.im; return res; }... /* implementace dalších funkcí */ M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 15/27
Moduly komplexní čísla Použití modulu (main.c): #include <stdio.h> #include "complex.h" int main(void) { TCOMPLEX x = {1,1}, y = {2,2}, z; printf ( "x=" ); cplxprint ( x ); printf ( "\n" ); printf ( "y=" ); cplxprint ( y ); printf ( "\n" ); z = cplxadd ( x, y ); printf ( "x+y=" ); cplxprint ( z ); printf ( "\n" ); z = cplxsub ( x, y ); printf ( "x-y=" ); cplxprint ( z ); printf ( "\n" ); printf( "abs(x)=%f\n", cplxabs(x) ); return 0; } M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 16/27
Moduly a projekty Velké programy obsahují obvykle několik (i mnoho) modulů. Moduly je třeba kompilovat a cílový program z nich pospojovat (link modules, build task). Vývojová prostředí (IDE - Integrated Development Environment Netbeans, CodeBlocks, MSVC,... ) poskytují projekty (řešení) pro spojování modulů. Projekt obsahuje seznam modulů, volby pro kompilaci (compile options) a volby pro sestavení (link options). IDE zná závislosti modulů. Závislosti jsou využity pro rekompilaci modulů, ve kterých byly provedeny změny. Když je program vyvíjen bez vývojového prostředí, používá se obvykle utilita make řízená souborem Makefile. Soubor Makefile je nutno připravit ručně. M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 17/27
Moduly kompilace.c/.cpp zdrojové soubory, implementace (definice) funkcí..h hlavičkové zdrojové soubory (header files). Deklarace funkcí, datové typy..o/.obj objektové soubory. Strojový kód (zkompilovaný z.c/.cpp/.h souborů) s neřešenými referencemi mezi moduly. Objektový soubor může: importovat symbol, např. když je třeba volat funkci ze standardní knihovny, exportovat symbol, např. funkce main ze souboru v němž je definována..a/.lib knihovna objektových souborů..so/.dll sdílená knihovna objektových souborů. bez přípony/.exe spustitelný soubor. Spustitelný soubor je výstupem sestavovacího programu (linker, task builder). Ten řeší reference mezi objektovými soubory a knihovnami. M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 18/27
Moduly kompilace complex.h compilation stdio.h complex.c libc.a main.c complex.o main.o link complex M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 19/27
Moduly sdílené proměnné Funkce a typy mohou být sdíleny mezi moduly. To je obecná technika. C i C++ povolují i sdílení globálních proměnných mezi moduly. Tato technika však není doporučována: je vždy dobře se vyhnout použití globálních proměnných, sdílení proměnné zesiluje risk neočekávaných přepsání hodnoty. Jedině zdůvodněné je použití sdílených konstant (např. předem vypočítaných tabulek). Toto nebude fungovat: /* complex.h */... TCOMPLEX g_imag = {0,1};... /* Proměnná bude deklarována v každém modulu, * který obsahuje #include "complex.h". Program bude * mít více instancí proměnné g_imag, a proto si linker * bude stěžovat na vícenásobně definovaný symbol. */ M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 20/27
Moduly sdílené proměnné /* complex.h */... extern TCOMPLEX g_imag; /* jen deklarace (klíčové slovo extern), bez initializace */... /* complex.c */ #include "complex.h"... TCOMPLEX g_imag = {0,1}; /* definováno a inicializováno zde (jen v tomto modulu) */... /* main.c a další moduly */ #include "complex.h"... TCOMPLEX res = cplxadd ( g_imag, g_imag ); /* použití proměnné */... M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 21/27
Moduly hlavičkové soubory Hlavičkové soubory popisují rozhraní (interface) modulů: datové typy, prototypy funkcí, deklarace extern proměnných. Hlavičkové soubory jsou užity: kompilátorem: aby věděl, které funkce a typy jsou použitelné, programátorem: aby věděl, jak modul použít. Je dobrým zvykem do hlavičkových souborů umístit popisné komentáře. Hlavičkové soubory neslouží pro umístění: definicí funkcí (těla funkce), definicí proměnných (bez extern). Je-li definice proměnné či funkce v hlavičkovém souboru, linker ohlásí chyby a program nesestaví. M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 22/27
Více o preprocesoru Preprocesor je první fází kompilace modulu. Preprocesor slouží pro: vkládání hlavičkových souborů (vkládání může být zahnízděno, tj. include v includovaném souboru do libovolné hloubky): #include <stdio.h> #include "myheader.h" podmíněnou kompilaci (část zdrojového textu může být vynechána pro vlastní kompilaci): #ifndef PROGTEST... #endif makra. #define M_PI 3.14159 #define MAX(x,y) ((x)>(y)?(x):(y)) Preprocesorové direktivy začínají znakem křížek (hash #). M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 23/27
Preprocesor jednoduchá substituce Identifikátor definovaný použitím direktivy #define je všude v dalším zdrojovém textu nahražen jeho substitucí. Víceřádkové substituce lze zavést, pokud jako poslední řádku uvedeme obrácené lomítko (backslash \). Substituce mohou být vnořené. Substituce nebude aplikována na řetězce a komentáře. Substituci je možno zrušit direktivou #undef. #define TRUE 1 #define M_PI 3.14159 #define M_PI_2 (2*M_PI) Několik substitucí je standardně definováno kompilátorem: DATE /* datum kompilace */ TIME /* čas kompilace */ FILE /* jméno zdrojového souboru */ M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 24/27
Preprocesor makra Makra substituce mají jeden či více parametrů. Parametry jsou bez úprav vloženy do substitučního textu. Parametry maker i celá substituce jsou obvykle uzavřeny do kulatých závorek, aby se dosáhlo požadovaného chování makra. #define MAX(x,y) ((x)>(y)?(x):(y)) #define SQR(x) ((x)*(x)) #define SQR_BAD(x) x*x MAX(i+4,3) /* ((i+4)>(3)?(i+4):3) */ SQR(100) /* ((100)*(100)) */ SQR(10+10) /* ((10+10)*(10+10)) */ SQR_BAD(10+10) /* 10+10*10+10 */ M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 25/27
Preprocesor podmíněný překlad Preprocesor může být použit na vypuštění částí zdrojového textu při kompilaci. Vypuštěný text je preprocesorem přeskočen a kompilátor ho tedy nezpracuje (jako kdyby neexistoval). Tato technika je užitečná, je-li je program určen pro různé platformy, které mají různé API (Application Programming Interface), pro ladicí výstupy po odladění nekompilované, apod. /* Windows kompilátory automaticky definují identifikátor * WIN32. Použijeme preprocesorovou definici pro různá * volání API */ #ifdef WIN32 Sleep ( 1000 ); /* Windows spí 1000ms */ #else sleep ( 1 ); /* UNIX spí 1 s */ #endif M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 26/27
Otázky a odpovědi Otázky... M. Baĺık, L. Vagner a J. Vogel, ČVUT FIT ADT, moduly, BI-PA1 27/27