Tartalmi kivonat
C programozási feladatgyűjtemény Poppe András – Kocsis Tamás BME Mérnöki Továbbképző Intézet, 1992. Tartalomjegyzék Előszó iii 1. C alapok 1.1 Ciklusok 1.11 Feladat: Fahrenheit-Celsius átszámoló program 1.12 Feladat: Fahrenheit-Celsius átszámoló for ciklussal 1.2 Egykarakteres I/O 1.21 Feladat: Karakterszámlás 1.3 Elágazás: if, switch 1.31 Feladat: Sorszámlálás 1.4 Önálló feladatok 1.41 Feladat: Blank-ek számlálása 1.42 Feladat: Blank-ek számlálása tı́pusonként 1.5 Az operátorok gyakorlása 1.51 Feladat: A sizeof egyszerű használta 1.52 Feladat: A sizeof és 2-vel való szorzás a shifteléssel 1.53 Feladatok: Aritmetikai műveletek int-ekkel 2. Bonyolultabb szerkezetek 2.1 Az előfeldolgozó használata
2.11 Szimbólumok használata, feltételes fordı́tás 2.12 Feladat: Programfordı́tás körülményeinek kiiratása 2.13 Feladat: Az #ifdef alkalmazása 2.14 Fordı́tási időben élő szimbólumok 2.15 Új és régi stı́lusú függvénydeklarácók 2.16 Feladat: 2.17 Makródefiniciók 2.18 Feladatok: min, max, pow, toupper, tolower 2.2 Tömb-, pointer- és függvénytı́pusok 2.3 Karaktertömbök és pointerek i . . . . . . . . . . . 1 1 1 3 5 6 7 7 8 8 8 8 8 8 8 11 11 12 13 14 15 15 17 17 18 19 23 ii 2.31 Feladat: saját strcpy 2.32 Feladat: saját strlen 2.4 Függvénypointerek 2.5 Több modulból álló programok készı́tése . . . . 23 25 25 30 3. A dinamikus tárkezelés alapjai 3.1 Dinamikus adatok 3.11 Feladat: lineáris egyenletrendszer megoldása
33 33 35 4. Az operációs rendszerrel való kapcsolat 4.1 Folyam jellegű I/O 4.11 Feladatok: File-ok másolása 4.12 File-nyitási hibák, azok kezelése 4.2 A main argumentumai 4.21 Feladat: A copy parancs – saját kivitelben . . . . . . . . . . . . . . . . . . . . . . . . . 39 39 40 40 41 41 5. Fejlettebb technikák 5.1 Struktúrák – láncolt lista 5.11 Fealadat: Láncolt lista készı́tése 5.2 Menürendszer 5.3 Összetett mintapélda 5.31 A tervezés egyes fázisai 5.32 A menükezelő rendszer listája . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 43 43 46 47 47 55 Irodalomjegyzék . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Előszó A C programozási nyelv az egyik legnépszerűbb
programfejlesztési eszköz a világon. Ez nagy részt a mikró- és miniszámı́tógépek, illetve az ún munkaállomások (worktstation-ök) világméretű térnyerésének köszönhető. Ezen gépkategóriák Magyarországon, illetve szükebben a Budapesti Műszaki Egyetemen elterjedt reprezentánsain is (IBM PC/AT, VAX/MicroVAX, SUN, stb.) hatékony C fejlesztői környezeteket találhatunk A programfejlesztők szı́vesen dolgoznak a C-vel, mert általános célú, alkalmas igen nagy lélegzetű csoportmunkákban való felhasználásra, nagyon jó hatásfokú kódot lehet vele előállı́tani, mégis magas szinten struktúrált, átfogóan szabványosı́tott nyelv. Ez utóbbi azt jelenti, hogy egy adott géptı́pus adott operációs rendszerére kidolgozott – bizonyos programı́rási szabályokat figyelembe vevő – program viszonylag kis munkával, jól meghatározott helyeken való módosı́tással
átı́rható más számı́tógép tetszőleges (a C nyelvet támogató) operációs rendszere alá. Itt azonban rögtön meg kell jegyeznünk azt is, hogy nagyon könnyű C nyelven áttekinthetetlen, nehezen megérthető és módosı́tható programokat készı́teni. Nagyon fontos tehát a fegyelmezett, körültekintő programozási stı́lus alkalmazása, aminek az elsajátı́tása kb. annyi munkát igényelhet, mint maguknak a nyelvi elemeknek a megtanulása. Tekintve, hogy a C népszerűsége nő, egyre többen szeretnék a nyelvet elsajátı́tani. Ehhez egyre több, a C nyelvet ismertető könyv áll rendelkezésre, de ezek közt kevés tartalmaz olyan mintaprogramot, illetve mintafeladatot, amelyek segı́tenék elmélyı́teni a C nyelv ismertét. Feladatgyűjteményünk – legalábbis úgy hisszük – ezt a hiányt igyekszik pótolni olymódon, hogy egyes C nyelvi elemekhez kapcsolódva mintaprogramokat,
illetve programrészleteket közöl, illetve feladatkitűzéseket tartalmaz. A hiánypótláson túl, másik célunk az, hogy segı́tséget nyújtsunk egy tiszta, a nyelvi elemeket jól kihasználó, portábilis C programozási stı́lus elsajátı́tásához. Azt is igyekszünk bemutatni – egy, a BME Villamosmérnöki Karának nappali tagozatán szokásos programozási nagy házi feladat megoldásának ismertetésével – hogy milyen az ún. öndokumentáló program, egy iii iv Előszó kész, nagyobb lélegzetű C programot hogy dokumentáljunk, ehhez milyen segédprogramokat vehetünk igénybe. A szövegben az IBM PC kompatibilis gépeken széles körben elterjedt TURBO/BORLAND C(++) fordı́tókkal, illetve a VAX tı́pusú számı́tógépek VMS operációs rendszere alatt elérhető VAX-C fordı́tóval egyaránt lefordı́tható programpéldákat szerepeltetünk. Feltételezzűk, hogy e
feladatgyűjteményt forgató olvasóknak valamilyen más programozási nyelv – például a Pascal – használatában már van némi rutinjuk. A C nyelvet természetesen egy példatárból nem lehet megtanulni, ı́gy javasoljuk, hogy a mintaprogramok, illetve a kitűzött feladatok feldolgozását egy, a C nyelvet ismertető könyv tanulmányozásával párhuzamosan végezze az Olvasó. Feladatgyűjteményünk anyagának felépı́tése olyan, hogy többé-kevésbé követi a BME Mérnöki Továbbképző Intézete által kiadott Az IBM PC programozása Turbo C 2.0 nyelven c jegyzet [1], illetve a ComputerBooks kiadónál 1991-ben megjelent Bevezetés a BORLAND C++ programozásba c. könyv [2] anyagát A példatár szövegében – ajaánlott kiegészı́tó olvasmányként – ez utóbbi munka fejezetszámaira fogunk hivatkozni. Haszonnal forgathatja az olvasó Kerninghan és Ritchie A C programozási nyelv
cı́mű könyvét [3], illetve e könyv második, csak angol nyelven hozzáférhető kiadását [4] is: bizonyos feladatkitűzéseket onnan vettünk át. Az egyes C nyelvi implementációkra vonatkozó részletes ismeretekre nincs szükség e feladatgyűjtemény használata során, mindazonáltal célszerű lehet ezek forgatása komolyabb programfejlesztési munkáknál. Köszönetnyilvánı́tás Szeretnénk megköszönni Benkő Tibornénak azt, hogy fáradhatatlanul bı́ztatott minket arra, hogy a BME Villamosmérnöki Karának nappali tagozatos 1. évfolyamos hallgatóinak oktatása során összegyűjtött tapasztalatainkat jelen példatár összeállı́tásával közzé tegyük Köszönet illeti Verhás Pétert is, aki HION nevű programját rendelkezésünkre bocsátotta. Ez a program tette lehetővé, hogy a magyar nyelvű, ékezetes szövegfile-jainkat gond nélkül használhassuk a
TEXszövegformázó rendszer LATEXmakrócsomagjával. Budapest, 1998. szeptember 6 A szerzők 1. fejezet C alapok Jelen fejezet célja az, hogy megéreztesse a C programozás ı́zét. Mivel a teljesen portábilis programozási stı́lust igyekszünk bemutatni, az itt ismertett példák, illetve a kitűzött feladatok akár TURBO C-vel, akár BORLAND C++-szal, akár VAX C-vel lefordı́thatók. Lehetőség szerint ANSI C fordı́tási opciót használjunk, ha IBM PC-n dolgozunk A továbbiakban feltételezzük, hogy az olvasó alaposan ismer már egy programozási nyelvet, például a Pascal-t. Először egyszerű példákat közlünk mind Pascal, mind C nyelven – részben csak egyszerű szintaxis váltással, részben kihasználva a C nyújtotta tömörı́tési lehetőségeket, majd egyes problémáknak a C nyelvű megvalósı́tását közöljük, végül pedig csak feladatkiı́rásokat adunk meg, a C nyelvet
tanulókra bı́zva az egyes feladatok konkrét, C nyelvű megvalósı́tását. E fejezet feldolgozásához javasolt olvasmány [2]-ból: 2.1, 22, 244, 245, 25, 27 fejezetek 1.1 1.11 Ciklusok Feladat: Fahrenheit-Celsius átszámoló program Készı́tsünk olyan programot, amely egy adott tartományon belül, adott lépésközzel kilistázza a Fahrenheit fokokban adott hőmérséklet Celsius fokokban számolt értékét. Ezzel a mintaprogrammal kezdődik Kerninghan és Ritchie könyve is. Mi először a C-ben még járatlan olvasó kedvéért Pascal nyelven közöljük a megoldást, majd megadjuk ugyanezt a programot C-ben is. A C-re való áttérést egyszerű szintaxis-váltással oldottuk meg Igy a program egy 1 2 1. FEJEZET C ALAPOK C fordı́tóvál már lefordı́tható, de még nem ”C nyelvű”. Ezalatt azt értjük, hogy egyáltalán nem használja ki a C lehetőségeit A C-ben kódolt programváltozat
fokozatos finomı́tásával jutunk el egy már C programnak nevezhető változathoz. A Pascal változat: PROGRAM FAHRANHEIT(INPUT, OUTPUT); VAR FAHR, CELS: INTEGER; LOWER, UPPER, STEP: INTEGER; BEGIN LOWER:=0; UPPER:=300; STEP:=20; FAHR:=LOWER; WRITELN; WHILE(FAHR <= UPPER) DO BEGIN CELS:=5*(FAHR-32) DIV 9; WRITELN(FAHR,’ ’,CELS); FAHR:=FAHR+STEP; END; END. A C változat: #include <stdio.h> main() { int fahr, cels; int lower, upper, step; lower = 0; upper = 300; step = 20; fahr = lower; printf(" "); while(fahr <= upper) { 1.1 CIKLUSOK 3 cels = 5 * (fahr - 32) / 8; printf("%d %d ",fahr,cels); fahr = fahr + step; } } 1.12 Feladat: Fahrenheit-Celsius átszámoló for ciklussal Alakı́tsuk át a C változatot úgy, hogy a while ciklus helyett for ciklussal működjön! (Lásd [2]-ból a 2.73-as részt) Gondoljuk végig, hogy mi az inicializáló rész, amit lehet, azt a veszsző operátor segı́tségével rakjuk a
for ciklus inicializáló kifejezés részébe! Használjuk ki a C nyújtotta tömörı́tési lehetőségeket! Megoldás: #include <stdio.h> main() { int fahr, cels; int lower, upper, step; printf(" "); for (fhar = lower = 0, upper = 300, step = 20; fahr <= upper; fahr += step) { cels = 5 * (fahr- 32) / 8; printf("%d %d ",fahr,cels); } } További lehetőség: Az inicializálást áthelyezzük a deklarációs részbe, azaz inicializált változókat hozunk létre. Ekkor a for ciklus inicializáló része egy üres utası́tás lesz: #include <stdio.h> main() { int fahr = 0, cels; 4 1. FEJEZET C ALAPOK int lower = 0, upper = 300, step = 20; printf(" "); for ( ; /* initialization - empty statement / fahr <= upper; /* condition */ fahr += step) /* stepping */ { cels = 5 * (fahr- 32) / 8; printf("%d %d ",fahr,cels); } } Szimbólumok használata: Használjunk szimbólikus konstansokat a
konkrét numerikus értékek helyett a főprogramon belül! (Lásd [2] 2.3-as, az ún előfeldolgozóról szóló fejezetént, azon belül a 231-es szakaszt) Az előző programváltozat szimbólikus konstansok felhasználásával tehát ı́gy néz ki: #include <stdio.h> #define LOWER 0 #define UPPER 300 #define STEP 20 main() { int fahr = LOWER, cels; int lower = LOWER, upper = UPPER, step = STEP; printf(" "); for ( ; /* initialization - empty statement / fahr <= upper; /* condition */ fahr += step) /* stepping */ { cels = 5 * (fahr- 32) / 8; printf("%d %d ",fahr,cels); } } 1.2 EGYKARAKTERES I/O 1.2 5 Egykarakteres I/O Gyakoroljuk a szabványos input/output egykarakteres kezelését! Ehhez az stdio.h könyvtár getchar (egy karakter beolvasása a billentyűzetről) és putchar (egy karakter nyomtatása a képernyőre) rutinjait használjuk fel. (Lásd [2] 2.102 fejezetének elejét!) Használni fogjuk az EOF
(end-of-file) szimbólumot is, amely ugyancsak az stdio.h-ban van definiálva Induljunk ki az alábbi egyszerű Pascal programból: PROGRAM CHARCOPY(INPUT, OUTPUT); VAR CH: CHAR; BEGIN WHILE NOT EOF DO BEGIN READ(CH); WRITE(CH); END; END. Ennek C megfelelője: #include <stdio.h> main() { int ch; /* int and char are compatible / ch = getchar(); while (ch != EOF) { putchar(ch); ch = getchar(); } } Megjegyzés: Mivel a VAX gépeken a VMS operációs rendszer alatt ún. bufferelt, echózott I/O van, ezért egészen addig, amig az első RETURN-t nem ütjük le a terminál billentyűzetén, gyűlnek a karakterek (és ki is ı́ródnak a képernyőre), a VMS csak ezután adja át az input buffer tartalmát a szabványos bemenetet olvasó rutinnak (a Pascal READ-nek, illetve a C getchar-nak). A file-végét a a PC-ken a CTRL-Z jelenti A VAX CTRL-Z-t még beolvasott karakternek tekinti, de az EXIT üzenet után beáll az EOF állapot, ı́gy a
program leáll. 6 1. FEJEZET C ALAPOK Továbbfejlesztés: Használjuk ki, hogy a C értékadó operátor mellékhatása az, hogy a balérték mint kész kifejezés azonnal felhasználható. Ilyenformán a while ciklus logikai kifejezését adó relációs művelet baloldalán álló ch változónak magában a while-feltétel kifejezésben adhatunk értéket. A műveletek helyes kiértékelési sorrendjét azzal biztosı́tjuk, hogy az értékadó műveletet zárojelek közé tesszük. (A zárójel-pár is egy operátor; hatása az, hogy az operandusát azonnal kiértékeli.) #include <stdio.h> main() { int ch; /* int and char are compatible / while ((ch = getchar()) != EOF) { putchar(ch); } } 1.21 Feladat: Karakterszámlás Az előbbi példa alapján ı́rjunk olyan C programot, amely megszámlálja, hány karaktert olvastunk be a szabványos bemeneti állományról! Annyi módosı́tásra van
szükség, hogy a ciklustörzsben nem iratjuk ki a beolvasott karaktert, hanem csak egy számláló értéket növeljük. Ime a program: #include <stdio.h> main() { int n = 0; while (getchar() != EOF) { n = n + 1; /* or n += 1; or n++; */ } printf(" Number of characters read from stdin: %d ",n); } 1.3 ELÁGAZÁS: IF, SWITCH 7 Amint azt a programbeli megjegyzésből is láthatjuk, az igencsak Pascalos n = n + 1; utası́tást sokkal tömerebben is leı́rhatjuk C-ben az ún. postincrement operátor felhasználásával: n++; 1.3 Elágazás: if, switch 1.31 Feladat: Sorszámlálás Első lépésben bővı́tsük ki úgy az előbbi, karakterszámláló programot, hogy azt is számlálja, hány sorból állt az input. Ehhez pusztán a beolvasott ’ ’ karaktereket kell külön számlálni: #include <stdio.h> main() { int n, nl; int ch; n = nl = 0; while ((ch = getchar()) != EOF) { n++ if (ch == ’ ’) nl++;
} printf(" Number of characters read from stdin: %d ",n); printf("Number of lines read from stdin: %d ",nl); } Most tömörı́tsünk egy kicsit ezen a programon! Kihasználva azt, hogy a relációs operátorok ligikai értéket szolgáltatnak, ami vagy 1 (ha igaz a reláció), vagy 0 (ha nem igaz), a következőképpen ı́rhatjuk át programunkat: #include <stdio.h> main() { int n, nl; int ch; n = nl = 0; while ((ch = getchar()) != EOF) { 8 1. FEJEZET C ALAPOK n++ nl += (ch == ’ ’); } printf(" Number of characters read from stdin: %d ",n); printf("Number of lines read from stdin: %d ",nl); } 1.4 1.41 Önálló feladatok Feladat: Blank-ek számlálása Irjunk önállóan olyan programot, amely azt számlálja meg, hogy együttvéve hány ún. blank karakter, azaz szóköz, tabulátor (’ ’) vagy újsor karakter (’ ’) jön be az inputról! (Az if utası́tást és a logikai VAGY
műveletet felhasználva.) 1.42 Feladat: Blank-ek számlálása tı́pusonként Irjunk önállóan olyan programot, amely megszámlálja, hogy hány szóköz, tabulátor (’ ’) és újsor karakter (’ ’) jön be az inputról! Használjuk a switch utası́tást! 1.5 1.51 Az operátorok gyakorlása Feladat: A sizeof egyszerű használta Irjunk olyan programot, amely a sizeof operátor segı́tségével meghatározza és a képernyőre kiı́rja az alap-adatı́pusok méretét! 1.52 Feladat: A sizeof és 2-vel való szorzás a shifteléssel Irjunk olyan programot, amely a blara shiftelés operátorának a segı́tségével kiı́rja a képernyőre 2 minden olyan hatványát, amely elfér egy int-ben. 1.53 Feladatok: Aritmetikai műveletek int-ekkel Gyakoroljuk az aritmetikai operátorok (összeadás, kivonás, szorzás, osztás, maradékképzés) használatát int tı́pusú adatok esetén. Irjunk olyan
programot, amely a) előállı́tja egy egész szám összes osztóját, 1.5 AZ OPERÁTOROK GYAKORLÁSA 9 b) előállı́tja egy egész szám prı́mtényezős felbontását, c) eldönti egy egész számról, hogy tökéletes szám-e (Egy tökéletes szám eggyel nagyobb, mint az összes valódi osztójának az összege.), d) megadja két egész szám legkisebb közös többszörösét. 10 1. FEJEZET C ALAPOK 2. fejezet Bonyolultabb szerkezetek Az előző fejezet végére az alaputası́tások használata során sikerült eltávolodnunk a Pascal-szerű megoldásoktól. Most sorra vesszük azokat a lehetőségeket, amelyek a C-t igazán hatékony programozási nyelvvé teszik Először áttekintjük az előfeldolgozó használatát, ennek kapcsán utalunk arra, hogy készı́thetők portábilis programok a feltételes fordı́tási direktı́vák felhasználásával. Ezt követően azzal
foglakozunk, hogy ı́rhatunk ún makrókat E fejezet folytatásaképpen áttekintjük azt, hogy a C alaptı́pusaiból hogy származtathatunk további tı́pusokat, áttekintjük a mutatók és a tömbök kapcsolatát. A függvénypointerek használatát a qsort függvény példáján keresztül mutatjuk be. Szintén a függvénypointerek kapcsán egy flexibilis numerikus integráló függvényt ismertetünk. Ebből, és néhány integrálandó függvényből, valamint egy main függvényből elkészı́tünk egy több forrásmodulból álló programot 2.1 Az előfeldolgozó használata Az előfeldolgozó egy sororientált szövegfeldolgozó (más szóval makrónyelv), ami semmit sem ”tud” a C nyelvről. Ez két fontos következménnyel jár: az előfeldolgozónak szóló utası́tásokat nem ı́rhatjuk olyan kötetlen formában, mint az egyéb C utası́tásokat (tehát egy sorba csak egy
utası́tás kerülhet és a parancsok nem lóghatnak át másik sorba, hacsak nem jelöljük ki folytatósornak); másrészt minden, amit az előfeldolgozó művel, szigorúan csak szövegmanipuláció, függetlenül attól, hogy C nyelvi alapszavakon, kifejezéseken vagy változókon dolgozik. Az előfeldolgozó és az ún belső fordı́tó 11 12 2. FEJEZET BONYOLULTABB SZERKEZETEK C fordı́tóprogram forrásszöveg előfeldolgozó belső fordı́tó tárgykód 2.1 ábra Az előfeldolgozó kapcsolata a környezettel Szimbólum STDC FILE LINE DATE TIME ´ Ertelmez és, érték 1 értékkel definiálva van, ha ANSI C (egész) a feldolgozás alatt álló file neve (sztring) a feldolgozás alatt álló sor száma (egész) a fordı́tás dátuma (sztring) a fordı́tás ideje (sztring) 2.1 táblázat Előre definiált szabványos szimbólumok az ANSI C-ben kapcsolatát szemlélteti a 2.1 ábra A
preprocesszornak szóló parancsokat a sor elején (esetleg szóközök és/vagy tabulátorok után) álló # karakter jelzi. A legfontosabb utası́tások: #define, #undef, #include, #if, #ifdef, #else, #elif, #endif 2.11 Szimbólumok használata, feltételes fordı́tás Az előző fejezetben már láttuk, hogy a #define direktı́va segı́tségével hogy ”nevezhetjük el” számkonstansainkat. Természetesen a #define-nal létrehoztt szimbólumokat nemcsak konstans kifejezések elnevezésére használhatjuk, hanem például feltételes fordı́tásvezérlésre Ezáltal egyes programrészletek lefordı́tását kikapcsolhatjuk, illetve bekapcsolhatjuk A feltételes fordı́tási direktı́vák használata elsősorban a több, különböző operációs rendszer alatti fordı́tásra szánt programokra jellemző. Általában minden C nyelvi rendszer előre definiál egyes szimbólumokat, ı́gy például olyanokat,
amelyek megléte vagy hiánya alapján eldönthető, hogy milyen operációs rendszer alatt történik a fordı́tás. Maga az ANSI C szabvány is előre definiál egyes szimbólumokat, amelyek alapján a program fordı́tás körülményeiről szerezhetünk információkat. Ezeket a szimbólumokat az 2.1 táblázatban soroljuk fel Bár a legújabb VAX C sokmindenben megfelel az ANSI C-nek, mégsincs 2.1 AZ ELŐFELDOLGOZÓ HASZNÁLATA Szimbólum VAX vagy vax VMS vagy vms VAXC vagy vaxc VAX11C vagy vax11c TURBOC MSDOS BORLANDC TCPLUSPLUS cplusplus 13 ´ Ertelmez és, érték A VAX C-ben 1 értékkel definiálva van A VAX C-ben 1 értékkel definiálva van A VAX C-ben 1 értékkel definiálva van A VAX C-ben 1 értékkel definiálva van Minden Turbo/Borland C-ben definiálva van Általában minden PC-s C-ben definiálva van. A Borland C++-ban mindig definiáltva van. A verziószámra utal. Csak C++ üzemmódban van
definiálva, ekkor a verziószámot adja. C++ üzemmódban 1 értékkel van definiálva, egyébként definiálatlan. 2.2 táblázat A Borland C++-ban és a VAX C-ben definiált, az operációs rendszerre, illetve a fordı́tóra utaló szimbólumok előre definiálva a STDC szimbólum, jóllehet, az 2.1 táblázatban szereplő többi szimbólum létezik benne. A Borland C++-ban a STDC szimbólum akkor van definiálva, ha az Options menüben ANSI C kompatibilisre állı́tottuk be a fordı́tót. Az ANSI C által megadott előredefiniált szimbólumokon kı́vül – mint már emlı́tettük – minden nyelvi rendszerben vannak olyan előredefiniált szimbólumok, amelyek az adott nyelvi rendszert, és az operációs rendszert azonosı́tják. A VAX C-ben, illetve a Borland C++-ban előforduló ilyen szimbólumokat a 2.2 táblázatban foglatuk össze 2.12 Feladat: Programfordı́tás körülményeinek kiiratása A
előzőek alapján ı́rjunk olyan programot, amely abban az esetben, ha ANSI C, vagy VAXC kompatibilis fordı́tóprogrammal fordı́tották le, kiı́rja a szabványos kimenetre, hogy • milyen nevű forrásállományból fordı́tották, • mikor (dátum, idő) történt a fordı́tás, • kiı́rja, hogy a main függvény hány sorból áll. 14 2. FEJEZET BONYOLULTABB SZERKEZETEK A megoldás: /* File: test.c */ #include <stdio.h> int sor = 0; main() { #if defined( STDC ) || VAXC sor = LINE ; printf("The compilation circumstances of %s: ", FILE ); printf("Date of compilation: %s ", DATE ); printf("Time of compilation: %s ", TIME ); printf("Approximate length of ’main’: %d ", LINE - sor); #else printf("The compiler is not ANSI C/VAX-C compatible "); printf("No way to identify compilation circumstances "); #endif } Az előbbi programot egy PC-n, a Borland C++-szal
ANSI C üzemmódban lefordı́tva, majd az object modulból szerkesztett .exe programot lefuttatva a következő outputot kapjuk a képernyőn: The compilation circumstances of TEST.C: Date of compilation: Mar 21 1992 Time of compilation: 11:30:35 Approximate length of ’main’: 6 A fenti példa kapcsán bemutatott leheteőségek jelentősége abban rejlik, hogy egy már kész, a felhasználók számára csak exe file formájában rendelkezésre álló programban is elhelyezhetünk olyan teszt-részleteket, amelyek segı́tségével például a felhasználó egy részletes hibajelentést küldhet a program fejlesztőinek. 2.13 Feladat: Az #ifdef alkalmazása Egészı́tsük ki az előbbi test.c programot úgy, hogy attól függően, hogy PC-n a DOS alatt, vagy egy VAX-on, a VMS operációs rendszer allat fordı́tják le, más-más azonosı́tó szöveget ı́rjon ki. 2.1 AZ ELŐFELDOLGOZÓ HASZNÁLATA 2.14 15
Fordı́tási időben élő szimbólumok Természetesen nem csak az adott C nyelvi rendszer által előre definiált szimbólumok meglétét vizsgálhatjuk az #ifdef preprocesszor utası́tással, hanem mi magunk is létrehozhatunk szimbólumokat a fordı́tás idejére. Ezeket nem kell a forrásprogramjainkban elhelyezni, hanem fordı́tási időben, a fordı́tó program számara paraméterként adhatjuk meg. A BORLAND C++ integrált környezetében az Options | Compiler | Code genaration dialógus doboz Defines mezőjébe ı́rt sztringre, illetve a VAX C esetében a CC parancs /DEFINE= kapcsolója után ı́rt sztringre hivatkozva a C forrásprogramban, fordı́táskor úgy találjuk, hogy az adott szimbólum definiálva van. Tekintsük a következő példát: /* File: testsymb.c */ #include <stdio.h> main() { #ifdef MYSYMBOL printf("MYSYMBOL has been defined for %s ", FILE ); #else printf("MYSYMBOL has not been
defined for %s ", FILE ); #endif } Ha tehát a fenti programot egy VAX gépen, VMS-ben a CC TESTSYMB /DEFINE=MYSYMBOL paranccsal fordı́tjuk le, akkor MYSYMBOL has been defined for TESTSYMB.C üzenetet fogja kiı́rni a képernyőre a futtatható program, mı́g az egyszerű CC TESTSYMB fordı́tási paranccsal fordı́tva, a futtatható program a második üzenetet ı́rja majd ki Hasonló kisérlet végezhető PC-ken a MYSYMBOL szimbólum fordı́tási időre történő definiálásával (az Options | Compiler | Code genaration | Defines mező kitöltésével) 2.15 Új és régi stı́lusú függvénydeklarácók Az ANSI C szabvány szerint minden függvény deklarációjakor nemcsak a visszatérési tı́pust kell megadnunk, (ha nem tesszük, akkor definició szerint int tı́pusú visszatérési értéket tételez fel a fordı́tó – ez sok baj forrása lehet), hanem azt is pontosan meg kell adnunk, mennyi, és
milyen tı́pusú paraméterrel rendelkezik egy függvény. Ez a szoros deklarációs kényszer lehetőséget teremt arra, hogy a C fordı́tó figyelmeztessen minket, ha esetleg hiányos aktuális paraméterlistával, vagy esetleg nem a deklaráció szerinti (vagy azokkal kompatibilis) tı́pusú adatokkal aktivizálunk egy függvényt. Tekintsünk egy példát az ANSI C szerinti függvénydeklarációra: 16 2. FEJEZET BONYOLULTABB SZERKEZETEK double mypower(double x, double y); A mypower tehát egy double visszatérési értéket szolgáltató, 2 double paramétert váró függvény. A deklarációból a formális paraméterek azonosı́tói elhagyhatók (Természetesen a függvénydefiniciónál, tehát amikor a függvénytörzset is megadjuk, már ki kell ı́rnunk a formális paraméterek azonosı́tóit is). A függvények kötelező deklarációjának, vagy más szakkifejezéssel élve, a
protı́pusmegadásnak egy másik előnye is van: az ilyen, úgynevezett új stı́lusú függvénydeklarációt alkalmazó C programjaink minden további nélkül beilleszthetők egy objektum-orientált C++ programba, ahol a pontos prototı́pusmegadás alapkövetelmény. Az új stı́lusú függvénymegadás mellett létezik azonban a régi stı́lus is. A régi stı́lusú deklarációknál csak a visszatérési érték tı́pusát és a függvény azonosı́tóját adjuk meg, a paraméterlistáról nem mondunk semmit. Csak a függvény definiciónál adjuk meg a paraméterlistát. A fenti függvényünk régi stı́lusú deklarációja a következőképpen néz ki: double mypower(); A régi stı́lusú függvénydefinició pedig ı́gy kezdődik: double mypower(x, y) double x, y; majd ezt követi a függvény törzse. A régi és az új függvénydeklarációs stı́lus alkalmazása általában
kizárja egymást. Hogy készı́thetünk mégis olyan programokat, amelyek akár egy régi C fordı́tóval, akár a legújabb fordı́tókkal, vagy akár egy C++ fordı́tóval is lefordı́tható? Nos, a megoldást természetesen a feltételes fordı́tás, illetve az egyes nyelvi implementációk által előre definiált szimbólumok felhasználása jelenti. Tekintsük az alábbi példát: /* * A PROTOTYPES szimb´olum csak a szabv´anyos C ford´ıt´ok * sz´am´ara lesz defini´alva: */ #undef PROTOTYPES #ifdef STDC /* Ha ANSI C kompatibilis a ford´ıt´o / #define PROTOTYPES 1 /* akkor kell f¨uggv´enyprotot´ıpus */ #endif 2.1 AZ ELŐFELDOLGOZÓ HASZNÁLATA 17 /* A f¨uggv´enydeklar´aci´ok ekkor ´ıgy n´ezhetnek ki: */ double mypower( #ifdef PROTOTYPES double, double #endif ); A fenti módon definiált PROTOTYPES szimbólumot a függvénydefiniciónál is felhasználjuk: /* A mypower f¨uggv´eny r´egi ´es ´uj
st´ılus´u definici´oja: double mypower #ifdef PROTOTYPES (double x, double y) /* ´uj st´ılus #else (x, y) double x, y; /* r´egi st´ılus #endif { /* Ide ker¨ul maga a f¨uggv´enyt¨orzs } 2.16 */ */ */ Feladat: Írjunk egy olyan C nyelvű faktoriális-számı́tó programot, amelyben a felhasználóval való kommunikációt a main végzi, és a faktoriális értékét egy saját függvény hı́vásával végzi! A faktoriális számı́tó függvény ne végezzen I/O műveletet! Úgy ı́rjuk meg a függvényeink (main, faktoriális számı́tó) definicióit, hogy a programunk mind régi stı́lusú C fordı́tóval, mind pedig prototı́pust igénylő fordı́tóval lefordı́tható legyen! Alkalmazzuk a korábban leı́rtakat! A fordı́táshoz a Unix cc-t, illetve a C++ üzemmódba kapcsolt Borland C++-t használjuk! 2.17 Makródefiniciók A #define direktı́vát nemcsak fordı́tásvezérlésre, illetve
szimbólikus konstansok definiálásra használhatjuk, hanem paraméterekkel rendelkező ún. makrók, azaz egyszerű rutinok ı́rására is. Például a mathh szabványos fejlécfile-ban definiált abs rutin makró megvalósı́tása ı́gy néz ki: #define abs((x)) ((x) > 0 ? (x) : (-(x))) */ 18 2. FEJEZET BONYOLULTABB SZERKEZETEK Figyeljük meg, hogy a makró paraméterét zárójelekkel védjük. Ez azért történik ı́gy, hogy összetett kifejezésekkel meghı́va a makrót, a makródefinicó behelyettesı́tése után se keletkezhessen hibás kifejezés (lásd precedenciák). 2.18 Feladatok: min, max, pow, toupper, tolower Oldjuk meg a következő feladatokat! a.) A fenti minta alapján készı́tsük el a min(a,b), illetve a max(a,b) makrókat. Irjunk kipróbáló főprogramot e makrók használatához! b.) Az xy = exp(y · log(x)) összefüggés felhsználásával ı́rjuk meg a mypower(x,y)
hatványozó makrót! Ügyeljünk arra, hogy a makró az x = 0 esetre is helyes eredményt (0.0) adjon Ne feledjük, hogy a megoldáshoz szükséges exp, illetve log függvények double visszatérési értékűek! (A mathh szabványos fejlécfile-ban vannak deklarálva) Irjunk egy keretprogramot, amellyel kipróbálhatjuk a saját, mypower hatványozó makrónkat! c.) Próbáljuk saját makróként megı́rni a ctypeh fejlécfile-ban definiált toupper, illetve tolower rutinokat! Nevezzük el a saját változatunkat mytoupper-nek, illetve mytolower-nek. (A paraméterként kapott karaktert nagybetűre, illetve kisbetűre cserélik) Ügyeljünk arra, hogy tényleg csak a betü karakterek esetében kell a konverziót elvégezni. A következő programpélda a c.) feladatban emlı́tett toupper, illetve tolower szabványos makrók használatát szemlélteti: /* * File: pelda.c * * Tartalom: Kisbet˝u-nagybet˝u felcser´el˝o
mintaprogram * */ #include <stdio.h> #include <ctype.h> /* A modulban defini´alt f¨uggv´enyek: */ void main(void); /* ======================================================== / void main() { register c; while ((c = getchar()) != EOF) { /* c-be olvasunk, file v´eg´eig */ if (isupper(c)) /* Ha nagybet˝u, akkor. */ 2.2 TÖMB-, POINTER- ÉS FÜGGVÉNYTÍPUSOK 19 { c = tolower(c); /* . kisbet˝ure cser´elj¨uk, */ } else /* . egy´ebk´ent pedig */ { c = toupper(c); /* . nagybet˝ure cser´elj¨uk */ } /* . Az ’if’ utas´ıt´as v´ege */ putchar(c); /* A megv´altoztatott c-t ki´ırjuk / } /* . A ’while’ ciklus v´ege */ } /* . A ’main’ blokk v´ege */ Módosı́tsuk tehát ezt a példaprogramot úgy, hogy a szabványos makrók helyett a saját makróinkat használják a karakterkonverzióra. 2.2 Tömb-, pointer- és függvénytı́pusok A C alaptı́pusaiból (char, int, float, double) és ezek módosı́tó jelzőkkel
képzett változataiból ún. származtatott tı́pusokat, és ezek felhasználásával származtatott tı́pusú tárolási egységeket hozhatunk létre. Például az int alaptı́pusból létrehozhatjunk egészre mutató pointer tı́pust, az egészt tartalmazó tömbök tı́pusát, illetve egészt visszaadó függvények tı́pusát. Ezekket a tı́pusokat felhasználva létrehozhatunk egészre mutató pointereket, egészt tartalmazó tömböket, egész visszatérési értékű függvényeket. Egy alaptı́pusból kétféleképpen hozhatunk létre származtatott tı́pusú tárolási egységet: 1. vagy a tárolási egység (változó, függvény) deklarációja során az adott tárolási egységgel kapcsolatban adjuk meg, hogy annak tı́pusa hogy származtatható a deklaráció alaptı́pusából, 2. vagy pedig általában definiáljuk a származtatott tı́pust a typedef kulcsszó
segı́tségével, és utána az ı́gy létrehozott új tı́pusba, mint alaptı́pusba tartozó tárolási egységként deklaráljuk a kérdéses objektumot. Példák az első esetre: double d, dtomb[20], *dmut, dfugg(int); Az alaptı́pus a double. Ilyen tı́pusú tárolási egység a d változó – amely, mint tudjuk, tárterületfoglaló tárolási egység. A fenti példában 2 további tárterületfoglaló tárolási egységet definiálunk, ezeknek az azonosı́tói rendre dtomb, illetve dmut. Az előbbi egy 20 elemű double alaptı́pusú tömb, az utóbbi pedig egy double-ra mutató (inicializálatlan) pointer. A fenti példában szereplő utolsó elem – a dfunc – egy kódgeneráló tárolási egységet, 20 2. FEJEZET BONYOLULTABB SZERKEZETEK Operátor ( ) [ ] * Megnevezés függvénytı́pust képző operátor tömbtı́pust képző operátor mutatótı́pust képző operátor Jelleg
postfix postfix prefix 2.3 táblázat Tı́pusmódosı́tó operátorok. A precedencia felülről lefelé csökken azaz egy függvényt deklarál. dfunc ”egy double-t visszadó, egy int paramétert váró” tı́pusú függvény azonosı́tója (Vegyük észre a különbséget a definició és a deklaráció között: a definició létre is hozza a tárolási egységet, mı́g a deklaráció csak azt mondja meg, hogy milyen tı́pusú az illető tárolási egység – egy függvény teljesértékű megadásához a függvénytörzsre is szükség lenne.) Egy származtatott tı́pus megadásának a logikája a következő: megadjuk a definiálandó/deklarálandó tárolási egység alaptı́pusát (ez itt most a double), majd megadjuk a tárolási egység azonosı́tóját, és hozzákapcsolunk egy ún. tı́pusmódosı́tó operátort Természetesen egy adott tárolási egység
azonosı́tójához nemcsak egy tı́pusmódosı́tó operátor kapcsolható, hanem több is: double d2dimtomb[20][5], *dmutmut, dmutfunc(int); Itt d2dimtomb egy 20·5-ös, 2 dimenziós double alaptı́pusú tömb, dmutmut egy double-ra mutató pointerre mutató pointer, dmutfunc pedig egy double-ra mutató pointer visszatérési értéket adó, egy int paraméterrel rendelkező függvény. Ez utóbbi példa azt sejteti, hogy nem mindig egyszerű dolog egy bonyolultabb származtatott tı́pus értelmezése Ez egyrészt a tı́pusmódosı́tó operátorok különböző precedenciája miatt van ı́gy, másrészt a * pointertı́pust képző operátor ún. prefix operátor, mı́gy a [ ] és a ( ) operátor ún. postfix operátor Könnyebben tudunk összetetteb származtatott tı́pusba tartozó tárolási egységeket deklarálni, ha a typedef kulcsszó segı́tségével magukat a származtatott tı́pusokat is
deklaráljuk, és a bonyolultabb szerkezeteket lépésről lépésre hozzuk létre. A typedef használatának általános sémája a következő: Új tı́pust mindig valamilyen már meglévő tı́pusból (elemi tı́pusból, struktúrákból, vagy typedef-fel már korábban definiált tı́pusból) hozhatunk létre úgy, hogy megnevezzük az új tı́pust: 2.2 TÖMB-, POINTER- ÉS FÜGGVÉNYTÍPUSOK 21 typedef int ip; Tehát a fenti példában megnevezett új tı́pus az ip. Vegyük észre, hogy a fenti tı́pusdeklaráció olyan, mintha ip ”typedef” tárolási osztályú int tı́pusú változó lenne. Persze nem az, hanem csak az int tı́pussal megegyező értelmű újabb tı́pus azonosı́tója A változódeklarációval analóg logikát folytatva, alakı́tsuk át ip értelmezését. Legyen ip például egy int-re mutatú pointer tı́pusának az azonosı́tója: typedef int *ip; Következő
példánkban egy egész tı́pust visszaadó, két egész paramétert váró függvény tı́pusát definiáljuk: typedef int ifunc(int, int); Ebből a tı́pusból most – fenti logikát követve, a megfelelő tı́pusmódosı́tó operátor segı́tségével – egy pointertı́pust származtatunk: typedef ifunc *ifuncptr; Megjegyzendő, hogy a tı́pusmódosı́tó operátorok a szó szorosan vett értelmében véve nem operátorok, mert nem valamilyen adathalmazon értelmezett művelet végrehajtására szóló utası́tást jelentenek, hanem csak a C forrásprogramok fordı́tásakor van szerepük. Természetesen a tı́pusmódosı́tó operátoroknak van végrehajtható műveletet jelentő párjuk. Ezek a * indirekció operátor, a [ ] indexelő operátor és a ( ) függvényaktivizáló operátor. Összefoglalva az eddigieket: Alaptı́pusú tárolási egység char ch=’a’; int i=100; float f=3.14;
Tı́pusmódosı́tó operátor * pointertı́pust képző op. ( ) függvénytı́pust képző op. [ ] tömbtı́pust képző op. Származtatott tı́pusú tárolási egység char *chp=&ch; Alaptı́pust képző operátor * indirekció pointer int ifv(); ( ) fv.hı́vás függvény float fvek[5]; tömb [ ] indexelés Alaptı́pusú kifejezés értelmezése *chp az a karakter, amire chp mutat (az ’a’) i = ifv(); az ifv függvény által visszaadott egész f=fvek[1] fvek[1] egy float szám fvek 2. eleme 22 2. FEJEZET BONYOLULTABB SZERKEZETEK Megjegyzések: 1. A C-ben a tömbök indexelése mindig 0-tól kezdődik A C-ben nincs automatikus indexhatár ellenőrzés. 2. Egy tömbváltozó azonosı́tója önmagában, mint kifejezés, a tömb kezdőcı́mét jelenti, tehát alaptı́pus* tı́pusú kifejezés. Például a char string[ ] = "Ez egy sztring"; definiciójú karaktertömb (melynek
mérete az inicializáló sztringkonstans méretével fog megegyezni) úgy is felfogható, mint egy inicializált karakterpointer konstans: const char *string = "Ez egy sztring"; ahol a pointerkonstansnak kezdőértékül a sztringkonstans kezdőcı́mét adtuk. Ezek alapján általában értelme van az alábbiaknak: char str1[ ] = "abcdefghijklmnopqrstuvwxyz"; char *str2; char str3[sizeof(str1)/sizeof(char)]; . str2 = str1; . *(str3 + 2) = str2[2] = ’C’; Tehát pointernek tömbcı́m adható kezdőértékül, illetve az indexelő operátor alkalmazható pointerkifejezésekre is. 3. Akár tömbváltozókra, akár pointerekre alkalmazható az ún pinteraritmetika Művelet pointer + int pointer - int pointer - pointer Eredmény pointer pointer int 4. A C-ben a függvények mindig érték szerint veszik át paramétereiket Egy függvényparaméterként megadott tömb valójában a tömb kezdőcı́mét
jelenti. Tehát például a 2.3 KARAKTERTÖMBÖK ÉS POINTEREK 23 char *strcpy(char d[ ], char s[ ]); formájú függvénydeklaráció, és a char *strcpy(char d, char s); formájú függvénydeklaráció lényegét tekintve egyenértékű. Az első esetben talán jobban látszik, hogy tömböket szeretnénk paraméterként átadni. 5. Egy függvényazonosı́tó önmagában, mint kifejezés, a függvényt alkotó programkód cı́mét jelenti. Általában úgy használjuk, hogy egy függvényekre mutató pointernek adjuk értékül Erre vonatkozó példát egy kicsit később mutatunk be. 6. A tı́pusmódosı́tó operátorokra vonatkozó ismereteket a 23 táblázatban foglaltuk össze 2.3 2.31 Karaktertömbök és pointerek Feladat: saját strcpy Próbáljuk megı́rni a string.h szabványos fejlécfile-ban deklarált strcpy függvényt! A függvénynek két paramétere van, mindkettő
egy-egy karaktertömb. A függvény feladata, hogy a második paraméterként kapott sztringet az első paraméterként kapott tömbbe másolja be Visszatérési értékül a másolat sztring cı́mét adja! 1. megoldás: char *strcpy(char d[ ], char s[ ]) { int i,l; l = strlen(s); for (i = 0; i < l; i++) d[i] = s[i]; return &d[0]; } Itt egyszerűen egy szabványos rutinnal (strlen) lekérdezzük a forrás sztring hosszát, majd egy for ciklust l-szer végrehajtva karakterenként másolunk. Ügyesebb megoldás, ha kihasználjuk, hogy a sztringek végét mindig az EOS karakter jelzi. Az EOS figyelésével a sztring hossza érdektelenné válik Ezt 24 2. FEJEZET BONYOLULTABB SZERKEZETEK szemlélteti a 2. megoldás: char *strcpy(char d[ ], char s[ ]) { int i; while((d[i] = s[i]) != EOS) i++; return d; } Ennél jobb megoldást adhatunk, ha kihasználjuk, hogy a tömbparaméterek a függvényeknak valójában pointerként
lesznek átadva. 3. megoldás: char *strcpy(char d, char s) { char *p = d; while ((*d = s) != EOS) { d++; s++; } return p; } Végül azt is kihasználhatjuk, hogy az indirekció operátora (*) magasabb precedenciájú, mint a pointerekre alkalmazott ++ operátoré, továbba kihasználhatjuk az is, hogy az EOS decimális értéke 0, azaz logikai értelembem hamis, ennélfogva a relációs műveletre nincs is igazán szükség a while ciklusból való kilépés eléréséhez. 4. megoldás: char *strcpy(char d, char s) { char *p = d; while ((*d++ = s++)) ; return p; } 2.4 FÜGGVÉNYPOINTEREK 2.32 25 Feladat: saját strlen Az előző feladat megoldása során tett megfontolásokat alkalmazva próbáljuk mi magunk megı́rni a string.h-ban deklarált strlen függvényt! A függvény bemenőpraméterként egy sztring kezdőcı́mét kapja, visszatérési értékül a sztring záró EOS nélküli hosszát adja. 2.4
Függvénypointerek Függvényekre mutató pointerekre sok esetben szükségünk lehet. Gondoljunk csak arra, hogy egy numerikus intgerálást végző rutinnak tetszőleges integrálandó függvény esetében működnie kell, lehetőleg változtatás nélkül. Nos, erre a C kiváló lehetőségeket nyújt. Mielőtt azonban egy univerzális integráló rutint ı́rnánk, teküntsünk egy egyszerűbb példát. Felhasználás: qsort Tegyük fel, hogy egy adott tı́pusú adathalmazt valamilyen szempont szerint rendeznünk kell. Legyen ez az adathalmaz mondjuk egy tömbben adott Felmerülhet bennünk, hogy az első félév során megismert valamelyik rendező algoritmust mi magunk lekódoljuk C-ben, és ezzel a probléma meg is van oldva. Nos, ez egy járható út, de két ellenvetésünk is lehet Az egyik az, hogy az adatrendezésre sok előre elkészı́tett rutin létezik, úgyhogy nagy valószinűséggel időt és
munkát pazarlunk a saját próbálkozásunkkal. A másik ellenvetés az lehet, hogy ha mégis nekilátunk egy rendező rutin ı́rásának, nagy valószinűséggel az általunk elkészı́tett változat túlságosan testreszabott lesz, azt később nehézkes lesz más programokban felhasználni. Az igzság az, hogy az adatrendezést igen egyszerűen megoldhatjuk az stdlib.h szabványos fejlécfile-ban deklarált qsort függvény felhasználásával Ez a rutin az ismert quicksort (gyorsrendező) algoritmussal dolgozik Prototı́pusa a következőképpen néz ki: void qsort(void *base, size t nelem, size t width, int (*fcmp) (const void *elem1, const void elem2)); Értelmezzük az egyes paramétereket! Az első paraméter, base a rendezendő tömb kezdőcı́me. Mivel tetszőleges tı́pusú adatok jöhetnek szóba, base-t ’általános pointertı́pusunak’ (void*) deklarálták. Majd a függvényhı́vás során nekünk
kell az ún tı́pusátalakı́tó (type cast) operátorral a mi mutatótı́pusunkat void* tı́pusúvá alakı́tanunk. A második paraméter (nelem) és a harmadik paraméter (width) tı́pusa size t. Ez egy szabványos tı́pusjelölés Lényegében ez egy int, de ez az alternatı́v név arra hı́vja fel a programozó 26 2. FEJEZET BONYOLULTABB SZERKEZETEK figyelmét, hogy itt az aktuális adattı́pusra vonatkozó méretinformációkat kell megadni. nelem-ben a rendezendő tömb méretét kell megadnunk, mı́g width-ben egy tömbelem méretét várja a rutin sizeof egységben. A qsort rutin utolsó paramétere fcomp. Ez egy függvénypointer tı́pusú paraméter Itt egy olyan függvény cı́mét kell megadnunk, amelyet a qsort a rendezés során szükséges elemösszehasonlı́tások elvégzésére használhat fel. Ez a függvény egész tı́pusú visszatérési értéket kell szolgáltasson. Bemenő
paraméterként két összehasonlitandó tömbelemre mutató pointert kap A visszatérési értéket a következőképpen kell szolgáltatnia az összehasonlı́tó függvénynek: -1 0 1 ha *elem1 < elem2 ha *elem1 == elem2 ha *elem1 > elem2 Lássunk egy példát a qsort használatára! #include <stdio.h> #include <stdlib.h> #include <string.h> int sort function (const void *a, const void b); char list[ ][4] = { "cat", "car", "cab", "cap", "can" }; #define LISTSIZE #define ELEMSIZE int main(void) { int i; sizeof(list)/sizeof(char*) sizeof(list[0])/sizeof(char) qsort((void*)list, LISTSIZE, ELEMSIZE, sort function); for (i = 0; i < LISTSIZE; printf("%s ", list[i++]) ; return 0; } /* ----------------------------------------------------- / int sort function(const void *a, const void b) { return strcmp((char*)a,(char)b); } /*
----------------------------------------------------- / 2.4 FÜGGVÉNYPOINTEREK 27 Tehát a list nevű, 4 karakteres sztringekből álló tömböt szeretnénk növekvő sorrendbe rendezni. list első dimenziójának a meghatározását a fordı́tóra bı́zzuk – hiszen ez az adat az inicializáló lista alapján egyértelműen kiderül. Ezt az értéket a sizeof operátor felhasználásával ki is számoltatjuk, és LISTSIZE szimbólumhoz rendeljük, hogy később egyszerűen használhassuk. Ugyancsak a preprocesszorral számoltatjuk ki egy listaelem méretét, és ezt az ELEMSIZE szimbólumhoz rendeljük. Ezeket a hozzárendeléseket persze megelőzte az összehasonlı́tó sort function függvényünk deklarálása. Vegyük észre, hogy e függvény tı́pusa megegyezik a qsort formális paraméterlistáján szereplő *fcmp tı́pusával. A qsort rutin meghı́vásakor csak list-et kellett void*
tı́pusúvá konvertálnunk. Utolsó paraméterként szerepel a hasonlı́tó függvény cime – sort function – összhangban az 5. megjegyzésben leı́rtakkal Jelen példánkban a hasonlı́tó függvényt igen könnyen elkészı́thettük. Mivel sztringkonstansokat kellett egymással összehasonlı́tanunk, egyszerűen a string.h szabványos fejlécfile-ban deklarált strcmp függvényt használhattuk, mert ennek paraméterezése és visszatérési értéke megfelel a qsort által megkı́vántaknak. Tulajdonképpen közvetlenül is megadhattuk volna strcmp-t a qsort hı́vásakor, de ekkor megoldásunk nem lett volna korrekt: *fcmp-nél void tı́pusú bemenő paraméterek vannak előı́rva, mı́g strcmp paraméterei char* tı́pusúak. A mi sort function függvényünknek tehát semmi más dolga nincs, mint ezt a tı́puskonverziót végrehajtani. Feladat: Definiáljunk egy tetszőleges tı́pust (lehet akár a
Pascal RECORDnak megfeflő struct is), ebből hozzunk létre egy rendezetlenül kitöltött tömböt, majd rendezzük a qsort rutinnal. Írjuk meg a rendezéshez szükséges hasonlı́tó függvényt E függvény módosı́tásával változtassuk meg a rendezési szempontot! Indirekt függvényhı́vás Láttuk, hogy a függvénypointerek használata nagy flexibilitást tud kölcsönözni kész rutinok számára. Ezt használjuk ki arra, hogy egy integráló függvényt ı́rjunk a félév elején megismert valamelyik numerikus integráló algoritmus felhasználásával. Az integráló függvény deklarációja a következő legyen: double integration(double a, double b, int n, double (*f)(double x)); Írjunk olyan főprogramot, amely egy-két ismertebb függvényosztályba tartozó függvény integrálját számolja ki! A szükséges főprogram egy lehetséges megvalósı́tását a következő oldalon
találhatjuk. Tekintsük át alaposan ezt 28 2. FEJEZET BONYOLULTABB SZERKEZETEK a listát. Próbáljuk megérteni tpedef utası́tásokat, illetve az integrandusz függvények deklarálásának kissé szokatlan módját. (Ez utóbbival kapcsolatban tessék arra gondolni, hogy a tı́pusmódosı́tó operátorokkal kapcsolatban leı́rt logikát követve, a kódgeneráló tárolási egységeket – a függvényeket – ugyanúgy deklaráljuk, mint a tárterületfoglaló tárolási egységeket Ha tehát a double-t visszaadó, egy doble paramétert váró függvény tı́pust typedef-fel definiáljuk – ez a mi esetünkben a dfunc tı́pus – akkor ennek felhasználásával tárolási egységeket deklarálhatunk. Megjegyzendő, hogy függvénydefiniciót már nem végezhetünk typedef-fel definiált függvénytı́pus segı́tségével. #include <math.h> typedef double dfunc(double x); typedef dfunc *dfp;
/* ----------------------------------------------------double integration(double, double, int, dfp); /* ----------------------------------------------------dfunc expon, /* a+bexp(cx) power, /* ax^y sinus, /* a+bsin(cx+d) polin; /* a+bx+cx^2+dx^3 */ */ */ */ */ */ /* The same as double expon(double x), power(double x), sinus(double x), polin(double x); */ /* ----------------------------------------------------- / dfp functions[ ] = { expon, power, sinus, polin }; "a*x^y", char *fstrings[ ] = { "a+bexp(cx)", "a+b*sin(cx+d)", "a+bx+cx^2+dx^3" }; double a = 0, b = 0, c = 0, d = 0; /* ----------------------------------------------------- / int main(void) { int i = -1; double xa, xb; while ((i < 0)||(i > 3)) { printf("0 - %s 1 - %s 2 - %s 3 - %s ", fstrings[0],fstrings[1],fstrings[2],fstrings[3]); scanf("%d",&i); putchar(’ ’); } switch(i) { 2.4 FÜGGVÉNYPOINTEREK case 2: case 3: printf("d=");
scanf("%lf",&d); case 0: printf("c="); scanf("%lf",&c); printf("b="); scanf("%lf",&b); case 1: printf("a="); scanf("%lf",&a); 29 putchar(’ ’); putchar(’ ’); putchar(’ ’); putchar(’ ’); } printf("xa="); scanf("%lf",&xa); putchar(’ ’); printf("xb="); scanf("%lf",&xb); putchar(’ ’); printf(" Integral of %s = %12.5g ",fstrings[i], integral(xa,xb,50,functions[i])); } /* ----------------------------------------------------- / double polin(double x) { return a + b*x + cxx + dxxx; } . /* ----------------------------------------------------- / double integration(double a, double b, int n, double (*f)(double x)); { double integr, x, dx; for (integr = 0, x = xa, dx = (xa-xb)/n; n; n--, x+=dx) { integr += (*f)(x) dx; } return integr; } /* ----------------------------------------------------- / Feladatok: Integrálás
függvénypointerrel adott integranduszokkal a) Dolgozzuk ki teljesen az integráló programot, futtassuk le! b) Bővı́tsük az integrálható függvényosztályok halmazát! c) Alakı́tsuk át a programot úgy, hogy az integration függvény egy további int tı́pusú paraméterével választhassunk különböző integrálási módszerek között. Ezt szintén indirekt (függvénypointer tömbön keresztül történő) függvényhı́vással valósı́tsuk meg! 30 2. FEJEZET BONYOLULTABB SZERKEZETEK 2.5 Több modulból álló programok készı́tése Az előző feladatok megoldásokar feltűnhetett, hogy milyen buta dolog az, ha olyan módosı́tásokat végzünk, aminek semmi köze az integration függvényhez, azt akkor is újra kell fordı́tanunk a valószinűleg szintén változatlan main-nel együtt. Vagy ha a numerikus integrálást végző függvények környékén történt változás,
akkor az integrálandó függvényeket fordı́tjuk újra Ezen csak úgy tudunk változtatni, ha a forrásállományunkat hatékony módon átszervezzük: több, egymástól függetlenül fordı́tható modulra bontjuk, és a végső futtatható program összeállı́tását az adott C nyelvi rendszer linkelő programjára bı́zzuk. Milyen részekre érdemes bontani a programunkat? Célszerű az alábbi felosztást követni: • mainint.c A main függvényt (főprogramot) tartalmazza • myfunc.c A saját, integrálandó függvények (például polin, expon, stb) definicóit tartalmazza. • numint.c A numerikus integrálás függvényeit (a külső modulokkal kapcsolatot tartó integration függvényt, valamint az egyes belső integráló függvényeket – ilyen lehet mondjuk egy simpson nevű függvény) tartalmazza. • myvars.c A több modul által is használt publikus globális változók definicióit
tartalmazza. Már majdnem optimális a forráspállomány felosztása. A fenti felosztás tényleg a program optimális modulstruktúrájának felel meg, de a forrásállományt még célszerű tovább tagolni. Ennek a további tagolásnak a célja az, hogy minden .c kiterjesztésű file-ból generált modul ugyanazokat a deklarációkat lássa, illetve az egyes modulok ezen deklárációk utján kapcsolatban legyenek egymással. Igy a következő ún deklarációs fejlécfile-okat célszerű még létrehozni: • mytypes.h A saját tı́pusdefinicióinkat (dfunc, dfp tartalmazza Ezt minden .c forrásállomány elejére épı́tsük be az #include "mytypesh" preprocesszor utası́tással! • myfunc.h Az egyes c állományokban definiált publikus függvények extern deklarációit tartalmazza. Ezt minden forrásállomány elejére, a mytypes.h után épı́tsük be az #include "myfunch"
preproceszszor utası́tással! 2.5 TÖBB MODULBÓL ÁLLÓ PROGRAMOK KÉSZÍTÉSE Élettartam statikus Láthatóság globális statikus modulra lokális blokkra lokális blokkra lokális statikus dinamikus Deklarátor helye bármely modulban minden blokkon kı́vül adott modulban minden blokkon kı́vül adott blokkban adott blokkban 31 Tárolási osztály extern static static auto, register 2.4 táblázat Változók élettartama, láthatósága és tárolási osztálya • myvars.h A myvarsc állományban definiált publikus globális változók extern deklarációit tartalmazza Ezt minden függvényeket definiáló forrásállomány elejére, a myfunch után épı́tsük be az #include "myvarsh" preprocesszor utası́tással! A változódejklarációk, illetve definiciók helyes kialakı́tásában nyújthat segı́tséget a tárolási osztályokat összefoglaló 2.4 táblázat Feladat:
A fenti elveknek megfelően szervezzük át programunkat, külön-külön fordı́tsuk le az egyes .c forrásmodulokat, majd linkeljük össze a programot. 32 2. FEJEZET BONYOLULTABB SZERKEZETEK 3. fejezet A dinamikus tárkezelés alapjai 3.1 Dinamikus adatok Pointereknek értékül eddig vagy egy változó cı́mét adtuk (a & – address of operátor segı́tségével), vagy egy tömb kezdőcı́mét. Azt is láttuk, hogy nincs elvi különbség egy tömbváltozó és egy azonos alaptı́pusba tartozó pointer között. A gondot csak az jelenthette, hogy a C tömbök mérte – hasonlóan a Pascal tömbökéhez – a deklarációkor (akár általunk explicit módon, akár egy inicializáló kifejezés által implicit módon) megadott, kötött érték. Hogy ı́rjunk ekkor olyan programot, amelyik mindig csak akkora tömböket használ, emekkorákra ténylegesen szükség van a programfutás során,
és a már nem szükséges tömbök által elfoglalt memóriaterületet fel is szabadı́tja? A fenti problémát számunkra az stdlib.h szabványos fejléc file-ban deklarált malloc memóriafoglaló, illetve a free memóriafelszabadı́tó függvények oldják meg. E függvények prototı́pusai ı́gy néznek ki: #include <stdlib.h> void *malloc(int size); void free(void*); A malloc függvény size darab byte tárolására szolgáló memóriaterületet kér az operációs rendszertől. Ha a kérés teljesı́thető, akkor lefoglalja az adott memóriablokkot, és visszatérési értékül egy, a blokkra mutató általános tı́pusú (void*) pointer értéket ad. Ha a memóriafoglalási kérés nem teljesı́thetű, visszatérési értékül NULL-t kapunk. A C-ben a NULL ugyanazt 33 34 3. FEJEZET A DINAMIKUS TÁRKEZELÉS ALAPJAI jelenti, mint a Pascal-ban a NIL: azaz ez a sehova se mutató pointer, amely
minden érvényes memóriacı́mtől egyértelműen megkülönböztethető. Tekintsünk egy példát! Keszı́tsünk egy n méretű double tı́pusú tömböt! A tömb méretét a felhasználó adja meg! #include <stdlib.h> . int n; double *tombmut; . printf("Size="); scanf("%d",&n); putchar(’ ’); tombmut = (double*) malloc(n sizeof(double)); if (tombmut == NULL) { printf("Unable to allocate for %d double numbers! ",n); exit(1); } . for (i = 0; i < n; i++) { tombmut[i] = 0.0; } . free(tombmut); . A fenti példában a következő érdekesebb megoldásokat alkalmaztuk. 1. Először is a malloc által visszaadott pointer kifejezés tı́pusát az ún type-cast operátorral olyan tı́pusúvá alakı́tottok, amilyen tı́pusú ponterre konkrétan szükségünk van. A type-cast operátor általános alakja: (új tı́pus) Hatása az, hogy operandusának tı́pusát új tı́pussá
alakı́tja. A mi konkrét esetünkben az általános mutatótı́pusból, a void*-ból csináltunk double-ra mutató tı́pust double-ot. 2. A malloc számára meg kell adnunk a lefoglalandó memóriablokk byte-okban kifejezett méretét. A felhasználótól mi csak a tömb logikai méretét kérdezzük meg (ez az n), a tényleges fizikai méret megállapı́tásához a logikai méretet meg kell szorozni a tömb alaptı́pusának byte-okban kifejezett méretével. Ez utóbbi adatot a sizeof operátorral állı́tottuk elő 3.1 DINAMIKUS ADATOK 35 3. Természetesen a malloc által visszaadott pointer-kifejezést meg kell vizsgálnunk, hogy nem NULL-e? Ha NULL, akkor a memóriafoglalási kérelem nem volt teljesı́thető. Ez sokszor fatális programfutási hibát jelez, ı́gy a megfelelő hibaüzenet kiratása után az exit függvény felhasználásával félbeszakı́tjuk a programfutást, és az operációs
rendszer számára egy megfelelő hibakódot átadunk. (Ezt a kódot megvizsgálhatja a programot aktivizáló parancs-file, vagy batch-file) Vegyük észre, hogy a Pascal NIL-hez hasonlóan a NULL tetszőleges pointer-tı́pussal kompatibilis. 4. Ha sikeres volt a memóriafoglalás, akkor a malloc által visszaadott, és megfelő tı́pusúvá alakı́tott pointerkifejezésre úgy tekinthetünk, mint egy tömb báziscı́mére, ı́gy akár indexelhetjük is, mint az igazi tömböket. (Annyi a különbség az igazi tömbökhöz képest, hogy tombmut értéke megváltoztatható. 5. Egy C program memória-használata attól lesz igazán dinamikus, hogy a nem használt memóraietrületek felszabadı́tja a program. A mi egyszerű példánkban ezt az utolsó utası́tás, a free(tombmut) végzi el Fontos, hogy ha a free függvénynek nehogy NULL értékű pointer-kifejezést adjunk át, mert különben ”elszállhat” a
programunk. 3.11 Feladat: lineáris egyenletrendszer megoldása Irjunk egy lineáris egyenletrendszer megoldására alkalmas programot! A program kérdezze meg az ismeretlenek számát, és futási időben foglajon helyet az együttható mátrixnak, a konstans vektornak, illetve az ismeretlenek vektorának! Az eredmények kiiratása után, de még a visszatérés előtt szabadı́tsuk fel a lefoglalt memóriát! Segı́tség a megoldáshoz: Célszerű egy már létező programot (akár Pascal, akár C) átı́rni, illetve módosı́tani. A vektorok számára a helyfoglalást a bevezető isemertető alapján könnyen megvalósı́thatjuk. Gondot csak a 2 dimenziós együttható mátrix jelenthet. Tegyük föl, hogy statikus helyfoglalás esetében a mátrix deklarációja a következő: #define N 3 double amat[N][N]; Az amat tömböt úgy is felfoghatjuk, mintha az alábbi módon lenne deklarálva: 36 3. FEJEZET A
DINAMIKUS TÁRKEZELÉS ALAPJAI #define N 3 double sor0[N], sor1[N], sor2[N]; double *amat[] = { sor0, / ez mind double tipusu / sor1, sor2 }; azaz amat nem más, mint double* tı́pusú pointerek tömbje. Itt még minden statikus – az egyes sorok mértét explicit módon definiáltuk, mı́g amat méretét implicit módon, az inicializáló kifejezés adja meg. Látjuk tehát, hogy amat egy double-ra mutató pointerek tömbjének kezdőcime, maga is egy – igaz, konstans – pointer, olyan, mintha double* tı́pusúnak deklaráltuk volna. Ennek alapján felı́rhatjuk most már a dinamikus helyfoglalásra alkalmas deklarációt is: double* amat; /* Sehova nem mutat, de majd fog! / Maga a dinamikus helyfoglalás két részből rakható össze. Először az amat nevű, n elemű (n futási időben megadott egész paraméter – az aktuális ismeretlenek száma) duplapontosságú valós számokra mutató pointerek tárolásár
szolgáló tömböt hozzuk létre az amat = ((double*)) malloc(n * sizeof(double)); utasással, majd minden egyes sor számára foglalunk helyet. Például az i-edik sort az amat[i] = (double*) malloc(n sizeof(double)); utası́tással hozhatjuk létre. A mátrix által elfoglalt memóriaterület felszabadı́tásakor fordı́tott sorrendet kell követnünk. Előszor az egyes sorokat szűntetjük meg, majd magát az amat tömböt. Fontos figyelmeztetések: 1. Attól, hogy egy pointert deklaráltunk, még nem lesz értéke, ı́gy sehova sem mutat! 2. Attól, hogy egy pointernek van értéke, azaz mutat valahová, még mindig nem biztos, hogy érvényes memória-területre mutat Azt a memóriaterületet, ahová egy pointerrel mutatni szeretnénk, LE KELL FOGLALNI! 3. A C-ben az indexelés 0-tól indul, és tömbméret - 1-ig tart A tömbtúlcı́mzés miatt nem szól a fordı́tó, legfeljebb elszáll a program Komolyabb
operációs rendszerekben (VMS, UNIX) maga az operációs rendszer figyelmeztet arra, hogy érvénytelen memóricı́mre hivatkozunk 3.1 DINAMIKUS ADATOK 37 Általában access violation hibaüzenet és rutin-hı́vási lista (symbolic stack dump) kiséretében a programfutást megszakı́tja az operációs rendszer. Sajnos a DOS ilyesmire nem figyel! 38 3. FEJEZET A DINAMIKUS TÁRKEZELÉS ALAPJAI 4. fejezet Az operációs rendszerrel való kapcsolat 4.1 Folyam jellegű I/O Tekintsük a korábbról már ismert, a szabványos bemeneti állományt a szabványos kinenetre másoló programot! #include <stdio.h> main() { int ch; /* int and char are compatible / ch = getchar(); while (ch != EOF) { putchar(ch); ch = getchar(); } } Most már tudjuk, hogy itt egyszerűen arról van szó, hogy az stdin előre definiált folyamból az stdout előre definiált folyamra másolunk. Ezek a folyamok – hacsak az operációs
rendszer szintjén át nem irányı́tottuk őket – a billentyűzethez, illetve a terminálképernyőhöz vannak hozzárendelve. 39 404. FEJEZET AZ OPERÁCIÓS RENDSZERREL VALÓ KAPCSOLAT 4.11 Feladatok: File-ok másolása a) Alakı́tsuk át a fenti programot úgy, hogy a getchar, illetve a putchar rutinok helyett az int fgetc(FILE *stream), illetve az int fputc(int c, FILE *stream) deklarációjú függvényekkel kezeljük az stdin-t és az stdout-ot! b) Alakı́tsuk át az a) pont szerint kidolgozott programot úgy, hogy az INPUT.TXT fizikai állományt az OUTPUTTXT fizikai állományba másoljuk át (Feltehetjük, hogy mindkét állomány az aktı́v könyvtárban van.) 4.12 File-nyitási hibák, azok kezelése Az előző b) feladat megoldása során felmerülhet a kérdés: mi van akkor, ha az input adatállomány nem létezik? Nos, ez könnyen ellenőrizhető. Ezt szemléltetjük az alábbi
programrészlettel: #include <stdio.h> . FILE *finp; char inpfname[80]; . printf("Name of the input file="); scanf("%s%,inpfname); putchar(’ ’); . finp = fopen(inpfname,"r"); if (finp == NULL) { printf("File %s does not exist! ",inpfname); } A mechanizmus hasonló, mint amit a dinamikus tárfoglalásnál közölt programrészletnél láthattunk. A felhasználótól bekérjük a megfelelő paramétert – ez itt a file neve; az ı́gy megadott sztring az adott operációs rendszerben érvényes teljes file-specifikáció lehet (drive, directory, filename, extension, version úgy, ahogy azt az operációs rendszer megkı́vánja). Ezután következik az operációs rendszerrel történő kapcsolatfelvétel Most memóriafoglalás helyett file-hozzárendelés történik Az ezirányú rendszer-szolgáltatás igénybevételének eredményét természetesen mindig meg kell vizsgálnunk:
sikerült-e létrehoznunk azt, amit akartunk. Most azt nézzük meg, hogy sikeres volt-e a file-megnyitás. Ha igen, akkor az ún file-pointer nem NULL értéket kap, ha sikertelen volt, akkor NULL lesz az értéke. 4.2 A MAIN ARGUMENTUMAI 41 A sikertelen file-megnyitásnak különböző oka lehet, a leggakoribb azonban az, hogy az olvasásra megnyitott file nem létezeik. A hiba pontos okáról is szerezhetünk információt, de ennek tárgyalására nem futja az időnkből. A lényeg az, hogy minden ún. file-megnyitás eredményét meg kell vizsgálnunk! (Ugyanı́gy, ha ı́rásra nyitunk meg egy file-t, akkor is elképzelhető, hogy NULL-t kapunk visszatérési értékül; például akkor, ha nemlétező könyvtárra hivatkztunk a file-specifikációban.) Feladat: Módosı́tsuk a file-másoló programunkat úgy, hogy mind az input, mind az output file nevét a felhasználó adja meg. Készüljünk fel
arra, hogy esetleg hibás file-specifikációt ad meg a felhasználó! 4.2 A main argumentumai A main függvénynek előre definiált argumentumai lehetnek – ezek az operációs rendszer által a programnak átadott paraméterek: main(argc, argv) int argc; char *argv[]; Az argc azt mutatja meg, hogy az operációs rendszer parancsértelmezőjében hány paramétert adtunk át a programnak. Ezek a paraméterek mindig sztringként kerülnek átadásra, a main második argumentuma, argv rendre ezekre a sztringekre mutat. Megjegyzendő, hogy argv[0] mindig a program nevét tartalmazza (teljes file specifikációval), és ennek megfelelően argc értéke mindig legalább 1. Ha tehát a DOS-ban a myprogexe, C nyelven megı́rt program a C:mydir könyvtárból lett behı́va, akkor a programon belül argv[0] értéke C:MYDIRMYPROG.EXE" lesz 4.21 Feladat: A copy parancs – saját kivitelben Alakı́tsuk át a file-másoló
programot úgy, hogy első parancs-sor paramétereként a forrás-file nevét, második paraméterként pedig a cél-file nevét vegye át az operációs rendszertől. A program neve legyen mycopy! Úgy ı́rjuk meg a mycopy programot, hogy ha 0 paraméterrel hı́vták meg (azaz argc == 1), akkor egy rövid helpet ı́rjon ki a képernyőre a hivási módjáról, majd a From: kérdéssel kérdezze meg az input file nevét, a To: prompttal pedig az output file nevére kérdezzen rá. Ha a felhasználó a From: file-t a parancssorban megadta, és csak a To: file hiányzik (argc == 2), akkor csak arra 424. FEJEZET AZ OPERÁCIÓS RENDSZERREL VALÓ KAPCSOLAT kell rákérdezni. Minden esetben ellenőrizzük, hogy létezik-e a From: file! (A joker karakterek, illetve a részleges output file specifikáció kezelésétől eltekintve ez a mycopy program a UNIX cp, illetve a VMS és a DOS 5.0 copy parancsának a keveréke.)
Fontos megjegyzés a VMS-ben dolgozók számára! A VMS első közelı́tésben nem támogatja, hogy a felhasználói programoknak parancs-sor paramétereket adhassunk át. Ez azt jelenti, hogy a parancs-sor paramétereket váró programok a VMS RUN parancsával nem futtathatók. A dolog mégsem katasztrófális: természetresen lehteőség van a C konvenció szerinti parancssor paraméterek átadására is. A fenti myprog program futtatása előtt adjuk ki a myprog:=="myprog.exe" parancsot a VMS-nek Ezután egyszerűen a myprog from.txt totxt begépelésére a myprog program megkapja a parancs-sor paramétereket, ı́gy a from.txt file-t a totxt állományba probálja átmásolni – feltéve persze, hogy maga a C program hibamentes! 5. fejezet Fejlettebb technikák 5.1 5.11 Struktúrák – láncolt lista Fealadat: Láncolt lista készı́tése Oldjuk meg C-ben a következő feladatot! Egy légitársaság
számı́tógépen tárolja az utaslistákat. Az összesı́tett állomány (UTAZASDAT) minden utas minden útjáról egy-egy bejegyzést tartalmaz. Egy bejegyzés szerkezete a következő: Járat száma Dátum Utas neve Légi km Jelleg – – – – – 8 karakter 6 karakter 32 karakter egész szám Szolgálati vagy Egyéni Készı́tsen olyan programot, amely kikeresi, és a TORZS.DAT nevű szöveges állományba kiı́rja a magánúton legtöbbet repült két utas nevét, valamint legutóbbi útjuk dátumát. A bemenő állományt a fscanf függvénnyel olvassa, a kimenő állományba szépen tabulálva az fprintf függvénnyel ı́rjon. A bemenő állomány mágnes-szalagon van, ı́gy csak egyszer olvasható be (Például egy VAX-on az MSA0: nevű eszközön – a szalagegységen – található az UTAZAS.DAT állomány) Segı́tség a megoldáshoz: Nyilvánvaló, hogy az input file-ból valamilyen struc
tı́pusú (a Pascal RECORD-nak megfelelő) adatokból képzett láncolt listába kell beolvasnunk az utasok adatait. Azért kell a dinamukis adatkezelés, mert az input file-t csak egyszer olvashatjuk be, nem tudjuk a méretét, viszont a már beolvasott adatokkal rendszeresen össze kell hasonlı́tanunk az 43 44 5. FEJEZET FEJLETTEBB TECHNIKÁK újólag beolvasott adatokat. Nézzük a szükséges adatstruktúrát! typedef enum trtyp {private, buisness} travel; typedef struct flgt { int char char int travel flgt flgtno; /* date[7]; /* name[32];/* flghtkm; /* type; /* *nextrec,/ *prevrec;/ } psngr rec; flight number */ date of the flight */ passenger’s name */ flight km */ 0:prv 1:bsns */ ->nxt record in lst*/ ->prv record in lst*/ Figyeljük meg, mennyivel világosabb a lista-szerkezet definicója a C-ben, mint a Pascal-ban! A C az enum, illetve struct tı́pusok deklarációjakor megengedi az ún. tı́puscı́mke megadását A dolog
lényege az, hogy a tı́puscı́mke a tı́pusdefiniciós blokkot pótolhatja. Ezt használhatjuk ki akkor, amikor listaszerkezetek kialakı́tására szolgáló önhivatkzó adatstruktúrákat hozunk létre. A mi példánkban a nextrec, illetve prevrec olyan pointer tı́pusú mezői a psngr rec struktúrának, amelyek psngr rec tı́pusú adatoktra képesek mutatni. Egy új rekord beolvasása: psngr rec int FILE *actptr, wrkptr; typ, endflag; *finput; / File pointer to UTAZAS.DAT eg on tape device EET751::MSA0: */ . /* We assume that actptr points to a valid data field / endflag = 5; while (endflag == 5) { workptr = (psngr rec*)malloc(sizeof(psngr rec)); if (workptr == NULL) { printf("Error building the dynamic list "); return 1; } actptr->nextrec = workptr; /* Build the chain */ (*workptr).prevrec = actptr; /* structptr->structfield or 5.1 STRUKTÚRÁK – LÁNCOLT LISTA (*structptr).structfield are the same! /* We assume that
finput != NULL 45 */ */ endflag = fscanf{finput,"%d%s%s%d%d", &(workptr->flgtno), workptr->date, workptr->name, &(workptr->flgtkm), &typ); /* read int from file / workptr->type = (travel)typ; /* make it enum */ . } Akinek már jól megy a C, megpróbálkozhat ennek a feladatnak a teljes kidolgozásával. A láncolt lista épı́tésének leállási feltétele az, hogy az input file-ból már nem tudunk olvasni. Ez például úgy derülhet ki, hogy folyamatosan ellenőrizzük, hogy az fscanf függvény mindig a specifikáció szerinti számú adatot olvasott-e be. Ez a szám a függvény visszatérési értéke A programból való kilépés előtt ne feledkezünk meg arról, hogy illik felszabadı́tanunk a programunk által lefoglalt memóriát (hasonlóan ahhoz, ahogy illik lezárni a lezáratlan file-okat is). 46 5.2 5. FEJEZET FEJLETTEBB TECHNIKÁK Menürendszer Ide jön egy
rövidebb menürendszer 5.3 ÖSSZETETT MINTAPÉLDA 5.3 47 Összetett mintapélda Jelen példánk egy igen flexibilis menükezelő rendszer vázát tartalmazza. Az itt felhasznált megoldások sokat segı́thetnek a tı́pusdefiniciókkal, struktúrákkal, pointerekkel, függvény-pointerekkel kapcsolatban leı́rtak megértésében. E példa fő célja a portábilis programozási stı́lus bemutatása, másrészt igyekszünk rávilágı́tani arra, hogy egy célszerűen megtervezett adat- és szubrutinstruktúra mennyire áttekinthetővé és könnyen módosı́thatóvá teszi a felhasználói programjainkat. Felhı́vjuk az olvasó figyelmét, hogy ezzel a példprogrammal nem azt akarjuk sugallni, hogy ez az igazi menükezelő, illetve felhasználói felület. Léteznek olyan objektum-orientált könyvtárak, amelyek az itt leı́rtaknál sokkal fejletteb felhasználói felületet valósı́tanak meg –
természetesen használatukhoz ismerni kell a C++-t, illetve ha Windows alkalmazói programot készı́tünk, a programvezérlésről és a menükről alkotott képünket mindenképpen át kell alakı́tanunk. 5.31 A tervezés egyes fázisai Minden programfejlesztési munka során előjön az a feladat, hogy az adott program számára egy felhasználói felületet (user interface-t) kell ı́rni. Ez a felület az esetek legnagyobb részében valamilyen menürendszert jelent. Egy menürendszert úgy célszerű kialakı́tani, hogy az általa nyújtott szolgáltatások az egész felhasználói programban igénybevehetők legyenek, és a felhasználói program többi részében lehetőség szerint ne kelljen a képernyőés billentyűzetkezeléssel foglalkozni. Az egész felhasználói program, illetve a hozzákapcsolódó menürendszer tervezésekor egy másik fontos szempont az, hogy a program belső vezérlési
szerkezetei tükrözzék azt a vezérlési szerkezetet, amit a felhasználó észlel a program használata során. Másképpen ezt úgy fogalmazhatjuk meg, hogy ne az egyes programfunkciók aktivizáljanak kisebbnagyobb menürutinokat, hanem egy átfogó, hierarchikus menürendszer gondoskodjon arról, hogy mindig a felhasználó kı́vánsága szerinti programfunkciók legyenek aktivizálva. Portabilitási megfontolások Ha fáradtságos munkával megtervezünk és létrehozunk egy, a fenti kı́vánalmaknak megfelelő felhasználói felületet, célszerű azt úgy programozni, hogy ne csak IBM-PC kompatibilis számı́tógépeken, a DOS operációs rendszer alatt, BORLAND C++ fordı́tóval lefordı́tva fusson, hanem jól körülhatárolt módosı́tások után bármely, C fordı́tóval rendelkező géptı́puson, bármely operációs rendszeren (pl. VT100-as terminálokkal rendelkező VAX 48 5. FEJEZET FEJLETTEBB
TECHNIKÁK gépeken) is használhassuk a megı́rt rutinjaink többségét. Ennek érdekében célszerű a megı́randó menürendszert 3 részre osztani. Az első rész tartalmazza a legmagasabb szintű függvényeket, amelyek vátoztatás nélkül portábilisak. A második, közbenső szint tartalmazza azokat a függvényeket, amelyeknek törzsét az aktuális C fordı́tó és operációs rendszer rendszerfüggvényei, illetve az aktuális számı́tógép-konfiguráció képernyője szerint módosı́tani kell. A harmadik, legalacsonyabb szinten célszerű elhelyezni a teljesen hardver-specifikus függvényeket Ilyenek lehetnek például az IBM PC BIOS rutinhı́vások. Jelen példánkban csak a legmagasabb szintű részeit mutatjuk be menükezelő rendszerünknek. A második, és harmadik csoprtba tartozó függvények közül csak a közvetlenül felhasznált függvények deklarációit közöljük
rövid magyarázatokkal. A látvány (look-and-feel) megtervezése Alapvetően a BORLAND C++ integrált fejlesztői környezetének menükoncepcióját igyekszünk megvalósı́tani a hot key-k kivételével. Igyekszünk egy egyszerű help-rendszert is megvalósı́tani, de nem célunk a BORLAND C++ környezetfüggő rendszerének a lemásolása. A menürendszert úgy látja a felhasználó, hogy több alfanumerikus ablak van a képernyőn. A BORLAND C++ erre ténylegesen is lehetőséget nyújtana, de a hordozhatóság miatt ezt nem használjuk ki. A menükezelő rendszerben az összes karakternyomtató utası́tás az egész képernyőre vonatkozik, mi magunk figyelünk arra, hogy csak a képernyő bekeretezett részén történjen nyomtatás. A képernyőn mi magunk hozunk létre keretezett részeket, dobozokat az IBM PC kiterjesztett karakterkészletével A felhasznált ’jobb felső sarok’, ’bal felső
sarok’, ’függőleges vonal’, stb. karakterek egyes számı́tógép terminálokon is léteznek, ”természetsen” más kódokkal, ı́gy célszerűen ezeket például a #define direktı́vával szimbólumokhoz rendeljük. Minden menü egy ilyen dobozba kerül, az egyes almenük dobozai a szülő menü dobozától egy kicsit lejebb kerülnek a képernyőre. Készı́tünk egy főmenü keretet is. Ennek a legfelső sora lesz a főmenű, azaz az egymástól független menüfák gyökerének a gyűjtőhelye. A főmenűből az egyes menüpontokat vagy egy dedikált billentyű leütésével, vagy a kurzor-mozgató nyilak (←, illetve nyilak) és az Enter billentyű segı́tségével választhatjuk ki. A kiválasztás hatására a menüpont alatt megjelenik a megfelelő almenü kerete, benne az egyes almenüpontokkal. Egy almenüponthoz vagy egy közvetlenül végrehajtható programrész, vagy egy további
almenü tartozik. Az almenük pontjait a ↑ ↓ kurzorvezérlő billentyűk és az Enter, illetve dedikált billentyűk segı́tségével választhatjuk ki. 5.3 ÖSSZETETT MINTAPÉLDA 49 Egy almenüből az Esc, vagy a minden menüben szereplő eXit menüponthoz rendelt X billentyű leütésével léphetünk ki. (Az eXit menüpontot és a hozzá tartozó X billentyűt a portabilitás miatt definiáltuk: egyes terminálokon az Esc billentyű kódja terminálvezérlő karakterszekvenciák része, ı́gy e billentyű leütését vagy nem tudjuk érzékelni, vagy a terminál ”megbolondul” tőle.) Egy menüpontként aktivizált programrészből, vagy egy almenüből visszatérve a hı́vó menü képe mindig regenerálódik, és az utoljára aktivizált menüpont marad kiválasztva, azaz egyszerűen csak az Enter billentyű leütésével újra aktivizálható. Leképezés adatstruktúrákra és
vezérlési szerkezetekre Az előbb vázolt megjelenés a képernyőn, illetve kezelési mód azt sugallja, hogy szükségünk van egy, a főmenüt leı́ró adatstruktúrára és az azt kezelő főmenü függvényre, illetve létre kell hozni egy olyan adatstruktúrát, amellyel leı́rhatjuk, hogy egy almenü hol helyezkedik el a képernyőn, milyen menüpontjai vannak, azokhoz milyen funkció (milyen végrehajtandó programrész, vagy milyen további almenü) tartozik, stb. Nyilvánvaló tehát, hogy kell egy adatstruktúra, ami az egyes menüpontokra vonatkozó információkat tartja nyilván (a menüpont neve, a hozzá tartozó help-információ, a hozzárendelt kiválasztó billentyű, kiválasztottáke, milyen feladatot lát el, esetleges paraméter). A menüpontokat menülistákba kell szerveznünk Egy ilyen listát ki kell egészı́tenünk a képernyőn való megjelenésre vonatkozó információkkal
(milyen hosszú a lista, hány karakter széles, a listát tartalmazó doboz hol helyezkedik el a képernyőn, stb.), és megadhatjuk azt is, hogy egy adott menü milyen hierarchia szinten helyezkedik el a menü-fán. A look-and-feel-re vonatkozó meggondolásokból következik, hogy az almenüket kezelő menüfüggvényt ugyanolyan funkcióként érdemes felfogni, mint a programunk ténylegesen végrehajtandó egyes részeit. Igy tehát célszerű az egyes menülistákat megszámozni, és a menükezelőnk ezen szám alapján tudja eldönteni, melyik menülistát kell megjelenı́tenie és kezelnie. Természetesen az is célszerű, hogy az egyes menüpontok a végrehajtandó programrészletekre vonatkozó default paramétereket tartalmaznak, és a menüpont kiválasztásakor ezen paraméterrel hı́vja meg a menükezelő a megfelelő részprogramot. Ilyen meggondolások mellett egy almenü megjelenı́tése belső
vezérlési szerkezetként úgy nyivánul meg, hogy a menükezelő függvény önmagát hı́vja meg úgy, hogy a rekurzı́v hı́vás alkalmával az almenü azonosı́tóját, mint paramétert használja. Hogy valósı́tsuk meg egy adott menüponthoz tartozó függvény aktivizálását? A válasz igen egyszerű: indirekt függvényhı́vást kell alkalmaznunk, 50 5. FEJEZET FEJLETTEBB TECHNIKÁK azaz a menüpont leı́ró struktúrában egy függvényre mutató pointermezőt kell deklarálnunk. Az egyes menüpontok definiálásakor ezt a struktúramezőt a ténylegesen meghı́vandó függvény cı́mével kell majd inicializálnunk A konkrét deklarációk Most tekintsük tehát az egyes tı́pusdeklarációkat! A menürendszerünk különböző menükből áll, a különböző menük pedig több menüpontból. Egy menüpont legfontosabb jellemzője az a függvény, amit a menüpont
kiválasztásakor aktivizálni kell. Ezek a függvények igen sokfélék lehetnek, ı́gy hagyományos C-ben célszerű a függvények cı́meit nyilvántartani. Ehhez két lépcsőben definiáljuk a fad (function address) tı́pust: typedef int intfunc(int);/* int-et visszaado, 1 int-et varo * fuggvenytipus */ typedef intfunc *fad; /* intfunc tipusu tarolasi * segre mutato tipus egy- * */ A fent definiált intfunc tı́pust felhasználhatjuk a majdan meghı́vandó egyes függvények előzetes deklarálására. A végrehajtandó függvényen kı́vül egy menüpont fontos jellemzője az illető menüpont neve (azonosı́tó szövege), az a nyomtatható karakter, amivel Enter megnyomása helyett kiválasztható a menüpont. Célszerű megengednünk, hogy a menüpont által meghı́vandó függvénynek egy, a menüpont leı́rásában tárolt paramétert is átadjunk. Ha a menürendszerünkhöz alkalmas help-rendszert is
szeretnénk, célszerű az egyes menüpontokhoz rendelt help-szövegre utaló információt (például egy file-indexet) is tárolni. Ezeket az informáiókat – a menüponthoz rendelt függvény cı́mével együtt – az alább deklarált menuitem (menüpont) struktúrába szerveztük: typedef struct { char *text; char key; int helpindex; fad function; int param; } menuitem; /* /* /* /* /* A A A A A menupont azonosito szovege menupontot kivalaszto betu menuponthoz rendelt help-kod menuponthoz tartozo fv-re mutat ’*function’ fuggveny parametere */ */ */ */ */ A figyeljük meg, hogy a fenti struktúra definicóból kimaradt a tı́puscı́mke, hiszen typedef-fel eleve azonosı́tót rendelünk hozzá – rekurzı́v adatdefinicóról pedig szó sincs. 5.3 ÖSSZETETT MINTAPÉLDA 51 Most lássuk, hogy szervezhetünk egy menüt a fenti módon deklarált menuitem struktúrák segı́tségével. A menüpontjainkat
célszerűen egy menuitem tı́pusú tömbben tároljuk, amelynek méretét is tudnunk kell. A menü tartalma mellett fontos annak megjelenése is. Szükségünk lehet arra, hogy a menüt keretező doboz tetején esetleg egy menünevet, egy fejlécet (header-t) is megjelenı́tsünk Fontos azt is tudnunk, hogy melyik x-y karakterpozicióba kerül a menüdoboz (annak például a bal felső sarka) a képernyőn, és az is lényeges információ, hogy hány karakterpoziciót foglal le a menüdoboz vı́zszintes és függőleges irányban. Azt is nyilvántarthatjuk egy menüről, hogy melyik menüpontot választottuk ki benne utoljára és fontos lehet az is, hogy az adott menü hol helyezkedik el egy hierarchikus menü-fán. Ezeket az információkat foglaltuk egybe az alábbi menutype struktúrában: typedef struct { char *header; int x; int y; int xs; int ys; int itemno; menuitem *items; int hierarch; int lastitem; } menutype; /* /*
/* /* /* /* /* /* /* A menu fejlecszovegere mutat A menudoboz bal felso sarkanak x es y koordinatai, valamint a menudoboz x es y iranyu merete. A menupontok szama A menupontok listajara mutat. Ha 1, kozvetlenul a fomenu hivja Utoljara kivalasztott pont szama */ */ */ */ */ */ */ */ */ A menuitem tı́pusból egy-egy inicializált tömböt szervezve hozhatjuk létre az egyes menük tartalmára vonatkozó adathalmazt. Egy ilyen lista kezdőcı́me kerül egy menutype struktúra items mezőjébe Egy-egy menutype struktúra egy komplett menü leı́rását tartalmazza. Ezekből a struktúrákból szintén egy tömböt szervezünk, ez lesz a menus tömb E tömb első néhány eleme egy-egy menüfa gyökerét (azaz a főmenü egyes pontjaiként aktivizálandó menüket) reprezentálja, a többi elem pedig az egyes fákra felfűzött almenüket ı́rja le. Tekintsük át tehát a teljes menürendszert definiáló adatstruktúrát: /*
Kulso fuggvenyek deklaracioja extern intfunc data, regr, save, r data, w data, statf, linf, barf, load; */ 52 5. FEJEZET FEJLETTEBB TECHNIKÁK /* A a menukezelo fuggveny prototipus erteku deklaracioja */ intfunc menu; /* El-ore hivatkozashoz intfunc dir, shell; /* Tovabbi fv-ek el-ore hivatkozshoz /* Az egyes menulistak (items 0 . items 3) es a menuk: menuitem items 0[ ] = { /* text key hlp func. param */ "Directory", ’D’, 1, dir, 0, "Os shell", ’O’, 2, shell, 0, "File", ’F’, 3, menu, 3,/*a 3.sz menu almenu lesz exitxt, ’X’,-1, NULL, 0 /*-1-es parameter: exit }; /* Tombmeret: #define N0 sizeof(items 0)/sizeof(menuitem) menuitem items 1[ ] = { "Default", ’D’, 4, data, 7, "Read data", ’R’, 5, r data,1, "List data", ’L’, 6, w data,2, "Statistics",’S’, 7, statf, 3, exitxt, ’X’,-1, NULL, 0 }; #define N1 sizeof(items 1)/sizeof(menuitem) menuitem items 2[ ] = {
"Regression",’R’, 8, regr, 4, "Plot", ’P’, 9, linf, 5, "Bar", ’B’,10, barf, 6, exitxt, ’X’,-1, NULL, 0 }; #define N2 sizeof(items 2)/sizeof(menuitem) menuitem items 3[ ] = { "Save", ’S’,11, savef, 0, "Load", ’L’,12, loadf, 0, exitxt, ’X’,-1, NULL, 0 }; #define N3 sizeof(items 3)/sizeof(menuitem) */ */ */ */ */ */ 5.3 ÖSSZETETT MINTAPÉLDA 53 /* A teljes menurendszer leirasa: menutype menus[ ] = {/* head. x y xs ys itemno items hier. last "", 9, 2, 13, N0+3, N0, items 0, 1, 0, "", 35, 2, 14, N1+3, N1, items 1, 1, 0, "", 61, 2, 14, N2+3, N2, items 2, 1, 0, "Files",11, 6, 8, N3+3, N3, items 3, 0, 1 }; */ */ Figyeljük meg, hogy a menülisták méretének meghatározását a fordı́tó programra bı́ztuk: a sizeof operátor segı́tségével megkapjuk mind az egyes menülistákat tartalmazó tömbök helyfoglalását byte-okban, mind a
menuitem tı́pus méretét; ezek hányadosa adja meg a menülista tömbök logikai méretét (azaz azt, hogy hány elemű egy menülista). Ezeket a kifejezéseket #define makróként definiáljuk, és az ı́gy kapott kifejezéseket használjuk fel a menus tömb inicializálására. Ez egy igen flexibilis megoldás, ugyanis egy menülista bővı́tése során a menus tömb inicializálásakor a menüdoboz méretére és a menülista hosszára vonatkozóan automatikusan helyes adatot fog a fordı́tó felhasználni. A menus tömb kitöltését legfeljebb csak akkor kell módosı́tani, ha egy új menülista-elem hossza nagyobb, mint az adott menüdobozhoz megadott xs érték. Saját include file-ok Menürendszerünk egy függvényekre mutató pointerekből álló tömb segı́tségével aktivizálja az egyes menüpontokhoz rendelt függvényeket. Ahhoz, hogy ezt a pointertömböt ki lehessen tölteni, szükség
van a saját függvényeink prototı́pusaira. Fontos, hogy csak int tı́pust visszaadó, egyetlen int tı́pusú paramétert váró függvényeket illeszthetünk be a menürendszerbe. Ha ettől eltérő rutinjaink vannak, akkor azokat ”fejeljük meg” úgy, hogy ennek a követelménynek eleget tegyenek. Ezeket a függvényeket vagy úgy deklaráljuk, ahogy azt az adatstruktúra leı́rásakor tettük, vagy egy include file-ba foglajuk a deklarációkat. A kettő egyszerre is alkalmazható, feltéve, ha a kétféle deklaráció összhangban áll egymással. Mi most a menükezelő rendszerben történő deklarációt alkalmazzuk, és csak a menükezelő rutinok deklarációit helyezzük el a saját file-ban. A bevezetőben emlı́tett, nem portábilis képernyőkezelő függvényeinket egy önálló .c file-ban érdemes tárolni, prototı́pusaikat szintén a függvény rpototı́pusokat tartalmazó include
file-unkban érdemes elhelyezni. Ezt az include file-t, amit például myfunc.h-nak nevezhetünk – majd a menükezelő rendszert tartalmazó .c file fogja behı́vni a 54 5. FEJEZET FEJLETTEBB TECHNIKÁK #include "myfunc.h" preprocesszor utası́tással. Érdemes a menükezelő rendszerünk által használt különféle szimbólumokat is – például egyes speciális billentyűk kódjainak szimbólikus neveit, mint például RIGHT ami a billentyű kódjának, LEFT, UP, DOWN, ESC, BEGIN, END, HELP rendre a ←, ↑, ↓, Esc, Enter, Home, End és az F1 billentyű kódjának felel meg az IBM PC-n – egy szimbólum file-ba foglalni. Legyen ennek a file-nak a neve például mysymb.h Ezt a file-t szintén az #include direktı́vával épı́thetjük be a rendszer minden egyes .c file-jába (Megjegyezzük, hogy ez a file akár #define-nal deklarált makrószerű konstansokat tartalmazhat, akár const-ként
definiált konstansok deklarációit tartalmazhatja – az itt közölt programrészletek szempontjából ez lényegtelen. Egy másik megjegyzés az egyes billentyűkhöz rendelt kódokra vonatkozik: A speciális billentyűkhöz célszerű 128-nál nagyobb kódokat rendelni. Így a billentyűzet kezelő függvény által visszadott billentyűkódok közül könnyen kiszűrhetők a közvetlen ASCII karakterkódok. A menükezelő rendszerben ezzel a feltételezéssel élünk. 5.3 ÖSSZETETT MINTAPÉLDA 5.32 55 A menükezelő rendszer listája /* * File: menu.c * * Tartalom: Menukezelo mintaprogram * */ #include #include #include #include <stdio.h> <string.h> <stdlib.h> <ctype.h> /* /* /* /* Standard i/o csomag Sztring- es memoriakezelo rutinok Altalanos celu standard fuggvenyek Karakterkezelo makrok */ */ */ */ #include "myfunc.h" /* Sajat fuggvenyek prototipusai */ #include
"mysymb.h" /* Szimbolumok (spec. billentyuk kodjai)*/ /* ======================================================== / /* Tipusdeklaraciok */ typedef int intfunc(int);/* int-et visszaado, 1 int-et varo * fuggvenytipus */ typedef intfunc *fad; typedef struct { char *text; char key; int helpindex; fad function; int param; } menuitem; typedef struct { char *header; int x; int y; int xs; int ys; int itemno; menuitem *items; /* intfunc tipusu tarolasi * segre mutato tipus /* /* /* /* /* /* /* /* /* /* /* /* A A A A A A A x a y A A egy- * */ menupont azonosito szovege menupontot kivalaszto betu menuponthoz rendelt help-kod menuponthoz tartozo fv-re mutat ’*function’ fuggveny parametere */ */ */ */ */ menu fejlecszovegere mutat menudoboz bal felso sarkanak es y koordinatai, valamint menudoboz x es iranyu merete. menupontok szama menupontok listajara mutat. */ */ */ */ */ */ */ 56 5. FEJEZET FEJLETTEBB TECHNIKÁK int hierarch; /* Ha 1, kozvetlenul a fomenu hivja
int lastitem; /* Utoljara kivalasztott pont szama } menutype; */ */ /* ======================================================== / /* Tarolasi egysegek deklaracioi, definicioi: */ static char exitxt[~] = "eXit"; /* Majd sokszor kell ez a sztring. */ /* Kulso fuggvenyek deklaracioja */ extern intfunc data, regr, save, r data, w data, statf, linf, barf, load; /* A a menukezelo fuggveny prototipus erteku deklaracioja */ intfunc menu; /* El-ore hivatkozashoz intfunc dir, shell; /* Tovabbi fv-ek el-ore hivatkozshoz /* Az egyes menulistak (items 0 . items 3) es a menuk: menuitem items 0[ ] = { /* text key hlp func. param */ "Directory", ’D’, 1, dir, 0, "Os shell", ’O’, 2, shell, 0, "File", ’F’, 3, menu, 3,/*a 3.sz menu almenu lesz exitxt, ’X’,-1, NULL, 0 /*-1-es parameter: exit }; /* Tombmeret: #define N0 sizeof(items 0)/sizeof(menuitem) menuitem items 1[ ] = { "Default", ’D’, 4, data, 7, "Read data",
’R’, 5, r data,1, "List data", ’L’, 6, w data,2, "Statistics",’S’, 7, statf, 3, exitxt, ’X’,-1, NULL, 0 }; #define N1 sizeof(items 1)/sizeof(menuitem) */ */ */ */ */ */ 5.3 ÖSSZETETT MINTAPÉLDA 57 menuitem items 2[ ] = { "Regression",’R’, 8, regr, 4, "Plot", ’P’, 9, linf, 5, "Bar", ’B’,10, barf, 6, exitxt, ’X’,-1, NULL, 0 }; #define N2 sizeof(items 2)/sizeof(menuitem) menuitem items 3[ ] = { "Save", ’S’,11, savef, 0, "Load", ’L’,12, loadf, 0, exitxt, ’X’,-1, NULL, 0 }; #define N3 sizeof(items 3)/sizeof(menuitem) /* A teljes menurendszer leirasa: menutype menus[ ] = {/* head. x y xs ys itemno items hier last "", 9, 2, 13, N0+3, N0, items 0, 1, 0, "", 35, 2, 14, N1+3, N1, items 1, 1, 0, "", 61, 2, 14, N2+3, N2, items 2, 1, 0, "Files",11, 6, 8, N3+3, N3, items 3, 0, 1 }; */ */ /* Mivel a főmenünek semmi más
funkciója nincs, mint a menu függvénynek átadni a vezérlést a megfelelő menüindexszel, komolyabb adatstruktúrákat nem definiáltunk a számára. Csak az alábbiakra van szükség a főmenühöz: */ static char main header[ ] = /* A fomenu fejlecszovege " Highly Portable Menu System "; */ static char options[ ]=/*Az egyes menuk kivalaszto gombjai / "FDP"; /*Sorrendjuk ugyan-az, mint az alab- / /*bi sztring-tomb el-emeinek sorrendje/ /* Az options sztring hossza adja meg, hogy a menus tömb hányadik eleméig tekintjük a menüket a főmenü részeinek. */ 58 5. FEJEZET FEJLETTEBB TECHNIKÁK static char *headers[ ]= { "File", / A fomenube felvett "Data", /* menuk fejlec szove"Plot" / gei. }; static int mainselect = 0; /* Az utoljara kiv.fomenu elem static char buffer[81]; /* Ide generaljuk a fomenut static int xp,yp,j; /* Segedvaltozok a rutinokhoz static char inpbuff[256]; /* Altalanos input
buffer */ */ */ */ */ */ */ /* A magyarázatok és deklarációk után következzenek maguk a függvények! A menükezelő rendszert úgy hoztuk létre, hogy programunk main-je csak ilyen rövid legyen: */ /*/ void main(void) /* Ez tetszoleges program ’main’-je lehet */ /*/ { displ ini(); /* A kepernyokezelo rendszer inicializalasa / main frame();/* Keretrajzolas a fomenuhoz: mint az IDE */ main menu(0);/* Fomenu. Addig fut, amig ESC-pel ki nem szallnak belole */ displ end(); /* A kepernyo alapallapotanak helyreallitasa */ exit(0); /* OK hibakod visszadasa az operacios rendszernek */ } /* Most tekintsük magát a menükezelő rutincsomagot! / /*/ int menu(int index)/*Az aktualisan hasznalando menu indexe / /* Funkció: A függvény a menus[index]-ben adott menüt megjelenı́ti a képernyőn. Az egyes menüpontokat a menüleı́rás szerinti dobozban jelenı́ti meg. A menus[index]lastitem indexű menüpont kiemelve látszik a képen A kiemelt
menüpontot a ↑ és ↓ kurzorvezérlőkkel változtathatjuk Ha leütjük az Enter billentyűt, akkor a kiemelt szinű menüpont függvényét hivjuk meg, ha pedig valamelyik menüponthoz rendelt nagybetűt ütjük le a billentyűzeten, akkor az illető menüpont függvénye lesz aktivizálva a menus[index].items[selected]param parameterrel, ahol index a kiválasztott menüpont indexe Amint a meghı́vott függvény visszaadja a vezérlést, 5.3 ÖSSZETETT MINTAPÉLDA 59 a menu szubrutin regenerálja az aktuális menülistát a keretezett dobozban. Ha menus[index].hierarch == 1 akkor a menu függvény visszatérési értéke – RIGHT ha a kurzorvezérlő gombot nyomták meg, – LEFT ha a ← kurzorvezérlő gombot nyomták meg. Minden egyéb esetben a visszatérési érték 0, tehát amikor – az ESC gombot nyomták meg (kilépés a menu függvényből), – olyan menüpontot választottak ki, amelynek a
helpindex-e -1 */ { int i, /* A menupontok szamat tesszuk bele */ l, /* for-ciklushoz ciklusvaltozo */ exit, /* Kilepest jelzo flag */ par, /* A kivalasztott fv. parametere */ cmd; /* A vezerlo-karakternek */ /* . E L O K E S Z I T E S E K */ j = menus[index].lastitem;/* j-ben az aktualis index i = menus[index].itemno; if (!i) return 0; /* Nulla meretu menuvel nem torodunk */ menu regen(index,1);/* A menut kiiratjuk a kepernyore exit = FALSE; /* A kilepest jelzo flag kezdoerteke */ */ /* . F O */ C I K L U S . */ while (! exit) /*Addig tart,amig exit igaz nem lesz { cmd = 0; /* Kezdetben ures parancs while (!(cmd == SELECT || cmd == CR)) { cmd = getkey(); /* VT100-on ketfele ENTER van, switch(cmd) /* ezert van CR is es SELECT is. { case BEGIN: o gotoxy(xp,yp+j); /* HOME-ot nyomott printf("%s",menus[index].items[j]text); */ */ */ */ */ 60 5. FEJEZET FEJLETTEBB TECHNIKÁK j = 0; o gotoxy(xp,yp); highlight(EMPHAS,menus[index].items[j]text); break; case END: o
gotoxy(xp,yp+j); /* END-et nyomott */ printf("%s",menus[index].items[j]text); j = i-1; o gotoxy(xp,yp+j); highlight(EMPHAS,menus[index].items[j]text); break; case UP: /* ’fel’ nyil */ { o gotoxy(xp,yp+j); printf("%s",menus[index].items[j]text); if (j > 0) j--; else j = i - 1; o gotoxy(xp,yp+j); highlight(EMPHAS,menus[index].items[j]text); } break; case DOWN: /* ’le’ nyil */ { o gotoxy(xp,yp+j); printf("%s",menus[index].items[j]text); if (j < i-1) j++; else j = 0; o gotoxy(xp,yp+j); highlight(EMPHAS,menus[index].items[j]text); } break; case HELP: /* F1-et nyomtak */ menus[index].lastitem = j; menu help(menus[index].items[j]helpindex); if (menus[index].items[j]helpindex >= 0 && menus[index].y + menus[index]ys > 11) menu regen(index,0); break; case ESC: /* ESC-et nyomtak */ exit = 1; cmd = SELECT; break; case LEFT: case RIGHT: 5.3 ÖSSZETETT MINTAPÉLDA 61 /* Ha ’main menu’ hivta ’menu’-t, akkor a ’jobbra’,
’balra’ nyilak eseten a menut toroljuk, es a nyil-gomb kodjat visszaadjuk. Igy a fomenu a roll-in menut felvaltja egy masikkal: */ if (menus[index].hierarch == 1) { menu remove(index); return cmd; } default: /* Kilepunk, ha dedikalt gombot nyomtak */ if (cmd < 128) { cmd = toupper(cmd); for(l = 0; l < i; l++) { if (menus[index].items[l]key == cmd) { o gotoxy(xp,yp+j); printf("%s",menus[index].items[j]text); cmd = SELECT; j = l; break; } } } break; } /* . end switch */ } /* . end while */ if (! exit) { exit = (menus[index].items[j]helpindex == -1); } /* Ezen a ponton már eldőlt, hogy ki akarunk-e lépni. Ha nem, akkor viszont tudjuk, hogy melyik menüpont függvényét kell aktivizálni: */ if (! exit) { /* Az ’eXit’ pontnak mindig -1 helpindexe legyen! */ 62 5. FEJEZET FEJLETTEBB TECHNIKÁK /* . A kivalasztott fuggveny aktivizalasa: /* (j indexeli a kivalasztott fuggvenyt) */ */ o gotoxy(xp,yp+j); highlight(EMPHAS,menus[index].items[j]text);
menus[index].lastitem = j; par = menus[index].items[j]param (*menus[index].items[j]function)(par); menu regen(index,0); } else { menu remove(index); } /* A menu-box regeneralasa */ } return 0; } /*/ void menu regen(int index, /* A regeneralando menu indexe */ int rem) /* TRUE: torolni kell a dobozt */ /* Funkció: A menus[index] menü regenerálása (újra rajzolja a dobozt, kiı́rja a menülistát és kiemelő szinnel nyomtatja az utoljára kiválasztott menüpontot.) Ha rem == 1 akkor a menü által elfoglalt képernyőterületet törli a menülista kiı́rása előtt, ha rem == 0, akkor nem töröl. */ { int i,k,l,m,n,xx,yy; int x1,x2; xp = menus[index].x; /* Pozicio, meret el-ovetele */ yp = menus[index].y; i = menus[index].itemno; xx = menus[index].xs; yy = menus[index].ys; /* Dobozrajzolas */ box draw(menus[index].header,xp,yp,xx,yy,rem); xp += 2; 5.3 ÖSSZETETT MINTAPÉLDA yp += 2; for (k = 0; k < i; k++) /* A menulista megjelenitese { o
gotoxy(xp,yp+k); if (k == menus[index].lastitem) { highlight(EMPHAS,menus[index].items[k]text); j = k; } else printf("%s",menus[index].items[k]text); } 63 */ } /*/ void menu remove(int index) /* A torlendo menu indexe */ /* Funkció: A menus[index] menü törlése a képernyőről */ { int xx,yy,x1,y1; x1 = menus[index].x; y1 = menus[index].y; xx = menus[index].xs; yy = menus[index].ys; box delete(x1,y1,xx,yy); } /*/ void box draw(char* header, /* ->a doboz fejlec-szevege / int xp, int yp,/* a doboz pozicioja, */ int xs, int ys,/* merete */ int rem) /* 1, ha torles kell, egyebkent 0 */ /* Funkció: Egy xs, ys méretű dobozt rajzol az xp, yp pozicióba. A keret felső részének közepére a header fejlécet ı́rja ki. Ha rem == 1, akkor a doboz rajzolása előtt törli a doboz által elfoglalandó területet. */ { 64 5. FEJEZET FEJLETTEBB TECHNIKÁK int l,n,xx,yy; int x1,x2; l = strlen(header); /* A fejlec hossza xx = xs-2; /* Egyeb adatok
el-okeszitese x1 = (xx - l)/2; x2 = xx - (x1 + l); yy = ys-2; if (rem) box delete(xp,yp,xs,ys); o gotoxy(xp,yp); /* A legfelso sor a fejleccel printf("%c",UPLEFT); for (n = 0; n < x1; n++) printf("%c",HORIZ); */ */ */ highlight(REVERSE|BRIGHT,header); for (n = 0; n < x2; n++) printf("%c",HORIZ); printf("%c",UPRIGHT); yp++; for (n = 0; n < yy; n++) /* Maga a doboz { o gotoxy(xp,yp+n); printf("%c",VERT); o gotoxy(xp+1+xx,yp+n); printf("%c",VERT); } o gotoxy(xp,yp+yy); /* A doboz legalso sora printf("%c",DOWNLEFT); for (n = 0; n < xx; n++) printf("%c",HORIZ); printf("%c",DOWNRIGHT); */ */ } /*/ void box delete(int xp, int yp, /* Egy dobozt torol */ int xs, int ys) /* Pozicio, meret */ /* Funkció: Egy xs, ys méretű dobozt töröl az xp, yp pozicióról. */ { int n, m; 5.3 ÖSSZETETT MINTAPÉLDA 65 for (n = ys-1; n >= 0; n--) { o gotoxy(xp,yp+n); for (m = 0; m
< xs; m++) putc(’ ’); } } /*/ void menu help(int index) /* A menupont help-indexe */ /* Funkció: Az index által meghatározott help-szöveget kikeresi egy help-file-ból, és kiı́rja a képernyőre. A kiı́ráshoz egy 7 soros ablakot nyit, a szöveget 7 soronként ı́rja ki Ha van még kiirandó szöveg, akkor a More üzenet után egy billentyűleütésre vár, ha nincs, akkor a Press any key . üzenet után törli a képernyőről a help-dobozt, és visszatér. A help-file formátuma a következő: index i sor 1 i sor 2 i . sor n i index j . n i n j ahol index i az i-edik help-index, n i az ehhez az indexhez tertozó helpszöveg sorainak a száma, valamint sor 1 i, . sor n i a help-szöveg egyes sorai. Formátum hiba, vagy file vége esetén szintén hibajelzés történik */ { static char hunex[] = "Unexpected end of the help-file!!!"; #define YP 24 FILE *fp; int i,j,k,err; if (index < 0) return; /* Negativra
visszater */ box draw(" HELP ",2,11,76,9,1);/* Help-box rajzolasa */ 66 5. FEJEZET FEJLETTEBB TECHNIKÁK fp = fopen(helpdat,"r"); /* Help-file megnyitasa */ if (fp == NULL) /* Ha nem letezik a file, hibajelzes */ { o gotoxy((80-(21+strlen(helpdat)))/2-1,16); printf("Help-file %s not found!",helpdat); goto helpend1; } i = -1; while (i != index) /* Help-index keresese a file-ban */ { err = fscanf(fp,"%d%d",&i,&j); if (err == EOF) /* Nem talaljuk: hibajelzes */ { o gotoxy(19,16); printf("No help is available for this menu item!"); goto helpend0; } if (err != 2) { o gotoxy((79-(31+strlen(helpdat)))/2,16); printf("Format error in the %s help-file!",helpdat); goto helpend0; } if (i != index)/* Ha meg nem talalja, tovabb olvas. */ { for (; j >= 0; j--) { if (NULL == fgets(inpbuff,74,fp)) { o gotoxy((79-strlen(hunex))/2,16); printf(hunex); goto helpend0; /* A ’goto’-t legfeljebb igy */ } /* hasznaljuk! */ }
} } for (k = i = 0; i < j; i++) { if (NULL == fgets(inpbuff,74,fp)) { o gotoxy((79-strlen(hunex))/2,16); 5.3 ÖSSZETETT MINTAPÉLDA 67 printf(hunex); goto helpend0; } o gotoxy(4,12+k); printf(inpbuff); k++; if (k == 7)/*Megvan a helpszoveg. 7-esevel kiirjuk: { o gotoxy(66,YP); highlight(BRIGHT,"More ."); bell(); err = getkey(); o gotoxy(66,YP); printf(" "); if (err == ESC)/* ESC-re kiszallunk { fclose(fp); box delete(2,11,76,9); return; } box draw(" HELP ",2,11,76,9,1); k = 0; } } helpend0: /* Minden befejezeskor ezeket a muveleteket fclose(fp); /* kell elvegezni, tehat takarekos meghelpend1: /* oldas a ’goto’ hasznalat. Csinjan banpress key();/* junk az ilyennel, hogy olvashato maradbox delete(2,11,76,9); / jon a programunk! */ */ */ */ */ */ */ } /*/ void main frame(void) /* Funkció: Keretet rajzol a főmenünek. Ha valamelyik függvény törli az egész képernyőt, akkor main frame meghı́vásával helyreállı́thatja
azt */ { erase(); box draw(main header,0,0,80,23,0);/* Main box fejleccel */ 68 5. FEJEZET FEJLETTEBB TECHNIKÁK main menu(1); /* Fomenu statusz-sora / } /*/ void main menu(int stl)/*Ha 1, akkor a statusz-sort kiirja / /* Funkció: A menükezelő rendszer fő rutinja, ezt kell a main-ből meghı́vni. A menus tömbből annyi menüt kezel közvetlenül, amennyi az options sztring hossza. A menü-opciókat a képernyő második, kivilágı́tott sorában jelenı́ti meg. Egy menüpont a szokásos módon választható (kurzorral kiemelés, majd Enter, vagy a kezdő betű leütése). Ha egy almenü él (azaz látszik a képernyőn), akkor a ←, illetve nyilakkal a szomszédos menüre válthatunk. */ { int i,j,k,l, posinc, hno,xp, cmd,flag; /* ’buffer’-ben lesz az inverzen kiirando statusz-sor hno = sizeof(headers)/sizeof(char*); posinc = 78/hno; xp = posinc/2; if (stl) { for (i = 0; i < 78; buffer[i++] = ’ ’) ; for (j = 0; j < hno;
j++) { l = strlen(headers[j]); for(k = 0; k < l; k++) buffer[xp+j*posinc+k] = (headers[j]+k); } buffer[78] = ’ ’; o gotoxy(1,1); highlight(REVERSE,buffer); */ 5.3 ÖSSZETETT MINTAPÉLDA 69 } /* A kivalasztott menut normal modon jelenitjuk meg: */ i = mainselect; xp++; if (stl) { o gotoxy(xp+i*posinc,1); printf(headers[i]); return; } /* A fo parancs-ciklus. Csak ESC-re lephetunk ki belole dontquit: /* Ide ugrunk, ha megse lepunk ki. flag = cmd = 0; while (cmd != ESC) { if (! flag) cmd = getkey(); flag = 0; switch (cmd) /* Nincs el-o almenu. Kurzorvezerlok { /* feldogozasa, status-sor modositasa case RIGHT: o gotoxy(xp+i*posinc,1); highlight(REVERSE,headers[i]); if (i < hno-1) i++; else i = 0; o gotoxy(xp+i*posinc,1); printf(headers[i]); break; case LEFT: o gotoxy(xp+i*posinc,1); highlight(REVERSE,headers[i]); if (i) i--; else i = hno-1; o gotoxy(xp+i*posinc,1); printf(headers[i]); break; case SELECT: crselect: /* Kivalasztottak egy almenut. Megje*/ /* gyezzuk
indexet ’mainselect’-ben */ mainselect = i; */ */ */ */ 70 5. FEJEZET FEJLETTEBB TECHNIKÁK flag = menu(i);/* A ’menu’ rutin behivasa switch (flag) /* Mi a visszateresi ertek? { case RIGHT: /* Stat.sor modositas o gotoxy(xp+i*posinc,1); highlight(REVERSE,headers[i]); if (i < hno-1) i++; else i = 0; o gotoxy(xp+i*posinc,1); printf(headers[i]); break; case LEFT: o gotoxy(xp+i*posinc,1); highlight(REVERSE,headers[i]); if (i) i--; else i = hno-1; o gotoxy(xp+i*posinc,1); printf(headers[i]); break; } l = strlen(headers[i]); o gotoxy(xp+i*posinc+l,1); break; default: if (cmd < 128) /*Kezdobetuvel valasztottak { cmd = toupper(cmd); for (l = 0; l < hno; l++) if (cmd == options[l]) { o gotoxy(xp+i*posinc,1); highlight(REVERSE,headers[i]); i = mainselect = l; o gotoxy(xp+i*posinc,1); printf(headers[i]); /* Ugy teszunk, mintha ’nyil+Enter’-rel valasztottak volna. */ cmd = SELECT; goto crselect; } } break; } */ */ */ */ 5.3 ÖSSZETETT MINTAPÉLDA 71 } /* Az
ESC-pel valo kilepes szandekat meg-erosittetjuk: box draw("",28,5,24,3,0); o gotoxy(30,6); highlight(BRIGHT,"Are you sure? (y/n) "); cmd = yesno(); box delete(28,5,24,3); o gotoxy(1,1); if (!cmd) goto dontquit; /*Nem lep ki, vissza az elejere erase(); */ */ } */ /* Két gyakori funkció portábilis megvalósı́tását találjuk itt. Ezek az aktı́v könyvtár tartalmának kiiratása a képernyőre, illetve az operációs rendszer parancs-értelmező burkának (command shell) az aktivizálása. Mindkettőt a system függvény segı́tségével oldjuk meg. A system argumentuma egy operációs rendszernek szóló parancsot tartalmazó sztring. Ezek a mi esetünkben egy-egy #define makróként lettek megadva, ı́gy azok operációs rendszertől függő feltételes fordı́tással megfelelően beállı́thatók. Tahát a system függvénynek (process.h) átadandó operációs endszer parancsok: */ #ifdef MSDOS
#define DIRSTR "dir /w/p" /* A burok (shell) ’dir’ parancsa #define SHELL "COMMAND.COM" /* Maga az operacios r. burok (shell) #endif */ */ /* A fenti sztringeket csak a DOS-ban adhatjuk át a system-nek, ezért használtuk az #ifdef MSDOS fordı́tásvezérlő direktı́vát. UNIX-ban a megfelelő sztringek értéke rendre "ls -C|more", illetve "sh" lenne */ /*/ int dir(int d) /* Az aktiv konyvtar tartalmat nezzuk meg */ /* d: Dummy parameter */ 72 5. FEJEZET FEJLETTEBB TECHNIKÁK /*/ { displ end(); system(DIRSTR); /* Az op. rendszer DIR parancsat kerjuk Ennel lehetne hatekonyabb meg-oldast is talalni, de ez igy portabilis. */ displ ini(); press key(); erase(); main frame(); return d; } /*/ int shell(int d)/* A parancsertelmezo burok hivasa */ /* d: Dummy parameter */ /*/ { displ end(); printf(" Type exit to return! "); system(SHELL); displ ini(); erase(); main frame(); return(x); } 5.3 ÖSSZETETT
MINTAPÉLDA 73 A saját include file tartalma A következő lista a myfunc.h include file javasolt tartalmát mutatja be: /* * File: myfunc.h * * Tartalom: Menukezelo mintaprogram fuggveny proto* * tipusai: el-orehivatkozasokhoz, illetve a * * kepernyokezelo rendszer hasznalatahoz. * */ /* ======================================================== / /* A menukezelo rendszer fuggvenyeinek deklaracioi */ void main menu(int stl); /* Fomenu-rutin. Ezt kell a mainbol meghivni */ void main frame(void); /* A fomenuhoz keretet rajzol a kepernyore */ int menu(int index); /* Ez a menurutin: az adott sorszamu menut kezeli */ void menu regen(int index); /* Az adott sorszamu menut regeneralja a kepen */ void menu remove(int index);/* Az adott sorszamu menut letorli a keprol */ void menu help(int index); /* Adott menuponthoz helpet ir ki egy file-bol */ void box draw(char *header, / Adott fejleccel, */ int xp,int yp,/* adott xp,yp poxicioban, */ int xs,int ys,/* adott xs,ys meretben dobozt /
int rem); /* rajzol, ha kell, torol alatta/ void box delete(int xp, /* Adott helyrol adott meretu */ int yp, /* dobozt torol */ int xs, int ys); /* ======================================================== / /* A keprnyokezelo rutinok prototipusai magyarazatokkal */ void o gotoxy(int x, int y); /* Sajat pozicionalo. x=0.24, y=079 void home(void); /* A kurzort a 0,0 pozicioba helyezi void erase(void); /* Torli a kepernyot es a 0,0-ba pozicional */ */ */ 74 5. FEJEZET FEJLETTEBB TECHNIKÁK void displ ini(void); int /* Bekapcsolja a kepernyokezelo rendszert, torol displ end(void); /* Kikapcsolja a kepernyokezelo rendszert, torol cursor left(int n); /* A kurzort egy pozicioval balra viszi cursor right(int n); /* A kurzort egy pozicioval jobbra viszi cursor up(int n); /* A kurzort egy pozicioval feljebb helyezi cursor down(int n); /* A kurzort egy pozicioval lejebb helyezi highlight(unsigned mode,/* ’mode’ szerinti attributumchar string);/ mal ’string’-et nyomtatja read
in(char* string); / Egy sztringet olvas be int yesno(void); void void void void void void /* y/Y/I/i/N/n (igen/nem) valaszt var. 0, ha ’nem’ input(char* string, int pos,int len);/* Sor-editor. Adott pozicion, adott hosszt edital void press key(void); /* A ’Press a key’ kiirasa utan gombnyomasra var void bell(void); /* Egy BEL karaktert kuld az stdout-ra: igy beep-el int getkey(void); /* A billentyuzetet kezeli, ASCII-t, illetve #define erteket ad vissza (pl. UP) */ */ */ */ */ */ */ */ */ int */ */ */ */ Irodalomjegyzék [1] Benkő Tiborné – Urbán Zoltán. Az IBM PC programozása Turbo C 20 nyelven. BME Mérnöki Továbbképző Intézete, 1990 Jegyzet [2] Benkő Tiborné – Poppe András – Benkő László. Bevezetés a BORLAND C++ programozśba. Computer Books, 1991 [3] B. W Kernighan – D M Ritchie A C programozási nyelv Műszaki Könyvkiadó., 1985 Fordította Dr Siegler András [4] B. W Kernighan – D M Ritchie The C
Programming Language Prentice Hall, 1988. Second Edition 75