You are on page 1of 80

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.1. Ciklusok . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.1.1. Feladat: Fahrenheit-Celsius átszámoló program . . . 1
1.1.2. Feladat: Fahrenheit-Celsius átszámoló for ciklussal 3
1.2. Egykarakteres I/O . . . . . . . . . . . . . . . . . . . . . . . 5
1.2.1. Feladat: Karakterszámlás . . . . . . . . . . . . . . . 6
1.3. Elágazás: if, switch . . . . . . . . . . . . . . . . . . . . . . 7
1.3.1. Feladat: Sorszámlálás . . . . . . . . . . . . . . . . . 7
1.4. Önálló feladatok . . . . . . . . . . . . . . . . . . . . . . . . 8
1.4.1. Feladat: Blank-ek számlálása . . . . . . . . . . . . . 8
1.4.2. Feladat: Blank-ek számlálása tı́pusonként . . . . . . 8
1.5. Az operátorok gyakorlása . . . . . . . . . . . . . . . . . . . 8
1.5.1. Feladat: A sizeof egyszerű használta . . . . . . . . 8
1.5.2. Feladat: A sizeof és 2-vel való szorzás a shifteléssel 8
1.5.3. Feladatok: Aritmetikai műveletek int-ekkel . . . . . 8

2. Bonyolultabb szerkezetek 11
2.1. Az előfeldolgozó használata . . . . . . . . . . . . . . . . . . 11
2.1.1. Szimbólumok használata, feltételes fordı́tás . . . . . 12
2.1.2. Feladat: Programfordı́tás körülményeinek kiiratása . 13
2.1.3. Feladat: Az #ifdef alkalmazása . . . . . . . . . . . 14
2.1.4. Fordı́tási időben élő szimbólumok . . . . . . . . . . . 15
2.1.5. Új és régi stı́lusú függvénydeklarácók . . . . . . . . . 15
2.1.6. Feladat: . . . . . . . . . . . . . . . . . . . . . . . . . 17
2.1.7. Makródefiniciók . . . . . . . . . . . . . . . . . . . . . 17
2.1.8. Feladatok: min, max, pow, toupper, tolower . . . . 18
2.2. Tömb-, pointer- és függvénytı́pusok . . . . . . . . . . . . . . 19
2.3. Karaktertömbök és pointerek . . . . . . . . . . . . . . . . . 23

i
ii

2.3.1. Feladat: saját strcpy . . . . . . . . . . . . . . . . . 23


2.3.2. Feladat: saját strlen . . . . . . . . . . . . . . . . . 25
2.4. Függvénypointerek . . . . . . . . . . . . . . . . . . . . . . . 25
2.5. Több modulból álló programok készı́tése . . . . . . . . . . . 30

3. A dinamikus tárkezelés alapjai 33


3.1. Dinamikus adatok . . . . . . . . . . . . . . . . . . . . . . . 33
3.1.1. Feladat: lineáris egyenletrendszer megoldása . . . . . 35

4. Az operációs rendszerrel való kapcsolat 39


4.1. Folyam jellegű I/O . . . . . . . . . . . . . . . . . . . . . . . 39
4.1.1. Feladatok: File-ok másolása . . . . . . . . . . . . . . 40
4.1.2. File-nyitási hibák, azok kezelése . . . . . . . . . . . . 40
4.2. A main argumentumai . . . . . . . . . . . . . . . . . . . . . 41
4.2.1. Feladat: A copy parancs – saját kivitelben . . . . . 41

5. Fejlettebb technikák 43
5.1. Struktúrák – láncolt lista . . . . . . . . . . . . . . . . . . . 43
5.1.1. Fealadat: Láncolt lista készı́tése . . . . . . . . . . . 43
5.2. Menürendszer . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.3. Összetett mintapélda . . . . . . . . . . . . . . . . . . . . . . 47
5.3.1. A tervezés egyes fázisai . . . . . . . . . . . . . . . . 47
5.3.2. A menükezelő rendszer listája . . . . . . . . . . . . . 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űsza-
ki 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ú, al-
kalmas 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ép-
tı́pus adott operációs rendszerére kidolgozott – bizonyos programı́rási sza-
bályokat figyelembe vevő – program viszonylag kis munkával, jól megha-
tá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 elsa-
já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 el-
sajá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 program-
ré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 el-
sajá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 megol-
dá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 megta-
nulni, ı́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árhu-
zamosan 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 BOR-
LAND 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 programo-
zá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ı́z-
tatott minket arra, hogy a BME Villamosmérnöki Karának nappali tago-
zatos 1. évfolyamos hallgatóinak oktatása során összegyűjtött tapasztala-
tainkat 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 tel-
jesen 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öz-
lünk mind Pascal, mind C nyelven – részben csak egyszerű szintaxis vál-
tá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 felada-
tok konkrét, C nyelvű megvalósı́tását. E fejezet feldolgozásához javasolt
olvasmány [2]-ból: 2.1, 2.2, 2.4.4, 2.4.5, 2.5, 2.7 fejezetek.

1.1. Ciklusok
1.1.1. 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 fo-
kokban 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 va-
ló á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 ért-


jü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("\n");
while(fahr <= upper)
{
1.1. CIKLUSOK 3

cels = 5 * (fahr - 32) / 8;


printf("%d\t%d\n",fahr,cels);
fahr = fahr + step;
}
}

1.1.2. Feladat: Fahrenheit-Celsius átszámoló for cik-


lussal
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.7.3-as részt.)
Gondoljuk végig, hogy mi az inicializáló rész, amit lehet, azt a vesz-
sző 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("\n");
for (fhar = lower = 0, upper = 300, step = 20;
fahr <= upper;
fahr += step)
{
cels = 5 * (fahr- 32) / 8;
printf("%d\t%d\n",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("\n");
for ( ; /* initialization - empty statement */
fahr <= upper; /* condition */
fahr += step) /* stepping */
{
cels = 5 * (fahr- 32) / 8;
printf("%d\t%d\n",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ó fejeze-
tént, azon belül a 2.3.1-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("\n");
for ( ; /* initialization - empty statement */
fahr <= upper; /* condition */
fahr += step) /* stepping */
{
cels = 5 * (fahr- 32) / 8;
printf("%d\t%d\n",fahr,cels);
}
}
1.2. EGYKARAKTERES I/O 5

1.2. 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.10.2 fejezetének elejét!) Használni fogjuk az EOF (end-of-file)
szimbólumot is, amely ugyancsak az stdio.h-ban van definiálva. Indul-
junk 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ód-
nak 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 get-
char-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 logi-
kai 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éke-
lé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.2.1. 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("\nNumber of characters read from stdin: %d\n",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. post-
increment operátor felhasználásával: n++;

1.3. Elágazás: if, switch


1.3.1. 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 ’\n’
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 == ’\n’) nl++;
}
printf("\nNumber of characters read from stdin: %d\n",n);
printf("Number of lines read from stdin: %d\n",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 re-
lá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 == ’\n’);
}
printf("\nNumber of characters read from stdin: %d\n",n);
printf("Number of lines read from stdin: %d\n",nl);
}

1.4. Önálló feladatok


1.4.1. Feladat: Blank-ek számlálása
Irjunk önállóan olyan programot, amely azt számlálja meg, hogy együtt-
véve hány ún. blank karakter, azaz szóköz, tabulátor (’\t’) vagy újsor
karakter (’\n’) jön be az inputról!
(Az if utası́tást és a logikai VAGY műveletet felhasználva.)

1.4.2. 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 (’\t’) és újsor karakter (’\n’) jön be az inputról! Használjuk a
switch utası́tást!

1.5. Az operátorok gyakorlása


1.5.1. Feladat: A sizeof egyszerű használta
Irjunk olyan programot, amely a sizeof operátor segı́tségével meghatároz-
za és a képernyőre kiı́rja az alap-adatı́pusok méretét!

1.5.2. Feladat: A sizeof és 2-vel való szorzás a shifte-


lé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.5.3. 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 prog-
ramot, 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ávo-


lodnunk a Pascal-szerű megoldásoktól. Most sorra vesszük azokat a lehe-
tő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. mak-
ró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, vala-
mint egy main függvényből elkészı́tünk egy több forrásmodulból álló prog-
ramot.

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 foly-
tató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 Értelmezés, érték


STDC 1 értékkel definiálva van, ha ANSI C (egész)
FILE a feldolgozás alatt álló file neve (sztring)
LINE a feldolgozás alatt álló sor száma (egész)
DATE a fordı́tás dátuma (sztring)
TIME 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.1.1. 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ét-
rehoztt szimbólumokat nemcsak konstans kifejezések elnevezésére használ-
hatjuk, hanem például feltételes fordı́tásvezérlésre. Ezáltal egyes program-
ré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, ame-
lyek 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 13

Szimbólum Értelmezés, érték


VAX vagy vax A VAX C-ben 1 értékkel definiálva van
VMS vagy vms A VAX C-ben 1 értékkel definiálva van
VAXC vagy vaxc A VAX C-ben 1 értékkel definiálva van
VAX11C vagy vax11c A VAX C-ben 1 értékkel definiálva van
TURBOC Minden Turbo/Borland C-ben definiálva van
MSDOS Általában minden PC-s C-ben definiálva van.
BORLANDC A Borland C++-ban mindig definiáltva van.
A verziószámra utal.
TCPLUSPLUS Csak C++ üzemmódban van definiálva, ekkor a
verziószámot adja.
cplusplus 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.1.2. Feladat: Programfordı́tás körülményeinek kiira-


tása
A előzőek alapján ı́rjunk olyan programot, amely abban az esetben, ha AN-
SI 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:\n",


__FILE__);
printf("Date of compilation: %s\n",__DATE__);
printf("Time of compilation: %s\n",__TIME__);
printf("Approximate length of ’main’: %d\n",
__LINE__ - sor);
#else
printf("The compiler is not ANSI C/VAX-C compatible\n");
printf("No way to identify compilation circumstances\n");
#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 rendel-
kezé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 prog-
ram fejlesztőinek.

2.1.3. 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 for-
dı́tják le, más-más azonosı́tó szöveget ı́rjon ki.
2.1. AZ ELŐFELDOLGOZÓ HASZNÁLATA 15

2.1.4. Fordı́tási időben élő szimbólumok


Természetesen nem csak az adott C nyelvi rendszer által előre definiált szim-
bó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\n",__FILE__);
#else
printf("MYSYMBOL has not been defined for %s\n",__FILE__);
#endif
}

Ha tehát a fenti programot egy VAX gépen, VMS-ben a CC TESTSYMB /DE-


FINE=MYSYMBOL paranccsal fordı́tjuk le, akkor MYSYMBOL has been defi-
ned for TESTSYMB.C üzenetet fogja kiı́rni a képernyőre a futtatható prog-
ram, mı́g az egyszerű CC TESTSYMB fordı́tási paranccsal fordı́tva, a futtat-
ható 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.1.5. Ú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ó sze-
rint 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 le-
hető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 pa-
ramétert váró függvény. A deklarációból a formális paraméterek azono-
sı́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él-
kü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 je-
lenti. 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.1.6. Feladat:
Írjunk egy olyan C nyelvű faktoriális-számı́tó programot, amelyben a fel-
haszná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.1.7. Makródefiniciók
A #define direktı́vát nemcsak fordı́tásvezérlésre, illetve szimbólikus kons-
tansok definiálásra használhatjuk, hanem paraméterekkel rendelkező ún.
makrók, azaz egyszerű rutinok ı́rására is. Például a math.h 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ör-


té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.1.8. 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 mypo-


wer(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ás-
hoz szükséges exp, illetve log függvények double visszatérési értékű-
ek! (A math.h 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 ctype.h 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 ka-
raktert 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 tartal-
mazó 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 objek-
tumot.

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él-
dában szereplő utolsó elem – a dfunc – egy kódgeneráló tárolási egységet,
20 2. FEJEZET. BONYOLULTABB SZERKEZETEK

Operátor Megnevezés Jelleg


( ) függvénytı́pust képző operátor postfix
[ ] tömbtı́pust képző operátor postfix
* mutatótı́pust képző operátor 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 pa-
ramé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 egy-
sé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ő: megad-
juk 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, dmut-


mut 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 egysze-
rű 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ár-
maztatott 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övet-
kező:
Új tı́pust mindig valamilyen már meglévő tı́pusból (elemi tı́pusból, struk-
túrákból, vagy typedef-fel már korábban definiált tı́pusból) hozhatunk lét-
re ú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 megegye-
ző é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 mu-
tatú 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 ér-


telmében véve nem operátorok, mert nem valamilyen adathalmazon értel-
mezett 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űve-


letet 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ı́pus- Származtatott Alaptı́pust Alaptı́pusú
tárolási egység módosı́tó tı́pusú tárolási képző kifejezés
operátor egység operátor értelmezése
char ch=’a’; * char *chp=&ch; * *chp
pointer- indirekció az a karakter,
tı́pust pointer amire chp
képző op. mutat (az ’a’)
int i=100; ( ) int ifv(); ( ) i = ifv();
függvény- fv.hı́vás az ifv függvény
tı́pust függvény által visszaadott
képző op. egész
float f=3.14; [ ] float fvek[5]; [ ] f=fvek[1]
tömb- indexelés fvek[1] egy
tı́pust tömb float szám
képző op. 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 kez-
dő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ó sztringkons-


tans 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. pintera-
ritmetika.

Művelet Eredmény
pointer + int pointer
pointer - int pointer
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éter-
ké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ügg-
vé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 2.3. táblázat-
ban foglaltuk össze.

2.3. Karaktertömbök és pointerek


2.3.1. 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 karak-
tertömb. A függvény feladata, hogy a második paraméterként kapott sztrin-
get 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 ki-
használhatjuk az is, hogy az EOS decimális értéke 0, azaz logikai értelem-
bem 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 25

2.3.2. Feladat: saját strlen


Az előző feladat megoldása során tett megfontolásokat alkalmazva próbál-
juk 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. Gondol-
junk 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 rende-
ző 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 va-
ló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 testresza-
bott 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 rende-
zé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ő pa-
ramé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 ha *elem1 < *elem2


0 ha *elem1 == *elem2
1 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 sizeof(list)/sizeof(char*)


#define ELEMSIZE sizeof(list[0])/sizeof(char)
int main(void)
{
int i;

qsort((void*)list, LISTSIZE, ELEMSIZE, sort_function);


for (i = 0; i < LISTSIZE; printf("%s\n", 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értel-
mű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 func-
tion függvényünk deklarálása. Vegyük észre, hogy e függvény tı́pusa meg-
egyezik 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á kon-
vertá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 hasz-
ná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 RECORD-


nak 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ük-
sé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 tar-
tozó 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 kapcso-
latban tessék arra gondolni, hogy a tı́pusmódosı́tó operátorokkal kapcsolat-
ban leı́rt logikát követve, a kódgeneráló tárolási egységeket – a függvénye-
ket – 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+b*exp(c*x) */
power, /* a*x^y */
sinus, /* a+b*sin(c*x+d) */
polin; /* a+b*x+c*x^2+d*x^3 */

/* The same as double expon(double x), power(double x),


sinus(double x), polin(double x); */
/* ----------------------------------------------------- */
dfp functions[ ] = { expon, power, sinus, polin };
char *fstrings[ ] = { "a+b*exp(c*x)", "a*x^y",
"a+b*sin(c*x+d)", "a+b*x+c*x^2+d*x^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\n1 - %s\n2 - %s\n3 - %s\n\n",
fstrings[0],fstrings[1],fstrings[2],fstrings[3]);
scanf("%d",&i); putchar(’\n’);
}
switch(i)
{
2.4. FÜGGVÉNYPOINTEREK 29

case 2:
case 3: printf("d="); scanf("%lf",&d); putchar(’\n’);
case 0: printf("c="); scanf("%lf",&c); putchar(’\n’);
printf("b="); scanf("%lf",&b); putchar(’\n’);
case 1: printf("a="); scanf("%lf",&a); putchar(’\n’);
}
printf("xa="); scanf("%lf",&xa); putchar(’\n’);
printf("xb="); scanf("%lf",&xb); putchar(’\n’);
printf("\nIntegral of %s = %12.5g\n",fstrings[i],
integral(xa,xb,50,functions[i]));
}
/* ----------------------------------------------------- */

double polin(double x)
{
return a + b*x + c*x*x + d*x*x*x;
}
...
/* ----------------------------------------------------- */
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 integranduszok-


kal
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ügg-
vé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ör-
nyé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 bont-
juk, é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 programun-
kat?

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ügg-
vé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állo-
má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 dekla-
rá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 "mytypes.h"
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 "myfunc.h" preprocesz-
szor utası́tással!
2.5. TÖBB MODULBÓL ÁLLÓ PROGRAMOK KÉSZÍTÉSE 31

Élettartam Láthatóság Deklarátor helye Tárolási osztály


statikus globális bármely modulban extern
minden blokkon kı́vül
statikus modulra adott modulban static
lokális minden blokkon kı́vül
statikus blokkra adott blokkban static
lokális
dinamikus blokkra adott blokkban auto,
lokális register

2.4. táblázat. Változók élettartama, láthatósága és tárolási osztálya

• myvars.h A myvars.c állományban definiált publikus globális vál-


tozók extern deklarációit tartalmazza. Ezt minden függvényeket de-
finiáló forrásállomány elejére, a myfunc.h után épı́tsük be az #inc-
lude "myvars.h" 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 hasz-
ná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ügg-
vé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ó álta-
lá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(’\n’);

tombmut = (double*) malloc(n * sizeof(double));


if (tombmut == NULL)
{
printf("Unable to allocate for %d double numbers!\n",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ú pon-
terre konkrétan szükségünk van. A type-cast operátor általános alak-
ja: (ú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álla-
pı́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 fel-
haszná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ál-
hatja 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öm-
bö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 egy-
szerű 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-ki-
fejezést adjunk át, mert különben ”elszállhat” a programunk.

3.1.1. 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 ismeretle-
nek 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 dekla-
rá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 dekla-
rá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 min-
den 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ál-
tuk 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 fogla-
lunk 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 sor-
rendet 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 seho-
va sem mutat!
2. Attól, hogy egy pointernek van értéke, azaz mutat valahová, még min-
dig nem biztos, hogy érvényes memória-területre mutat. Azt a me-
mó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úl-
cı́mzés miatt nem szól a fordı́tó, legfeljebb elszáll a program. Komo-
lyabb operációs rendszerekben (VMS, UNIX) maga az operációs rend-
szer 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 szab-
vá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.1.1. Feladatok: File-ok másolása


a) Alakı́tsuk át a fenti programot úgy, hogy a getchar, illetve a put-
char 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 OUTPUT.TXT fizikai állományba má-
soljuk át. (Feltehetjük, hogy mindkét állomány az aktı́v könyvtárban
van.)

4.1.2. 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(’\n’);
...
finp = fopen(inpfname,"r");
if (finp == NULL)
{
printf("File %s does not exist!\n",inpfname);
}
A mechanizmus hasonló, mint amit a dinamikus tárfoglalásnál közölt prog-
ramré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övet-
kezik az operációs rendszerrel történő kapcsolatfelvétel. Most memóriafog-
lalá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 azon-


ban 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ál-
nunk! (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önyv-
tárra hivatkztunk a file-specifikációban.)

Feladat: Módosı́tsuk a file-másoló programunkat úgy, hogy mind az in-


put, 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 ope-
rá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 pro-
gram 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 myprog.exe, C
nyelven megı́rt program a C:\mydir könyvtárból lett behı́va, akkor a pro-
gramon belül argv[0] értéke C:\MYDIR\MYPROG.EXE" lesz.

4.2.1. 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 paranc-
ssorban 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 parancs-
sor 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 to.txt be-


gépelésére a myprog program megkapja a parancs-sor paramétereket, ı́gy a
from.txt file-t a to.txt állományba probálja átmásolni – feltéve persze,
hogy maga a C program hibamentes!
5. fejezet

Fejlettebb technikák

5.1. Struktúrák – láncolt lista


5.1.1. 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 (UTAZAS.DAT) minden utas
minden útjáról egy-egy bejegyzést tartalmaz. Egy bejegyzés szerkezete a
következő:
Járat száma – 8 karakter
Dátum – 6 karakter
Utas neve – 32 karakter
Légi km – egész szám
Jelleg – 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 be-
menő á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, vis-
zont 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 flgtno; /* flight number */
char date[7]; /* date of the flight */
char name[32];/* passenger’s name */
int flghtkm; /* flight km */
traveltype; /* 0:prv 1:bsns */
flgt *nextrec,/* ->nxt record in lst*/
*prevrec;/* ->prv record in lst*/
} psngr_rec;

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ú adatok-
tra képesek mutatni.

Egy új rekord beolvasása:

psngr_rec *actptr, *wrkptr;


int typ, endflag;
FILE *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\n");
return 1;
}
actptr->nextrec = workptr; /* Build the chain */
(*workptr).prevrec = actptr;

/* structptr->structfield or
5.1. STRUKTÚRÁK – LÁNCOLT LISTA 45

(*structptr).structfield are the same! */

/* We assume that finput != NULL */

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 ki-


dolgozá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 folyam-
atosan 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. FEJEZET. FEJLETTEBB TECHNIKÁK

5.2. Menürendszer

Ide jön egy rövidebb menürendszer


5.3. ÖSSZETETT MINTAPÉLDA 47

5.3. Ö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, struk-
túrákkal, pointerekkel, függvény-pointerekkel kapcsolatban leı́rtak megér-
té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á te-
szi 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 Win-
dows 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.3.1. 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 szol-
gá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ás-
képpen ezt úgy fogalmazhatjuk meg, hogy ne az egyes programfunkciók
aktivizáljanak kisebbnagyobb menürutinokat, hanem egy átfogó, hierarchi-
kus 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ánal-
maknak 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ár-
mely 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átoz-
tatá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ű elhe-
lyezni 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ükon-
cepció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 ab-
lak 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üke-
zelő 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 kere-
tezett részeket, dobozokat az IBM PC kiterjesztett karakterkészletével. A
felhasznált ’jobb felső sarok’, ’bal felső sarok’, ’függőleges vonal’, stb. ka-
rakterek 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ólu-
mokhoz 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 me-
nü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álaszthat-
juk 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ü tarto-
zik. 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 termi-
ná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 ”meg-
bolondul” 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 utol-
já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 ke-
zelő 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, mi-
lyen 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üpon-
tokra 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ák-
e, milyen feladatot lát el, esetleges paraméter). A menüpontokat menülis-
tá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 al-
menü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él-
szerű 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égrehajtan-
dó 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úrame-
ző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 egy- *


* segre mutato tipus */
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 il-
lető menüpont neve (azonosı́tó szövege), az a nyomtatható karakter, amivel
Enter megnyomása helyett kiválasztható a menüpont. Célszerű megenged-
nü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 alkal-
mas 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. Eze-
ket 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; /* A menupont azonosito szovege */
char key; /* A menupontot kivalaszto betu */
int helpindex; /* A menuponthoz rendelt help-kod */
fad function; /* A menuponthoz tartozo fv-re mutat */
int param; /* A ’*function’ fuggveny parametere */
} menuitem;
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 adatdefini-
có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 tete-
jé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 (an-
nak 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; /* A menu fejlecszovegere mutat */
int x; /* A menudoboz bal felso sarkanak */
int y; /* x es y koordinatai, valamint */
int xs; /* a menudoboz x es */
int ys; /* y iranyu merete. */
int itemno; /* A menupontok szama */
menuitem *items; /* A menupontok listajara mutat. */
int hierarch; /* Ha 1, kozvetlenul a fomenu hivja */
int lastitem; /* Utoljara kivalasztott pont szama */
} menutype;

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 kez-
dő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ák-
bó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 fel-
fű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, r_data, w_data, statf,


regr, linf, barf,
save, 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ó prog-
ramra 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 menu-
item 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ı́t-
sé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énye-
ink 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ólu-
mokat 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, BE-
GIN, 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. (Megje-
gyezzü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 tartalmaz-
hatja – 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 55

5.3.2. A menükezelő rendszer listája


/************************************************************
* File: menu.c *
* Tartalom: Menukezelo mintaprogram *
*************************************************************/

#include <stdio.h> /* Standard i/o csomag */


#include <string.h> /* Sztring- es memoriakezelo rutinok */
#include <stdlib.h> /* Altalanos celu standard fuggvenyek */
#include <ctype.h> /* 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; /* intfunc tipusu tarolasi egy- *


* segre mutato tipus */

typedef struct
{
char *text; /* A menupont azonosito szovege */
char key; /* A menupontot kivalaszto betu */
int helpindex; /* A menuponthoz rendelt help-kod */
fad function; /* A menuponthoz tartozo fv-re mutat */
int param; /* A ’*function’ fuggveny parametere */
} menuitem;

typedef struct
{
char *header; /* A menu fejlecszovegere mutat */
int x; /* A menudoboz bal felso sarkanak */
int y; /* x es y koordinatai, valamint */
int xs; /* a menudoboz x es */
int ys; /* y iranyu merete. */
int itemno; /* A menupontok szama */
menuitem *items; /* A 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, r_data, w_data, statf,


regr, linf, barf,
save, 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 me-
nus[index].lastitem indexű menüpont kiemelve látszik a képen. A ki-
emelt menüpontot a ↑ és ↓ kurzorvezérlőkkel változtathatjuk. Ha leüt-
jük az Enter billentyűt, akkor a kiemelt szinű menüpont függvényét hiv-
juk 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 me-
nus[index].items[selected].param parameterrel, ahol index a kiválasz-
tott 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 ér-
té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 to-
roljuk, 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); /* A menu-box regeneralasa */


}
else
{
menu_remove(index);
}
}
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 63

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);
}
}
/************************************************************/
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 so-
ronké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 n_i
sor_1_i
sor_2_i
...
sor_n_i
index_j n_j
...

ahol index i az i-edik help-index, n i az ehhez az indexhez tertozó help-


szö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 meg- */
helpend1: /* oldas a ’goto’ hasznalat. Csinjan ban- */
press_key();/* junk az ilyennel, hogy olvashato marad- */
box_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éper-
nyő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 me-
nus 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áltha-
tunk.
*************************************************************/
{

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] = ’\0’;
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 ese-
tü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 hasz-
náltuk az #ifdef MSDOS fordı́tásvezérlő direktı́vát. UNIX-ban a meg-
felelő 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("\nType exit to return!\n\n");
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 main-


bol meghivni */
void main_frame(void); /* A fomenuhoz keretet rajzol a
kepernyore */
int menu(int index); /* Ez a menurutin: az adott sor-
szamu menut kezeli */
void menu_regen(int index); /* Az adott sorszamu menut rege-
neralja a kepen */
void menu_remove(int index);/* Az adott sorszamu menut letor-
li 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=0..79 */
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); /* Bekapcsolja a kepernyo-


kezelo rendszert, torol */
void displ_end(void); /* Kikapcsolja a kepernyo-
kezelo rendszert, torol */
void cursor_left(int n); /* A kurzort egy pozicioval
balra viszi */
void cursor_right(int n); /* A kurzort egy pozicioval
jobbra viszi */
void cursor_up(int n); /* A kurzort egy pozicioval
feljebb helyezi */
void cursor_down(int n); /* A kurzort egy pozicioval
lejebb helyezi */
void highlight(unsigned mode,/* ’mode’ szerinti attributum-
char* string);/* mal ’string’-et nyomtatja */
int read_in(char* string); /* Egy sztringet olvas be */

int yesno(void); /* y/Y/I/i/N/n (igen/nem)


valaszt var. 0, ha ’nem’ */
int input(char* string,
int pos,int len);/* Sor-editor. Adott pozici-
on, adott hosszt edital */
void press_key(void); /* A ’Press a key’ kiirasa u-
tan 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) */
Irodalomjegyzék

[1] Benkő Tiborné – Urbán Zoltán. Az IBM PC programozása Turbo C 2.0


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

You might also like