Tartalmi kivonat
2. tétel: A Pascal program szerkezete: A Pascal programot három fõ részre oszthatjuk: - programfej - deklarációs rész - programtörzs 1. Programfej: Mindössze esztétikai célokat szolgál Itt adhatjuk meg egy szóban a program nevét. Nem kötelezõ szerepelnie a programban 2. Deklarációs rész: A Pascal nyelv erõsen típusos nyelv, ami azt jelenti, hogy egy programban minden változót, melyet használni szeretnénk deklarálnunk kell, azaz elõre meg kell határoznunk a nevét és a típusát. Ezek a jellemzõk késõbb a program futása alatt természetesen nem változhatnak. A deklarációs részben történik a használni kívánt változók felsorolása, illetve típusuk meghatározása. Ezen kívül a deklarációs részben szerepelnek a program futása során alkalmazott konstansok (állandók), az általunk létrehozott típusok, az általunk létrehozott eljárások, függvények, és az ugró utasítás esetén alkalmazott címkék is. A deklarációs részhez
szokás kapcsolni a unit-ok hívását is 3. Programtörzs: Másnéven végrehajtási rész A programtörzs tartalmazza a program utasításait. { Programfej } program Programnév; { Deklarációs rész } { Unit-ok hívása } uses Unit 1, Unit 2, ., Unit n; { Címkék } label Cimke 1, Cimke 2, ., Cimke n; { Tipusdeklarációk, saját típusok leírása } type Tipus1= tipusleírás1; Tipus2= tipusleírás2; { Konstansok, állandók } const Konstans1= érték1; Konstans2= érték2; { Változók } var Változó1: tipus1; Változó2: tipus2; { Saját függvények } function Függvény1[(Paraméterlista)]: Tipus1; { Saját eljárások } procedure Eljárás1[(Paramáterlista)]; { Programtörzs } { Végrehajtási rész } begin utasítás 1; utasítás 2; . . . utasítás n; end. A programon belül majdnem bárhol elhelyezhetünk magyarázó szöveget, megjegyzést. A megjegyzések olvashatóbbá teszik a programot, érdemes rászokni a használatukra. A néhány szóból álló
magyarázó szöveg nagyon megkönnyíti a program esetleges késõbbi módosítását. A megjegyzést {.} vagy (*.*) zárójelek között kell elhelyezni a programsorok között. Néhány szóban a unit-okról: A Pascal nyelv utasításkészlete viszonylag kevés utasításból épül fel, mégis igen sokoldalúak a nyelv által biztosított lehetõségek. A nyelv ugyanis a kevés utasítás mellett rengeteg eljárást és függvényt biztosít számunkra. A unit-ok feladata az eljárások, függvények tárolása. Ezek az elõre definiált eljárások, függvények szerves részét képezik a Turbo Pascal rendszernek, alapvetõ mûveleteket végeznek (írás a képernyõre, olvasás a billentyûzetrõl, a képernyõ törlése, stb.) A unit-ok csoportokba szedve tartalmazzák az eljárásokat, függvényeket (például: a Graph unit-ban található az összes olyan eljárás és függvény, amely a grafikus képernyõ kezelését végzi), nekünk csak azokat a unit-okat kell
beépíteni a programunkba, amelynek az eljárásait, függvényeit használni szeretnénk. Ezeket a unitneveket kell megadnunk a Uses kulcsszó után Címkék: A Pascal programban használhatjuk (bár nem célszerû) a Goto utasítást, amely az általunk megadott címkéhez ugrik, itt folytatva a program végrehajtását. A programban elhelyezni kívánt címkéket szintén elõre deklarálni kell a Label kulcsszó után. A címkéket egyszerûen fel kell sorolnunk. Címke azonosítóként nem használhatjuk a foglalt szavakat Típusdeklarációk: A Pascal nyelv lehetõvé teszi, hogy saját magunk állítsunk elõ új adattipusokat a standard adattipusok felhasználásával. Az általunk elõállított típusok kapnak egy azonosítót, amellyel a változók (tipusos konstansok) deklarációja során erre a tipusra hivatkozni tudunk. Azonosítóként nem használhatjuk a foglalt szavakat. Példa: Program pelda; Type Szamtomb= array [1.10] of Integer; {Számokat tartalmazó 10 elemû
tömbtipus} Var A: Szamtomb; I: Byte; Begin For I:=1 to 10 do Begin Writeln(A(z) , I, . szam: ); Readln(A[I]); End; . . . End. A példában egy 10 elemû Integer értékeket tartalmazó tömböt definiáltunk Szamtomb azonosítóval. A saját típusok áttekinthetõbbé teszik a programot, amennyiben bonyolult tipusokkal dolgozunk. A tipusdeklaráció jelentõsége azonban a függvények, eljárások alkalmazásakor tapasztalható. A függvények, eljárások paraméterei, és visszatérési értéke csak egyszerû tipus lehet. Hogyan adhatunk át egy 10 elemû tömböt mégis egy függvénynek: Példa: Program Osszegez; Type Szamtomb= Array [1.10] of byte; Var A: Szamtomb; Function Sum(T: Szamtomb): Integer; Var I: Byte; O: Integer; begin O:= 0; For I:= 1 to 10 do O:= O+T[I]; Sum:= O; end; begin For I:=1 to 10 do Begin Writeln(A(z) , I, . szam: ); Readln(A[I]); End; Writeln(A tomb elemeinek osszege: , Sum(A)); end; Programunkban a Sum függvény segítségével összegezzük a
beolvasott A tömb elemeinek értékét, majd kiiratjuk az eredményt. Ha a Sum függvény deklarációjánál összetett tipust alkalmaztunk volna, hibaüzenet keletkezett volna a fordítás közben. Rossz példa (így soha!!!): Function Sum(T: Array [1.10] of Byte): Integer; Látható tehát, hogy a problémát csak úgy lehetett megoldani, hogy egy saját tipust deklaráltunk hozzá, amelyet a fordító már egyszerû tipusként értelmez. Konstansok: A konstansok segítségével szintén áttekinthetõbbé tehetjük a programunkat. A konstansok értéke a program futása során nem változhat meg. Olyan értékeket célszerû (és ésszerû) konstansként deklarálni, amelyek a program során többször is elõfordulnak. Konstans azonosítójaként nem alkalmazhatjuk a foglalt szavakat. Példa: Számítsuk ki az R sugarú kör kerületét és területét ! Program Kor; Const PI= 3.14; Var R: Real; K, T: Real; Begin Write(A kor sugara: ); Readln(R); K:= 2*RPI; T:= R*RPI; {A
kiiratas soran celszeru a :8:2 formatumstringet alkalmazni, mert egyebkent} {ember szamara nehezen emesztheto exponencialis format fogunk kapni.} Writeln(A kor kerulete: , K:8:2); Writeln(A kor terulete: , T:8:2); End. A példában a PI értékét konstansként definiáltuk, ezzel a programunkat olvashatóbbá téve. A konstansok és a változók közötti másik fontos különbség (amellett, hogy a konstansok értéke nem változhat) a memóriában való tárolás során jelentkezik. A változók az adatszegmensen vagy a veremben jönnek létre, aszerint, hogy a fõprogram, vagy egy alprogram használja õket. Ezzel szemben a konstansok a kódszegmensben kerülnek elhelyezésre A fenti szabály alól egyetlen kivétel a tipusos konstans, amelyet tulajdonképpen kezdõértékkel ellátott változónak tekinthetünk. Értéke a program futása során megváltozhat, de - a változókhoz hasonlóan - csak a tipus által meghatározott értékeket vehet fel. A tipusos konstans
deklarálása a többi konstanssal együtt történik. Példa: Const A=10; B=11; PI=3.14; J: Byte= 1; A végrehajtási részben a J azonosítót úgy használhatjuk, mint bármely deklarált változót, annyi különbséggel, hogy J már rendelkezik kezdõértékkel, míg a válzozók értéke határozatlan (amíg a fõprogramon beül nem adunk nekik kezdõértéket). Változók deklarálása: A program során használni kívánt változókat deklarálnunk kell, azaz meg kell határoznunk a változó tipusát. A változó tipusa befolyásolja, hogy a fordító a memóriában mennyi helyet foglal a változó számára, illetve azt, hogy milyen értéket kaphat a változó a program futása során. A változók deklarációja a Var kulcsszóval bevezetett részben történik. Formája: Var azonosító: tipus; Tehát elõször a változó azonosítóját kell meghatároznunk, majd kettõsponttal elválasztva a tipusát. A változók tipusa csak (a fordító számára) ismert tipus lehet,
azaz ha a változó tipusa nem a standard típusok közül kerül ki, a saját tipust elõzõleg deklarálni kell. (Lásd a tipusdeklarációknál.) A változók azonosítójaként nem alkalmazhatjuk a foglalt szavakat Példa: Var A: integer; S: string; I: byte; Függvények, eljárások: Az alprogramok delkarálása (definiálása) szintén a delkarációs részhez kapcsolódik. Ha a programunkat modulárisan szeretnénk felépíteni, akkor az egyes részfeladatok elvégzését külön eljárások, függvények segítségével végezzük. A deklarációs részben (tehát a felhasználási rész, a fõprogram elõtt) deklarálnunk kell legalább az eljárások, függvények fejét. A fõprogramban csak olyan függvényeket, eljárásokat hívhatunk meg, amelyeknek már legalább a neve ismert a fordító számára. A függvények neve nem lehet foglalt szó, és általában nem célszerû a standard unit-ok függvényeinek nevét alkalmazni, mert ezesetben csak speciális módon
tudjuk megkülönböztetni a két függvényt (eljárást). Általában az alprogramok fejével együtt a deklarációs részben történik az alprogram törzsének (végrehajtási részének) kifejtése. Példa: {Programfej} Program Eljárások; {Deklaracios resz} Var A, B: Integer; Procedure Kivonas(X, Y: Integer); Begin Writeln(A ket szam kulonbsege: , X-Y); End; Procedure Osszeadas(X, Y: Integer); Begin Writeln(A ket szam osszege: , X+Y); End; {Foprogram} begin Write(Az elso szam: ); Readln(A); Write(A masodik szam: ); Readln(B); Osszeadas(A, B); Kivonas(A, B); end. A példában két eljárást definiáltunk, amelyek a képernyõre írták két szám összegét, illetve különbségét. Az eljárások végrehajtási részét itt a deklarációs részben írtuk meg, közvetlenül az eljárás fejrésze után. A megoldás mûködik, de nem igazán elegáns Áttekinthetõbb programot kapunk, amennyiben kihasználjuk a Forward opció által biztosított lehetõségeket. Ha az alprogram
fejrészének deklarációja után szerepel a Forward kulcsszó, akkor nem kötelezõ azonnal definiálnunk az alprogram végrehajtási részét, ezt bármikor megtehetjük a program késõbbi részeiben, de a fõprogram blokkja, a végrehajtási rész elõtt. Ez a lehetõség szintén az esztétikai szempontok miatt lehet elõnyös, amikor egy nagyobb programot írunk. Példa: Olvassunk be 3 nevet egy tombbe! Alakitsuk at a neveket csupa nagybetus formara, majd irjuk ki oket. Az atalakitast es a kiirast egy eljaras segítségével oldjuk meg! Program Eljaras2; Type Tomb: Array [1.3] of String; Procedure NagyBetu(T: Tomb); forward; Var A: Tomb; I: Byte; Const Fejlec1= Kerek harom nevet; Fejlec2= A beolvasott nevek nagybetuvel irva:; Procedure NagyBetu(T: Tomb); Var J, N: Byte; K: Tomb; Begin {A tomb elemei egyesevel} For J:= 1 to 3 do {Az aktualis elem (string) atalakitasa nagybetusse} For N:=1 to length(S) do K[J][N]:= UpCaseT[J][N]; {A tomb eleminek kiirasa} For J:=1 to 3 do
Writeln(K[J]); End; {A foprogram} Begin ClrScr; Writeln(Fejlec1); For I:= 1 to 3 do Begin Writeln(Az, I, . nev: ); Readln(A[I]); End; ClrScr; Writeln(Fejlec2); NagyBetu(A); End. A programtörzs: A program végrehajtási részének is nevezhetjük, lényegében itt írjuk le, hogy mit fog csinálni a programunk, hogyan és mire használja fel a deklarációs részben deklarált változókat, konstansokat, alprogramokat. Nagyon fontos, hogy a fõprogram mindig a Pascal program végén helyezkedik el. A fõprogram egy Begin utasítással kezdõdik és egy End utasítással zárul Fontos: a fõprogram végét jelzõ End utasítás után minden esetben pontot kell tenni pontosvesszõ helyett, mivel ez jelzi a Pascal program végét a fordító számára. A Pascal programban minden utasítás végére pontosvesszõt kell tenni, kivétel a Begin utasítás, amely egy blokk kezdetét jelzi, és a fõprogramot záró End utasítás, amelynek a végére pont kerül. A Pascal programban
vegyesen használhatunk kis és nagybetûket, a Pascal fordító ezeket nem különbözteti meg. 3. tétel: Elemi adattípusok (adatábrázolás, értéktartomány, konstansok, mûveletek, fontosabb függvények, eljárások) A Pascal nyelv erõsen típusos nyelv, azaz minden a programban felhasználásra kerülõ változó típusát elõre meg kell határozni. A nyelv jónéhány elõre definiált standard típust tartalmaz, amelyet kiegészítenek a unit-okban definiált típusok is. Az elõbbiek bármely programban szabadon felhasználhatók, az utóbbiak használata elõtt a Uses utasítás segítségével meg kell hívni a típust tartalmazó unit-ot. A beépített és a unit-ok által rendelkezére bocsátott típusokon kívül lehetõségünk van a deklarációs részben saját típusok definiálására (deklarálására) is. Álalános szabály, hogy a változó bármely ismert típust felveheti, azaz a változó deklarációjának helye elõtt ismertté kell tenni a
típust a fordító számára (unit-hívás, típusdeklaráció, ha nem standard típus). A Turbo Pascal nyelv típusait a következõképpen csoportosíthatjuk: Egyszerû típusok: - Sorszámozott típusok: - Egész típusok - Karakteres típus - Logikai típus - Felsorolt típus - Résztartomány típus - Valós típusok - String típus Struktúrált (összetett) típusok: - Tömb típusok - File típusok - Record típus - Halmaz típus - Objektum típus Mutató típusok A típusok csoportosítása során a szempont az adott típusú változókon végezhetõ mûveletek köre volt, azonos csoportba tartozó típusokon hasonló mûveleteket lehet elvégezni. Egyszerû (elemi) adattípusok: Az elemi adattípsok közös jellemzõje, hogy az ilyen típusú változók egyszerre csak egy értéket képesek tárolni, értelmezési tartományuknak megfelelõen. Az elemi adattípusokat három alcsoportra osztottuk, az egyes alcsoportokba tartozó típusok hasonló jellemzõkkel,
tulajdonságokkal rendelkeznek. A sorszámozott típusoknál az értelmezési tartomány minden egyes eleme esetében egyértelmûen meghatározható az elemet megelõzõ és az elemet követõ elem (Pld: 1, 2, 3 vagy a, b, c). A valós típusok esetében az elõbbi kijelentés már nem igaz, mivel két valós érték között még végtelen sok valós érték található. A string típus esetében megoszlanak a vélemények arról, hogy egyszerû adattípus-e egyáltalán. Kezelését tekintve valóban tekinthetõ összetett típusnak (hiszen tulajdonképpen egy tömb, amelynek elemei karakter típusúak), de mivel string típusú változó szerepelhet a függvények paraméterlistájában, általában az egyszerû típusok közé sorolják. A sorszámozott típusok csoportját további alcsoportokra bonthatjuk: - numerikus típusok - karakteres típus - egyéb típusok (felhasználó által létrehozott típusok) A numerikus típusok: Egész típusok: Numerikus értékek, egész
számok tárolására alkalmas típusok (a nevébõl következtetni lehet erre. :-))) ) Valós értékek tárolására alkalmatlanok Aritmetikai mûveletek során használhatjuk fel ezeket a típusokat. A Turbo Pascal-ban ötféle elõre definiált egész típus áll rendelkezésre: shortint -128.127 elõjeles, 8 bites integer -32768.32767 elõjeles, 16 bites longint -2147483648.2147483647 elõjeles, 32 bites comp -263.263-1 elõjeles, 64 bites byte 0.255 elõjel nélküli, 8 bites word 0.65535 elõjel nélküli, 16 bites Az egész típusok memóriában történõ tárolása fixpontos ábrázolás szerint történik. Az alábbiakban az Integer típus ábrázolásán keresztül nézzük meg a fixpontos számábrázolás lényegét: Az Integer típus 2 byte-ot foglal a memóriában. Ez elméletileg 216 féle (65536) szám ábrázolását tenné lehetõvé, de ekkor csak pozitív számokat tudunk ábrázolni. Ebben a legegyszerûbb esetben nem kell mást tennünk, mint
a számot átalakítani 2-es számrendszerbe, és kiegészíteni annyi 0val, amely kitölti a 16 bitet. (A word típus így mûködik) Azonban szükségünk van negatív számokra is, melyeknek tárolására már nem megfelelõ az elõbbi módszer (honnan tudjuk, hogy a szám milyen elõjelû lesz?). A negatív számok ábrázolásához alkalmazták az ún. elõjelbitet, azaz a két byte legelsõ bitje határozza meg, hogy a szám milyen elõjelet kap. Ha az elõjelbit 0, akkor a szám pozitív, ha 1, a szám negatív Az elõjelbit alkalmazása csökkenti az ábrázolható számok értelmezési tartományát, mivel a 16 bitbõl egy az elõjelet határozza meg. Ilyen módon a legnagyobb ábrázolható szám 215-1, azaz 32767 lesz. Ezt a legnagyobb számot a következõképpen ábrázolhatjuk: 01111111111111112=3276710 A pozitív számok tárolása tehát a fentebb vázolt módszerrel történik (azért még késõbb bonyolódni fog). A negatív számok ábrázolása során a
gyorsabb számolás érdekében a szám a 16 bit szerinti kettes komplemens formában van tárolva. A kettes komplemens kód képzése során elõször kiszámoljuk a szám kettes számrendszerbeli formájának inverzét, majd az így kapott számhoz hozzáadunk egyet. Nézzünk egy példát: Ábrázoljunk -18 at kettes komplemes kódban: 18 kettes számrendszerbeli alakja: 0000000000010010 inverze: 1111111111101101 hozzáadva 1-et: 1111111111101110 A legnagyobb 16 biten ábrázolható negatív szám a -1, a legkisebb -32768. A fixpontos számábrázolás során amennyiben a típus 1 byte-nál nagyobb méretû, a szomszédos byte-okat felcseréljük, az ábrázolás byte-fordított formában történik. A byte fordított forma jellemzõ mind a pozitív, mind a negatív számok ábrázolása során. Nézzük ezek után az egyes egész típusok jellemzõit. Shortint: Értelmezési tartománya -128.127 Elõjeles típus, mérete 1 byte A negatív számok ábrázolása kettes komplemens
kódban történik, a típus méretébõl adódóan a byte-fordított forma nem jellemzõ. :-)) Byte: Értelmezési tartománya 0.255 Elõjel nélküli típus, mérete 1 byte A számok ábrázolása normál kettes számrendszerbeli alakjukkal történik, a byte-fordított forma és a kettes komplemens kód nem jellemzõ. Word: Értelmezési tartománya 0.65535 Elõjel nélküli típus, mérete 2 byte A számok ábrázolása kettes számrendszerbeli alakban történik, byte-fordított formában. Integer: Értelmezési tartománya -32767.32768 Elõjeles típus, mérete 2 byte A pozitív számok ábrázolása kettes számrendszerbeli alakjukkal, a negatív számok ábrázolása kettes komplemens kódban történik. Jellemzõ a byte-fordított tárolási forma Longint: Értelmezési tartománya -2147483648.2147483647 Elõjeles típus, mérete 4 byte A pozitív számok ábrázolása kettes számrendszerbeli alakban, a negatív számok ábrázolása kettes komplemens kódban történik.
Jellemzõ a byte-fordított tárolási forma Comp: Ez a típus a kevésbé ismert típusok közé tartozik, elég ritkán használjuk. A legnagyobb méretû egész típus, értelmezési tartománya -9,22*10-18.9,22*1018. Memóriában történõ tárolása az integer-hez hasonlóan, fixpontos, byte-fordított formában történik. A negatív számokat 64 bit szerinti kettes komplemens kódban tárolja. Az egészek között ez az egyetlen típus, amely igényli az aritmetikai társprocesszor jelenlétét, illetve ha fizikailag nincs, akkor annak emulációját. (Az N, és szükség esetén az E direktívát be kell kapcsolni.) A comp típust sokszor a valósak közé sorolják, mert igaz, hogy ábrázolása fixpontosan történik, de legtöbb egész paramétert váró függvény nem fogadja el a comp típust, illetve amikor egy ilyen változó tartalmát alapértelmezés szerint iratjuk ki a képernyõre, az eredmény lebegõpontosan jelenik meg. Valós típusok: Valós számok
(tizedestörtek) tárolására használhatjuk ezeket a típusokat. Természetesen tárolhatók egész számok is, de a valós típusú változóban tárolt egész számok teljesen eltérõen viselkednek az elõbbiekben tárgyalt egész típusú változókban tárolt egész számokhoz képest. A valós típusok az aritmetikai mûveletek sokkal szélesebb körét támogatják, mint az egész típusok. A Turbo Pascal-ban négyféle valós típust használhatunk: Típus neve Teljes mérete Mantissza mérete Karakterisztika mérete Tizedesjegyek száma Real 6 byte 39 bit 8 bit 11-12 Single 4 byte 23 bit 8 bit 7-8 Double 8 byte 52 bit 11 bit 15-16 Extended 10 byte 64 bit 15 bit 19-20 A valós számok ábrázolása a memóriában lebegõpontosan történik (ebbõl kifolyólag más jellemzõket írtam a táblázatba is, mint az egészek esetén). A lebegõpontos ábrázolás során a számot normál alakban tároljuk, tehát a következõ formában: x=m*2k ahol x a
tárolandó szám, m a mantissza, k a karakterisztika. Az értékek (x, m, k) nyilván 2-es számrendszerbeli formában vannak tárolva. A típus teljes méretét kiszámolhatjuk, ha összeadjuk a mantissza és a karakterisztika tárolásához szükséges biteket, majd a kapott értékhez hozzáadunk egyet, az elõjelbit miatt. Tehát a negatív számok tárolását itt is egy elõjelbit által oldották meg. Az elõjelbit a mantissza elõjelét tárolja, tehát a teljes szám elõjelét határozza meg. Az elõjelbit a mantissza elõtt alló legelsõ bit lesz. A karakterisztika tárolási mérete határozza meg a tárolható számok pontosságának mértékét (tizedesjegyek száma). Nyilvánvaló, hogy a 0-hoz közeli számok tárolását a karakterisztika negatív értékével lehet megvalósítani. Ehhez szükség lenne (elméletileg) még egy elõjelbitre, mivel a mantissza és a karakterisztika elõjele egymástól független. Annak érdekében, hogy ne kelljen még egy bitet
feláldozni az elõjel tárolásához, a karakterisztika tárolása eltolt ábrázolással történik. Nézzük meg a Real típus esetében hogyan történik az eltolt ábrázolás: A Real karakterisztikája $80-nal van eltolva. Ha a karaketerisztika értéke 3 lesz (azaz a mantisszát 23-nal kell szorozni), akkor a karakterisztika ábrázolt értéke $83 lesz, tehát a tényleges karakterisztika számításához ki kell vonnunk az ábrázolt értékbõl $80-at. Amennyiben negatív kitevõt szeretnénk ábrázolni, hasonlóképpen kell eljárnunk. Ha a karakterisztika értéke 5 lesz, akkor az ábrázolt érték: $75 Ebbõl kivonva a $80-at, eredményül -5-öt kapunk Természetesen a valós típusok esetén is érvényes a byte-fordított tárolási forma, tehát a lebegõpontos ábrázolás során is fel kell cserélni a szomszédos byte-okat a pontos tárolási kép meghatározása során. A valós típusok közül egyedül a Real típus nem igényli a numerikus processzor
(8087) jelenlétét (illetve emulációját), az összes többi valós típusnak szüksége van erre. Amennyiben gépünk tartamaz numerikus processzort, a program elején (legalábbis a valós típusok deklarálása elõtt) az N direktívát be kell kapcsolni, hogy a programunk kihasználhassa a numerikus processzort. Amennyiben ezt nem tesszük meg, a fordító hibaüzenetet fog küldeni a Real-tõl különbözõ valós típusok használata esetén. Ha gépünkben nincs numerikus processzor, a Turbo Pascal (5.5 és annál újabb változatai) képesek azt emulálni az E direktíva bekapcsolása esetén. Tehát amennyiben van numerikus processzorunk: {$N+}, amennyiben nincs: {$E+, N+} Ha esetleg ebben a formában érthetõbb: {$E+} {$N+} Nézzük ezek után az egyes valós típusok jellemzõit. Real: Értelmezési tartománya 2.9*10-39.17*1038. Mérete 6 byte A Turbo Pascal elsõ verziójától használható valós típus, az egyetlen valós típus, amely nem igényli a numerikus
processzort. Pontossága 11-12 tizedesjegy. Ezt a típus használjuk leggyakrabban a valós számok tárolásához Single: Értelmezési tartománya 1.4*10-45.34*1038. Mérete 4 byte A Turbo Pascal 40 verziójától használható valós típus, igényli a numerikus processzort vagy annak emulációját. Pontossága 7-8 tizedesjegy. Double: Értelmezési tartománya 4.9*10-324.17*10308. Mérete 8 byte A Turbo Pascal 40 verziójától használható, igényli a numerikus processzort vagy annak emulációját. Pontossága: 15-16 tizedesjegy. Extended: Értelmezési tartománya 3.3*10-4932.11*104932. Mérete 10 byte A Turbo Pascal 40 verziójától használható, igényli a numerikus processzort vagy annak emulációját. Pontossága: 19-20 tizedesjegy. A karekteres típus: Ebbe a csoportba egyetlen típust szoktunk sorolni, a char típust. Ez a típus egyetlen ASCII karakter tárolását teszi lehetõvé. A típus mérete 1 byte, elõjel nélküli típus Tehát egy char típusú
változóban 28 (256) féle értéket tudunk tárolni. Értelmezési tartománya az ASCII kódtábla elsõ karakterétõl, az utolsó karakteréig tart. A memóriában történõ tárolása a byte típushoz hasonlóan történik, valójában a karakter ASCII kódjának kettes számrendszerbeli formáját tároljuk. A képernyõn történõ megjelenítéskor a tárolt karakter jelenik meg. A logikai típus: A Pascal nyelv egyik speciális lehetõsége, hogy beépített logikai típussal rendelkezik, ez a Boolean típus. (Más nyelvek általában egész változókkal oldják meg) Ez a típus mindössze kétféle érték tárolására képes: logikai igaz (TRUE) és logikai hamis (FALSE). A típus a memóriában 1 byte-on tárolható. A TRUE(=1)FALSE(=0) reláció természetesen igaz A képernyõn történõ megjelenítés során a változó értéke szöveges formában kerül kiírásra, végig nagybetûvel írva. A felsorolt típus: A Pascal nyelv lehetõséget ad arra, hogy a
program írása során olyan egyedi típusokat deklaráljunk, amelyeknek értékkészletét magunk határozzuk meg. Ezek a típusok mindig sorszámozott típusok, az elemek azonosítóit azonban mi magunk adhatjuk meg. A felsorolt típus elemeinek felsorolása egyben nagyságrendi sorrendet is meghatároz, mivel a fordító automatikusan sorszámot rendel az egyes elemekhez. Ha másképpen nem rendelkezünk, az elsõ elem a 0 sorszámot kapja, a következõ elemek sorszáma pedig mindig eggyel növekszik az elõzõhöz képest. Nézzünk erre egy egyszerû példát: var Het: (Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap); A fesorolt típus deklarációja során az értékkészlet elemeit kerek zárójelek között, egymástól vesszõvel elválasztva soroljuk fel. A sorszámozás ebben az esetben automatikus, tehát a Hetfo a 0 sorszámot kapta, Pentek pedig a 4-et. Nagyon fontos, hogy az egyedi azonosítókat nem kell aposztrófok közé tenni, mivel nem stringekrõl van
szó. A felsorolt típussal deklarált azonosító csak olyan értéket vehet fel, amely a felsorolásban szerepel. A felsorolt típus memóriában történõ ábrázolása 1 byte-on történik. A résztartomány típus: A neve is mutatja ennek a típusnak a jelentõségét. A már ismert sorszámozott típusok egy-egy része, tartománya külö típusként használható a program során. Sokszor hasznos ez a lehetõség, ha tudjuk, hogy a felhasznált értékek milyen intervallumba fognak esni. A résztartomány típus deklarációja során a típus alsó és felsõ határértékeit kell feltüntetni, intervallum formájában. Például: Var Számjegy: 0.9; Nagybetu: A.Z; Egyjegyu: 0.9; Ketjegyu: 10.99; Haromjegyu: 100.999; A fenti forma alkalmazható a Var utasítás után. Ha külön típusként szeretnénk felhasználni: type Szamjegy= 0.9; . var S: Szamjegy; A felsorolt típusok memóriabeli mérete a típus felsõ határától függ. Mindig akkora területet használ fel a
fordító, amely minimálisan szükséges a felsõ érték tárolásához. Pld: 065000 tárolásához két byte szükséges, de 0.66000 tárolásához már négy byte A felsorolt típusok tárolása kettes komplemens kódban történik, több byte-os típusok esetén byte-fordított formában. A string típus: Ez a típus szöveges adatok tárolását teszi lehetõvé. Tulajdonképpen karakterek egymáshoz fûzött láncát jelenti. Az egymáshoz fûzött karakterek láncának hossza változó, mindössze a karakterek maximális száma van rögzítve, a legnagyobb hosszúság 255 karakter lehet. A string aktuális hosszán az éppen tárolt karakterek számát értjük. A string típus megítélése során eltérõek a vélemények: mivel karakterekbõl álló tömbrõl van szó, egyesek szerint az összetett típusok közé kellene sorolni. Mivel azonban a string típus a függvény paramétere is lehet, mások az egyszerû típusok közé sorolják. A stringek deklarációja: Var
S1: String; Ez a deklaráció legegyszerûbb formája, ebben az esetben a string maximális hosszsága 255 karakter lehet. A deklaráció során meg lehet határozni a változóban tárolható szöveg legnagyobb hosszúságát a következõ módon: Var S2: String[20]; Ebben az esetben a tárolható szöveg maximum 20 karakterõl állhat. A String típusú változók értéke beolvasható a billentyûzetrõl. Amennyiben a felhasználó a deklarált maximális hosszúságnál hosszabb szöveget szeretne megadni, a szövegnek csak az a része kerül tárolásra, amennyit a változó hosszúsága megenged, tehát csonkolás történik. A string változók maximális hosszúságára vonatozó korlátozás a string típus felépítésében keresendõ. A stringek memóriában történõ tárolása igen egyszerû: minden karakter egy byte-on van ábrázolva, ASCII kódjának kettes számrendszerbeli alakjában, az egyes karakterek egymás után helyezkednek el. A string hosszúsága
(memóriabeli mérete) a következõképpen határozható meg: a deklarációban szereplõ maximális hosszúsághoz hozzáadunk egyet. Tehát a Var S: String deklaráció által létrehozott változó a memóriában 256 byte-ot foglal. A stringek 0 byte-ja speciális feladatot lát el a string kezelése során, ez a byte tárolja a string aktuális hosszúságát. A 0. byte-ot általában "nem illik" módosítani Tehát egy string tárolását a következõképpen lehet elképzeni: Var S: String[6]; Begin S:= AAAA; End. Az S string memóriában történõ tárolása: Chr(4) Chr(65) Chr(65) Chr(65) Chr(65) határozatlan határozatlan 0. byte 1. byte 2. byte 3. byte 4. byte 5. byte 6. byte Tehát látható, hogy a string hosszúságát is egy ASCII karakterként tároljuk a string 0. bytejában Mivel a string aktuális hosszúsága 4 karakter, az ötödik és hatodik byte értéke nem meghatározható. A string típusú változók szoros kapcsolatban állnak a
tömbökkel. A string változó egyes byte-jai ugyanúgy érhetõk el, ahogy egy tömb elemei. Nyilván itt is figyelni kell az indexelés határaira A string minden egyes byte-ja (karaktere) egy karakter típusú értéknek felel meg. Var S: String[20]; Ch: Char; Begin S:= Pascal; Ch:= S[3]; Writeln(Ch); End. A program eredményként az "s" betût fogja kiírni. Az egyszerû típusokhoz kapcsolódó eljárások, függvények: Az egyszerû típusokon elvégezhetõ függvények, eljárások a System unit-ban vannak definiálva. Az eljárások, függvények mellett az egyszerû típusok felhasználhatók a Pascal kifejezések operandusaiként is, lásd a 6. tételben A függvények, eljárások a hozzájuk kapcsolódó típusok szerint csoportokra oszthatók: Matematikai függvények, eljárások: A numerikus típusokhoz kapcsolódó függvények, eljárások. Az eredmény, és a paraméterek jellegétõl függõen valós, illetve egész típusokon alkalmazhatók. Abs(x)
Az x paraméter abszolút értéke. x: valós, egész Eredmény: egész ArcTan(x) Az x paraméter arkusztangense (a tangens inverze). x: valós, egész Eredmény: valós. Cos(x) Az x paraméter koszinusza. A szöget radiánban kell megadni x: valós, egész Eredmény: valós. Exp(x) Az x paraméter e alapú hatványa (exponenciálisa). Eredmény: valós Frac(x) Az x paraméter törtrésze. x: valós, egész Eredmény: valós Int(x) Az x paraméter egészrésze (kerekítés nélkül). x: valós, egész Eredmény: egész Ln(x) Az x paraméter 10-es alapú logaritmusa. x: valós, egész Eredmény: valós Odd(x) Paritás tesztelése (páros szám-e x). x: egész Eredmény: logikai (False- páros, Truepáratlan) Pi Pi értéke. Random 0 és 1 közé esõ véletlenszám elõállítása. Eredmény: valós Random(x) 0 és x közé esõ véletlenszám elõállítása. x: egész Eredmény: egész Randomize A véletlenszám generátor indítása. (Véletlenszám elõállítása elõtt
mindenképpen szükséges) Round(x) Az x paraméter kerekített értékét adja vissza. x: valós, egész Eredmény: egész Sin(x) Az x paraméter szinusza. A szöget radiánban kell megadni x: valós, egész Eredmény: valós. Sqr(x) Az x paraméter négyzete. x: valós, egész Eredmény: x típusától függ Sqrt(x) Az x paraméter négyzetgyöke. x: valós, egész Eredmény: x típusától függ Trunc(x) Valós típusú érték 0 felé kerekített egész része, azaz a valós érték tizedespont utáni részét "levágja". x: valós Eredmény: egész Sorszámozott típusokhoz kapcsolódó függvények, eljárások: Ezek az eljárások, függvények kizárólag sorszámozott típusok esetében alkalmazhatók, paraméterük, eredményük egyaránt sorszámozott típus kell legyen. Dec(x) Az x paraméter értékének csökkentése 1-gyel. Dec(x, n) Az x paraméter értékének csökkentése n-nel. Inc(x) Az x paraméter értékének növelése 1-gyel. Inc(x, n) Az x
paraméter értékének növelése n-nel. Ord(x) Az x paraméter sorszámának értéke (pld. Karakterek esetén ASCII kód) Pred(x) Az x paramétert megelõzõ érték. (pld Ch:= Pred(B); Ch értéke: A) Succ(x) Az x paramétert követkõ érték. (pld Ch:= Succ(B); Ch értéke: C) Chr(x) Az x paraméter által meghatározott (ASCII kódú) karakter (pld: Ch:= Chr(65); Ch értéke: A) Stringekhez kapcsolódó függvények, eljárások: Ezek az eljárások a karakterláncok kezelését segítik. Található közöttük néhány olyan eljárás, amely egy Pascal kifejezésen kereszül is megoldható, ezekben az esetekben a programozó dönti el, hogy melyik megoldást választja. Concat(S1, S2, .) A függvény összefûzi a paraméterlistában szereplõ stringeket A paraméterek száma nincs meghatározva. Copy(S, I, N) A függvény az S stringbõl kimásol N darab karaktert, az I. karakterõl kezdõdõen S: String, I, N: Byte. Delete(S, I, N) Az eljárás az S stringbõl töröl N
darab karaktert, az I. karakterõl kezdõdõen S: String, I, N: Byte. Insert(S1, S2, I) Az eljárás S1 string I. karaktere után beszúrja az S2 stringet Length(S) A függvény visszaadja az S string hosszúságát. Eredmény: egész Pos(S1, S2) A függvény az S2 stringben keresi S1 stringet. Ha megtalálta, akkor egész értékként adja vissza, hol kezdõdik S2-ben S1, ha nem találta meg, 0 értéket ad vissza. Str(X:W, S) Az eljárás X egész értéket szöveggé alakítja át W mezõszélességgel. Eredmény: string (S). Str(X:W:D, S) Az eljárás X valós értéket szöveggé alakítja át W:D mezõszélességgel (W:D egészrész : tizedesjegyek) Eredmény: string (S). UpCase(CH) Az eljárás a CH karaktert átalakítja nagybetûvé. A CH típusa karakter (Char), csak az angol ABC betûivel mûködik helyesen. Val(S, V, Code) Az eljárás S stringet alakítja át numerikus értékké. A numerikus érték típusát a V paraméter típusa határozza meg, egész és valós
egyaránt lehet. Ha hiba történt az átalakítás során, a hiba fellépésének helyét a Code egész típusú változó tartalmazza. Hiba nélküli átalakítás esetén Code értéke 0. 4. tétel Összetett adattípusok (adatábrázolás, értéktartomány, konstansok, mûveletek, fontosabb függvények, eljárások) A programozási munka során gyakran találkozunk olyan feladattal, amelynek megoldásához nem megfelelõek az egyszerû adattípusok. Pld: Olvassunk be 10 egész számot, majd írjuk ki növekvõ sorrendben. A feladat megoldása elképezelhetõ egyszerû adattípusok alkalmazásával is, de létezik ennél sokkal hatékonyabb, gyorsabb megoldás is. Az ilyen jellegû problémák során alkalmazzuk az összetett típusokat. Az összetett (másnéven struktúrált) típusok jellemzõje ellentétben az egyszerû típusokkal - hogy egyszerre több érték tárolására is képesek A struktúrált típusok egyszerû típusokból - ún. komponens típusokból -
felépített, bonyolultabb adattípusok. A Pascal nyelvben használható összetett adattípusok: - Tömb típusok - File típusok - Record típus - Halmaz típus - Objektum típus A tömb típusok: A tömb típus olyan összetett típus, amely több azonos típusú érték egyidejû tárolását teszi lehetõvé. Homogén adathalmaz, elemeinek típusa nem lehet eltérõ Deklarációja: Var Tomb: Array [n.m, n1m1, n2m2, , nnmn] of elemtípus; A fenti deklaráció a tömbök deklarációjának általános alakja. A tömb lehet egy vagy több kiterjedésû (dimenziójú) is. A gyakorlatban leggyakrabban az egy és kétdimenziós tömböket használjuk. (Az egydimenziós tömböt vektorként, a kétdimenziós tömböt mátrixként szokták emlegetni.) Az általános deklarációban látható módon szögletes zárójelek között kell megadni a tömb elemeinek számát (dimenziónként), az elemek számát résztartományként határozzuk meg. Az elemek számának meghatározása után
meg kell adnunk az elemek típusát is, amely tulajdonképpen bármilyen (a tömb deklarációjának helye elõtt már ismert) típus lehet, akár felhasználó által definiált típus is. Egyetlen fontos megkötés, hogy amennyiben összetett típust szeretnénk megadni a tömb elemtípusaként, az összetett típushoz a tömb deklarációja elõtt hozzá kell rendelni egy típusnevet. (Hasonló megkötés, mint a függvények, eljárások paraméterezésénél.) Nézzünk néhány példát a tömbök deklarációjára: Öt elemû tömb, mely egész értékeket tartalmazhat: Var T: Array [1.5] of Integer; Háromelemû tömb, melynek elemei stringek: Var T: Array [1.3] of String; 3x3-as mátrix, amely karaktereket tárol: Var M: Array [1.3, 13] of Char; 6x2-es mátrix, amely valós értékeket tárol: Var M: Array [1.6, 12] of Real; A következõ deklarációs részben olyan tömböt deklarálunk, amely felhasználó által definiált (felsorolt) típusú elemek tárolására
alkalmas. Type Het= (Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap); Var Napok: Array [1.4] of Het; . A bonyolult tömbök deklarációja során a tömbtípust általában külön típusként szoktuk deklarálni, azaz ahelyett hogy a deklarációs részben többször leírnánk ugyanazt a tömbtípust, külön nevet rendelünk hozzá, kihasználva a felhasználói típus létrehozásának lehetõségét. Type Matrix= Array [1.5, 13] of Longint; . Var Tomb: Matrix; Tomb2: Matrix; . A tömbök deklarálhatók típusos konstansként is. Ebben az esetben a 3 tételben leírtaknak megfelelõen a deklarációs részben a const foglalt szó után deklaráljuk a tömböt. A típusos konstansként deklarált tömbnek a következõ formában adhatunk kezdõértéket: Const Tomb: Array [1.5] of byte= (1, 2, 3, 4, 5); Nagyon fontos, hogy a zárójelek között szereplõ értékek száma pontosan annyi lehet, amennyi a tömb elemszáma. Ha ettõl eltérünk, a fordító hibaüzenetet
küld Természetesen a többdimenziós tömbök is szerepelhetnek a programban típusos konstansként. Nézzünk erre is egy példát: Const Matrix: Array [1.2, 14] of Char= ((A, C, E, G), (B, D, F, H)); A példában egy 2 sorból és 4 oszlopból álló mátrixot deklaráltunk. Tehát a kétdimenziós tömbök deklarációja során az elsõ szám a sorok számát, a második szám az oszlopok számát határozza meg! Az értékadás során itt is zárójelek között adtuk meg a tömb elemeinek értékét. Figyelni kell arra, hogy az értékeket sorfolytonosan adjuk meg, minden sor zárójelbe kerül, a sorokat vesszõvel választjuk el. A tömbök nem szerepelhetnek a programban típusnélküli konstansként. A tömbök memóriában foglalt méretét kiszámolhatjuk az elemtípus mérete és az elemszám szorzataként. Az utolsó példa Matrix nevû tömbje: 2*4=8 elemet tartalmaz, azaz mérete 8 byte (a Char 1 byte-os). A tömbök memóriában történõ ábrázolása a tömb
dimenzióinak számától függõen másképpen történik. Egydimenziós tömbök esetében az elemek tárolása sorban történik, az egyes elemek egymás után következnek. Kétdimenziós tömbök esetében a tárolás sorfolytonosan van megvalósítva. Tehát az egy sorban (logikai sor) található elemek egymás után helyezkednek el a memóriában. Egydimenziós tömbök ábrázolása: Tomb[1], Tomb[2], Tomb[3], Tomb[4], Tomb[5] Kétdimenziós tömbök ábrázolása: Matrix[1,1], Matrix[1,2], Matrix[1,3], Matrix[1,4], Matrix[2,1], Matrix[2,2], Matrix[2,3], Matrix[2,4] Az memóriabeli ábrázolás módját természetesen befolyásolja a tömb elemtípusa is, de a fenti elvek minden elemtípus esetében érvényelülnek. A változóként deklarált tömbök a program futása során kapnak értéket. Leggyakoribb eset az elemenkénti értékadás, azaz a tömb elemeinek értékét egyenként határozzuk meg. A tömb elemeire a tömbnév után kapcsos zárójelek között
megadott indexszámmal tudunk hivatkozni. Pld: Tomb[1]:= 4; A tömbök közötti közvetlen értékadás csak abban az esetben lehetséges, ha a két tömb azonos típusú. A típusazonosságnál azonban nem elégséges feltétel, az elemek számának és típusának azonossága (nem értem miért!?). Egy példán szemléltetve: Var T1, T2: Array [1.5] of Integer; T3: Array [1.5] of Integer; Begin . T1:= T2; {Mûködik} T3:= T2; {Típuskeveredési hiba} . End. Fontos mûvelet lehet a tömbök összehasonlítása (egyenlõségvizsgálat) is, amely szintén csak azonos típusú tömbök esetében végezhetõ el. Az elõbbi példánál maradva: . T1=T2; T2=T3; . {Mûködik} {Típuskeveredési hiba} A tömbök kezelése során figyelni kell az indexhatárok átlépésére, amely alapértlmezés szerint fordítási hibához vezet. A tömb indexének szélsõ értékeit lekérdehetjük a Low és High függvények segítségével. A Low függvény a tömb legkisebb indexének értékével
tér vissza, a High a legmagasabb indexértéket szolgáltatja. Pld: Var T1, T2: Array [1.5] of Integer; Begin Writeln(Also hatar: , Low(T1)); {1} Writeln(Felso hatar: , High(T1)); {5} End. A file típusok: A file típusok lehtõvé teszik a háttértárakon található adatállományokhoz történõ hozzáférést, biztosítják azok kezelését a Pascal programból. Bõvebben lásd a 10 tételben A record típusok: A record típus egy olyan összetett adatszerkezet, amely egyszerre több adatot képes tárolni, az adatok különbözõ típusúak lehetnek (szemben a tömbbel). Az egyik legrugalmasabb Pascal adatszerkezet. Elsõsorban az állománykezelés során használjuk ki a lehetõségeit, de ezen kívül még nagyon sok helyen hasznos lehet az alkalmazása. Deklarációja: Var R: Record Azonosito: Word; Nev: String[50]; Eletkor: Byte; Lakcim: String[80]; End; A deklaráció során a record és az end foglalt szavak között soroljuk fel a rekord mezõit, azaz a rekordot
felépítõ elemeket, és azok típusát. A record mezõi között szerepelhetnek egyszerû és összetett típusú mezõk egyaránt, tehát a recordnak lehet akár tömb típusú mezõje is. Pld: Var R: Record Azonosito: Word; Konyvek: Array [1.10] of String; End; (A fenti két record akár egy könyvtári nyilvántartás kiindulási pontja is lehetne. :-)) ) A record típusú változók egymásba is ágyazhatók, tehát a record típusú változónak lehet record típusú mezõje is. Pld: Var R: Record Azonosíto: Word; Adatok: Record Nev: String; Eletkor: Byte; Lakcim: String; End; End; Az elõbbi rekord kicsit másképpen felírva így néz ki. A record mezõire hivatkozhatunk, a record típusú változó azonosítója után pontot téve a mezõ azonosítójával. Tehát: RAzonosito egy Word típusú változóként kezelhetõ A record típusú változók esetében általában a mezõnkénti értékadást célszerû választani, a recordok közötti közvetlen értékadás
feltételei azonosak a tömb típus esetében leírt feltételekkel. A bonyolult rekordtípusokat célszerû saját típusként deklarálni. Ha függvénynek, eljárásnak szeretnénk paraméterként átadni egy rekordot, akkor is különt kell deklarálni a rekord típust. Type Rek= Record Nev: String; Eletkor: Byte; End; Var E, F: Rek; G: Rek; Begin E.Nev:= Karoly; E.Eletkor:= 23; E:= F; {Mûködik} G:= E; {Mûködik} . End. A With utasítás segítségével némelyest egyszerûsíthetõ a rekordok kezelése. A With utasítás segítségével kijelölhetjük azt a rekordot, amelyen dolgozni szeretnénk. Ezek után a rekord mezõire egyszerûen a mezõk azonosítójával tudunk hivatkozni. Példa: . With E do Begin Nev:= Karoly; Eletkor:= 23; End; . E.Eletkor:= 24; . Bonyolult felépítésû, egymásba ágyazott rekordok esetén a With utasítás nagyon hasznos lehet, a program forrását áttekinthetõbbé, érthetõbbé teszi. A rekordok deklarálhatók típusos konstansként
is. Ebben az esetben már a deklaráció során megadhatjuk az egyes elemek kezdõértékét. Rekordok esetén a típusos konstans deklarálása elõtt a rekordtípust külön deklarálni kell, ami a tömb esetében nem volt szükséges. Az alábbi deklaráció hibát eredményez: Const R: Record Nev: String= Karoly; Eletkor: Byte= 23; End; A megfelelõ forma: Type Rek= Record Nev: String; Eletkor: Byte; End; Const R: Rek= (Nev: Karoly; Eletkor: 23); A tömbökhöz hasonlóan a rekordok sem deklarálhatók típus nélküli konstansként. A rekordok memóriában foglalt méretét a mezõk méretének összeadásával számíthatjuk ki. A halmaz típus: A Pascal nyelv egyedülálló lehetõsége a halmazok kezeléséhez a halmaz típus, és a hozzá kapcsolódó mûveletek. A halmazokra azonban vonatkoznak bizonyos megkötések: a halmaz elemeinek maximális száma 256 lehet, a halmaz elemei csak sorszámozott típusok lehetnek. Deklarációja: Var Halmaz: Set of Alaptípus; Pld:
Var Betuk: Set of a.z, AZ; Lotto: Set of 1.99; ASCII: Set of Char; Az alaptípus - mint fentebb írtam - csak sorszámozott típus lehet, de nem kizárólag a standard típusok közül kerülhet ki, lehet a felhasználó által definiált (pld felsorolt, vagy résztartomány) típus is. Ilyen módon nagyon sok feladat megoldására alkalmasak lehetnek a halmazok akár változó, akár konstans formában. A változóként deklarált halmazok üres halmazként jönnek létre, a program során a felhasználónak (programozónak) kell meghatározni a halmaz elemeit. A halmaz elemeit értékadó utasítással lehet a halmazhoz rendelni, a halmaz elemeit (illetve az intervallumot) kapcsos zárójelek között kell felsorolni. Példa: Betuk:= [a, b, c, d, e]; Lotto:= [7.19]; ASCII:= [#1.#13, #32, az, 09, AZ, #185, #186, #192]; A halmaz elemeit megadhatjuk felsorolással, résztartomány konstansként, vagy akár a kettõt kombinálva is. Felsorolásnál az elemek közé vesszõt kell tenni A
halmazokat deklarálhatjuk típusos konstansként is. Ebben az esetben a halmaz elemeit azonnal meghatározhatjuk. Példa: Const Egyjegyu: Set of byte= [0.9]; A halmazokon végzett mûveletek közül a leggyakoribb a tartalomvizsgálat, azaz megvizsgáljuk, hogy adott érték eleme-e a halmaznak. Pld: If A in Szamok then . else . A tartalomvizsgálat során a vizsgált elem típusának meg kell egyeznie a halmaz alaptípusával. Az eredmény logikai típusú lesz. Vizsgálhatjuk két halmaz egyenlõségét is, tehát azt, hogy a halmazok elemei azonosak-e: Var Szamok1, Szamok2: Set of byte; Begin . If Szamok1=Szamok2 then . else . . End. Az eredmény logikai típusú lesz. Az elõbbi mûvelethez hasonlóan vizsgálhatjuk a halmazok különbözõségét (nem különbségét): If Szamok1<Szamok2 then . else . Az eredmény logikai típusú lesz. A halmazok unióját (egyesítését) a Pascal nyelv a következõképpen jelöli: Szamok3:= Szamok1+Szamok2; Az eredmény halmaz típusú
lesz. A halmazok metszetét is képezhetjük: Szamok3:= Szamok1*Szamok2; Az eredmény halmaz típusú lesz. Megvizsgálhatjuk a halmazok különbségét is. A halmazok különbsége a példában: Az eredményhalmaz (Szamok3) minden olyan elemet tartalmazni fog, amely Szamok1-nek eleme, de Szamok2-nek nem eleme. Szamok3:= Szamok1-Szamok2; Az eredmény halmaz típusú lesz. A halmazok memóriában történõ tárolása minden eddigi típustól eltérõen történik. A halmaz elemeinek maximális száma 256 lehet. Minden elemet 1-1 bit-en tárolunk, hiszen mindössze annyit kell eldönteni, hogy eleme-e a halmaznak. Amennyiben az érték eleme a halmaznak, a hozzá tartozó bit értéke 1, egyébként 0. Tehát a legnagyobb halmaz mérete a memóriában 256 bit, azaz 32 byte. (Példa: H1: Set of Byte) Ha a halmaz elemeinek lehetséges számát csökkentjük (például az alaptípus megváltoztatásával), akkor a halmaz memóriabeli mérete ennek megfelelõen alakul, tehát minden
lehetséges elemhez 1 bit tartozik. Tehát a H2: Set of 09; deklarációval létrehozott halmaz lehetséges elemszáma 10 (0 és 9 közé esõ számjegyek), ezért a memóriában számára 10 bit kerül lefoglalásra. Objektum típus: A Turbo Pascal nyelvet az 5.5 verziótól kezdõdõen felkészítették az objektum-orientált programozási technika megvalósítására. Az objektumok leginkább a rekordokhoz hasonlítanak, de fontos különbség, hogy az adatok tárolása mellett az adatokat kezelõ eljárásokat, függvényeket is tartalmazzák. Fontos megjegyezni, hogy a Turbo Pascal nem objektum-orientált programozási nyelv, de ezt a lehetõséget is támogatja. (ún vegyes nyelv) 5. tétel: Mûveletek, kifejezések kiértékelése, precedencia szabály. Az értékadó utasítás Standard beviteli és kiviteli eljárások. Mûveletek, kifejezések, precedencia szabályok: A programozási nyelvekben a mûveleteket (matematikai, logikai, stringmûvelet, stb.) kifejezésekként
írjuk le. A kifejezések operandusokból és operátorokból épülnek fel Az operandusok határozzák meg a mûveletben szerplõ értékeket, az operátor(ok) pedig azt, hogy milyen mûveletet kell végrehajtani az operandusokon. A kifejezések tartalmazhatnak függvényhívásokat is, amelyek általában operandusként viselkednek, de befolyásolják a mûveletek végrehajtási sorrendjét. A kifejezéseket két tulajdonsággal szokták jellemezni: érték és típus. Minden kifejezésnek van értéke, melyet a kifejezés kiértékelése (kiszámítása) után tudunk megállapítani. A kifejezés típusa határozza meg, hogy a kifejezés értéke milyen típusú adat lesz, ennek meghatározásához a kifejezés értékét nem szükséges meghatározni. A kifejezés típusát a kifejezésben szereplõ operátorok és operandusok együtt határozzák meg. A kifejezés kiértékelése a kifejezésben szereplõ összes mûvelet elvégzését jelenti. A Pascal operátorokat két
csoportra lehet osztani: - egyoperandusú operátorok - kétoperandusú operátorok Az egyoperandusú mûveletek esetén mindig az operátor áll elõl, ezt követi az operandus. A kétoperandusú mûveletek esetén az operátor elõtt és után egyaránt áll egy-egy operandus. Az egyoperandusú operátorokat unary, a két operandusú operátorokat binary operátornak is nevezik. A legegyszerûbb kifejezések egyetlen operátorból és egy vagy két operandusból épülnek fel (operátortól függõen). Ezen mûveletek kiértékelése során semmilyen probléma nem merül fel, a kifejezés kiértékelése balról jobbra haladva történik. Ezeket a kifejezéseket egyszerû kifejezésnek nevezzük. Az összetett kifejezések azonban több operátort és operandust is tartalmazhatnak, azaz a kifejezés kiértékelése során több mûveletet is végre kell hajtani. A kérdés, hogy a mûveleteket milyen sorrendben hajtsuk végre. A mûveletek végrehajtásának sorrendjét a precedencia
szabály határozza meg. A precedencia szabály tehát az összetett kifejezések kiértékelése során maghatározza a mûveletek elvégzésének sorrendjét. A Pascal nyelv a kifejezések kiértékelése során a következõ sorrendet követi: 1. zárójelek 2. függvényhívások 3. egyoperandusú operátorok (- + /elõjelek/ @ not ^) 4. multiplikatív operátorok (* / div mod and shl. shr) 5. additív operátorok (+ - or xor) 6. relációk (< <= = < =) 7. balról jobbra haladás szabálya A fenti szabályok tehát a Pascal nyelv precedencia szabályai. A precedencia szabályokat csak akkor lehet alkalmazni, ha egy összetett kifejezésben szereplõ mûveletek különbözõ precedenciával rendelkeznek. A precedencia szabályokon kívül fontos még figyelembe venni, hogy a kfejezések kiértékelése - amennyiben azonos precedenciával rendelkezõ mûveletekbõl épül fel - balról jobbra haladva történik. Tehát a precedencia szabályokat három megjegyzéssel
kell kiegészíteni: • A zárójelek használatával megváltoztathatjuk a mûveletek kiértékelésének sorrendjét. (Egyes vélemények szerint a zárójelek nem tartoznak a precedencia szabályok közé.) • Ha nem alkalmazunk zárójeleket, akkor elõször a precedencia szabály alapján próbálja meg programunk meghatározni a kifejezés végrehajtási sorrendjét. • Ha a kifejezésben azonos precedenciájú mûveletek szerepelnek, akkor a mûveletek a felírásuk sorrendjében, azaz balról jobbra haladva értékelõdnek ki. Példákon keresztül: Zárójelekkel megváltoztatott mûveleti sorrend: (3+5)*9=72 Különbözõ precedenciájú mûveletek kiértékelése: 3+5*9=48 (mivel 59=45 és 45+3=48) A balról jobbra haladás szabálya alapján: 3+8-2=9 A Pascal nyelv mûveleteit (operátorait) a következõképpen csoportosíthatjuk: Aritmetikai mûveletek: + pozitív elõjel +a negatív elõjel -a + összeadás a+b kivonás a-b * szorzás a*b / osztás a/b mod
maradék a mod b div egészosztás a div b Logikai mûveletek: not logikai tagadás not a and logikai és a and b or logikai vagy a or b xor logikai kizáró vagy a xor b Bitenkénti logikai mûveletek: not tagadás bitenként and bitenkénti és or bitenkénti vagy xor bitenkénti kizáró vagy shl biteltolás balra shr biteltolás jobbra not a a and b a or b a xor b a shl b a shr b Összehasonlító (relációs) mûveletek: = egyenlõ a=b < nem egyenlõ a<b < kisebb a<b nagyobb ab <= kisebb vagy egyenlõ a<=b = nagyobb vagy egyenlõ a=b Halmazmûveletek: * halmazok metszete + halmazok uniója halmazok különbsége = halmazok azonossága < halmazok különbözõsége <= részhalmaz (a bal oldali halmaz részhalmaza a jobb oldalinak) = részhalmaz (a jobb oldali halmaz részhalmaza a bal oldalinak) in tartalmazásvizsgálat Az operátorok és operandusok együtt határozzák meg a kifejezés típusát. Az operandusok típusa többféle lehet, de van
néhány olyan operátor, amely eleve kizárja (a mûvelet jellege miatt) hogy tetszõleges típusú operandusokat használjunk, ilyen pld. a mod és a div mûvelet, amely csak egész számok esetén van értelmezve. Az aritmetikai mûveletek többségénél az operandus(ok) típusa határozza meg az eredmény típusát. Kivétel az osztás, ahol minden esetben valós típusú eredményt kapunk Az egészosztás és a maradékképzés csak egész számok esetén értelmezett, az eredmény is mindig egész típusú lesz. A logikai mûveletek eredménye minden esetben logikai típusú lesz, mivel az operátorok csak logikai típusúak lehetnek. A bitenként végzett logikai mûveletek segítségével az egész típusú változók bitjeihez férhetünk hozzá közvetlenül, beállíthatjuk vagy törölhetjük azokat. A mûveletek bármely egész típus esetében végrehajthatóak. Az összehasonlító mûveletek segítségével két kifejezés vagy változó értékét hasonlíthatjuk
össze, az eredmény logikai típusú lesz. A halmazmûveletek esetén az operátorok minden esetben kétoperandusúak, és egyetlen kivétellel mindig két halmaz áll az operátor két oldalán. Ez a kivétel a tartalmazásvizsgálat, amikor a bal oldalon egy a halmaz alaptípusával egyezõ típusú értéknek (változó vagy konstans) kell szerepelnie. Az értékadó utasítás: Ahhoz, hogy programunkban késõbb felhasználhassuk a kifejezés kiértékelése után kapott eredményt, a kifejezés értékét meg kell õriznünk egy változóban (típusos konstansban). A kifejezések értékét megõrizhetjük az értékadó utasítás segítségével. A programban használt változók, típusos konstansok által tárolt értéket az értékadó utasítás segítségével változtathatjuk meg. Az értékadó utasítás minden programozási nyelv alapvetõ eleme. A Pascal nyelvben a második legegyszerûbb utasításként tartják számon - mivel a legegyszerûbb utasítás az üres
utasítás. Az értékadó utasítás egyik oldalán egy változó (típusos konstans) azonosítója, a másik oldalán egy kifejezés vagy változó áll. Az értékadó utasítás végrehajtása után a bal oldalon álló változó felveszi a jobb oldalon álló kifejezés vagy változó értékét. Az értékadó utasítás során gyakran használunk konstans kifejezést. Pld: A:= 5; A:= C*B; Az értékadó utasítás során nagyon fontos, hogy az utasítás két oldalán álló változó és kifejezés típusa azonos, de legalább komaptíbilis legyen. Amennyiben a két oldal típusa eltérõ, a fordítás során hibaüzenetet kapunk. A típuskompatibilitás kicsit enyhébb megkötés a típusazonosságnál A típuskompatibilitás esetei: • A típusok azonosak (pld: Integer - Integer) • Az egyik típus résztartománya a másiknak (pld: Integer - Byte) • Mindkét típus ugyanannak a típusnak a résztartománya (felhasználó által létrehozott típusok esetén)
• Mindkét típus halmaz, kompatibilis alaptípussal • Mindkét típus karaktertömb, elemszámuk azonos (pld String[40] - String[40]) • Az egyik típus String, a másik karakter (String - Char) Standard beviteli és kiviteli eljárások: A standard perifériák a felhasználóval való kapcsolattartás alapvetõ eszközei. A Pascal nyelv szabványos eszközök által támogatja ezen perifériák kezelését, tehát egy-egy beépített eljárás segítségével tudunk a billentyûzetrõl beolvasni, illetve a képernyõre írni egy vagy több adatot a programunkból. Az alapértemezett perifériákat kezelõ eljárásokat a System unit tartalmazza, tehát minden különösebb elõkészület nélkül használhatjuk õket bármely Pascal programban. Írás a képernyõre: A képernyõre íráshoz két eljárást alkalmazhatunk: Write(paraméterek); Writeln(paraméterek); A két eljárás nagyon hasonlóan mûködik, de egy lényeges különbséget megfigyelhetünk: A Write
eljárás miután a paraméterlistában felsorolt adatokat kiírta a képernyõre, a kurzort a kiírás utáni pozícióban hagyja. A Writeln utasítás ezzel szemben a kiírás után a kurzort a következõ sor elejére állítja, azaz soremelést végez. Mindkét eljárás a kurzor aktuális pozíciójában írja ki a paraméterként megadott adatokat. Az eljárások paraméterei sokfélék lehetnek. Mivel standard eljárásokról van szó, úgy alakították ki õket, hogy lehetõség szerint a legtöbb eredményt, kimeneti információt kezelni tudják, tehát ezek az eljárások nemcsak szövegek, hanem számok, változók, kifejezések eredményének kiíratására is alkalmasak (szemben a grafikus felület hasonló nem standard eljárásaival, amelyek csak szövegek megjelenítésére alkalmasak). A Write és Writeln eljárások paraméterlistájában több paramétert is felsorolhatunk, egymástól vesszõvel elválasztva, a paraméterek típusa különbözõ lehet. A
paraméterlistában szerepelhet: - Aposztrófok között megadott szöveg (szövegkonstans) - Változók, konstansok azonosítója - Aritmetikai kifejezések - Logikai kifejezések - Függvényhívások A szövegek kiírása során nincs semmilyen probléma, értelemszerûen a megadott szöveg kiírásra kerül, legyen szó akár szöveges konstansról, akár String vagy Char típusú változóról. Numerikus értékek esetén az egészek megjelenítése nem okoz problémát. A lebegõpontos (bocs, valós) értékek esetén az alapértelmezés szerinti formátum a lebegõpontos forma (azaz a normál alak). Ez a felhasználó számára elég nehezen emészthetõ, ezért a Pascal lehetõséget biztosít a lebegõpontos számok formázott (tizedes alakban történõ) megjelenítésére. Ebben az esetben a megfelelõ változó, konstans, kifejezés azonosítója után kettõsponttal elválasztva meg kell adni az összes számjegy számát (mezõszélesség, tizedes és egészrész
együtt), majd újabb kettõspont után a tizedesjegyek számát. A mezõszélességen belül a numerikus értékek jobbra vannak igazítása A logikai kifejezések megjelenítése során a Pascal a logikai kifejezés értékétõl függõen a TRUE vagy FALSE szöveget jeleníti meg a képernyõn. Egy érdekesség, hogy a mezõszélességet szöveges adatok esetén is alakmazhatjuk. A szöveges adatok a mezõszélességen belül jobbra vannak igazítva. Egy példa a fentebb leírtakra: Olvassunk be egy szöget fokban, majd írjuk ki táblázatos formában a szög szinuszát, koszniuszát, tangensét és kotangensét. Var Alfa, Rad: Real; Begin {Olvassunk be egy szoget} Write(Kerek egy szoget: ); Readln(Alfa); {Alakitsuk at radianba, mert a szogfuggvenyek} {a megadott szogeket radianban ertelmezik} Rad:= Alfa/180*2Pi; {Irjuk ki a szogfuggvenyek erteket tablazatos} {formaban. Hagyjunk ki elotte ket ures sort } Writeln; Writeln; Writeln(Alfa:5:2, sinusa::15, sin(Rad):8:2);
Writeln(Alfa:5:2, koszinusza::15, cos(Rad):8:2); Writeln(Alfa:5:2, tangense::15, sin(Rad)/cos(Rad):8:2); Writeln(Alfa:5:2, kotangense::15, cos(Rad)/sin(Rad):8:2); Writeln; End. A Write és Writeln eljárások összetett típusú változók, konstansok esetén nem alkalmazhatók! Olvasás a billentyûzetrõl: A Turbo Pascal-ban a szabványos bemeneti periféria a billentyûzet, innen lehet a változók értékét beolvasni. A billentyûzetrõl való olvasáshoz szintén két eljárást alkalmazhatunk: Read(paraméterek); Readln(paraméterek); Mindkét eljárás felfüggeszti a program futását, és megvárja, amíg a felhasználó begépeli a kívánt értéket. A Read és Readln eljárások csak numerikus és karakteres adatok beolvasására alkalmasak, az összetett és logikai változók értékét nem lehet billentyûzetrõl beolvasni. A két eljárás mûködése között ebben az esetben is megfigyelhetõ a különbség. A Pascal programok a változók beolvasását két
lépésben végzik el. Elsõ lépésben a billentyûzeten begépelt adatok egy pufferbe kerülnek, majd az adatbevitel végén (amikor a felhasználó leüti az Enter billentyût) a puffer tartalma másolódik át a megadott változókba. A Pascal semmilyen ellenõrzést nem végez arra vonatkozóan, hogy a pufferbe milyen és mennyi adat kerül. (A puffer maximum 127 byte adatot képes tárolni, stringek esetén fontos lehet!) A Read eljárás a pufferbõl kiolvassa a számára szükséges adatokat, és ezeket törli is a pufferbõl. Ha a felhasználó a szükségesnél több adatot adott meg, a fel nem használt adatok a pufferben maradnak, és a következõ Read utasításnál kerülnek felhasználásra. Amikor tehát a Read eljárást alkalmazzuk, akkor az elõször ellenõrzi a puffer tartalmát, és csak akkor vár a billentyûzeten történõ begépelésre, ha a puffer üres. (Számtalan hiba forrása lehet!) A Readln eljárás is a pufferbõl olvassa ki a szükséges adatokat,
és ezeket törli is. Ha azonban a pufferben felesleges adat is található, az eljárás a saját adatainak kiolvasása után ezeket is kitörli, azaz kiüríti a puffert az olvasás után. Nagyon fontos, hogy sem a Read, sem a Readln eljárás nem végez semmilyen ellenõrzést a pufferben lévõ adatokon, azokat kritikátlanul elfogadja. Ha a felhasználó hibásan adta meg a szükséges adatokat a program mûködésének zavarát okozhatja (szám helyett szöveg megadása), tehát a program írása során nagyon figyelni kell a beolvasott adatok helyességének ellenõrzésére. 6. tétel: A Turbo Pascal ciklusszervezõ utasításai: A ciklusokról általában: A programok írása során gyakran felmerülõ probléma, hogy egy utasítást egymás után többször meg kellene ismételni. Az ilyen ismétlõdõ tevékenységek programnyelven történõ megfogalmazását segítik a ciklusszervezõ utasítások. Általában minden magasszintû programozási nyelvben megtalálhatók
ezek az utasítások. Az alacsonyszintû programozási nyelveknél (assembbly, gépi kód) egy feltételes utasítás és egy ugró utasítás kombinációjával lehet az ilyen jellegû feladatokat megoldani. A Pascal nyelv háromféle ciklusszervezõ utasítással segíti a programozást: - For ciklus: akkor célszerû alkalmazni, amikor elõre ismerjük a tevékenység (uatítások) ismétlésének számát - While . do ciklus: akkor célszerû alkalmazni, amikor nem tudjuk elõre, hányszor kell a tevékenységet megismételni - Repeat . Until ciklus: a While do ciklushoz hasonlóan nem szükséges elõre ismerni az ismétlések számát, mégis különbséget kell tennünk a két ciklus között, a késõbbiek látni fogjuk, mi a különbség közöttük, mikor melyiket célszerû alkalmazni. A ciklusok felépítése, csoportosítása: A ciklusok két részbõl épülnek fel: - Az ismétlések számát meghatározó ciklusfej (amely általában a ciklusutasítást, vagy annak egy
részét is tartalmazza) - Az ismétlõdõ utasítás(oka)t tartalmazó ciklusmag, amely az ismétlõdõ tevékenység leírását tartalmazza A ciklusfejben található az ún. ciklusfeltétel, amely meghatározza, hogy a ciklus ismétlése milyen feltétel teljesülése esetén fejezõdik be. A ciklusszervezõ utasításokat általában a ciklusfeltétel kiértékelésének helye alapján szoktuk csoportosítani: - Elöltesztelõ ciklus: A ciklusfeltétel kiértékelése a ciklusmag lefutása elõtt történik. Ilyen a For és a While . do ciklus - Hátultesztelõ ciklus: A ciklusfeltétel kiértékelése a ciklusmag után történik. Ilyen a Repeat Until ciklus. Pontos definíció, általános szabály nem létezik arra, hogy mikor melyik ciklust érdemes alkalmazni, ezt mindig a probléma jellege dönti el, mégis néhány szempontot érdemes figyelembe venni: - Ha elõre tudjuk, hogy a ciklusmagot hányszor kell végrehajtani, célszerû a For ciklust alkalmazni. (Példa: Az
elsõ N db természetes szám összegének meghatározása) - Ha a ciklusmagot legalább egyszer végre kell hajtani, célszerû a Repeat . Until ciklust választani. (Példa: Olvassunk be természetes számokat 0 végjelig) - Ha azt szeretnénk, hogy a ciklusmag csak egy feltétel teljesülése esetén fusson le, illetve ha ez a feltétel nem teljesül, akkor a ciklusmagot egyszer sem szeretnénk végrehajtani, célszerû a While . do ciklust alkalmazni (Példa: Jelenítsük meg a képernyõn egy szöveges állomány tartalmát.) A For ciklus: A For ciklust akkor érdemes alkalmazni, amikor elõre meg tudjuk határozni az ismétlések számát. A For ciklus elõltesztelõ ciklus A For ciklust két formában alkalmazhatjuk: For ciklusváltozó:= kezdõérték to végérték do ciklusmag; illetve For ciklusváltozó:= kezdõérték downto végérték do ciklusmag; Az elsõ változat esetén a ciklusváltozó értéke növekedni fog (minden ismétléskor 1-gyel), amíg el nem éri a
végértéket. A második változatnál a ciklusváltozó értéke minden ismétléskor 1-gyel csökken, amíg el nem éri a végértéket. A do foglalt szó után következõ ciklusmag egy utaítást tartalmazhat, ha több utasítást szeretnénk a ciklusmagban elhelyezni, a begin . end; utasításokat kell alkalmaznunk. A ciklusmag ismétléseinek száma a következõképpen határozható meg: - növekvõ (to) ciklus esetén: ismétlésszám=végérték-kezdõérték - csökkenõ (downto) ciklus esetén: ismétlésszám=kezdõérték-végérték A For ciklusmagjának utasításai csak akkor kerülnek végrehajtásra, ha: - növekvõ (to) ciklus esetében teljesül a kezdõérték<=végérték - csökkenõ (downto) ciklus esetében teljesül a kezdõérték=végérték feltétel. Egyébként a ciklusmagban elhelyezett utasítás(ok) egyszer sem kerülnek végrehajtásra. A For ciklus esetében a ciklusváltozó értékének változtatása automatikusan történik, a ciklusmag
minden egyes végrehajtása után növekszik vagy csökken az értéke. Ezért a ciklusmagon belül nem "illik" a ciklusváltozó értékét megváltoztatni, mert ez a ciklus mûködését kiszámíthatatlanná teszi. Az elméleti lehetõsége azonban adott, a ciklusváltozó értéke a ciklusmagon belül megváltoztatható, szintaktikai hibát nem okoz (legrosszabb esetben végtelen ciklust hozunk létre). A For ciklus ciklusváltozója - az automatikus léptetés miatt - csak sorszámozott tipus lehet, más tipusok esetén 97-es kódú hibát kapunk a program fordítása során, a fordító jelzi, hogy ilyen tipusú változó a For ciklusnál nem használható. A For ciklust alkalmazzuk általában a tömbök kezelése során, összegzéskor, stb. Példák a For ciklus alkalmazására: Számítsuk ki az elsõ N db természetes szám összegét! Var N: integer; Sum: longint; Begin Write(N= ); Readln(N); For N:=0 to N do Sum:= Sum+N; Writeln(Az elso , N, termeszetes szam
osszege: , Sum); End. Töltsünk fel egy 10 elemû tömböt [1.90] közé esõ véletlenszámokkal! Var Tomb: Array [1.10] of integer; I: byte; Begin Randomize; For I:= 1 to 10 do Begin Tomb[I]:= Random(90)+1; Writeln(A tomb , I, . eleme: , Tomb[I]; End; End. Mivel a For ciklus magjában több utasítást is elhelyeztünk, szükséges volt azokat a Begin . End; közé elhelyezni. A Randomize utasítás a véletlenszámok generálását teszi lehetõvé, a Random függvény pedig a véletlenszámokat állítja elõ. A Random(MAX) függvény egy [0MAX-1] intervallumba esõ egész számot ad vissza, ezért kell 1-et hozzáadni. A While . do ciklus: A While . do ciklus a For ciklushoz hasonlóan elõltesztelõ ciklus, de az elõbbivel szemben nem kötelezõ a ciklusba lépéskor meghatározni a ciklus iterációinak (ismétlésének) számát. A While . do ciklus szintaktikája: While ciklusfeltétel do utasítás; A ciklusmag addig ismétlõdik, amíg a ciklusfeltétel igaz. A For
ciklushoz hasonlóan a ciklusmag itt is egy utasításból áll, ha több utasítást szeretnénk a ciklusmagban szerepeltetni, akkor a Begin . End; foglalt szavak között kell õket elhelyezni A While do ciklus esetében - szemben a For ciklussal, amely ilyen értelemben rendhagyó - a ciklusfeltétel értékét nekünk kell változtatni a ciklusmagon belül! Amennyiben ezt nem tesszük meg, és a ciklusfeltétel igaz értéke a ciklusmagon belül nem változik meg, akkor végtelen ciklust kapunk. Nagyon gyakori hibalehetõség! A While . do ciklus ciklusmagjának utasításai csak akkor kerülnek végrehajtásra, ha a ciklusba lépéskor a ciklusfeltétel igaz. Amennyiben a ciklusfeltétel hamis, a ciklusmag végrehajtása egyszer sem történik meg. A While . do ciklus ciklusfeltétele egy logikai kifejezés vagy változó (nem véletlenül írtam ebben a sorrendben az elsõt sokkal gyakrabban használjuk). Ha más tipust szeretnénk alkalmazni ciklusfeltételként a fordító
40-es hibaüzenetet küld, amely azt jelzi, hogy a fordító logikai tipusú kifejezést vagy változót vár az adott helyen. A While . do ciklust általában filekezelésnél használjuk, mert ha egy üres állományból olvasunk, az EOF függvény tesztelésével már a ciklus elején kiszûrhetjük ezt az információt, a program nem fut feleslegesen (programtól függõen minimum 5-6 utasítás végrehajtásának idejét megspórolhatjuk). Példák a While . do ciklus alkalmazására: Olvassunk be egy számot, döntsük el, prímszám-e: Var A: Integer; O: Integer; Prim: Boolean; Begin Write(Kerek egy egesz szamot: ); Readln(A); Prim:= True; O:= 2; While (O<Sqrt(A)) do Begin if (A mod O)=0 then Prim:= False; O:= O+1; End; If Prim then Writeln(Primszam) else Writeln(Nem primszam); End. A szám beolvasása után egy ciklust futtatunk, amelyben vizsgáljuk, hogy a szám osztható-e maradék nélkül valamely 1-tõl és önmagától különbözõ számmal (ezért O:= 2, a
ciklusváltozó indulóértéke). A ciklus akkor ér véget, ha elérjük a szám négyzetgyökét (O<Sqrt(A)) A ciklusba lépés elõtt a számról feltételezzük, hogy prim, ezért az ún. jelzõváltozó (Prim) értékét igazra állítjuk. Ha a ciklus futása során találunk olyan számot, amellyel az A szám maradék nélkül osztható, a jelzõváltozó értékét hamisra állítjuk, mert mostmár tudjuk, hogy a szám nem prímszám. Ha nem találunk ilyen számot, a jelzõváltozó értéke a ciklus végéig nem változik A ciklusból kilépve a Prim változó értéke alapján tudjuk eldönteni, hogy elõfordult-e olyan szám, amely valódi osztója volt a beolvasott számnak. Írjuk ki a képernyõre az AUTOEXEC.BAT állomány tartalmát: Var F: Text S: String; Begin {Az állomány megnyitása} Assign(F, C:AUTOEXEC.BAT); Reset(F); {Olvasás az állományból, amíg nincs vége a file-nak} {Minden olvasott sort azonnal kiírunk a képernyõre} While (not Eof(F)) do Begin
Readln(F, S); Writeln(S); End; {Az állomány lezárása} Close(F); End. A példban a While . do ciklus alkalmazása volt a legcélszerûbb, mert amennyiben az állomány üres, akkor a ciklusmagot figyelmen kívül hagyta volna a program. A For ciklust nem tudtuk volna alkalmazni, mert nem tudjuk elõre megmondani, hogy az állomány hány sort tartalmaz (nem tudtuk volna hol fejezzük be a ciklust!). A ciklusfeltétel: az Eof függvény akkor ad vissza igaz értéket, ha a file végén állunk (bõvebben lásd a filekezelésnél), ezt a kifejezést nyilván "meg kell fordítanunk", negálnunk kell, ezt a not operátor segítségével tettük meg. A Repeat . Until ciklus: A Repeat . Until ciklus a Pascal nyelv egyetlen hátultesztelõ ciklusa A While do ciklushoz hasonlóan nem kötelezõ elõre meghatározni az iterációk számát. A Repeat Until ciklus szintaktikája: Repeat utasítás 1; utasítás 2; . . . utasítás n; Until ciklusfeltétel; A ciklusmag addig
ismétlõdik, amíg a ciklusfeltétel hamis. Az eddigi ciklusutasításokhoz képest eltérés, hogy a ciklusmag több utasítást is tartalmazhat, a Begin . End; utasítászárójelek használata lehetséges, de nem kötelezõ. A ciklusfeltétel értékét ennél a ciklustipusnál is nekünk kell változtatni, nincs automatikus léptetés. A ciklusfeltétel változtatása itt is a ciklusmagon belül kell történjen, elmulasztása hasonó hibákat okoz, mint a While . do ciklus esetében A Repeat . Until ciklus ciklusmagja minden esetben végrehajtásra kerül legalább egyszer, akkor is, ha a ciklusfeltétel a ciklusba lépéskor már igaz (ebben az esetben azonban a ciklusmag megismétlésére nem kerül sor). A Repeat . Until ciklus ciklusfeltétele egy logikai kifejezés vagy változó (hasonlóan a While do ciklushoz, itt is gyakrabban fordul elõ a kifejezés). Ha más tipust szeretnénk alkalmazni ciklusfeltételként a fordító 40-es hibaüzenetet küld, amely azt jelzi,
hogy a fordító logikai tipusú kifejezést vagy változót vár az adott helyen. A Repeat . Until ciklust általában akkor alkalmazzuk, ha több adat beolvasására kerül sor, és az adatsor végét végjel határozza meg, ha a program végén szeretnénk várni egy billentyû leütéséig kilépés elõtt, vagy ha valamilyen több lépésbõl álló tevékenységet szeretnénk egymás után többsször végrehajtani (lásd a Pascal feladatoknál a grafikus példák 8. feladatát!) Példák a Repeat . Until ciklus alkalmazására: Olvassunk be egész sztámokat 0 végjelig, határozzuk meg a számok átlagát. Var X, N: Integer; Sum: Longint; Begin Writeln(Egesz szamokat olvasunk be 0 vegjelig.); N:= 0; Sum:= 0; Repeat Write(X= ); Readln(X); Sum:= Sum+X; N:= N+1; Until X=0; Writeln(A beolvasott szamok atlaga: , Sum/(N-1):10:2); End. Az egész számokat nem szükséges külön-külön tárolnunk, a feladat szempontjából erre nincs szükség, ezért nem alkalmazunk tömböt. A
számokat az X nevû változóba fogjuk beolvasni Mielõtt a ciklust elkezdenénk futtatni, a ciklusban felhasznált változók értékeit beállítjuk (N:= 0; - eddig egyetlen számot sem olvastunk be, Sum:= 0; - a beolvasott számok összege 0). A ciklusban számoljuk a beolvasott számokat (N), és a számok összegét is (Sum). A ciklus akkor ér véget, amikor 0 kerül beolvasásra. Az átlag számításánál a számok számából el kell venni 1-et, mert a ciklus a 0 beolvasásakor is növelte N értékét, a végjelet pedig általában nem szoktuk a számításnál figyelembe venni. 7. tétel: Szelekciós utasítások: Egyszerûbb programok esetében a program felépítése általában úgy alakul, hogy elegendõ a programsorok egymás utáni végrehajtása, nem szükséges döntési szerkezetet alkalmaznunk, legtöbbször a ciklusokra sincs szükségünk. Egy egyszerû program lehet a következõ: Számítsuk ki egy A oldalhosszúságú négyzet kerületét és
területét: Var A: Integer; Begin Writeln(A negyzet oldala: ); Readln(A); Writeln(A negyzet kerulete: , 4*A); Writeln(A negyzet terulete: , A*A); End. A program nagyon szepen mûködik, amíg a felhasználó helyes adatokat ad meg a négyzet oldalaként, de amint nem megfelelõ adattal próbál dolgozni (pld.: A négyzet oldala: -4), a program nem az elvárható módon fog reagálni (pld.: helytelen eredményt ad, futási hibával leáll, stb). Valamilyen módon ezeket a hibákat ki kellene küszöbölni, méghozzá feltételes szerkezetek, ellenõrzések beiktatásával, amelyek képesek lekezelni a felhasználó által okozott problémákat, mert a programnak nem lenne kötelezõ elszállni, ha egy szám helyett egy betût gépelünk be. A fentiekbõl látható, hogy minden magasszintû programozási nyelvnek részét képezik a feltételes szerkezetek, a szelekciós utasítások. A Pascal nyelv kétféle feltételes utasítást tartalmaz: az If . then else ; utasítás
kétirányú elágaztatást biztosít egy feltétel teljesülésétõl függõen, a Case . of utasítás többirányú elágazást (ún esetszétválasztást) tesz lehetõvé Az If . then else; utasítás: Az utasítás kétirányú elágazást tesz lehetõvé, egy feltételtõl függõen a program végrehajtása két irányban folytatódhat, a feltételtõl függõen bizonyos tevékenységeket végrehajthatunk, vagy kihagyhatunk programunk során. Az utasítás szintaktikája: If feltétel then utasítás1 else utasítás2; A feltétel határozza meg, hogy a program végrehajtása "melyik irányban halad tovább": ha a feltétel igaz, akkor az utasítás1, ha hamis az utasítás2 fog lefutni. Minden esetben érvényes, hogy csak az egyik ágon található utasítások kerülnek végrehajtásra, tehát ha a feltétel igaz volt, csak az utasítás1 kerül feldolgozásra, utasítás2-t a fordító figyelmen kívül hagyja. A megfelelõ utasítások végrehajtása után a
program a feltételes szerkezet után következõ elsõ utasításnál folytatódik. Az If utasítás feltétele lehet egy logikai kifejezés vagy egy logikai tipusú változó is, felváltva alkalmazzuk mindkettõt (a ciklusoktól eltérõen, ahol egyértelmûen gyakoribb a kifejezés). Ha az If utasítás valamelyik ágában több utasítást szeretnénk elhelyezni, akkor ezt a Begin . End; foglalt szavak segítségével oldhatjuk meg Fontos, hogy az igaz (then) ág lezárása után nem szabad pontosvesszõt tenni, amennyiben azt még egy hamis (else) ág is követi (fordítási hibát okoz). Az If utasítás másik gyakran alkalmazott formája, amikor a hamis (else) ágat elhagyjuk. Ebben az esetben az utasítás szintaktikája: If feltétel then utasítás1; Ennél a formánál amennyiben a feltétel teljesül, akkor az utasítás1 végrehajtásra kerül, egyébként az utasítás1-et figyelmen kívül hagyva folytatódik a program végrehajtása a feltételes szerkezet után
következõ elsõ utasítással. Talán ritkábban alkalmazott formája az If utasításnak, amikor azt szeretnénk elérni, hogy az utasítás2 akkor kerüljön végrehajtásra, ha a feltétel nem teljesül. A megoldás egyszerû: meg kell fordítani a feltételt, célszerû a not operator segítségével: If not(feltétel) then utasítás2; Ebben az esetben az utasítás2 akkor fog végrehajtásra kerülni, amennyiben a megfordított feltétel teljesül, tehát amennyiben a feltétel (változó vagy kifejezés) hamis értéket vesz fel. A feltételes utasítások tetszõleges szintig egymásba ágyazhatók. Az egymásba ágyazásnak olyan feltételeknél van értelme, ahol a lehetõségek kizárják egymást. (pld: Három szám közül melyik a legkisebb.) Példák az If . then else ; utasítás alkalmazására: Olvassunk be egy számot, majd állapítsuk meg, hogy egész szám-e: Var A: real; Begin Write(Kerek egy szamot: ); Readln(A); If A=Int(A) then Writeln(Egesz szam) else
Writeln(Valos szam); End. Az A változót valós tipusúnak deklaráljuk, mivel valós tipusú változóban tudunk egész értéket tárolni, fordítva viszont nem. A feltétel: amennyiben az A változó értéke egyenlõ önmaga egészrészével (az Int(A) függvény egy valós szám egész részét adja vissza kerekítés nélkül), akkor egész számról van szó. Olvassunk be egy egész számot, amely 1 és 10 közé esik (egyenlõség megengedett), majd állapítsuk meg, hogy páros szám-e: Var A: Integer; Begin Write(Kerek egy egesz szamot 1 es 10 kozott: ); Readln(A); If ((A=1) and (A<=10)) then begin If (A mod 2)=0 then Writeln(Paros szam) else Writeln(Paratlan szam); end else Writeln(A szam nem esik bele a megadott intervallumba.); End. A szám beolvasása után elõször azt vizsgáljuk, hogy a szám 1 és 10 közé esik-e (az AND operátor a két feltétel együttes vizsgálata miatt szükséges). Amennyiben a feltétel igaz, akkor (a then ágban) újabb vizsgálat
következik, amely a szám oszthatóságára irányul. Ezalapján tudjuk eldönteni, hogy a szám páros vagy páratlan. Ha azonban a szám nem esik bele a megadott intervallumba, a hamis (else) ágban egyetlen utasítás szerepel, kiírjuk a képernyõre, hogy a beolvasott szám nem felel meg a feltételeknek. Olvassunk be két számot, majd osszuk el az elsõt a másodikkal! Figyeljünk a 0-val való osztásra! Var Osztando, Oszto: Integer; Begin Write(Osztando= ); Readln(Osztando); Write(Oszto= ); Readln(Oszto); If Oszto=0 then Writeln(0-val valo osztas nincs ertelmezve) else Writeln(A ket szam hanyadosa: , Osztando/Oszto:10:2); End. Amennyiben osztóként 0 értéket olvasunk be, az eredmény helyett egy hibaüzenetet kapunk. A Case . of utasítás: Az utasítás lehetõvé teszi, hogy a program végrehajtása egy változó értékétõl függõen több irányban folytatódjon, illetve, ha az egyik utasításban deklarált egyik feltétel sem teljesül, akkor is meghatározható,
hogy milyen utasítások fussanak le. Az utasítás szintaktikája: Case kifejezés of érték 1: utasítás 1; érték 2: utasítás 2; . . . érték n: utasítás n; else egyéb utasítás; End; A Case utasítás végrehajtása során elõször a kifejezés (változó) kerül kiértékelésre (általában változót használunk). A kifejezés értékétõl függõen a megfelelõ ág utasítása(i) kerülnek végrehajtásra, tehát ha a kifejezés értéke érték 1, akkor az utasítás 1, ha érték 2, akkor utasítás 2, stb. hajtódik végre Ha a listában nincs felsorolva a kifejezés aktuális értéke, akkor az egyéb utasítást fogja a program végrehajtani, amely az else ágban található. A kifejezés helyett sokszor egy változót szoktunk feltételként alkalmazni. A feltétel csak sorszámozott tipusú kifejezés vagy változó lehet. A feltételt szelektornak, a megadott értékeket esetkonstansoknak nevezzük. Az esetkonstansok és az utasítások közé
pontosvesszõt kell tenni (pld: érték 1: utasítás 1). Az esetkonstansok után egy utasítást adhatunk meg, ha több utasítást szeretnénk végrehajtani, azokat Begin . End; foglalt szavak közé kell írni Ha valamelyik esetkonstanshoz (az else ágra is igaz) tartozó utasítás végrehajtása megtörtént, a program a Case utasítást záró End; után folytatódik. Az esetkonstansok, mint nevük is mutatja, konstans értékek lehetnek, tehát nem használhatunk esetkonstansként változót vagy tipizált konstanst, mivel ezek értéke a program futása során változhat. Használhatunk viszont tipusnélküli konstansokat Ezek alkalmazása a programot szebbé teheti, nagyobb méretû programok esetén azonban áttekinthetetlenné teheti a forráskódot. Esetkonstansként gyakran kell alkalmaznunk intervallum értékeket. Ezeket a konstansokat a résztartomány tipusok deklarációja során alkalmazott módszerrel tehetjük meg. (Lásd a második példát.) Olvassuk be egy szám
formájában az osztályzatot, majd írjuk ki szövegesen. Var O: Byte; Begin Write(Az osztályzat (1.5): ); Readln(O); Case O of 1: Writeln(Elégtelen); 2: Writeln(Elégséges); 3: Writeln(Közepes); 4: Writeln(Jó); 5: Writeln(Jeles); else Writeln(Ilyen osztályzat nincs); End; End. Olvassunk be tíz pozitív egész számot. Számoljuk meg, hogy a beolvasott számok között hány egyjegyû, kétjegyû, háromjegyû, négyjegyû, ötjegyû szám szerepelt. Var X: Word; E J, K J, H J, N J, O J: Byte; N: Byte; Begin E J:= 0; K J:= 0; H J:= 0; N J:= 0; O J:= 0; For N:= 1 to 10 do Begin Write(A(z) , N, . szam= ); Readln(X); Case X of 0.9: E J:= E J+1; 10.99: K J:= K J+1; 100.999: H J:= H J+1; 1000.9999: N J:= N J+1; 10000.65535: O J:= O J+1; End; End; Writeln(A beolvasott szamok megoszlasa:); Writeln(Egyjegyu: , E J); Writeln(Ketjegyu: , K J); Writeln(Haromjegyu: , H J); Writeln(Negyjegyu: , N J); Writeln(Otjegyu: , O J); End. A feladat megoldásához egy ciklus és egy szelekciós
utasítás kombinációját kell alkalmaznunk. A számokat nem szükséges tömbben tárolni, mert mindössze a számjegyek számának eldöntéséig van szükségünk az értékre. A különbözõ nagyságredû számok elõfordulásának számát változókban tároljuk (E J - egyjegyû, K J - kétjegyû, H J - háromjegyû, N J négyjegyû, O J - ötjegyû). Ezen változók értékét a ciklusba lépés elõtt le kell nullázni Mivel tudjuk, hogy 10 számot kell beolvasnunk, célszerû a For ciklus alkalmazása. A ciklusmagon belül történik az érték beolvasása, majd kiértékelése. Megvizsgáljuk a lehetséges eseteket, és a megfelelõ változó értékét megnöveljük. Az else ág alkalmazása ebben az esetben felesleges, mert minden esetet megvizsgálunk (A hatjegyû szám begépelése már hibaellenõrzési kérdés. Egyes esetekben futási hibához, más esetekben hibás mûködés mellett tovább fut a program, de ezt más, itt nem tárgyalandó módszerekkel ki lehetne
szûrni). A ciklus futása a tizedik szám beolvasása után befejezõdik. Végül kiiratjuk hogyan oszlottak meg a beolvasott számok 8. tétel: Eljárások, függvények. Érték- és címszerinti paraméterátadás, lokális és globális változók. Rekurzivitás. A programok írása során gyakran találkozunk olyan feladatokkal, amelyek a programban többször megismétlõdnek, amelyeket többször végre kell hajtani. Az ilyen problémák esetén kétféle módszer közül választhatunk: Monolitikus programozás: A program egyetlen blokkból épül fel, ez a blokk tartalmaz minden utasítást, amelyet a program során végre kell hajtani. Ennél a módszernél a többször végrehajtásra kerülõ utasítassorozatot minden egyes alakalommal újra le kell írni. Hátránya, hogy a program forráskódja nagyon nagy méretû lehet, illetve a módosítás nehézkessé válik. Moduláris programozás: Az ismétlõdõ tevékenységeket alprogramként írjuk le, így amikor
szükségünk van rá, egyszerûen hivatkozunk az alprogramra. Ennek a módszernek nagy elõnye, hogy mivel csak egyszer írjuk le az ismétlõdõ programrészeket, a forráskód áttekinthetõvé válik, a késõbbi módosításkor pedig csak az alprogramot kell módosítani, amikor hivatkozunk rá, akkor az elõzõekben módosított tevékenység kerül végrehajtásra. A Turbo Pascal nyelv támogatja mind a monolitikus, mind a moduláris programozási módszert. Elsõsorban nagy méretû programok, komplex feladatok esetében célszerû a moduláris technikát választani, kismértetû programoknál feleslegesen bonyolítaná a forráskódot. A Pascal nyelv nagyon sok beépített eljárást és függvényt tartalmaz, az utasításkészlete viszonylag kicsi. A beépített eljárások, függvények már a legegyszerûbb programokban is felhasználásra kerülnek. Ilyen például a Writeln eljárás, vagy a Sin függvény Már ezek használata során is látható az eljárások és
függvények közötti különbség. A függvények egy értéket adnak vissza a meghívás után, Pld: X:= Sin(Alfa); az eljárások pedig valamilyen tevékenységet hajtanak végre. A beépített függvények, eljárások mellett a Pascal támogatja a programozó által készített függvények, eljárások használatát is. Eljárások: Egy-egy jól körülhatárolt részfeladat megoldásához készült programrészleteket eljárásoknak nevezzük. Az eljárásokat (és a függvényeket is) a fõprogram blokkja elõtt, a deklarációs részben kell szerepeltetni. Az eljárások általános felépítése: {Eljárásfej} Procedure Eljárásnév(formális paraméterek); {Lokális deklarációk} Label .; Const .; Type .; Var .; {Az eljárás törzse} Begin . End; Az eljárások felépítése hasonlít egy Pascal program felépítéséhez, eltérés csak az eljárás fejrészében és az eljárást lezáró End szónál tapasztalható. Az eljárásfejet a procedure foglalt szó
vezeti be, azt követi az eljárás neve, amelynek segítségével az eljárásra késõbb hivatkozni tudunk. Az eljárás neve után zárójelek között szerepelnek az eljárás által használt paraméterek A paraméterek segítségével tudunk az eljárásnak értékeket átadni az eljárás hívása során. Az eljárásnak átadott paraméterek típusa csak külön névvel rendelkezõ típus lehet, típusleírást itt nem adhatunk meg (pld: nem szerepelhet: Array [1.5] of Byte) Az eljárásfejet a lokális deklarációk követik. Az eljárások (függvények) használhatnak belsõ változókat, amelyeket csak az eljárás törzsén belül lehet felhasználni. Ezek az ún lokális változók A lokális változók elérhetõsége az alprogram törzsére korlátozódik, élettartamuk az eljárás futásától az eljárás befejezéséig tart. (Tehát a változóknak a fordító az alprogram hívásakor foglalja le a helyet a memóriában, és az alprogram befejezésekor ezeket a
memóriaterületeket felszabadítja.) Az alprogramokban deklarált lokális változók a Stack nevû memóriarészben kapnak helyet. A fentiekben vázlatosan összefoglaltuk az alprogramok általános jellemzõit. A általános bemutatás során megpróbáltam az alprogramok összes tulajdonságát összegyûjteni, és minden lehetõséget megemlíteni. Nézzük meg néhány konkrét példán keresztül, hogyan készíthetünk saját alprogramokat. Az alprogramok legegyszerûbb változatai a paraméter nélküli eljárások. Ezek az eljárások mindig ugyanazt a tevékenységet hajtják végre. Lássunk egy egyszerû példát: Uses CRT; {Sajat keszitesu eljaras} Procedure Elvalaszt; {Az eljaras lokalis deklaracioi} Var I: Integer; {Az eljaras torzse} Begin For I:= 1 to 80 do Writeln(-); End; {A foprogram} Begin ClrScr; Elvalaszt; Writeln(Sajat keszitesu eljaras hasznalata); Elvalaszt; Repeat Until Keypressed; End. Az Elvalaszt nevu eljaras mindig ugyanazt a tevékenységet hajtja
végre, a képernyõ teljes szélességében megrajzol egy szaggatott vonalat. Az eljárás tevékenysége során használja az I változót, amelyet lokális változóként deklaráltunk. Az alprogramok további lehetõségeket biztosítanak, ha kihasználjuk az alprogramok paraméterezhetõségét. A paraméterek segítségével univerzális alprogramokat készíthetünk, testreszabhatjuk az alprogramunk mûködését az átadott értékek segítségével. Nézzünk egy példát: Írjunk eljárást, amely kiírja a képernyõre két szám összegét. Uses CRT; Procedure Osszead(A, B: Integer); Begin Writeln(A ket szam osszege: , A+B); End; Var X, Y: Integer; Begin ClrScr; Write(Az elso szam: ); Readln(X); Write(A masodik szam: ); Readln(Y); Osszead(X, Y); Osszead(12, 5); Repeat Until Keypressed; End. A példában látható, hogy a paraméterezett alprogram fejrészében már szerepel a paraméterek felsorolása - tipussal együtt. Ez az ún formális paraméterlista, amely az
eljárás deklarációja során meghatározza, hogy a késõbbi híváskor hány és milyen típusú paramétert kell átadni az eljárásnak. A formális paraméterlistában szereplõ paraméterek azonosítója az eljárás törzsében változóként használható fel. Ha azonban a paraméterlistában egy változó nevét adtuk meg, és annak értékét az alprogram módosítja, a fõprogramba visszatérve a változó értéke változatlan marad. (Legalábbis az érték szerinti paraméterátadás esetén, merthogy ez az volna) Az eljárás meghívása során az ún. aktuális paraméterlistában már konkrét értékeknek kell szerepelni, változó vagy konstans formájában. Ha az aktuális paraméter változó, akkor annak csak az értéke érdekes az alprogram számára. Amennyiben a paraméterként megadott változók értékét úgy szeretnénk módosítani az eljáráson belül, hogy a változás megmaradjon a fõprogramba visszatérve is, akkor a paraméterátadás egy másik
módját kell választani, a cím szerinti paraméterátadást. A cím szerinti paraméterátadás során az alprogramnak nem a változó értékét, hanem a változó memóriabeli címét adjuk át (erre utal az elnevezés is). Tehát ha azt szeretnénk, hogy az eljárásunk egy változó értékét megváltoztassa, akkor ezt a paraméterátadási módot kell választani. Nagyon jó példa erre a beépített Inc és Dec eljárás, amelyek a megadott változó értékét növelik, illetve csökkentik. Figyelni kell arra, hogy cím szerinti paraméterátadást alkalmazva az eljárás hívásakor az aktuális paraméterlistában csak változóneveket adhatunk meg, mivel ezek címe kerül átadásra. Amennyiben konstans paramétereket adunk meg (Rossz példa: Inc(10); ) a program fordításakor hibaüzentetet kapunk. Ha az eljárásnak cím szerint szeretnénk a paramétereket átadni, a paraméterek neve elõtt szerepeltetni kell a Var kulcsszót (foglalt szót). Példa: Írjunk
eljárást, amely egy stringet nagybetûssé alakít! Uses CRT; Procedure Konvert(var S: String); Var I: Byte; Begin For I:= 1 to Length(S) do S[I]:= UpCase(S[I]); End; Var Szov: String; Begin ClrScr; Write(Kerek egy szoveget: ); Readln(Szov); Konvert(Szov); Writeln(Szov); Repeat Until Keypressed; End. Cím szerinti paraméterátadás esetén változók nevét kell szerepeltetnük az aktuális paraméterek listájában. Egyetlen kivétel a típusos konstans, amelyet még megenged a Turbo Pascal rendszer, mivel ennek értéke a program futása során megváltozhat. (A típusos konstansokat bõvebben lásd a 3. tételben!) A kétféle paraméterátadási forma vegyesen is használaható, ekkor a formális paraméterek felsorolásakor figyelni kell arra, hogy melyik paramétert hogyan szeretnénk átadni. A paraméterlistában különbözõ tipusú paramétereket is megadhatunk, a különbözõ tipusú paramétereket egymástól pontosvesszõvel választjuk el. Pld: Procedure Proba(A,
B: Integer; S: String; var Log: Boolean); Az alprogramok készítése során célszerû szem elõtt tartani, hogy az alprogramok csak egy-egy részfeladatot oldanak meg, de azt a lehetõ legjobban. Ezért célszerû a paraméterek rendszerét és az alprogram által végzett tevékenységet úgy kialakítani, hogy az esetleg egy másik program írása során is felhasználható, általános érvényû legyen. Az eljárások általános ismertetésénél szó volt róla, hogy az eljárások paraméterei csak olyan típusú adatok lehetnek, amelyek elõre definiált névvel rendelkeznek. Azt azonban nem korlátozza a nyelv, hogy a típus beépített vagy a felhasználó által definiált típus-e. Tehát ha az alprogramoknak paraméterként egy összetett típusú változót szeretnénk átadni, akkor ehhez elõször a típusdeklarációs részben kell meghatározni ennek a típusnak a leírását. Példa: Írjunk eljárást, amely kiszámítja egy 10 elemû (Byte) tömb elemeinek
összegét. Az eljárás elsõ paramétere a tömb, a második paramétere egy változó legyen, amelyben visszaadja a tömb elemeinek összegét. Uses CRT; {A tombtipus deklaracioja} Type TTomb= Array [1.10] of Byte; {Az osszegzest vegzo eljaras} Procedure TOsszeg(T: TTomb; var Sum: Word); Var I: Integer; Begin Sum:= 0; For I:= 1 to 10 do Sum:= Sum+T[I]; End; {A foprogram} Var V: TTomb; N: Byte; O: Word; Begin {A tomb feltoltese veletlenszamokkal} Randomize; For N:= 1 to 10 do Begin V[N]:= Random(255); Writeln(A tomb , N, , . eleme: , V[N]); End; {A tomb elemeinek osszegzese} Writeln; Writeln; TOsszeg(V, O); {Az eredmeny megjelenitese} Writeln(A tomb elemeinek osszege: , O); Repeat Until Keypressed; End. Függvények: Nagyon sokszor van szükségünk olyan alprogramokra, amelyek egy számítást végeznek el, egy eredményt adnak vissza. Az elõzõ példák között is szerepeltek már ilyenek A probléma megoldható eljárások segítségével is, ahogy azt fentebb láthattuk,
szükség van egy cím szerint átadott paraméterre, amelyben az eredményt elhelyezhetjük. Van azonban egy ennél elegánsabb megoldás is a fenti problémára, a függvények definiálása. A függvény olyan alprogram, amely a hívás és végrehajtás után egy értékkel tér vissza a hívás helyére. Ezt az értéket visszatérési értéknek nevezzük A függvény jellegû alprogramokat a változókhoz hasonló módon használhatjuk, állhatnak egy értékadási mûvelet jobb oldalán, egy kifejezésben, de meghívhatjuk õket az eljárásokhoz hasonló módon is - más kérdés, hogy ilyenkor a függvény által szolgáltatott érték elvész. A Pascal nyelv beépített függvényei (Sin, Cos, Keypressed, ReadKey, Abs, Ord, Chr, stb.) mellett magunk is írhatunk ilyen jellegû alprogramokat. Általában nem nehéz eldönteni, hogy egy részfeladat megoldása során eljárást vagy függvényt alkalmazzunk-e. A függvények felépítése alig különbözik az eljárások
felépítésétõl: Function Fgvnev(formális paraméterek): FTipus; {Lokális deklarációk} Label .; Const .; Type .; Var .; {A függvén törzse} Begin . Fgvnev:= Ertek; End; A függvények esetében a felrészben azt is meg kell határozni, hogy a függvény milyen típusú értékkel térjen vissza. A visszatérési érték típusa - a paraméterek típusához hasonlóan - csak elõre névvel ellátott típus lehet (összetett típus esetén elõbb típusdefiníció szükséges). A függvények másik különlegessége, hogy kötelezõen tartalmazniuk kell egy olyan sort, ahol a függvény visszatérési értékét meghatározzuk (Fgvnev:= .) A függvény törzsén belül a függvény nevét változóként használhatjuk, a visszatérési értéket egy értékadó utasítással határozhatjuk meg. Az azonban nics kikötve, hogy a függvény a függvénytörzs hanyadik utasításaként kaphat értéket, tehát nem kötelezõen az utolsó utasítás a visszatérési érték
meghatározása. Egy példa függvények használatára: Írjunk függvényt, amely kiszámolja két szám szorzatát. Uses CRT; Function Szoroz(A, B: Integer): Longint; Begin Szoroz:= A*B; End; Var E: Longint; Begin E:= Szoroz(5,7); Writeln(5*7: , E); Writeln(9*3: , Szoroz(9,3)); End. A fenti példa a lehetõ legegyszerûbb függvényt mutatja be, itt a függvénytörzsben mindössze a kötelezõ értékadást végezzük el. Természetesen nem ilyen egyszerû problémák esetén célszerû függvényt írni, de a példában látható a függvények használatának lehetõsége. A függvények az eljárásokhoz hasonlóan használhatnak paramétereket, itt is lehetséges cím és érték szerinti paraméterátadás. A paraméterekre az eljárások esetében leírt megkötések vonatkoznak itt is Példa: Írjunk függvényt, amely kiszámítja egy 10 elemû (Byte) tömb elemeinek összegét. A függvény paramétere az összegzendõ elemeket tartalmazó tömb. Uses CRT; {A tombtipus
deklaracioja} Type TTomb= Array [1.10] of Byte; {Az osszegzest vegzo fuggveny} Function TOsszeg(T: TTomb): Word; Var I: Integer; Sum: Word; Begin Sum:= 0; For I:= 1 to 10 do Sum:= Sum+T[I]; TOsszeg:= Sum; End; {A foprogram} Var V: TTomb; N: Byte; O: Word; Begin {A tomb feltoltese veletlenszamokkal} Randomize; For N:= 1 to 10 do Begin V[N]:= Random(255); Writeln(A tomb , N, , . eleme: , V[N]); End; {A tomb elemeinek osszegzese} Writeln; Writeln; {Az eredmeny megjelenitese} Writeln(A tomb elemeinek osszege: , TOsszeg(V)); Repeat Until Keypressed; End. A lokális és globális változók: A moduláris programozás során a problémát részfeladatokra bontva oldjuk meg. Minden egyes alprogram egy-egy külön mûködõ, önálló egység, amely viszont jól illeszthetõ a problémamegoldás, a programozás menetébe, tehát a program során jól felhasználható. Az alprogramok fõprogrammal, illetve más alprogramokkal történõ érintkezését általában paramétereken keresztül
valósítjuk meg. A paraméterek - akár cím szerinti, akár érték szerinti átadású paraméterek - a Stack nevû memóriarészben helyezkednek el. Ha sok alprogram használja egyidõben ezt a memóriaterületet, akkor az könnyen betelhet, ami programunk futási hibával történõ leállását eredményezi. A paramétereken keresztül történõ kommunikáció az alprogramok között a leghatékonyabb, legáttekinthetõbb megoldást eredményezi, néha azonban mégis érdemes egy másik módszert választanunk. Az alprogramok közötti kommunkiáció másik lehetéséges megoldása a globális - tehát minden alprogram által elérhetõ - változók használata, amelyeket a fõprogram deklarációs részében szoktunk deklarálni. Ezek a változók minden alprogram számára elérhetõek, módosíthatóak A globális változók használatának hátránya, hogy a nagy méretû programokat áttekinthetetlenné, kiszámíthatatlanná teszi, hiszen ki tudja, melyik eljárás mit
csinál a változóval, és amit csinál azt a program melyik pontján csinálja. Számtalan hiba elkerülhetõ a globális változók mellõzésével, egyes esetekben mégis nagyon hasznos lehet, ha élünk az általuk nyújtott lehetõségekkel. A globális és lokális változók kapcsán ki kell térni két nagyon fontos kérdésre: A változók élettartama: A változók akkor jönnek létre - akkor kapnak a memóriában területet - amikor a változót deklaráló blokkba belépünk. Tehát ha a változó egy alprogram lokális változója, a változóhoz tartozó memóriaterület akkor kerül lefoglalásra, amikor az alprogramot elkezdjük végrehajtani. Ha az alprogram futása befejezõdött, felszabadításra kerül a memóriában lefoglalt hely. Nyilván a fõprogram változói számára a program indulásakor történik meg a memóriaterület lefoglalása, és egészen a program befejezéséig tart a változók élettartama. A változók láthatósága: Ez a kérdéskör
szintén a Pascal programok blokkszerkezetével áll kapcsolatban. A változók azon a blokkon belül láthatók, amely blokk deklarációs részében a változót deklaráltuk. Amennyiben ez a blokk újabb blokkokat tartalmaz, akkor a belsõ (egymásba ágyazott) blokkokból is elérhetõ ugyanaz a változó. Érdekes kérdés, hogy mi történik, ha két egymásba ágyazott blokk deklarációs részében ugyanolyan néven deklarálunk két - esetleg különbözõ típusú - változót. Problémát nem jelent, bár célszerû elkerülni, ugyanis a külsõ blokkban a változót az ott deklarált tipussal és az ennek megfelelõ értékekkel használhatjuk, amikor azonban belépünk a belsõ blokkba, az újabb deklaráció hatására az elõzõ változó elérhetetlenné válik, ugyanazon a néven egy másik - esetleg más típusú változót érhetünk el. A rekurzív alprogramok: Egy speciális lehetõség az alprogramok készítése során a rekurzió módszerének alkalmazása,
amely azt jelenti, hogy az alprogramok meghívhatják önmagukat is. A rekurzív alprogramok készítése néhány problémánál nagyon egyszerû programozási lehetõséget biztosít számunkra. Általában minden rekurzív problémának létezik egy iteratív (ciklussal történõ) megodása is. Ha a két lehetõség közül választanunk kell, célszerûbb az iteratív megoldást alkalmazni, mert a rekurzív programok futási ideje és memóriaigénye lényegesen nagyobb, mint az iteratív megoldásé. Ha mégis a rekurzív megoldás mellett döntünk, célszerû a lokális (az alprogram által használt) változók számát a minimálisra csökkenteni, mivel az alprogram minden egyes hívásakor a Stack területen foglal helyet ezen változók számára, így ha sok a rekurzív hívás, a Stack terület hamar megtelhet, a program futási hibával leáll. Egy tipikusan rekurzív probléma a Fibonacci számsor problémája. Megoldása a Feladatok lapon található, a Rekurzív
alprogramok használata részben: Feladat A másik tipikusan rekurzív probléma az N! (N faktoriális) kiszámítása. Ennek a feladatnak hatékony rekurzív és iteratív megoldása is létezik. Számoljuk ki N! értékét rekurzív alprogram használatával: Function Fakt(N: Byte): Word; Begin If N=0 then Fakt:= 1 else Fakt:= N*Fakt(N-1); End; Var X: Byte; Begin Write(A szam: ); Readln(X); Writeln(A szam faktorialisa: , Fakt(X)); End. Tehát a Fakt fügvény önmagát hívja meg egészen addig, amíg a legeszerûbb N értéket kapja meg paraméterként (N értéke minden hívásnál csökken). Amikor elérte azt az értéket, ahol már pontosan meghatározható - számolás nélkül - N! értéke, akkor a Fakt visszatérési értékét pontosan megállapítja. Ezután tér vissza a függvény futás alatt álló példányaihoz, és végzi el a szorzást lépésenként. Az iteratív megoldás: Function Fakt(N: Byte): Word; Var I: Byte; F: Word; Begin F:= 1; For I:= 1 to N do F:=
F*1; Fakt:= F; End; Var X: Byte; Begin Write(A szam: ); Readln(X); Writeln(A szam faktorialisa: , Fakt(X); End. A rekurzió módszere használható az alprogramok esetében is. Itt is meg kell határozni a kilépési feltételt, tehát azt a feltételt, amelynek teljeseülése esetén az alprogram már nem kerül újból meghívásra. Az eddigiekben megnéztük, hogyan tudja egy függvény (vagy eljárás) önmagát újra és újra meghívni, ezt a fajta rekurziót önrekurziónak nevezzük. Létezik a rekurzív alprogramoknak egy bonyolultabb változata, amikor két alprogram kölcsönösen hívja egymást. Az ilyen jellegû rekurziót kölcsönös rekurziónak nevezzük A kölcsönös rekurzió jellemzõ példája a Hanoi tornyai nevû feladat. Az alprogramok speciális lehetõségei: A Pascal nyelvben definiáltak az alprogramok készítéséhez néhány olyan különleges lehetõséget, amely speciális alprogramok írása során nagyon hasznos lehet számunkra, a minél
egyszerûbb és hatékonyabb megoldás érdekében. Elõzetes deklaráció: A Pascal nyelveben minden azonosítót deklarálni kell a felhasználási helye elõtt. Vonatkozik ez a változókra, konstansokra, típusokra, és az alprogramokra is. Egyszerû alprogramok készítésénél ez egyszerûen megvalósítható, semmilyen nehézséget nem okoz. A gondok akkor jelentkeznek, amikor két, egymást kölcsönösen hívó alprogramot szeretnénk definiálni. Ebben az esetben lehet hasznos az elõzetes deklaráció, amely lehetõvé teszi, hogy az alprogram fejét és törzsét ne egyszerre írjuk le. Az elõzetes deklaráció során elõször csak a függvény fejrészét kell deklarálni, a törzs a deklarációs rész késõbbi szakaszaiban bárhol elhelyezhetõ. Az elõzetes deklaráció a Forward opció segítségével lehetséges. Az elõzetes deklaráció általánosan: Procedure Eljarasnev(Form Par); Forward; . . {Egyéb deklarációk} . Procedure Eljarasnev(Form Par); Var .
{Lokális deklarációk} Begin . . {Függvénytörzs} . End; . . . Példa: Procedure Olvas(var A, B: real); Forward; Procedure Szamol(var X, Y: real); Var A1, B1: Real; Begin Olvas(A1, B1); X:= A1+B1; Y:= A1-B1; End; Procedure Olvas(var A, B: real); Begin Write(Az elso szam: ); Readln(A); Write(A masodik szam: ); Readln(B); End; Var X, Y: Real; Begin Writeln(Szamolas.); Szamol(X, Y); Writeln(A ket szam összege: , X); Writeln(A ket szam különbsége: , Y); End. Assembly nyelvû alprogramok készítése: Ha azt szeretnénk, hogy egy rutin igazán gyorsan mûködjön, érdemes Assembly nyelven megírni. Az eljárások, függvények Assembly nyelven is megírhatók, de ezt az alprogram fejrészében jelezni kell az Assembler opcióval. Az Assembly nyelvû alprogramok általános felépítése: Procedure Eljarasnev(Form Param); Assembler; Asm . . {Assembly utasítások} . End; Assembly nyelvû alprogramok esetén az alprogram törzsét nem a Begin . End; hanem az Asm . End; foglalt
szavak között kell megírni Megszakítás típusú alprogramok készítése: Ha memóriarezidens programot szeretnénk írni, mindenképpen élni kell ezzel a lehetõséggel. A megszakítás típusú alprogramok hozzárendelhetõk valamely megszakításhoz, és másodpercenként 18.3-szer végrehajtásra kerülnek Ha a programot úgy fejezzük be, hogy a megszakítás és az eljárás összerendelése megmarad, akkor rezidens programokat írunk. (No jó ezt azért nagyon leegyszerûsítettem, de nem kell ebbe túl mélyen belemenni.:-)) ) A megszakítás jellegû alprogramok definiálása az Interrupt opcióval történik. Általános formája: Procedure Eljarasnev(Form Param); Interrupt; Begin . End; Megjegyzésként talán még annyi, hogy az ilyen jellegû alprogramok általában nem használnak paramétereket. Tehát deklarációjuk általában: Procedure Eljarasnev; Interrupt; 9.tétel: Fájlkezelés, fájlok szerkezete, megnyitása, írása, olvasása, egyéb fájlkezelõ
utasítások. Tipizált és szövegfájlok. Általában minden magasszintû programozási nyelv támogatja a háttértárakon lévõ adatállományok kezelését. Ezt a támogatottságot több szempont is indolkolja nézzük meg a két legfontosabbat. A program futása során elõállított eredményeket változókban tároljuk, amelyek a memóriában helyezkednek el. Az eredményeket kiírathatjuk a képernyõre, a változók tartalmát megõrízhetjük a program futásának végéig. Azonban a gép kikapcsolásakor a memória tartalma minden esetben törlésre kerül, tehát a következõ bekapcsoláskor már nem tudjuk az elõzõleg már kiszámított értékeket, eredményeket felhasználni. A másik probléma, hogy "a memória mindig kicsi". Tehát mindig léteznek olyan problémák melynek megoldása során a memória által biztosított tárolókapacitás kevésnek bizonyul, azaz hamar korlátokba ütközhetünk. Ha az adatainkat szeretnénk tartósan megõrízni,
akkor igénybe kell vennünk a háttértárak által biztosított lehetõségeket. Amellett, hogy az adatainkat a háttértárakon tartósan megõrízhetjük, nagy elõny még, hogy a háttértárak tárolókapacitása lényegesen meghaladja a memória kapcitását, tehát sokkal több adatot tudunk itt tárolni. Az állományok kezelésérõl általában: Az állományok kezelése az operációs rendszer feladatai közé tartozik. Tehát amikor szeretnénk valamelyik állományt elérni a programunkból, az operációs rendszer szolgáltatásait kell igénybe vennünk (bár ez legtöbb esetben nem tûnik fel, mivel a magasszintû nyelvek tartalmazzák az állományok kezelését segítõ eljárásokat, függvényeket). Az állományok kezelése során minden programozási nyelv és minden operációs rendszer esetén hasonló lépéseket kell végrehajtanunk, nyilván igazodva az operációs rendszer és a programozási nyelv sajátosságaihoz (szintaktika, file-ok felépítése,
stb). Ezek az általános lépések a következõk: 1. Adatállományok elérésének biztosítása: Kapcsolatot kell teremteni egy a programozási nyelv által is kezelhetõ objektum és az fizikai állomány között. Általában egy változón keresztül történik a kapcsolat létesítése, de más megoldás is elképzelhetõ. Egy eljárás vagy függvény segítségével rendeljük össze a változót és a fizikai állományt (részletesen lásd késõbb). Ezt a változót file változónak szokás nevezni, ugyanis ezzel a változóval tudunk hivatkozni az állományra a filekezelõ mûveletek során. 2. Adatállományok megnyitása: Az állomány tartalmát csak akkor tudjuk elérni (olvasni, írni, módosítani), ha az állományt megnyitjuk, nem elegendõ a logikai kapcsolat kialakítása. A megnyitás során dõl el, hogy milyen mûvelelteket tudunk majd végezni az állománnyal (ui. meg lehet nyitni egy állományt csak olvasás, csak írás vagy mindkettõ céljából, ez
általában beállítható). 3. Állománykezelõ mûveletek: Az elõzõ lépéseken túljutva kezdõdhet az állományokkal végzett tényleges munka, azaz az állomány tartalmának feldolgozása, módosítása (olvasási, írási mûveletek). A feldolgozás során szükségünk lehet arra, hogy az állomány különbözõ részein dolgozzunk, azaz pozícionálnunk kell az állományban (Pld. egy állományban elõször az elsõ közvetlen ezután az utolsó sort szeretnénk feldolgozni, az összes többi sort szeretnénk kihagyni), erre egyes fájltipusoknál lehetõségünk van, más tipusoknál nincs. Minden programozási nyelv esetében másképpen alakul, hogy az állományokat hogyan csoportosítja és melyik állományon milyen mûveleteket enged meg. 4. Adatállományok lezárása: Nagyon fontos lépés, különösen akkor, ha az állomány tartalmát módosítottuk. Ahhoz, hogy a program által végzett módosításokat megõrízhessük, a megnyitott állományokat le
kell zárni a program befejezése elõtt. Az adatállományok csoportosítása a Turbo Pascal programozási nyelvben: A Pascal nyelv az állományoknak három csoportját különbözteti meg: - szöveges állományok - típusos állományok - típusnélküli állományok A háromféle állománytípus kezelése során jelentõs eltéréseket tapasztalhatunk. A három csoport közül az elsõ kettõt nézzük meg részletesen a továbbiakban. A szöveges állományok jellemzõi, kezelése: A szöveges állományok kötetlen szerkezetû, szekvenciális állományok. A szöveges állományok bármely szövegszerkesztõvel létrehozhatók, ASCII karakterekbõl épülnek fel. Kötetlen a szerkezetük, hiszen semmilyen megkötés nincs arra vonatkozóan, hogy egy szöveges állomány egy sora milyen hosszú lehet, illetve az is lehetséges, hogy a szöveges állomány sorai eltérõ hosszúságúak. A kötetlen szerkezet nagy szabadságot enged meg a szöveges állományok létrehozása
során, azonban ennek következményeképpen a programok csak egymás után sorban dolgozhatják fel az állományban tárolt adatokat. Ez azt jelenti - ahogyan arra már korábban utaltam - hogy a szöveges állomány esetén az egymást követõ adatokat csak sorban tudjuk elérni, tehát az esetleges felesleges adatokat is fel kell dolgozni, hogy a számunkra fontos adatokat elérhessük (ha csak az elsõ és az utolsó elõtti sorra volna szükségünk, akkor is végig kell lépkedni a köztük lévõ sorokon is). A szöveges állományok feldolgozása során is a fenti négy lépésen leírtakat kell megvalósítani a Pascal nyelv utasításai által. A szöveges állományok kezeléséhez kapcsolódó utasítások, függvények: Assign eljárás: A fizikai állomány és a file-változó összerendelése. Szintaxisa: Assign(F, FNev); F: Text; - A szöveges állományok eléréséhez használt file-változót Text tipusú változóként kell deklarálni. Mindig egy változó FNev:
String; - A fizikai állomány elérési útja és neve. Ha az elérési utat nem adjuk meg, az aktuális könyvtárban keresi a megadott nevû állományt. Változó és konstans egyaránt megadható. Az Assign eljárás mindössze a változó-állomány hozzárendelést valósítja meg, amely mindössze egy logikai kapcsolatot jelent. Ilyenkor még nem használjuk a fizikai állományt, tehát filenévként olyan file is megadható, amely még nem létezik. Reset eljárás: Szöveges állomány megnyitása olvasásra. Szintaxisa: Reset(F); F: Text; - A szöveges állomány eléréshez használt változó. Minden esetben egy változót kell megadni. Az utasítás végrehajtása után az állomány elejérõl tudunk adatokat olvasni. Append eljárás: Szöveges állomány megnyitása hozzáfûzésre. Szintaxisa: Append(F); F: Text; - A szöveges állomány eléréshez használt változó. Minden esetben egy változót kell megadni. Az utasítás végrehajtása után az állomány
végére tudunk adatokat írni. Rewrite eljárás: Új állomány létrehozása, illetve létezõ állomány felülírása. Szintaxisa: Rewrite(F); F: Text - A szöveges állomány eléréshez használt változó. Minden esetben egy változót kell megadni. A Rewrite eljárás nem kizárólag szöveges állományok esetén alkalmazható, minden filetipusnál használhatjuk a fent leírt mûveletek elvégzéséhez. Write és Writeln eljárás: Írás a szöveges állományba. Szintaxisa: Write(F, S); F: Text; - A szöveges állomány eléréshez használt változó. Minden esetben egy változót kell megadni. S - A második paraméter típusa nincs deklarálva, mivel szöveg, karakter, illetve szám tipusú változó vagy konstans egyaránt alkalmazható. Az állományba írni kívánt szöveg, karakter esetleg szám. Az eljárás ugyanúgy mûködik, mint a képernyõre történõ írás esetén, de meg kell adni elsõ paraméterként a file-változót. Read és Readln eljárás:
Olvasás a szöveges állományból. Szintaxisa: Read(F, S); F: Text; - A szöveges állomány eléréshez használt változó. Minden esetben egy változót kell megadni. S - A második paraméter típusa nincs deklarálva, mivel szöveg, karakter, illetve szám tipusú változó (és csak változó) egyaránt alkalmazható. Az állományból olvasni kívánt szöveg, karakter esetleg szám. Az eljárás ugyanúgy mûködik, mint a billentyûzetrõl történõ olvasás esetén, de meg kell adni elsõ paraméterként a file-változót. Mivel a szöveges állományok szerekezete kötetlen, így különbözõ "tipusú" adatok tárolására alkalmasak. Tehát egy szöveges állomány egyik sorában egy egész számot a másik sorában egy lakcimet is tárolhatunk. Ezért nem biztos, hogy minden esetben megfelelõ tipusú értéket tud a Read és a Readln eljárás beolvasni. Az eredmény ugyanaz, mint a billentyûzetrõl történõ hibás adat bevitelekor (szám helyett
szöveget ad meg a felhasználó): a program futási hibával leáll. A késõbbiekben ismertetésre kerülõ módszerrel ez kiküszöbölhetõ. Eof függvény: File vége teszt. Szintaxisa: B:= Eof(F); F: Text; - A file-változó, amely a fizikai állományt azonosítja. A függvény visszatérési értéke Boolean tipusú. Ha az állomány végén állunk, a függvény TRUE értéket ad vissza, egyébként értéke FALSE. Az Eof függvény más állománytipusoknál is alkalmazható. Eoln függvény: Sorvége teszt. Szintaxisa: B:= Eoln(F); F: Text; - A file-változó, amely a fizikai állományt azonosítja. A függvény visszatérési értéke Boolean tipusú. Ha egy sor végén állunk, a függvény TRUE értéket ad vissza, egyébként visszatérési értéke FALSE. Az Eoln függvényt csak szöveges állományok esetében alkalmazhatjuk (más állománytipusnál nincs is értelme). Akkor lehet hasznos az alkalmazása, amikor az állományt karakterenként dolgozzuk fel.
Close eljárás: Az állomány lezárása. Szintaxisa: Close(F); F: Text; - A file-változó, amely a fizikai állományt azonosítja. Ezzel az eljárással zárhatjuk le a megnyitott állományokat a szükséges mûveletek elvégzése után. Alkalmazása minden esetben célszerû, de különösen fontos, amikor valamilyen módosítást végeztünk az állomány tartalmában. Módosítás esetén, ha az állomány lezárása nélkül fejezzük be a programot, az elvégzett változatások nem lépnek érvénybe, a fizikai állomány tartalma változatlan marad. A szöveges állományok kezelése: Const Szov: String= Ez a szoveg az allomany vegere kerul; Var F: Text; S: String; C: Char; Begin {Kapcsolat létesítése a file-változó és a fizikai állomány között} Assign(F, szoveg.txt); {Az állomány megnyitása olvasásra} Reset(F); {VAGY} {Az állomány megnyitása hozzáfûzésre} Append(F); {VAGY} {Az állomány létrehozása} Rewrite(F); {Az állomány feldolgozása}
{Általában kétféle mûveletet végezhetünk az állományokkal:} {Az állomány tartalmának olvasása. A tartalomra általában} {teljes egészében kiváncsiak vagyunk, mondjuk, ha szeretnénk} {megjeleníteni a képernyõn} {Az Eof függvénnyel figyeljük, hogy van-e még feldolgozandó adat} {nincs-e vége az állománynak} While not Eof(F) do Begin {egy sor olvasása az állományból} Readln(F, S); {VAGY} {Egy karakter olvasása az állományból} Read(F, C); {A kiolvasott adatokat feldolgozó utasítások} . End; {Az állomány tartalmának módosítása. A szöveges állományok} {esetében csak az állomány végére tudunk adatokat írni, beszúrásra} {nincs lehetõségünk} {Egy konstansként megadott szöveget beszúrunk az állomány végére} Writeln(F, Szov); {VAGY} Write(F, Szov); {A munka befejezése után lezárjuk az állományt} Close(F); {és befejezzük a programot.} End. Példák a szöveges állományok használatára: Olvassuk be egy szöveges
állomány elérési útját és nevét, majd írjuk ki a tartalmát a képernyõre. (Az állomány feldolgozása soronként történik) Uses CRT; Var F: Text; S: String; FNev: String; Begin ClrScr; Write(Az allomany neve: ); Readln(FNev); Assign(F, FNev); {$I-} Reset(F); If IOResult<0 then Begin Writeln(Hiba az allomany megnyitasa soran.); Halt(1); End; {$I+} While not Eof(F) do Begin Readln(F, S); Writeln(S); End; Close(F); End. Olvassuk be egy szöveges állomány elérési útját és nevét. Olvassunk be egy tetszöleges karaktert (vezérlõkarakterek kivéve). Számoljuk meg, hogy a beolvasott karakter hányszor fordul elõ a szöveges állományban. (Az állomány feldolgozása karakterenként történik) Uses CRT; Var F: Text; C, K: Char; FNev: String; Sum: Longint; Begin ClrScr; Write(Az allomany neve: ); Readln(FNev); Write(A keresett karakter: ); Readln(K); Sum:= 0; Assign(F, FNev); {$I-} Reset(F); If IOResult<0 then Begin Writeln(Hiba az allomany megnyitasa
soran.); Halt(1); End; {$I+} While not Eof(F) do Begin Read(F, C); If UpCase(C)=UpCase(K) then Sum:= Sum+1; End; Close(F); Writeln(A keresett karakter elofordulasainak szama: , Sum); End. Az állomány feldolgozása karakterenként történik, ezért az olvasás során a Readln helyett a Read eljárást használjuk. A Sum változóban tároljuk az elõfordulások számát, ezért a változót a feldolgozási ciklusba lépés elõtt le kell nullázni. Az UpCase függvény segítségével a keresett és az állományból olvasott karaktert nagybetûvé alakítjuk, így kiküszöböljük a kis- és nagybetûk megkülönböztetését. A szöveges állományok szerkezete kötetlen, bármely szövegszerkesztõ segítségével létrehozhatók. Az állomány tartalma az ASCII karakterkészlet elemeibõl tevõdik össze A kötetlen szerkezet miatt az állományok feldolgozása csak szekvenciális módszerrel lehetséges, azaz az állomány tartalmát csak soronként (esetleg
karakterenként) dolgozhatjuk fel. Típusos állományok jellemzõi, kezelése: A típusos állományok kötött adatszerkezettel rendelkeznek, lehetõvé téve ezzel a direkt pozícionálást is. Kezelésük lépései megegyeznek a szöveges állományok kezelésének lépéseivel: - elõkészületi lépések - állomány megnyitása - az állomány adatait kezelõ mûveletek - állomány lezárása A típusos állományok elérése is egy változón keresztül történik, de a változó típusa némelyest különbözik a szöveges állományoktól. Általános deklarációja: Var F: File of Adattípus; Az Adattípus helyére azt a típust kell írnunk, amilyen adatokat az állományban találhatunk. Példa: Egy egész számokat tartamazó állomány: Var F: File of Integer; A típusos állományok esetében is el kell végezni a file-változó és a fizikai file összerendelését, az Assign eljárás segítségével. Hasonlóképpen történik, mint szöveges állományok esetén:
Assign(F, C:SZAMOK.DAT); A típusos állományok megnyitása minden esetben a Reset eljárás segítségével történik. A Reset eljárás a típusos állományokat írásra és olvasásra nyitja meg (alapértelmezés szerint). Tehát itt mindkét mûvelettípust egyszerre végezhetjük. Példa: Reset(F); Az állományok megnyithatók csak olvasásra, csak írásra is. Ha a Reset eljárás alapértelmezett mûködésén módosítani szeretnénk, akkor a FileMode nevû beépített változó értékét kell megváltoztatnunk a Reset eljárás meghívása elõtt. A FileMode változó a System unit-ban van deklarálva, lehetséges értékei: 0 - a Reset eljárás csak olvasásra nyitja meg az állományt. 1 - a Reset eljárás csak írásra nyitja meg az állományt. 2 - a Reset eljárás írásra ÉS olvasásra nyitja meg az állományt. Ez az alapértemezett mûködés A típusos állományok esetében csak a Read és Write eljárásokat használhatjuk az olvasás, illetve az írás
során. A Readln és Writeln eljárásoknak nem lenne értelme, hiszen az állomány adatai nem sorokba vannak elrendezve. Az eljárásokat a szöveges állományoknál már megismert módon használhatjuk: Read(F, V); - olvasás az állományból. Write(F, V); - írás az állományba. A V paraméter típusának meg kell egyezni a file-változó deklarációjában szereplõ alaptípussal. (File of Integer tipusú állománynál V típusa is kötelezõen Integer kell legyen.) A típusos állományok esetén lehetõségünk van a direkt pozícionálásra, nem kötelezõ az adatokat egymás után feldolgozni. A file-mutató (lásd késõbb) mozgatása a Seek eljárás segítségével történik. A Seek eljárásnak paraméterként meg kell adni, hogy az állomány hanyadik rekordjára szeretnénk mozgatni a file-mutatót. A rekordok sorszámozása 0-val kezdõdik Példa: Seek(5); A Seek eljárásnak csak olyan számot adhatunk meg, amely 0 és az állomány utolsó rekordjának sorszáma
közé esik, ha a szám ezen az intervallumon kívül esik, a program futási hibával leáll. A futási hiba kiküszöbölhetõ a már említett $I direktíva kikapcsolása segítségével. Ha a pozícionálás sikertelen volt, az IOResult változó értéke 0-tól különbözõ lesz. Visszatérve a file-mutatóra: A file-mutató egy olyan változó, amely megmutatja, hogy az állományban melyik lesz a következõ feldolgozásra kerülõ rekord. Ennek a mutatónak a pozícióját teszteli az Eof függvény is. A file-mutató nem keverendõ össze a Pointer típussal, mivel a file-mutató egy Longint típusú érték. A programozónak az esetek legnagyobb százalékában nem kell a file-mutatóval közvetlenül foglalkozni, a Pascal eljárásain, függvényein keresztül indirekt módon kezeljük. A típusos állományoknál is használhatjuk az Eof függvényt az állomány végének tesztelésére. Mivel az adatok nem sorokba vannak szervezve, nyilván az Eoln függvénynek
nincs értelme, ezért típusos állományoknál nem használható. Példák a típusos állományok használatára: Hozzunk létre egy állományt, amelyet rekord típusú adatokkal töltünk fel! Uses CRT; Type Szemely= Record Nev: String; Lakcim: String; Eletkor: Byte; End; Var F: File of Szemely; Adat: Szemely; New: Boolean; Ch: Char; Begin Assign(F, C:ADAT.DOC); {$I-} Rewrite(F); If IOResult<0 then Begin Writeln(Hiba lepett fel az allomany letrehozasa soran); Halt; End; {$I+} Repeat Write(Nev: ); Readln(Adat.Nev); Write(Lakcim: ); Readln(Adat.Lakcim); Write(Eletkor: ); Readln(Adat.Eletkor); Write(F, Adat); Write(Uj rekord (I/N): ); Readln(Ch); If (Ch=I) or (Ch=i) then New:= True else New:= False; Until not New; Close(F); End. A típusos állomány típusának meghatározásánál (File of Adattipus) csak egyszerû tipusokat használhatunk, tehát ha összetett tipusokkal (rekord, tömb) szeretnénk dolgozni, akkor a típust a típusdeklarációs részben elõre deklarálni
kell. A program addig olvassa be az adatokat, amíg a beolvasás után feltett kérdésre Igen (I) választ adunk. Olvassuk be és jelenítsük meg a képernyõn az elõbbi állomány tartalmát. Uses CRT; Type Szemely= Record Nev: String; Lakcim: String; Eletkor: Byte; End; Var F: File of Szemely; Adat: Szemely; Begin Assign(F, C:ADAT.DOC); {$I-} Reset(F); If IOResult<0 then Begin Writeln(Hiba lepett fel az allomany megnyitasa soran); Halt; End; {$I+} ClrScr; Writeln(Az ADAT.DOC allomany tartalma:); Wrteln(------------------------------------); While not Eof(F) do Begin Read(F, Adat); Writeln(Nev: , Adat.Nev); Writeln(Lakcim: , Adat.Lakcim); Writeln(Eletkor: , Adat.Eletkor); Writeln; End; Close(F); End. Keressük meg és írassuk ki azoknak a személyeknek az adatait, akik idõsebbek 20 évnél. Használjuk fel az elõbbi példákban létrehozott állományt. Uses CRT; Type Szemely= Record Nev: String; Lakcim: String; Eletkor: Byte; End; Var F: File of Szemely; Adat: Szemely;
Begin Assign(F, C:ADAT.DOC); {$I-} Reset(F); If IOResult<0 then Begin Writeln(Hiba lepett fel az allomany megnyitasa soran); Halt; End; {$I+} ClrScr; Writeln(Az ADAT.DOC allomany tartalma:); Wrteln(------------------------------------); While not Eof(F) do Begin Read(F, Adat); If Adat.Eletkor20 then Begin Writeln(Nev: , Adat.Nev); Writeln(Lakcim: , Adat.Lakcim); Writeln(Eletkor: , Adat.Eletkor); Writeln; End; End; Close(F); End. 10. tétel: A CRT unit fontosabb beépített konstansai, eljárásai, függvényei, használata Minden program egyik legfontosabb része - a lényegi problémamegoldás és a hibakezelés mellett - a felhasználói felület, melyen keresztül a felhasználó a programmal kapcsolatot tart. Nagyon fontos a megfelelõ kialakítása, mivel a program használata során mindig tudni kell milyen lehetõségek állnak rendelkezésre a továbblépésre, illetve hogyan lehet javítani hiba esetén. A felhasználói felület kialakítása során két lehetõség
közül választhatunk: szöveges és grafikus felület. Figyelembe kell venni a program által támasztott követelményeket - függvényábrázolást oktató programot nem célszerû szöveges felületen írni - és a számítógép hardver kiépítettségét (videokártya, monitor). A hardver tényezõk ma már kevésbé okoznak problémát az SVGA monitorok széleskörû elterjedése miatt. Amennyiben a szöveges felület mellett döntünk, Turbo Pascal programok esetében a CRT unit tartalmazza a szükséges eljárásokat, függvényeket, konstansokat. A szöveges felületet használó programok elsõ sora általában: Uses CRT; azaz a program elején meg kell hívni a CRT unit-ot, hogy eljárásait, függvényeit, konstansait használhassuk a programunk írása során. A szöveges mód egyik fontos jellemzõje a képernyõ karakterfelbontása, amely megmutatja, hogy vízszintesen és függõlegesen hány karakter jeleníthetõ meg a képernyõn. A karakterfelbontás általában
80x25 karakter, azaz a képernyõ egy sorába 80 karaktert írhatunk és 25 sor fér el egyszerre a képernyõn. Ettõl eltérõ értékkel találkozhatunk EGA és VGA monitorok esetében Az EGA és VGA monitorok karakterfelbontásai: Monitortipus EGA Oszlopok száma Sorok száma 80 25 80 43 40 25 80 25 80 43 80 50 40 25 VGA A képernyõ tipusa meghatározza a karakterfelbontás mellett az alkalmazható karakter és háttérszínek számát is. Színes monitor esetén 16 féle karakterszínt és 8 féle háttérszínt állíthatunk be. Monokrom monitorok esetében (Hercules, MonoVGA) a különféle színeket árnyalatok jelzik. Mind a karakterfelbontást, mind a karakter- és háttérszínt beállíthatjuk a CRT unit eljárásainak és konstansainak segítségével. A CRT unit beépített konstansai: A képernyõ üzemmódjához kapcsolódó beépített konstansok: Konstans Érték Karakterfelbontás Értelmezés BW40 0 40x25 Fekete-fehér BW80 2 80x25
Fekete-fehér CO40 1 40x25 Színes CO80 3 80x25 Színes Mono 7 80x25 Egyszínû (Hercules, MDA) Font8x8 256 80x43 80x50 EGA: 80x43 üzemmód VGA: 80x50 üzemmód A színekhez kapcsolódó beépített konstansok: Konstans Érték Szín Konstans Érték Szín BLACK 0 Fekete LIGHTBLUE 9 Világoskék BLUE 1 Kék LIGHTGREEN 10 Világoszöld GREEN 2 Zöld LIGHTCYAN 11 Világostürkiz CYAN 3 Türkiz LIGHTRED 12 Világospiros RED 4 Piros LIGHTMAGNET 13 A Világoslila MAGNETA 5 Lila YELLOW 14 Sárga BROWN Barna WHITE 15 Fehér LIGHTGRAY 7 Világosszürke BLINK 128 Villogás DARKGRAY 8 Sötétszürke 6 A fenti konstansokat a szövegszín, háttérszín és képernyõüzemmód beállításánál használhatjuk. Ezen kívül beállíthatunk még néhény speciális jellemzõt a CRT unit beépített változóinak segítségével. A változók beállításai programunk futása során fejtik ki hatásukat A CRT unit beépített
változói: CheckBreak: boolean A programok futása általában megszakítható a Ctrl+Break billentyûkombináció segítségével. A CheckBreak változó segítségével ennek figyelését le lehet tiltani, tehát ha a programunk elején a CheckBreak változónak False értéket adunk, akkor a Ctrl+Break billentyûkombinációval a programot nem lehet megszakítani. Ha a változó értéke True, akkor a Ctrl+Break billentyûkombinációval a program megszakítható. Alapértelmezés szerinti értéke: True A CheckBreak változó értékét akkor célszerû beállítani, amikor a program már "be van lõve", mert egy végtelen ciklus esetén hasznos lehet, ha meg tudjuk szakítani a program futását. CheckSnow: boolean CGA tipusú monitorok esetében befolyásolja a megjelenítés minõségét. DirectVideo: boolean A Write és Writeln eljárások mûködését befolyásolja. Ha értéke True, akkor a Write és Writeln eljárások közvetlenül írhatnak a videomemóriába
(amelynek kezdõcíme: $B800:$0000), ha értéke False, akkor a képernyõre íráshoz az eljárások BIOS megszakításokat használnak. A videomemória közvetlen kezelése lényegesen gyorsabb mûködést eredményez, ezért csak nagyon indokolt esetben célszerû a változó értékét módosítani. LastMode: Word Az utoljára beállított szöveges üzemmód kódját tartalmazza. Alkalmazása akkor szükséges, ha a program felváltva használ szöveges és grafikus képernyõt. WindMin: Word; WindMax: Word A beállított szöveges ablak (lásd késõbb) bal felsõ és jobb alsó sarkának abszolút (teljes képernyõre vonatkoztatott) koordinátáit tartalmazzák. A WindMin a bal felsõ, a WindMax a jobb alsó sarok koordinátáit tárolja. A változók alsó byte-ja az X, a felsõ byte-ja az Y koordináta tárolására szolgál. Az értékek a Low és High függvényekkel nyerhetõk vissza Példa: Uses CRT; Var X 1, Y 1, X 2, Y 2: Byte; Begin {Bal felso sarok koordinatai} X 1:=
Low(WindMin); Y 1:= High(WindMin); {Jobb also sarok koordinatai} X 2:= Low(WindMax); Y 2:= High(WindMax); Writeln(A szoveges ablak sarkainak koordinatai:); Writeln(Bal felso sarok: X= , X 1, Y= , Y 1); Writeln(Jobb also sarok: X= , X 2, Y= , Y 2); End. A CRT unit eljárásai, függvényei: A CRT unit eljárásait, függvényeit négy csoportba sorolhatjuk: 1. Mûködési paraméterek beállítása 2. Be- és kiviteli mûveletek 3. Képernyõkezelés 4. Egyéb eljárások, függvények Mûködési paraméterek beállítása: Ebbe a csoportba tanroznak azok az eljárások, amelyek a szövegszínt, háttérszínt, üzemmódot állítják be. HighVideo eljárás: A karakterszín intenzitásának növelése. LowVideo eljárás: A karakterszín intenzitásának csökkentése. NormVideo: Az alapértelmezés szereinti karakterszín és háttérszín beállítások visszaállítása. Az alapértelmezett karakterszín: LIGHTGRAY, az alapértelmezett háttérszín: BLACK. TextBackground
eljárás: A háttérszín beállítása. Szintaxisa: TextBackground(Háttérszín); A háttérszín beállításához használhatjuk a CRT unit beépített konstansai és azok értékét is. Tehát TextBackground(BLACK) és TextBackground(0) egyenértékû. A beállítás után a Write és Writeln eljárások a beállított háttérszínnel fogják a képernyõre írni az adatokat. Ha a teljes képernyõre szeretnénk a beállításokat érvényesíteni, alkalmazzuk a ClrScr eljárást. TextColor eljárás: A karakterszín beállítása. Szintaxisa: TextColor(Karakterszín); A karakterszín beállításához használhatjuk a CRT unit beépített konstansai és azok értékét is. Tehát TextColor(BLUE) és TextColor(1) egyenértékû. A beállítás után a Write és Writeln eljárások a beállított karakterszínnel fogják a képernyõre írni az adatokat. TextMode eljárás: A képernyõ üzemmódjának (karakterfelbontás) beállítása. Szintaxisa: TextMode(Mod); Az uzemmod
beállításához használhatjuk a CRT unit beépített konstansait és azok értékét is. Tehát TextMode(Font8x8) és TextMode(256) egyenértékû. Az üzemmód megváltoztatását automatikus képernyõtörlés elõzi meg (mivel a karakteres videomemória mérete megváltozik). Window eljárás: A szöveges képernyõablak kijelölése. Szintaxisa: Window(X1, Y1, X2, Y2); Ahol X1 és Y1 a képernyõablak bal felsõ sarka, X2, Y2 a képernyõablak jobb alsó sarka. A sarkok koordinátái mindig a teljes képernyõre vontakozó abszolút koordináták. A szöveges képernyõn kijelölt alapértelmezett ablak mindig - a karakterfelbontáshoz igazodva teljes méretû. Tehát, ha a karakterfelbontás 80x25, akkor az ablak koordinátái: 1,1,80,25 Az ablak méretét és helyzetét a Window eljárás segítségével megváltozathatjuk. A szöveges képernyõn egyszerre csak egy ablakot használhatunk. Miután az aktív ablak méretét módosítottuk a további eljárások ezen az ablakon
belül relatív koordinátákkal dolgoznak. Be- és kiviteli mûveletek: A be- és kiviteli mûveletek csoportjába tartoznak azok az eljárások és függvények, melyek segítségével az I/O egységek kezelését tudjuk megvalósítani. Read eljárás: Egy vagy több változó értékének beolvasása a billentyûzetrõl. Szintaxis: Read(VáltozóLista); A változók értékeit ENTER választja el, tehát ha több változót olvasunk be egyszerre (ami szerintem nem célszerû), akkor minden érték után le kell ütni az ENTER-t. Readln eljárás: Egy vagy több változó értékének beolvasása a billentyûzetrõl. Szintaxis: Read(VáltozóLista); A változók értékeit vesszõvel választhatjuk el egymástól. ReadKey függvény: Egy karakter beolvasása a billentyûzetrõl. Szintaxisa: ch:= Keypressed; vagy Keypressed; A második esetben a beolvasott karakter értéke elvész. Keypressed függvény: Billentyûlenyomás ellenõrzése. Szintaxisa: Log:= Keypressed vagy
Keypressed; Visszatérési értéke logikai (Boolean) érték, amely True, ha történt billentyûlenyomás, egyébként False; Leggyakrabban ciklusfeltételben alkalmazzuk: Repeat Until Keypressed; Write eljárás: Adatok írása a képernyõre. Szintaxisa: Write(Adatlista); Az adatlistában szerepelhetnek konstans értékek, konstans azonosítók, illetve változóazonosítók. Megadhatjuk az egyes adatok után a mezõszélességet, amely meghatározza, hogy az adat hosszúságától függetlenül hány karakterhelyet foglalhat el a képernyõn. A mezõszélességet általában numerikus adatok esetében határozzuk meg, mivel alapértelmezés szerint az eljárás normál alakban írja a képernyõre a lebegõpontos számokat (változókat, konstansokat egyaránt). A mezõszélességet egy számmal adjuk meg, és kettõsponttal választjuk el az adattól. Pld: Writeln(A:10:2); ahol A Real tipusú változó. Writeln eljárás: Adatok írása a képernyõre. Szintaxisa:
Writeln(Adatlista); Használata hasonló az elõzõ eljáráshoz, közöttük a különbség, hogy a Writeln eljárás az adatsor képernyõre írása után elvégzi a soremelést, tehát a Writeln használata után a kurzor a következõ sor elejére ugrik. Képernyõkezelés: Ebbe a csoportba soroljuk azokat az eljárásokat, függvényeket, amelyek a kurzor mozgatásával, illetve a szöveges képernyõ sorainak, oszlopainak kezelésével kapcsolatos feladatokat látják el. ClrScr eljárás: A képernyõ törlése. Szintaxisa: ClrScr; A képernyõ tartalmát törli, a kurzor az elsõ oszlop elsõ sorába ugrik. Az eljárás figyelmebe veszi a beállított háttérszínt ("iylen színûre törli a képernyõt"). ClrEol eljárás: Az eljárás törli az aktuális (a kurzort tartalmazó) sor tartalmát, a kurzor pozíciójától a sor végéig. Szintaxisa: ClrEol; WhereX függvény: A kurzort tartalmazó oszlop sorszáma. Szintaxisa: X:= WhereX; A függvény
visszatérési értéke Byte tipusú. WhereY függvény: A kurzort tartalmazó sor sorszáma. Szintaxisa: Y:= WhereY; A függvény visszatérési értéke Byte tipusú. DelLine eljárás: A kurzort tartalmazó sor törlése. Szintaxisa: DelLine; A kurzor sorától lentebb lévõ sorok felfelé mozdulnak. Hasonló a ClrEol eljáráshoz, de a DelLine esetén a teljes sor törlére kerül, míg a ClrEol eljárás csak a sor tartalmát módósítja. InsLine eljárás: Üres sor beszúrása. Szintaxisa: InsLine; A kurzort tartalmazó sor felé egy üres sort szúr be. A lentebb lévõ sorok lefelé mozdulnak Egyéb eljárások, függvények: Ebbe a csoportba tartoznak a hangkeltéssel kapcsolatos, illetve a programfutás felfüggesztését segítõ eljárások. Delay eljárás: A program futásának felfüggesztése. Szintaxisa: Delay(Idotartam); Az idõtartamot ezredmásodpercben kell megadni. Az Idotartam Word tipusú változó vagy konstans. Sound eljárás: A belsõ hangszóró
bekapcsolása. Szintaxis: Sound(Frek); A Frek egy Word tipusú változó vagy konstans, a hangmagasságot határozza meg. NoSound eljárás: A belsõ hangszóró kikapcsolása. Szintaxisa: NoSound; Ha a Sound eljárással a belsõ hangszórót bekapcsoltuk, minden esetben alkalmazni kell a NoSound eljárást, mert a hangszóró mindaddig nem kapcsol ki, amíg ezt az eljárást meg nem hívjuk (vagy a gépet ki nem kapcsoljuk). 12. tétel: A Graph unit fontosabb beépített konstansai, függvényei, eljárásai; használata A szöveges felület kezelésénél már volt szó arról, milyen fontos része a programoknak a felhasználói felület. Ennek kialakítása során két lehetõség közül választhatunk, a szöveges és a grafikus felület között. A CRT unit és a szöveges felület használatáról a 11 tételben részletesen volt szó. A grafikus felülettel rendelkezõ programok lényegesen meghaladják a szöveges felülettel rendelkezõk lehetõségeit, a megjelenés
esztétikusabb, szebb, áttekinthetõbb. A grafikus felület egyetlen hátránya, hogy kialakítása során sokkal inkább figyelembe kell venni a számítógép hardware kiépítettségét, mint szöveges felület esetén. A 80x25 karakterfelbontást szöveges képernyõ esetén - már a CGA monitorok is képesek voltak teljesíteni, a grafikus programok esetében sokkal nagyobb eltérésekkel kell számolni az egyes képernyõtipusok esetén. Manapság az SVGA monitorok széleskörû elterjedése miatt a különbségek már némelyest elhalványulni látszanak, de még mindig nagy odafigyelés szükséges egy olyan program megírásánál, amelyet hardvertõl függetlenné szeretnénk tenni. A grafikus képernyõn rasztergrafikát használhatunk, ami azt jelenti, hogy a megjelenõ kép képpontokból (pixel) épül fel. A felbontás megmutatja, hogy a kép vízszintes és függõleges irányban hány képpontból épül fel. A különbözõ képernyõtipusok felbontása különbözõ
lehet Grafikus felbontások képernyõtipusok szerint: Monitortipus Felbontás Színek száma Vezérlôprogram Hercules 720x348 2 HERC.BGI 640x200 2 320x200 4 EGA 640x350 16 EGAVGA.BGI VGA 640x480 16 EGAVGA.BGI IBM8514 640x480 256 IBM8514.BGI PC3270 720x350 2 PC3270.BGI CGA CGA.BGI A grafikus képernyõ kezeléséhez ismernünk kell a grafikus képernyõn alkalmazott koordináta rendszert is. A koordináta rendszer hasonló a szöveges képernyõhöz, az alapértelmezés szerinti origo (kiindulópont) a képernyõ bal felsõ sarkában van. Eltérés azonban a szöveges képernyõhöz képest, hogy míg karakteres felületen a bal felsõ sarok koordinátái (1;1) voltak, grafikus felületnél a (0;0) koordinátájú pont lesz az origo. A bal alsó sarok koordinátái is eltérõek a szöveges felülethez képest, a felbontás értékénél eggyel kisebb lesz a maximális érték. Egy példán keresztül szemléltetve egy VGA képernyõ esetében a bal
felsõ sarok koordinátái: (0,0); míg a jobb alsó sarok koordinátái: (639,479) lesznek. A Turbo Pascal feljleszõi környezet a fenti képernyõtipusokhoz beépített vezérlõmodulokat tartalmaz. Ezek az állományok a BGI könyvtárban találhatók, kiterjesztésük BGI Ezek a modulok (vezérlõprogramok) hivatottak elfedni a különbözõ hardver eszközök sajátosságait, és egységes felületet biztosítani a kompatibilis eszközök kezelése során (egy S3 Trio 64 V+ és egy EGA vezérlõ ugyanazt a vezérlõprogramot használja, az EGAVGA.BGI állományt) A Pascal programok tehát egy egységes felületen keresztül kezelik a grafikus képernyõt. A grafika használatához szükséges, hogy a programok elérjék a grafikus vezérlõprogramot (.BGI) és a Graph unit-ot (GRAPHTPU), amely a grafikus felület kezeléséhez szükséges eljásokat, függvényeket, konstanokat tartalmazza. A grafikus felületet használó Pascal programokban tehát meg kell hívni a Graph
unit-ot a Uses Graph; utasítás segítségével. Azonban - a szöveges móddal ellentétben - ez még nem elegendõ a grafikus képernyõ használatához: mivel a monitor alapértelmezett üzemmódja a szöveges üzemmód, a programban kell elhelyezni a grafikus módot inicializáló utasítás(oka)t (InitGraph), csak ezután kezdhetjük el a grafikus felületen végzett munkát. A program végén nagyon ajánlott a grafikus üzemmódból visszaváltani szöveges üzemmódba a CloseGraph eljárás segítségével. A Graph unit lehetõségeinek áttekintése: A Graph unit igen sok (több, mint 70) eljárást, függvényt tartalmaz a grafikus felület kezeléséhez, emellett igen sok beépített konstans is rendelkezésünkre áll. Az eljárásokat, függvényeket hét csoportba sorolhatjuk: - a grafikus rendszer vezérlése - rajzolás és festés - képernyõ és ablakkezelés - szövegek kezelése - színek kezelése - hibakezelés - grafikus információk lekérdezése A Graph
unit beépített konstansai: Itt csak az általánosan használt konstansok ismertetésére szorítkoznék, ami az egyes eljárásoknál speciális és fontos azt ott fogom megemlíteni. A beépített konstansok egy fontos csoportja a színeket deklaráló konstans értékek. Megegyeznek a CRT unit esetében tárgyalt elnevezésekkel és értékekkel, tehát a BLACK=0 az elsõ, a WHITE=15 az utolsó használható szín. Néhány képernyõtípus azonban nem képes mind a 16 színt kezelni, ezért a Graph unit hozzáadott még egy konstans értéket a CRT unitnál megismertekhez, ez a MAXCOLOR nevet viseli, és az adott képernyõn megjeleníthetõ legnagyobb színértéket jelenti. A másik fontos csoport a hibakódokhoz tartozó konstans értékek csoportja, ezek alapján tudjuk kideríteni, milyen hiba lépett fel a program futása során. Ezek leírása részletesen megtalálható a Turbo Pascal Help-ben, most csak a fontosabbakat emítem: grOK=0 - nincs hiba grNoInitGraph=-1 - nem
hívtuk meg az InitGraph eljárást, vagy az sikertelenül fejezõdött be grFileNotFound=-3 - nem található a .BGI állomány grError=-11 - grafikus hiba (hardware) grIOError=-12 - grafikus I/O hiba (hardware) A Graph unit eljárásai, függvényei: A grafikus rendszer vezérlése: InitGraph eljárás: A grafikus képernyõ inicializálása, átváltás szöveges módról grafikus módra. Szintaxisa: InitGraph(GD, GM, Utvonal); GD: Integer - változó vagy konstans, amely tartalmazza a grafikus vezérlõ (képernyõ) tipusát. GM: Integer - változó vagy konstans, amely tartalmazza a grafikus üzemmódra jellemzõ értéket (lásd késõbb) Utvonal: String - változó vagy konstans, amely tartamazza a BGI állomány elérési útvonalát. Az InitGraph eljárás számára meghatározhatjuk a képernyõtipust (GD paraméter) konstansként is, de ez nem tanácsos. Ha a képernyõtípust konstansként adjuk meg, akkor programunk máris elveszti hordozhatóságát, mivel minden
más képernyõtipus esetén hibaüzenettel leáll. Egy gyakran alkalmazott lehetõség, hogy az InitGraph eljárással kiderítjük a képernyõ tipusát. Ha ezzel a lehetõséggel szeretnénk élni, akkor a GD változónak a DETECT értéket kell adni az InitGraph hívása elõtt (példa késõbb). A különbözõ képernyõtipusok felülrõl kompatibilisek egymással, tehát egy VGA monitoron minden gond nélkül alkalmazhatjuk a CGA monitorokra jellemzõ felbontást. Azt, hogy a képernyõnkön milyen felbontást szerenénk alkalmazni, az InitGraph második paramétere (GM) határozza meg. A harmadik paraméter nagyon fontos. Meg kell határozni az InitGraph számára, hogy melyik meghajtón, milyen könyvtárban keresse a BGI kiterjesztésû állományokat. A példában olyan módon hívjuk meg az InitGraph eljárást, hogy bármilyen grafikus vezérlõt képes legyen inicializálni: Uses Graph; Var GD, GM: Integer; Begin GD:= DETECT; InitGraph(GD, GM, C:TPBGI); . CloseGraph; End.
Ha a GM változónak nem határozzuk meg az értékét, az InitGraph automatikusan a képernyõn használható maximális felbontást fogja alkalmazni. DetectGraph eljárás: A grafikus vezérlõ felderítése. Szintaxisa: DetectGraph(GD, GM); Egyenértékû az InitGraph eljárás alkalmazásával GD:= DETECT paraméter mellett. Az eljárás meghívása után a GD és GM változók értéke a grafikus hardvernek megfelelõen be lesz állítva, tehát a paraméterek csak változók lehetnek. Akkor célszerû alkalmazni, ha a program futását szeretnénk a hardvertõl függõvé tenni. Példa: Ha a gépen nem VGA vezérlõt találunk, akkor hibaüzenettel kilépünk a programból. Uses Graph; Var GD, GM: Integer; Begin DetectGraph(GD, GM); If GD<VGA then Begin Writeln(A program futtatasahoz VGA vezerlore van szukseg.); Halt; End; InitGraph(GD, GM, C:TPBGI); . CloseGraph; End. CloseGraph eljárás: A grafikus mód lezárása. Szintaxisa: CloseGraph; Az eljárás segítségével
befejezhetjük a grafikus mód használatát és visszatéhetünk szöveges üzemmódba. Mivel a szöveges üzemmód az alapértelmezett, ezt az eljárást tanácsos minden grafikus program végén meghívni. Rajzolás és festés: Circle eljárás: Kör rajzolása. Szintaxisa: Circle(X, Y, Radius); X, Y: Integer; - A kör középpontja Radius: Word; - A kör sugara Az eljárás egy teljes kört rajzol, melynek középpontját X,Y, sugarát Radius határozza meg. Mindhárom paraméter lehet konstans és változó egyaránt. Ellipse eljárás: Ellipszisív rajzolása. Szintaxisa: Ellipse(X, Y, KSzog, VSzog, RX, RY); X, Y: Integer; - Az ellipszis középpontja KSzog, VSzog: Word; - Az ellipszisív kezdõ és végszöge RX, RY: Word; - A vízszintes és a függõleges sugár Bármely paraméter lehet változó és konstans egyaránt. A szögek értékét fokokban kell megadni A grafikus képernyõn a szögek értelmezése a következõképpen történik: Ha a teljes kört egy órához
hasonlítjuk: 0 fok - 3 óra; 90 fok - 6 óra; 180 fok - 9 óra; 270 fok - 12 óra; 360 fok - 3 óra. Tehát a szögek az óramutató járásával ellentétes irányban növekednek Figyelni kell arra, hogy a szögfüggvények radiánban, a rajzoló eljárások fokban értelmezik a megadott szögeket. GetX, GetY függvény: A grafikus kurzor koordinátáinak lekérdezése. Szitnaxisa: X:= GetX; vagy Y:= GetY; Ez a két paraméter nélküli függvény lekérdezi és visszaadja a grafikus kurzor (aktuális képpont) koordinátáit. Visszatérési értékük Integer tipusú A grafikus kurzor a képernyõn nem látható Line eljárás: Szakasz (egyenes) rajzolása. Szintaxisa: Line(X1, Y1, X2, Y2); X1, Y1: Integer; - A szakasz kezdõpontjának koordinátái X2, Y2: Integer; - A szakasz végpontjának koordinátái Az eljárás a koordinátapárok között rajzol egy egyenest (szakaszt). A paraméterek egyaránt lehetnek változók és konstansok. A koordináták abszolút koordináták
LineRel eljárás: Szakasz rajzolása az aktuális pontból, relatív koordinátákkal. Szintaxisa: LineRel(RX, RY); RX, RY: Integer; - A szakasz végpontjának relatív koordinátái vízszintsen és függõlegesen. Az eljárás az aktuális pontból (ahol a grafikus kurzor áll) egy egyenest rajzol, amelynek végpontját a relatív koordináták határozzák meg. Tehát a végpont koordinátái mindig annak függvényében kerülnek megállapításra, hogy hol áll a grafikus kurzor. Az eljárás a grafikus kurzort áthelyezi az RX, RY által meghatározott pontba. Példa: A Feladatok lapon Grafikus programok: Rajzoljunk szabályos hatszöget. LineTo eljárás: Szakasz rajzolása az aktuális pontból, abszolút koordinátákkal. Szintaxisa: LineTo(X, Y); X, Y: Integer; - A szakasz végpontjának abszolút koordinátái. Az eljárás az aktuális pontból egy egyenest rajzol, amelynek végpontja az X,Y által meghatározott pont. A végpont helyzetét a paraméterek abszolút
pontként határozzák meg, tehát az aktuális pozíció (grafikus kurzor koordinátái) csak a szakasz hosszát befolyásolja. Az eljárás a grafikus kurzort áthelyezi az X,Y pontba. MoveRel eljárás: Az aktuális képpont (grafikus kurzor) mozgatása relatív koordinátákkal. Szintaxisa: MoveRel(RX, RY); RX, RY: Integer; - A relatív koordináták vízsztintes és függõleges irányban MoveTo eljárás: Az aktuális képpont (grafikus kurzor) mozgatása abszolút koordinátákkal. Szintaxisa: MoveTo(X, Y); X, Y: Integer; - Az abszolút koordináták vízszintes és függõleges irányban Példa: Rajzoljunk egy egyenlõ szárú háromszöget. Uses Graph, CRT; Var GD, GM: Integer; Begin DetectGraph(GD, GM); InitGraph(GD, GM, C:TPBGI); MoveTo(GetMaxX div 2, 20); LineRel(-50, 120); MoveRel(50, -120); LineRel(50, 120); LineRel(-100, 0); Repeat Until Keypressed; CloseGraph; End. Rectangle eljárás: Téglalap rajzolása. Szintaxisa: Rectangle(X1, Y1, X2, Y2); X1, Y1, X2, Y2: Integer;
- A téglalap sarkainak koordinátái. A koordinátákra semmilyen megkötés nem vonatkozik, tehát nem állnak egymással kapcsolatban. A következõ két eljárás egyaránt helyesen mûködik: Rectangle(100, 100, 400, 250); Rectangle(420, 270, 120, 120); FillEllipse eljárás: Színnel kitöltött teljes ellipszis rajzolása. Szintaxisa: FillEllipse(X, Y, RX, RY); X, Y: Integer; - Az ellipszis középpontja RX, RY: Word; - A vízszintes és a függõleges sugár Az eljárás hasonlóan mûködik az Ellipse eljáráshoz, közöttük a különbség, hogy a FillEllipse eljárás egy színnel kitöltött ellipszist rajzol. A kitöltés színét a SetFillStyle eljárás alkalmazásával határozhatjuk meg (bõvebben késõbb). Az eljárás csak teljes ellipszist tud rajzolni. FloodFill eljárás: Terület kitöltése. Szintaxisa: FloodFill(X, Y, KSzin); X,Y: Integer; - A kitöltendõ terület egy pontja KSzin: Word; - A területet határoló vonal színe A FloodFill eljárás
kétféleképpen alkalmazható: - Ha az X,Y paraméter által meghatározott pont egy - KSzin által meghatározott színû kerettel zárt alakzat belsejébe esik, akkor az alakzatot kitölti a SetFillStyle által meghatározott kitöltési színnel. - Ha az X,Y paraméter által meghatározott pont nem egy zárt alakzat területére esik, akkor az eljárás a teljes képernyõt befesti a SetFillStyle eljárás által meghatározott színnel - kivételt képeznek a festés alól azok a zárt alakzatok, melyek KSzin által meghatározott színû kerettel határoltak. Nézzünk két példát a fentiekre: Program Pelda1; Uses Graph; Var GD, GM: Integer; Program Pelda2; Uses Graph; Var GD, GM: Integer; Begin GD:= DETECT; InitGraph(GD, GM, C:TPBGI); Begin GD:= DETECT; InitGraph(GD, GM, C:TPBGI); SetFillStyle(SolidFill, LightBlue); SetColor(Red); Rectangle(100, 100, 400, 250); Rectangle(420, 270, 120, 120); FloodFill(10, 10, Red); SetFillStyle(SolidFill, LightGreen); SetColor(Red);
Rectangle(100, 100, 400, 250); Rectangle(420, 270, 120, 120); FloodFill(121, 121, Red); Readln; CloseGraph; End. Readln; CloseGraph; End. A Pelda1 programban a két téglalap közös részét töltjük ki világoszöld színnel, a Pelda2 programban a két téglalap fekete, a képernyõ többi része világoskék színû lesz. PutPixel eljárás: Egy képpont kifestése adott színnel. Szintaxisa: PutPixel(X, Y, Szin); X, Y: Integer; - A képpont koordinátái (konstans és változó egyaránt lehet a paraméter) Szin: Word; - A képpont színe (konstans és változó egyaránt lehet a paraméter) GetPixel függvény: Egy képpont színének lekérdezése. Szintaxisa: Szin:= GetPixel(X, Y); X, Y: Integer; - A képpont koordinátái (konstans és változó egyaránt lehet a paraméter) A függvény visszatérési értéke Word tipusú. Pixelgrafikához tartozó példa a Feladatok lapon található Grafikus programok közül a csigavonal rajzolása. Hibakezelés: GraphResult függvény:
Grafikus hiba lekérdezése. Szintaxisa: E:= GraphResult; A függvény visszatérési értéke Integer. Alkalmazása a DOS unitnál tárgyalt DosError változóhoz hasonló. A $I direktíva kikapcsolt állapota mellett lehetõséget biztosít a grafikus hibák saját eljárással történõ lekezelésére. Példa: Uses Graph; Var GD, GM: Integer; Begin {$I-} GD:= DETECT; InitGraph(GD, GM, ); if GraphResult<0 then Begin Writeln(Grafikus hiba.); Halt; End; . CloseGraph; End. GraphErrorMsg függvény: A függvény értéke a paraméterként megadott grafikus hibakódhoz tartozó angol nyelvû hibaüzenet. Szintaxisa: S:= GraphErrorMsg(HibaKod); HibaKod: Integer; - a grafikus hiba kodja. Példa: Uses Graph; Var GD, GM: Integer; Begin {$I-} GD:= DETECT; InitGraph(GD, GM, ); if GraphResult<0 then Begin Writeln(GraphErrorMsg(GraphResult)); Halt; End; . CloseGraph; End. Képernyõ- és ablakkezelés: ClearDevice eljárás: Ez a paraméter nélküli eljárás törli a grafikus
képernyõt. Szintaxisa: ClearDevice; SetViewPort eljárás: Ablak definiálása a grafikus képernyõn. Szintaxisa: SetViewPort(X1, Y1, X2, Y2, Clip); X1, Y1: Integer; - Az ablak bal felsõ sarkának koordinátái X2, Y2: Integer; - Az ablak jobb alsó sarkának koordinátái. Clip: Boolean; - A vágás módjának meghatározása. False: nincs vágás, azaz a rajz "kilóghat" az aktív grafikus ablakból, ha erre van lehetõség. True: vágás az ablak határainál, azaz a rajz csak az ablak határain belül lehet, ha ezen kívülre kerülne, akkor az ablak szélénél a rajz vágásra kerül, tehát csak annyi látszik belõle, amennyi az ablakon belülre esik. Ha a grafikus ablakot újradefiniáljuk, az elõzõ ablak tartalma is megmarad. ClearViewPort eljárás: Az aktív grafikus ablak törlése (rajzok törlése az ablakból). Szintaxisa: ClearViewPort; Példa: Uses Graph, CRT; Var GD, GM: Integer; S: String; Begin GD:= DETECT; InitGraph(GD, GM, C:TPBGI);
SetFillStyle(SolidFill, LightGray); Bar(0, 0, GetMaxX, GetMaxY); SetViewPort(5, 5, GetMaxX-5, GetMaxY-5, False); ClearViewPort; SetFillStyle(SolidFill, RED); FillEllipse(GetMaxX div 2, GetMaxY div 2, GetmaxX div 3, GetMaxY div 4); Repeat Until Keypressed; CloseGraph; End. Színek kezelése: SetColor eljárás: Az aktuális szín beállítása. Szintaxisa: SetColor(Szin); Szin: Word; - a kívánt szín. Változó és konstans egyaránt lehet Az eljárás által beállított szín lesz az a szín, amelyet a rajzoló eljárások használnak vonalak, körvonalak húzásához. SetBkColor eljárás: Az aktuális háttérszín beállítása. Szintxisa: SetBkColor(Szin); Szin: Word; - a kívánt szín. Változó és konstans egyaránt lehet Az eljárás beállítja az aktív grafikus ablak háttérszínét. A beállítás az eljárás hívása után automatikusan megtörténik - szemben a CRT unit hasonló eljárásával, ahol ehhez egy képernyõtörlésre volt szükség. GetColor függvény:
Az aktuális szín lekérdezése. Szintaxisa: Szin:= GetColor; Szin: Word; - az aktuális szín. GetBkColor függvény: Az aktuális háttérszín lekérdezése. Szintaxisa: Szin:= GetBkColor; Szin: Word; - az aktuális háttérszín. SetFillPattern eljárás: A kitöltési minta beállítása. Szintaxisa: SetFillPattern(Minta, Szin); Minta: FillPatternType; - a kitöltési minta. Általában konstansként adjuk meg Szin: Word; - a minta színe. A minta meghatározásakor általában a Graph unit elõre definiált konstnsait használjuk, amelyek a következõk: EmptyFill, SolidFill, LineFill, LtSlashFill, SlashFill, BkSlashFill, LtBkSlashFill, HatchFill, XHatchFill, InterleaveFill, WideDotFill, CloseDotFill. A szin meghatározása során a már megismert konstansokat alkalmazhatjuk. Szöveg kiírása a képernyõre: OutText eljárás: Szöveg kiírása az aktuális pontba (a grafikus kurzor pozíciójába). Szintaxisa: OutText(S); S: String; - a kiírandó szöveg. OutTextXY
eljárás: Szöveg kiírása a képernyõ meghatározott pontjába. Szintaxisa: OutTextXY(X, Y, S); X, Y: Integer; - a szöveg pozíciója. S: String; - a kiírandó szöveg. Az eljárás - alapértelmezésben - a szöveg bal felsõ sarkát az X, Y által meghatározott pontba igazítja. SetTextStyle eljárás: A szöveg stílusának beállítása. Szintaxisa: SetTextStyle(BTip, Irany, Meret); BTip: Word; - a betûtipust határozza meg. Általában konstans értéket használunk, de lehet változó is. Irany: Word; - a szöveg kiírásának irányát határozza meg, lehet vízszintes és függõleges irányt használni. Meret: CharSizeType; - a szöveg méretét határozza meg. Tulajdonképpen egy egész szám, amely 1 és 10 közé esõ értéket vehet fel. Változó és konstans egyaránt lehet A BTip paraméter lehetséges értékei: DefaultFont, TriplexFont, SmallFont, SansSerifFont, GothicFont. Az Irany paraméter lehetséges értékei: HorizDir - vízszintes felirat; VertDir -
függõleges felirat. TextWidth függvény: A szöveg szélességét határozza meg pixelekben, figyelembe véve a beállított szövegstílust. Szintaxisa: N:= TextWidth(S); S: String; - a szöveg. Visszatérési értéke Word; TextHeight függvény: A szöveg magasságát határozza meg pixelekben, figyelembe véve a beállított szövegstílust. Szintaxisa: N:= TextHeight(S); S: String; - a szöveg. Visszatérési értéke Word; A fenti eljárások, függvények alkalmazására példa a Feladatok lapon található az Alprogramok használatánál (3-as feladat). Grafikus információk lekérdezése: GetX függvény: Az aktuális képpont (grafikus kurzor) vízszintes koordinátájának lekérdezése. Szintaxisa: X:= GetX; Visszatérési értéke Integer; GetY függvény: Az aktuális képpont (grafikus kurzor) függõleges koordinátájának lekérdezése. Szintaxisa: Y:= GetY; Visszatérési értéke Integer; GetMaxX: A grafikus képernyõ vízszintesen utolsó pontjának
lekérdezése (a grafikus képernyõ szélességének lekérdezése). Szintaxisa: Szel:= GetMaxX; Visszatérési értéke Integer; GetMaxY: A grafikus képernyõ függõlegesen utolsó pontjának lekérdezése (a grafikus képernyõ magasságának lekérdezése). Szintaxisa: Mag:= GetMaxY; Visszatérési értéke Integer; A grafikus információk lekérdezésére igen nagy szükségünk van, amikor grafikus hardvertõl független programot szeretnénk írni. Az alábbi példaprogram VGA képernyõre van optimalizálva, de mûködik minden más képernyõtipus esetén is. uses Graph, CRT; var GD, GM: Integer; I: Word; C: Word; N, M: Byte; begin GD:= DETECT; InitGraph(GD, GM, C:TPBGI); I:= 0; C:= 0; M:= GetMaxY div 5; Repeat SetColor(C); For N:= (GetMaxX div 5) downto (GetMaxX div 6) do begin M:= N div 2; Ellipse(GetMaxX div 2, GetMaxY div 2, I*15, (I+1)15, N, M); end; Inc(I); Inc(C); if C=GetMaxColor-1 then C:= GetMaxColor-(GetMaxColor-1); Until I=360; Repeat Until Keypressed;
CloseGraph; end. 13. tétel: A DOS unit fontosabb beépített konstansai, függvényei, eljárásai; használata: A DOS unit szerepe, jelentõsége a Pascal programok készítése során: Minden programozási nyelv esetén megfigyelhetõ a fejlesztõrendszer, az adott nyelv és az operációs rendszer szoros kapcsolata. Igaz ez a megállapítás minden operációs rendszerre és minden programozási nyelvre. Akár a C nyelvet és a Linux operációs rendszert választjuk, akár a Delphi fejlesztõrendszert, amely Windows 9x alatti programok készítésére alkalmas, akár a Turbo Pascal és a DOS kapcsolatát vizsgáljuk meg, mindenütt tapasztalható, hogy a programozási nyelv eszközei és az operációs rendszer szoros kapcsolatban állnak egymással. A programozás során felmerülhetnek olyan problémák, melyek csak az operációs rendszeren keresztül oldhatók meg. Nagyon jó példa erre a filekezelés Találkozhatunk azonban olyan estekkel is, amikor az operációs
rendszer alacsonyszintû szolgáltatásait (megszakítások) kell igénybe vennünk, vagy éppen egy BIOS hívás segítségével tudjuk megoldani a feladatot. Az utóbbi az esetben is az operációs rendszeren keresztül tudjuk elérni a BIOS szubrutinjait. A Turbo Pascal környezetben ezt a kapcsolatot a DOS unit biztosítja, ez a unit tartalmazza azokat az eljárásokat, függvényeket, konstansokat, amelyek szükségesek az operációs rendszer szolgáltatásainak igénybevételéhez. Ezen túlmenõen a System unit egy része is ezt a kapcsolatot hivatott erõsíteni, a legalapvetõbb problémák a System unit eljárásaival, függvényeivel oldhatók meg. A System unit fontosabb változói, függvényei, eljárásai: Az egyik legalapvetõbb lehetõség az operációs rendszer és a program között megvalósított paraméterátadás. Lehetõségünk van arra, hogy a programunkban olyan paramétereket használjunk, melyeket a felhasználó a program indításakor a parancssorban
ad meg (Hasonlóan, mint a DIR parancs paramétereit, pld: DIR C:WINDOWS). Jó példa erre, amikor egy olyan programot szeretnénk írni, amely megkeres egy állományt a lemezen. Ha a program indításakor megadta a felhasználó a keresett állomány nevét, akkor azonnal elkezdjük a keresést, ha nem adott meg nevet, akkor a keresés elõtt rákérdezünk, mit szeretne keresni. A paraméterátadás problémáját a System unitban oldották meg, két függvény segítségével. ParamCount függvény: A program paramétereinek száma. Szintaxisa: PSzam:= ParamCount; A függvény Word tipusú értékkel tér vissza. A függvény a parancssorban megadott paramétereket veszi figyelembe. Külön paraméternek számítanak az egymástól szóközzel elválasztott értékek. Ezalól csak az elsõ paraméterként megadott érték kivétel, melyet bármilyen DOS által elfogatott karakterrel elválaszthatunk a program nevétõl. Tehát ha a parancssor: PARPROBA/W /E /D ahol PARPROB.EXE a
futtatható állomány, a ParamCount által visszaadott érték 3 lesz ParamStr függvény: A program paramétereinek értéke. Szintaxisa: Par1:= ParamStr(ParSzam); A függvény String tipusú értékkel tér vissza. A ParSzam határozza meg, hogy hanyadik paramétert kérdezzük le. A ParSzam Word tipusú változó vagy konstans lehet Tehát ha a parancssor: PARPROBA/W /E /D ahol PARPROB.EXE a futtatható állomány, a ParamStr(1) függvény értéke: /W, ParamStr(2) értéke: /E, ParamStr(3) értéke: /D. Ha a ParamStr függvényt a 0 paraméter lekérdezésével hívjuk meg, a visszaadott érték a futtatható állomány neve és elérési útja lesz. Ha a ParamCount függvény által szolgáltatott értéknél nagyobb számot alkalmazunk, tehát egy nem létezõ paraméterre hivatkozunk, a ParamStr függvény üres string értéket ad vissza A másik szintén a System unit által megoldott probléma az állományok és könyvtárak kezelése. Az állományok kezelése nem
kapcsolódik szorosan ide (részletezve a 10-es tételben), bár az operációs rendszer szolgáltatásai közé tartozik. A könyvtárkezelõ eljárások, függvények tárgyalása (rövidsége miatt is) ide tartozhat. (ZKerekes szerint) ChDir eljárás: Könyvtárváltás. Szintaxisa: ChDir(Konyvtar); Az eljárás paramétereként meg kell adni a könyvtár teljes elérési útját, meghajtóval együtt. Pld: ChDir(C:WINDOWS); MkDir eljárás: Könyvtár létrehozása. Szintaxisa: MkDir(Konyvtar); Az eljárás paramétereként meg kell adni a könyvtár teljes elérési útját, meghajtóval együtt. Pld: ChDir(C:SAJAT); RmDir eljárás: Könyvtár törlése. Szintaxisa: RmDir(Konyvtar); Az eljárás paramétereként meg kell adni a könyvtár teljes elérési útját, meghajtóval együtt. Pld: ChDir(C:VACAK); Az eljárás alkalmazása során figyelembe kell venni a DOS alatt végzett könyvtártörlés szabályait, tehát itt is csak olyan üres könyvtárat tudunk törölni, amely
éppen nem az aktuális könyvtár, és az aktuális könyvtárnál alsóbb szinten helyezkedik el a könyvtárrendszerben. GetDir eljárás: Aktuális könyvtár lekérdezése. Szintaxisa: GetDir(Drive, Konyvtar); Az eljárás paraméterei: Drive: Byte - Meg kell adnunk annak a meghajtónak a számát, amelyiken szeretnénk az aktuális könyvtárat lekérdezni. Számozás: 0 - aktuális meghajtó; 1 - A: meghajtó; 2 - B: meghajtó; 3 - C: meghajtó; . Könyvtar: String - Minden esetben egy változó. Ebben a változóban kapjuk vissza a könyvtár elérési útját és nevét. A DOS unit fontosabb beépített konstansai, változói: Ahogy az elõbb láthattuk a System unit is tartalmaz néhány eljárást, függvényt, amely az operációs rendszerrel való kapcsolat létesítését segíti, de az ilyen célú alprogramok, változók, konstansok, típusok legnagyobb része a DOS unitban kapott helyet. A DOS unit beépített konstansai: A beépített konstansok legfontosabb
csoportját a file-attribútumokhoz kapcsolódó konstansok alkotják, amelyeket felhasználhatunk az olyan eljárásoknál (értelemszerûen), amelyek a file-ok attribútumát módosítják, de fontos szerepük van a file-ok keresése során is. A konstansok: Konstans Érték Attribútum ReadOnly $01 Csak olvasható Hidden $02 Rejtett SysFile $04 Rendszerfile VolumeID $08 Lemezcimke Directory $10 Könyvtár Archive $20 Archiválandó AnyFile $3F Bármilyen file A DOS unit beépített változói: A DOS unit legfontosabb beépített változója a DosError változó. Mint a nevébõl is látszik a hibakezelés során játszik jelentõs szerepet. A DosError változó segítségével tudjuk figyelni, hogy a DOS unit eljárásai sikeresen befejezõdtek-e. Természetesen egy operációs rendszer szintû hiba - hiszen ezek az eljárások ilyen hibával szakadnak meg - alapértelmezés szerint megszakítaná a program futását, ezért ha magunk szeretnénk ezeket a
hibákat kezelni, akkor az {$I}direktívát kell alkalmaznunk. Ha a saját hibakezelést választjuk (egyes esetekben célszerû), nagyon figyelmesen kell eljárnunk, mert egy lekezeletlen hiba a program megszakításánál súlyosabb következményekkel járhat. Nézzünk egy példát az eddig leírtakra Hozzunk létre a C: meghajtón egy könyvtárat, amelynek neve legyen SAJAT. Automatikus hibakzelés alkalmazása: Uses DOS; Begin MkDir(C:SAJAT); Writeln(A C:SAJAT konyvtarat letrehoztuk); End. Ha valamilyen hiba lép fel a program futtatása során - mondjuk a könyvtár már létezik - futás közbeni hiba lép fel, a program angolul kiirja a hibauzenetet és megszakad. Saját hibekezelés alkalmazása: Uses DOS; Begin {Magunk szeretnénk a hibákat lekezelni, } {ezért az I direktívát kikapcsoljuk} {$I-} MkDir(C:SAJAT); {$I+} {Ha hiba lépett fel tenni kell valamit} If DosError<0 then Begin {Hibauzenet kiirasa} Writeln(Hiba lepett fel a konyvtar letrehozasa soran); {A
program futasanak megszakitasa} Halt(1); End; {Ha nem lepett fel hiba} Writeln(A C:SAJAT konyvtarat letrehoztuk); End. A második megoldás esetén a hibakezelést magunk végezzük el. A hibát okozó mûvelet elõtt az I direktíva kikapcsolásával letiltottuk a futás közben fellépõ I/O hibák automatikus kezelését. A kritikus mûvelet után célszerû a direktívát bekapcsolni, mivel ez az alapértelmezett érték, és a programtovábbi részében is elõfordulhatnak hasonló hibák, esetleg olyanok, amelyekre nem számítunk, és nem is gondoljuk, hogy ezeket le kellene kezelnünk. A mûvelet után ellenõrizzük a DosError változó értékét, amely a mûvelet sikeres befejezése után 0, egyébként a hiba kódját tartalmazza. Ennél az egyszerû programnál a hibakezelés a hibaüzenet kiirásából és a program megszakításából áll, de ennél sokkal bonyolultabb esetek is elõfordulhatnak. A Dos hibák kódjai: Ezeket az értékeket veheti fel a DosError
változó futási hiba esetén. 0: nincs hiba 2: a file nem létezik 3: az útvonal nem létezik 5: a hozzáférés nem megengedett 6: helytelen file-kezelés 8: kevés a memória 10: helytelen környezet (DOS környezeti változó beállításánál) 11: helytelen formátum 18: nincs több file (keresésnél) A DOS unit beépített típusai: A beépített típusok egyik része a DOS unit eljárásai, függvényei által elõállított eredmények kezelését teszi egyszerûbbé, másik része a megszakításkezelést, a regiszterek elérését, kezelését segíti. Registers: record A regiszterek eléréséhez a deklarációs részben fel kell vennünk egy változót, amelynek típusa Registers lesz. A Registers típus a DOS unit beépített típusa, rekord tipus Mezõi segítségével elérhetõvé válnak a processzor regiszterei. Használata: Uses DOS; Var R: Registers; Begin . R.AH:= $09; R.AH:= 65; . End. A Registers rekord mezõi a processzor regiszterei: AX, BX, CX, DX, BP,
SI, DI, DS, ES, Flags: Word; - 16 bites regiszterek (bõvebben lásd Assembly-nél) AH, AL, BH, BL, CH, CL, DH, DL: Byte; - 8 bites regiszterek, az elõzõ regiszterek alsó és felsõ byte-ja DateTime: record Az idõ és a dátum kezelését segítõ típus. Mezõi: Year, {év} Month, {hónap} Day, {nap} Hour, {óra} Min, {perc} Sec: Word; {másodperc} SearchRec: record A file-ok keresése során alkalmazott FindFirst és FindNext eljárások használnak ilyen tipusú változókat. Az egyes mezõkben a keresõ eljárások által megtalált állomány adatai vannak tárolva. Fill: Array [1.21] of Byte; {az op rendszer által lefoglalt mezõ, módosítani nem szabad} Attr: Byte; {az állomány attribútumai, amelyeket maszkolással nyerhetünk ki a mezõbõl} Time: Longint; {az állomány utolsó módosításának ideje, tömörített formában} Size: Longint; {az állomány mérete byte-ban} Name: String[12]; {az állomány neve és kiterjesztése} A DOS unit eljárásai,
függvényei, a DOS unit használata: A DOS unit használatához a deklarációs részben szerepelnie kell a következõ sornak: Uses DOS; A DOS unit fontosabb eljárásait, függvényeit az alábbi négy csoportba sorolhatjuk: - Katalógus (könyvtár) és lemezkezelés - Dátum és idõ kezelése - Külsõ programok indítása - Megszakítások kezelése Katalógus és lemezkezelés: Ezeknek az eljárásoknak, függvényeknek az állományok kezelése során van fontos szerepük. FindFirst eljárás: Az elsõ megfelelõ bejegyzés keresése. Szintaxisa: FindFirst(Ut, Attr, Rek); Ut: String - meghatározza, hogy melyik könyvtárban keressük a feltételeknek megfelelõ állományt. Attr: Byte - meghatározza, hogy a bejegyzés milyen attribútumokkal rendelkezik. Az attribútumokhoz tartozó konstansokat lásd fentebb. Elsõsorban akkor van szerepe, ha könyvtárakat keresünk. Általában az AnyFile konstans kerül erre a helyre, ekkor minden állományt és könyvtárat figyelembe
veszünk a keresésnél. Rek: SearchRec - a keresés eredménye. Az eljárás végrehajtása után a Rek rekord mezõiben találjuk meg az állomány (könyvtár) jellemzõit. Minden esetben egy változót kell átadni az eljárásnak. Pld: FindFirst(C:TP*.PAS, AnyFile, Rek); A keresett bejegyzés lehet egy állomány és egy könyvtár egyaránt, ezt az Attr paraméter értéke határozza meg. Általában a FindNext eljárással együtt használjuk, az összes olyan állomány keresésére, amelyek valamilyen feltételnek eleget tesznek. Ha nem talál a feltételeknek megfelelõ bejegyzést az eljárás, a DosError változó értékét 18-ra állítja, ha az Ut paraméterben megadott könyvtár nem létezik, akkor a DosError változó értékét 2-re állítja. FindNext eljárás: A következõ megfelelõ bejegyzés keresése. Szintaxisa: FindNext(Rek); Rek: SearchRec - egy elõzõleg a FindFirst eljárás által feltöltött rekord. Minden esetben változót kell átadni az
eljárásnak, mivel az eredményt is ebben a rekordban kapjuk vissza. A FindFirst eljárás után ciklusban alkalmazva megkereshetjük az összes adott feltételnek megefelelõ bejegyzést az adott könyvtárban. Ha az eljárás nem talál megfelelõ bejegyzést, akkor a DosError változó értékét 18-ra állítja. A FindFirst és FindNext eljárások együttes alkalmazására példa a Feladatok lapon található, a Rendszerprogramozás részben: Feladat FSearch függvény: Megadott állomány keresésére szolgál. Szintaxisa: FSearch(FNev, Ut); FNev: PathStr; - az állomány neve. A PathStr a DOS unit egyik kevésbé fontos beépített típusa, deklarációja: type PathStr= String[79]; Tehát tulajdonképpen egy String[79] tipusú paraméterrõl van szó. Az állomány nevén kívül megadhatunk itt egy útvonalat is, ahol az állományt elõször keresi az eljárás. Összefoglalva az állománynevet kétféleképpen lehet megadni: vagy csak a nevet és a kiterjesztést adjuk meg
(pld. TURBOEXE) vagy megadjuk az útvonalat is (C:TPBINTURBO.EXE) (Bár az utóbbinak nincs túl sok értelme, lévén szó egy olyan eljárásról, amelynek az a feladata, hogy keressen meg egy állományt.) Ut: String; - Ebben a paraméterben adhatjuk meg, hogy mely könyvtárakban keresse az eljárás a megadott állományt. A DOS Path környezeti változójához hasonló formában adhatjuk meg a kívánt könyvtárakat, pontosvesszõvel elválasztva. Pld.: Keressük meg a TURBOEXE állományt, a keresés során vegyük figyelembe a következõ könyvtárakat: C:, C:WINDOWS, C:TP, C:TOOLS FSearch(TURBO.EXE, C:;C:WINDOWS;C:TP;C:TOOLS); A fenti három alprogram egy összetettebb alkalmazására szeretnék egy példát hozni, de mivel ebben más eljárások, függvények is szerepelnek, ez a tétel végére került. Dátum és idõ kezelése: A DOS unit segítségével tudjuk módosítani, illetve lekérdezni a rendszerdátumot és a rendszeridõt Pascal programjainkból.
Lekérdezhetjük, illetve módosíthatjuk az állományok utolsó módosításának dátumát és idejét. A rendszeridõ és rendszerdátum kezelése egyszerûbb, nézzük elõször azt. A rendszeridõ és dátum kezeléséhez a következõ eljárásokra van szükségünk: GetTime eljárás: A rendszeridõ lekérdezése. Szintaxisa: GetTime(Ora, Perc, MP, SzMP); Az Ora, Perc, MP, SzMp Word tipusú változók, az eljárás végrehajtása után ezek tárolják a visszaadott idõpontot. GetDate eljárás: A renszerdátum lekérdezése. Szintaxisa: GetDate(Ev, Ho, Nap, NapNev); Az Ev, Ho, Nap, NapNev Word típusú változók. Az elõzõ eljáráshoz hasonlóan mûködik, egyetlen eltérés a NapNev nevû változó, amelyben a nap sorszámát kapjuk meg. A sorszám alapján kiiratható a nap neve. A sorszámozás 0-tól 6 ig tart a következõképpen: 0- Vasárnap; 1Hétfõ; 2- Kedd; 6- Szombat SetTime eljárás: A rendszeridõ beállítása. Szintaxisa: SetTime(Ora, Perc, MP, SzMP);
Hasonlóan mûködik a rendszeridõ lekérdezéséhez, a különbség annyi, hogy itt már nemcsak változókat, hanem konstans értékeket is használhatunk. SetDate eljárás: A rendszerdátum beállítása. Szintaxisa: SetDate(Ev, Ho, Nap, NapNev); Hasonlóan mûködik a rendszerdátum lekérdezéséhez, a különbség annyi, hogy itt már nemcsak változókat, hanem konstans értékeket is használhatunk. A rendszerdátumot és renszeridõt kezelõ eljárások alkalmazására példa a Feladatok lapon található, a Rendszerprogramozás részben: Feladat A DOS unit beépített tipusainál találkoztunk már a SearchRec rekorddal, amely egy állomány adatait tartalmazza. A rekord mezõi között szerepel - sok más mellett - az állomány utolsó módosításának ideje. A Time mezõ tipusa azonban Longint, ez tartalmazza a szükséges dátumot és idõt. A Turbo Pascal (szûkebben értelmezve a DOS unit) kétféle dátumot ismer: tömörítetlen dátum, amely egy DateTime tipusú
rekordban helyezkedik el, illetve a tömörített dátum, amelyet egy Longint tipusú változóban tárolhatunk. A kétféle dátumforma között két eljárás biztosítja az átjárhatóságot (konverziót). PackTime eljárás: Tömörítelen dátum (rekord) konvertálása Longint formátumba. Szintaxisa: PackTime(D, L); D: DateTime; - a tömörítendõ dátumot tartalmazó változó. L: Longint; - a tömörített dátumot tároló változó. Az eljárás mindkét paramétere változó kell legyen. A D változóban tárolt dátumot az eljárás tömöríti, a tömörített dátumot az L változóban adja vissza. UnPackTime eljárás: Tömörített dátum (Longint) konvertálása Record formátumba. Szintaxisa: UnPackTime(L, D); L: Longint; - a tömörített dátum. Változó és konstans lehet egyaránt D: DateTime; - a kicsomagolt dátumot tároló rekord tipusú változó. Az eljárás elsõ paramétere változó és konstans egyaránt lehet, a második paraméter csak változó
lehet, mivel ebben kapjuk vissza a kitömörített dátumot. A fenti két eljárást kell alkalmaznunk, amikor egy FindFirst vagy FindNext eljárás által megtalált file utolsó móosítási dátumához szeretnénk hozzáférni, és akkor is amikor egy általunk meghatározott állomány adataira vagyunk kiváncsiak. GetFTime eljárás: A file utolsó írásának idejét kérdezhetjük le ezzel az eljárással. Szintaxisa: GetFTime(F, T); F: File; - az állományleíró változó. Mielõtt a GetFTime eljárást használnánk, az állományhoz egy állományleíró változót kell hozzárendelni az Assign eljárás segítségével. Bõvebben lásd a Filekezelésnél (10-es tétel). T: Longint; - a tömörített idõt tároló változó. Ez a paraméter minden esetben egy változó kell legyen. SetFTime eljárás: A file utolsó írásának idejét állíthatjuk be ezzel az eljárással. Szintaxisa: SetFTime(F, T); Az eljárás használata a GetFTime eljáráshoz hasonlóan
történik, annyi különbséggel, hogy itt a T nemcsak változó, hanem konstans érték is lehet. Külsõ programok indítása: A DOS unit lehetõséget biztosít arra, hogy programunkból más - az operációs rendszer által futtatható - programokat hívjunk meg. Ehhez az alábbi eljárásokra és függvényekre van szükségünk: Exec eljárás: Külsõ program futtatása. Szintaxisa: Exec(Programnev, Parameterek); Programnev: String; - a futtatható állomány elérési útját, nevét és kiterjesztését tartalmazza. Parameterek: String; - ha a külsõ programnak paramétereket szeretnénk átadni, azokat ebben a változóban kell elhelyezni. Az eljárás EXE és COM kiterjesztésû programok futtatására alkalmas, a BAT kiterjesztésû kötegelt állományok indítása közvetlenül nem megoldható (egy trükk segítségével igen). SwapVectors eljárás: A megszakítási vektorok tartalmának elmentése. Szintaxisa: SwapVectors; A SwapVectors eljárást célszerû meghíni
az Exec eljárás alkalmazása elõtt és után, hogy a hívott és az általunk írt program megszakításai ne keveredjenek össze. Általában minden program használja a megszakításokat, ezért célszerû minden esetben alkalmazni ezt az eljárást, ha külsõ programot szeretnénk programunkból futtatni. DosExitCode függvény: Az utoljára hívott külsõ program visszatérési kódja. Szintaxisa: Kod:= DosExitCode; A függvény visszatérési értéke alapján megtudhatjuk, hogy a program sikeresen fejezõdött-e be. Megszakítások kezelése: Ebbe a csoportba tartoznak azok az eljárások, függvények, amelyek az operációs rendszer és a BIOS alacsony szintû szolgáltatásainak igénybevételéhez szükségesek. Intr eljárás: Megszakítás hívása. Szintaxis: Intr(N, R); N: Word - ezt a megszakítást fogja az eljárás aktivizálni. R: Registers; - ez a paraméter a regiszterek aktuális értékét tartalmazza. Minden esetben változó kell legyen. Magyarázat
helyett álljon itt egy egyszerû példa a megszakítások használatára. Írjunk ki egy karaktert a képernyõre. Uses DOS; Var R: Registers; {A regiszterek eléréséhez} Begin R.Ah:= $9; {Az AH regiszterbe a HEXA 9 érték kerül, ez a megszakítás alfunkciója} R.Al:= Ord(A); {Az AL regiszterbe a karakter ASCII kódja kerül} R.Bl:= $07; {Karakterszín és háttérszín beállítása, fekete háttér, szürke karakterszín} Intr($10, R); {Meghívjuk a HEXA 10 sorszámú megszakítást, a beállított regiszterekkel} End. MsDos eljárás: A $21 megszakítás hívása. Szintaxisa: MsDos(R); R: Registers; - ez a paraméter a regiszterek aktuális értékét tartalmazza. Minden esetben változó kell legyen. Az eljárás egyenértékû az Intr($21, R) eljárás hívásával. Mivel a $21 sorszámú megszakítás a DOS funkciókat aktivizálja, kiemelt szerepe van - ezeket használjuk leggyakrabban - ezért külön eljárást kapott a DOS unitban. Egy példa az MsDos eljárás
használatára: Kérdezzük le az operációs rendszer verziószámát. (Windows alatt is mûködik!) Uses DOS; Var R: Registers; {A regiszterek eléréséhez} Begin R.Ax:= $3000; {A HEXA 3000 alfunkciot szeretnenk hasznalni} MsDos(R); {A $21 megszakitas meghivasa} Writeln(Az operacios rendszer verzioszama: , R.Al, , RAh); End. 11. tétel: Mutató típusok. A Turbo Pascal memória térképe Láncolt listák. Mutató típusok: Az írható-olvasható memória nagyon fontos erõforrása minden számítógépes rendszernek, kezelését általában az operációs rendszer végzi. A programok futtatása szintén az operációs rendszer feladatai közé tartoznak, minden program használja a számítógép memóriáját. A Pascal programok DOS alatt futnak, a DOS által kezelt memóriaterületeket tudják kihasználni. Tehát a Pascal program annyi memóriát használhat fel, amennyit a DOS biztosít, illetve szabadon hagy számára. Mivel az operációs rendszer alapértelmezés szerint
a 640 KB hagyományos (konvencionális) memóriát képes kihasználni, így a programok számára ennek a memóriaterületnek a szabadon maradt része áll rendelkezésre. Komoly adatkezelõ programok esetén igen szûk korlátot jelent ez a kb. 600 KB A konvencionális memórián felüli területek felhasználásához speciális (mellesleg elég bonyolult) programozási technikára van szükség (a HMA és az EMS területek elérése megszakításokon keresztül történik). Általában más eszközökhöz érdemes nyúlnunk, ha a memória kevésnek bizonyul. A Pascal programok általában(?!) statikus memóriakezelést alkalmaznak, amelynek lényege, hogy a deklarált változóknak szükséges memóriaterület a program indításakor lefoglalásra kerül, és a program futásának végén automatikusan szabadul fel. A program futása közben - statikus helyfoglalású változók használata esetén - nincs lehetõségünk a memóriaterületek felszabadítására, a változónak
szükséges memóriaterület akkor is lefoglalt marad, ha a változót éppen nem használjuk. A Pascal nyelv lehetõséget biztosít az ilyen jellegû problémák megoldására azzal, hogy támogatja a dinamikus memóriahasználatot. Ennek lényege, hogy a változók deklarációja során nem történik meg automatikusan a változó számára szükséges memóriaterület lefoglalása, arról a késõbbiekben a programozónak kell gondoskodnia, akkor amikor a változót használni szeretné. Amennyiben a változóra - illetve annak tartalmára - már nincs szüksége lehetõsége van a változó számára lefoglalt memóriaterület felszabadítására. Természetesen a memóriaterület felszabadításakor a változó tartalma elvész. A dimanikus memóriakezelés alkalmazásához új típusok bevezetésére van szükség, amelyek lehetõvé teszik, hogy a memóriaterület a program futása közben kerüljön lefoglalásra. A dinamikus memóriakezeléshez szükséges típus a mutató
típus. A mutató típusok egy memóriacím tárolására alkalmasak, méretük 4 byte. A Turbo Pascal kétféle mutató típus használatát teszi lehetõvé: - Típusos mutató - Típus nélküli mutató A típusos mutatók: A típusos mutatók olyan memóriacímre mutatnak, ahol a mutató típusának megfelelõ változót tárolhatunk. Deklarációjuk: Var PInt: ^Integer; PReal: ^Real; A fenti változók deklarációja során a memóriában csak a mutatónak szükséges 4 byte kerül lefoglalásra. A PInt nevû változó egy egész számra, míg a PReal nevû változó egy valós számra mutató memóriacímet tárol, segítségükkel egy egész, illetve egy valós típusú változót tudunk helyettesíteni. (Természetesen a mutatók használatának bonyolultabb adatok esetén van igazi értelme, mivel az Integer típus 2 byte-os, a mutató több helyet foglal, mint maga a változó.) A deklaráció után a statikus változók esetén értelmezve van az értékadó utasítás
(többek között), míg a dinamikus változók esetén a PInt:= 10; utasítás hibához vezet. Nemcsak az utasítás alakja nem megfelelõ (hiszen a megfelelõ forma: PInt^:=10;), hanem a dinamikus változók használata elõtt egy nagyon fontos lépést kell végrehajtanunk, memóriaterületet kell lefoglalni a dinamikus változó számára. A memóriafoglalás típusos mutatók esetén a New eljárás segítségével történik. A Nem eljárásnak paraméterként meg kell adni a dinamikus változó azonosítóját, tehát: New(PInt); Amennyiben nincs a memóriában elegendõ szabad hely a változó számára, akkor a program a 203-as hibaüzenettel megszakad. Ezt kiküszöbölhetjük egy egyszerû ellenõrzés beiktatásával: If (MaxAvail<sizeof(Integer)) then Writeln(Nincs eleg memoria) else New(PInt); A MaxAvail függvény segítségével lekérdezhetjük, hogy van-e elegendõ memória a változó számára. (Részletesebben késõbb) Amennyiben sikerült a változónak
memóriaterületet foglalni, jöhet a változóval végzett munka. Ekkor már felhasználhatjuk a dinamikus változónkat minden olyan helyen, ahol a statikus változókat használhatnánk, a különbség mindössze annyi, hogy a dinamikus változó azonosítója után ^ jelet kell tennünk. Az ^ jel jelenti a fordító számára, hogy a dinamikus változó értékével szeretnénk dolgozni. Miután a változóra már nincs szükségünk, az általa foglalt memóriaterületet fel kell szabadítani. A memóriaterület felszabadítása a Dispose eljárás segítségével történik. A Dispose eljárás paramétereként is a dinamikus változó azonosítóját kell megadnunk. Esetünkben: Dispose(PInt); A dinamikus változók a memóriában egy elkülönített területen, az ún halomterületen (HEAP) helyezkednek el. Ez a terület a dinamikus változók számára van fenttartva, méretét a Pascal fordító állítja be. A HEAP terület mérete a program elején közvetlenül
beállítható az $M direktíva segítségével. Az $M direktíva globális direktíva, a program elején kell szerepelnie Használata: {$M stackméret, heap min, heap max} Példa: {$M 16384, 0, 655360} A stackméret paraméter a verem méretét határozza meg, értéke 1024 és 65520 között változhat. A heapmin paraméter azt a minimális halomterületet határozza meg, amely a program elindításához feltétlenül szükséges, míg a heapmax a program futása során felhasználható heap terület méretét definiálja. A dinamikus változókat használó programok esetén érdemes lehet a memóriaterületek méretét a program elején beállítani. A dinamikus változók használata a nagyméretû adatszerkezetek kezelése során lehet hasznos, sok elemet tartalmazó tömbök, bonyolult rekordok használata esetén nagyon hasznos lehet a dinamikus memóriakezelés lehetõsége. A típusos mutatók deklarációja során azonban nem használhatunk összetett típusokat csak akkor,
ha azok külön névvel rendelkeznek. Tehát amikor szeretnénk egy tömb vagy rekord típusú dinamikus változót deklarálni, elõtte a típusdeklarációs részben azonosítót kell rendelni az összetett típushoz. Példa: Type TTomb= Array [1.10] of Longint; Var PTomb1: ^Array [1.5] of Longint; {Fordítási hibát okoz} PTomb2: ^TTomb; {Mukodik} . Nézzünk ezek után egy példát a típusos mutatók használatára: Generáljunk 10 egész számot (Word), majd keressük meg a legnagyobbat és a legkisebbet. (A feladat megoldásához használjunk dinamikus helyfoglalású tömböt!) Type TTomb= Array [1.10] of Word; Var PT: ^TTomb; Min, Max: Word; I: Byte; Begin Randomize; If (MaxAvail<sizeof(TTomb)) then Begin Writeln(Nincs eleg memoria.); Halt; End else New(PT); For I:= 1 to 10 do PT^[I]:= Random(65535); Min:= PT^[1]; Max:= PT^[1]; Writeln(A tomb elemei: ); Writeln; Writeln(PT^[1]); For I:= 2 to 10 do Begin Writeln(PT^[I]); If PT^[I]<Min then Min:= PT^[I]; If PT^[I]Max
then Max:= PT^[I]; End; Dispose(PT); Writeln; Writeln; Writeln(A tomb legkisebb eleme: , Min); Writeln(A tomb legnagyobb eleme: , Max); End. A feladat megoldásához nem kellene feltétlenül tömböt használni (fõleg nem dinamikus helyfogalású tömböt), de a dinamikus változók kezelésének minden lépését tartalamazza a példa. A típus nélküli mutatók: A mutatók másik nagy csoportját a típus nélküli mutatók képezik. A típusos mutatók esetében a deklaráció során határoztuk meg, hogy a mutató milyen típusú értékre mutathat, a típus nélküli mutatók esetén ezt a programozó döntheti el. A típus nélküli mutatók deklarációja: Var P: Pointer; A típusos mutatókhoz hasonlóan a deklaráció után itt is mindössze a memóriacím tárolásához szükséges 4 byte kerül lefoglalásra. A mutató használatának lépései egyeznek a típusos mutatók esetén leírt lépésekkel. Tehát a típusnélküli mutatók esetében is le kell foglalni a
szükséges memóriaterületet a mutató használata elõtt. Mivel a típusnélküli mutató nem korlátozza a felhasználható adattípusokat, a memóriaterület lefoglalását ebben az esetben másképpen kell elvégezni, a New eljárást most nem használhatjuk erre a célra. A helyfoglalás a GetMem eljárás segítségével történik: GetMem(P, méret); A második paraméter határozza meg a mutató által jelölt adat tárolásához szükséges memóriaterület méretét. Ha a típus nélküli mutató egy Integer típusú számokat tartalmazó öt elemû tömbre mutat, ennek a memóriában 5x2 byte méretû területet kell lefoglalni, tehát: GetMem(P, 10); Sokkal általánosabb megoldás, ha felhasználjuk a sizeof függvényt, hogy megállapíthassuk a szükséges terület méretét. A sizeof függvény csak akkor alkalmazható, ha az összetett típushoz elõzõleg típusazonosítót rendelünk. A sizeof függvény alkalmazásával az elõzõ példa: Type Tomb= Array [1.5] of
Integer; Var P: Pointer; Begin . GetMem(P, sizeof(Tomb)); . End. A memóriaterület lefoglalása után a típus nélküli mutatót ugyanúgy használhatjuk, ahogy a típusos mutatókat. Pld: P^[1]:= 10; Miután az adatokra már nincs szükségünk, fel kell szabadítani a lefoglalt memóriaterületeket. Típus nélküli mutatók esetén a GetMem eljárás "párját", a FreeMem eljárást használhatjuk erre a célra. A FreeMem eljárás használatakor is meg kell adni a mutató azonosítóját, és a felszabadítandó terület méretét. Az elõbbiekhez hasonlóan ezt célszerûen a sizeof függvénnyel határozhatjuk meg. FreeMem(P, sizeof(Tomb)); A GetMem eljárás által maximálisan lefoglalható memóriaterület mérete 65521 byte lehet, a Pascal programok memóriahasználata miatt (késõbb bõvebben). A GetMem eljárás használatakor is érdemes ellenõrízni a memóriafoglalás sikerességét. Ezt megtehetjük a már megismert módon, de érdemes lehet egy másik
lehetõséget is alkalmazni. A Pascal nyelv tartalmaz egy mutató konstanst, a határozatlan mutatót. A határozatlan mutató nem jelöl memóriacímet, azt jelzi, hogy a mutató "nem mutat sehová". A határozatlan mutató a nil Ezt a konstanst is felhasználhatjuk a memóriafoglalás sikerességének ellenõrzéséhez. A 203-as futás közbeni hiba kiküszöbölése érdekében itt is alkalmaznunk kell a MaxAvail függvényt. Type Tomb= Array [1.5] of Longint; Var P: Pointer; Begin if (MaxAvail=sizeof(Tomb)) then GetMem(P, sizeof(Tomb)); if P=nil then Begin Writeln(Nincs eleg memoria); Halt; End; . FreeMem(P, sizeof(Tomb)); End. Mûveletek a mutatókkal: A mutatók esetében mindössze néhány speciális mûveletet értelmezünk. Összehasonlítás: A mutatókat összehasonlíthatjuk egymással, illetve a beépített mutatókonstanssal. Az eredmény logikai típusú lesz. Általában ellenõrzés céljából használjuk Pld: If P=nil then Halt; vagy If P<nil then
. Értékadás: A mutatóknak értéket adhatunk, hogy meghatározzuk, milyen memóriacímre mutassanak. A mutatók mérete 4 byte, ebbõl 2-2 byte szükséges a memóriacímek offszet és szegmens címének tárolásához. A mutató által tárolt memóriacímet többféleképpen meghatározhatjuk: - automatikusan (GetMem, New) - függvények segítségével (Addr, Seg, Ofs) - felhasználói értékadás Az elsõ lehetõséggel foglalkoztunk az eddigiekben. A második lehetõség a késsõbbiekben kerül tárgyalásra. A mutatóknak magunk is adhatunk értéket, az értékadás során használhatunk - egy már használatban lévõ mutatót: P:= PT; - a Pascal beépített mutatókonstans értékét: P:= nil; - a mutató által mutatott adatnak is adhatunk értéket: P^:= 10; Típuskonverzió: A típus nélküli mutatók egyszerû és összetett típusokra egyaránt mutathatnak. Egyszerû típusok esetén az értékadás az elõbbiekben megismert módon történhet. (P^:= 10) Ha
összetett típusra mutat a típusnélküli mutató, akkor típuskonverziót kell alkalmaznunk. Rossz példa: P^[2]:= 5; A fenti formában az értékadás nem mûködik a típusnélküli mutatók esetén. Amennyiben a típusnélküli mutató összetett típusú értékre mutat, akkor típuskonverziót kell alkalmaznunk, azaz a mutató azonosítója elõtt meg kell jelölni az összetett típust. Pld: Type TTomb= Array [1.7] of Longint; Var P: Pointer; Begin GetMem(P, sizeof(TTomb); TTomb(P^)[1]:= 16; . End. A fenti típuskonverzió feltétele, hogy az összetett típus önálló azonosítóval rendelkezzen, tehát szerepeljen a típusdeklarációs részben. Függvények, eljárások: A Pascal nyelv tartalmaz néhány speciális függvényt, amely a mutatók használata során segíti a munkánkat. Addr függvény: Bármely Pascal objektum (függvény, változó, eljárás) címét lekérdezjetjük segítségével. Pld: P:= Addr(A); @ (címe) operátor: Hatása azonos az Addr
függvénnyel. Pld: P:= @A; Seg függvény: Egy Pascal objektum (függvény, változó, eljárás) címének szegmens része. Pld: Seg(A); Ofs függvény: Egy Pascal objektum (függvény, változó, eljárás) címének offszet része. Pld: Ofs(A); Ptr függvény: Egy konstans memóriacímre mutathatunk rá segítségével. Pld: P:= Ptr($B800, $0000); Elsõ paramétere a memóriacím szegmens, második az offszet része. Egy példa a Ptr függvény használatára: A szöveges képernyõ memóriájának közvetlen kezelése egy típus nélküli mutató által. Uses CRT; Type Src= Array [1.25, 180, 12] of Byte; Var P: Pointer; Begin ClrScr; P:= Ptr($B800,$0000); Src(P^)[10,5,1]:= 65; Readln; End. A láncolt listák: A mutatók által biztosított lehetõségeket legjobban a listaszerkezetek kialakítása során használhatjuk ki. A listák hasonló módon kezelhetõk, mint a tömbök, homogén adatszerkezetek, de méretük nincs rögzítve. A Pascal nem tartalmaz beépített
listaszerkezetet, felhasználói típusok definiálásával hozhatjuk létre õket. A listák listaelemekbõl épülnek fel, minden listaelem tartamaz egy adatot, és egy mutatót, amely a lista következõ elemére mutat. A listák létrehozásához Pascal nyelvben a record adatszerkezetet használjuk fel. Adatelem Mutató Mivel a mutató ugyanolyan típusú adatelemre mutat, mint a mutatót tartalmazó elem, önhivatkozó struktúrát kell létrehozni. Ehhez a típusdeklarációs részben deklarálni kell az adatelemek típusát, és az adatelemekre mutató pointer típusát. Type PElem: ^TElem; TElem: Record Adat: Integer; Mut: PElem; End; A listának két kiemelt szerepû eleme van: az elsõ elem, és az aktuális. Az új elem felvételéhez szintén deklarálni kell egy változót. A deklarációs részben mindhárom elemet deklarálni kell Var Elso, Akt, Uj: PElem; Mindhárom elem dinamikus helyfoglalású, tehát a lista létrehozásakor az elsõ elemnek helyet kell
foglalni a memóriában. Begin New(Elso); Elso^.Adat:= 1; Elso^.Mut:= nil; Akt:= Elso; . A következõ elemre mutató mezõ nil értéket kap, mivel nincs következõ elem. Az aktuális elem az elsõ elem lesz. Ha a listához egy újabb adatot szeretnénk hozzáfûzni, akkor dinamikus helyfoglalással hozunk létre egy újabb elemet. New(Uj); Uj^.Adat:= 2; Uj^.Mutato:= nil; Akt^.Mutato:= Uj; Akt:= Uj; Az új elem adat mezõjét értelemszerûen feltöltjük (Uj^.Adat:= 2;) A mutató mezõ ismét nil értéket kap, mivel az új elem lesz a lista utolsó eleme. Ezután az új elemet hozzá kell fûznünk a listához! Az aktuális elem az utolsó elem (mivel egyetlen elemünk van, így az elsõ), tehát az aktuális elem mutató mezõjét ráirányítjuk az újonnan létrehozott elemre. Ezzel az új elemet a listához fûztük. Ahhoz, hogy az aktuális elem az utolsó elem legyen, az Akt nevû változó értékét is meg kell változtatni, az Akt:= Uj; utasítással az aktuális elem
ismét az utolsó elem lesz. Így a listának már két eleme van. A lista elemeinek kiíratásához vissza kell ugranunk a lista elejére, azaz az elsõ elemre. Ekkor az aktuális elem az elsõ elem lesz. Ezután egy ciklus segítségével írathatjuk ki a lista elemeit Célszerû határozatlan ismétélésszámú ciklust alkalmazni, mert a ciklus végét az utolsó elem mutató mezõjében található nil érték fogja jelezni. A ciklus magján belül az aktuális elemet változtatjuk, minden ismétléskor a következõ elemet tesszük aktuálissá. Akt:= Elso; Writeln(Akt^.Adat); While Akt^.Mutato<nil do Begin Akt:= Akt^.Mutato Writeln(Akt^.Adat); End; Szükséges, hogy az elsõ elem kiíratása a ciklusba lépés elõtt megtörténjen, másképpen nem lesz teljes a lista. A listából törölhetünk egy elemet néhány egyszerû mutatómûvelet segítségével. Az elemek törlése során egy látszólagos visszásság tapasztalható a változónevek tekintetében, ez
mindössze abból adódik, hogy mindössze az elnevezés miatt nem akartam egy újabb változóval kiegészíteni a deklarációs listát. Elsõ lépésként aktuálissá kell tenni a törlendõ elem elõtti elemet A törlendõ elemre az Új nevû változó fog mutatni, amelyet egy értékadó utasítással érünk el (Uj:= Akt^.Mutato;), ez látszólag visszásnak tûnik, de célunk, hogy minél kevesebb változóval dolgozzunk. Miután kijelöltük a törlendõ elemet, az aktuális elem mutatóját átirányítjuk a törlendõ elemrõl (hiszen ez a következõ elem, erre mutat ez a mutató) a törlendõ utáni elemre. Ezzel a törlendõ elemet kiiktattuk a listából. Ezek után már nincs más dolgunk, mint a törlendõ elem számára lefoglalt memóriaterület felszabadítása a Dispose eljárással. A programrészlet valahogy így néz ki (a második elemet akarjuk törölni): . {A törlenõ elem elõtti elem lesz az aktuális} Akt:= Elso; {Az Uj nevû változó jelöli ki a
törlendõ elemet} Uj:= Akt^.Mutato; {Az aktuális elem mutatóját átállítjuk a kettõvel} {utána következõ elemre} Akt^.Mutato:= Akt^Mutato^Mutato; {A törlendõ elem által foglalt memóriát felszabadítjuk} Dispose(Uj); . Végül egy teljes példa a láncolt listák kezeléséhez: Uses CRT; Type PElem= ^TElem; TElem= Record Adat: Longint; Mutato: PElem; End; {Uj elem beszurasa a lista vegere} Procedure Hozzaad(Ertek: Longint; Start: PElem); Var Akt, Uj: PElem; Begin {A lista elso elemere allunk} Akt:= Start; {Megkeressuj az utolso elemet} While Akt^.Mutato<nil do Akt:= Akt^.Mutato; {Lefoglaljuk a memoriaban az uj elem helyet,} {es feltoltjuk az uj elemet} New(Uj); Uj^.Adat:= Ertek; Uj^.Mutato:= nil; {Hozzafuzzuk a listahoz az uj elemet} Akt^.Mutato:= Uj; {Az uj elemet tesszuk aktualissa} Akt:= Uj; {Felszabaditjuk az elemnek lefoglalt helyet} Dispose(Uj); End; {Elem torlese a listabol} Procedure Torol(Sorszam: Word; Start: PElem); Var I: Word; Akt, Torol: PElem; Begin
{A lista elejere allunk} Akt:= Start; {A sorszam alapjan megkeressuk a torlendo elem} {elotti elemet} For I:= 1 to Sorszam-1 do Akt:= Akt^.Mutato; {Kijeloljuk a torlendo elemet} Torol:= Akt^.Mutato; {A torlendo elemet kiiktatjuk a listabol} Akt^.Mutato:= Akt^Mutato^Mutato; {Felszabaditjuk a torlendo elem helyet a memoriaban} Dispose(Torol); End; {A lista elemeinek megjelenitese} Procedure Listaz(Start: PElem); Var Akt: PElem; Begin {A lista elejere allunk} Akt:= Start; {Kiiratjuk az elso elemet} Writeln(Adat: , Akt^.Adat); {Ciklusban vegigmegyunk a lista elemein } {A lista veget a mutato nil erteke jelzi} While Akt^.Mutato<nil do Begin Akt:= Akt^.Mutato; Writeln(Adat: , Akt^.Adat); Readln; End; End; {A foprogram} Var Elso: PElem; Valasz: Byte; N: Longint; T: Word; Begin ClrScr; Writeln(Egy elem felvetele kotelezo); New(Elso); Write(Az elso elem erteke: ); Readln(N); Elso^.Adat:= N; Elso^.Mutato:= nil; Repeat ClrScr; Writeln(Uj elem.1); Writeln(Elem torlese.2); Writeln(Lista
megjelenitese.3); Writeln(Kilepes.4); Writeln; Writeln; Write(Valasztas:); Readln(Valasz); Case Valasz of 1: Begin ClrScr; Writeln(Az uj elem erteke: ); Readln(N); Hozzaad(N, Elso); End; 2: Begin ClrScr; Writeln(Az torlendo elem sorszama: ); Readln(T); Torol(T, Elso); End; 3: Begin ClrScr; Listaz(Elso); Writeln; Writeln(ENTER .); Readln; End; 4: ; End; Until Valasz=4; End. 1. Tétel : A Pascal programok memóriatérképe: A tétel elején volt már szó arról, hogy a Pascal programokat DOS operációs rendszer alatt futtathatjuk. A DOS alatt futó programok a 8086-os processzor regisztereit használják fel a memória címzéséhez (eléréséhez). A regiszterek mérete 16 bit, ami azt jelenti, hogy a legnagyobb megcímezhetõ memória mérete 1 MByte. Ahhoz, hogy 1 MByte memóriát címezni tudjuk 20 bites fiikai címekre van szükség. Mivel a regiszterek mérete 16 bit, ezért nem lehet direkt címeket használni a memória eléréséhez. A megoldást a szegmentált
címzés biztosítja A memóriát részekre (szegmensekre) osztjuk, így a teljes cím két részbõl épül fel: szegmens cím (16 bit), megmutatja, hogy a memória melyik részében található a keresett rekesz, offszet cím, amely a szegmensen belül megmutatja, hogy hányadik byte-on található a kívánt adat. A 8086-os mikroprocesszor maximálisan 4 szegmenst tud megcímezni a szegmensregiszterek használatával: CS - kódszegmens; DS - adatszegmens; SS - veremszegmens; ES - extra szegmens (másodlagos adatszegmens). A Turbo Pascal programok a memóriacímeket 4 byte méretû adatként tárolják, ebbõl két byte a szegmens, 2 byte az offszet cím. Ezeket a címeket tárolhatjuk a mutató típusok segítségével Mivel a 16 biten aábrázolható legnagyobb szám 65535, így a szegmensek mérete sem lépheti túl ezt a korlátot (az offszet cím segítségével ennyi byte-ot tudunk címezni egy szegmensen belül). A szegmensregiszterek tartalma megváltoztatható, így
speciális módszerek segítségével kihasználhatnánk az operációs rendszer által biztosított 640 KByte-ot, de alapértelmezés szerint a szegmensek mérete 64 KByte. Az egyes szegmensek a program egyes részeit tartalmazzák: - Kódszegmens: A program futtatható része, és a konstansok. - Adatszegmens: A program során felhasznált változók, és típusos konstansok. - Stack szegmens: Az alprogramok által használt változók, illetve a dinamikus változók. Az ábrán látható egy Pascal program memóriatérképe. A memória részei (kicsit részletesebben): - Kódszegmens: A program kódját tartalmazza (az EXE állomány tartalmának legnagyobb része). Itt található a fõprogram és a felhasznált unit-ok kódja, minden egyes unit-nak (és a fõprogramnak is) egy-egy 64 KB méretû memóriaterület van fenntartva. A unit-ok használata tehát az egyik módja a 64 KB-os határ átlépésének. A kódszegmensen találhatjuk meg a unit-okban és a fõprogramban
felhasznált típus nélküli konstansokat is! - Adat szegmens: Mint az ábrán is látható, a programban (unit-okban is) felhasznált változók és típusos konstansok kerülnek ebbe a szegmensbe. Mérete rögzített, maximum 64 KByte lehet Tehát a programban felhasznált adatok (változók) méretének összege nem haladhatja meg a 64 KByte-ot. Ez a korlátozás is feloldható a unit-ok alkalmazásával. - Verem szegmens: A verem szegmens három részbõl épül fel: Stack, Overlay puffer és a Heap. A Stack nevû területet az alprogramokban deklarált változók használják. Az Overlay puffernek az Overlay technikát használó programok esetén van szerepe, míg a Heap a dinamikus változók által felhasznált memóriaterület. Érdekessége a Stack és Heap területnek, hogy ellenkezõ irányban növekednek. 15. tétel: Az Assemly programozás alapjai: a 80*86 processzor regiszterei, címzési módok, PC megszakítások, a hadver programozás szintjei A Assembly
programozási nyelv az egyik legrégebbi programozási nyelv, közvetlenül a gépi kódú programozás után jelent meg. Létjogosultságát bizonyítja, hogy speciális problémák megoldására még ma is alkalmazzák. Manapság a hardverközeli programok készítésének leghatékonyabb programozási nyelve. Az Assembly programok utasításai, szerkezete a gép logikához igazodik. Az Assembly programok nagyon gyors, és hatékony programok, mivel a processzor lehetõségeit maradéktalanul képesek kihasználni. Az Assembly nyelvet háromféle módszerrel tudjuk felhasználni: - Magasszintû programozási nyelvek programjainak forráskódjába Assembly nyelvû utasításokat ágyazunk. - Tárgykód szintjén szerkesztjük be a magasszintû programba (Pascal esetén $L direktíva) - "Valódi" Assembly programokat írunk. Több magasszintû programozási nyelv támogatja az Assembly programrészletek beillesztését, Turbo Pascal, Borland C, stb. A hardver programozás
szintjei: A hardver egységeket programjaink írása során többféleképpen kezelhetjük. A hardver programozása során négy szintet kell elkülönítenünk: Magasszintû programozási nyelv utasításaival: A legkevésbé hatékony, de a programozó szempontjából a legkényelmesebb eljárás, amikor az adott programozási nyelv utasításainak segítségével kezeli a harver egységeket. Pld: Writeln(Pascal); DOS (operációs renszer szintû) megszakítás használatával: Az operációs rendszer szintjén definiált rutin, amely a hardver egységeket, erõforrásokat kezeli. A DOS (Windows 9x) operációs rendszerek esetében ez a 21h sorszámú megszakítás, amelynek a 2h alfunkciója kezeli a képernyõt. A megszakítás segítségével egy karaktert tudunk a kurzor pozíciójába kiírni. A szükséges programrészlet: Uses DOS; Var R: Registers; Begin . R.AH:= 2; R.AL:= ord(P); Intr($21, R); R.AH:= 2; R.AL:= ord(A); Intr($21, R); R.AH:= 2; R.AL:= ord(S); Intr($21,
R); . End. BIOS megszakítás használatával: A BIOS is definiálja a standard perifériák kezeléséhez szükséges megszakításokat. A BIOS megszakítások a 10h sorszámot kapták, a 14 alfunció segítégével egy karaktert írhatunk ki a képernyõre a kurzor pozíciójába. A szükséges programrészlet: Uses DOS; Var R: Registers; Begin . R.AH:= 14; R.AL:= ord(P); Intr($10, R); R.AH:= 14; R.AL:= ord(A); Intr($10, R); R.AH:= 14; R.AL:= ord(S); Intr($10, R); . End. Közvetlen hardverhozzáférés használatával: Uses CRT; Var Kep: Array [1.25, 180, 12] of byte absolute $B800:$0000; Begin ClrScr; Kep[1][1][1]:= Ord(P); Kep[1][2][1]:= Ord(a); Kep[1][3][1]:= Ord(s); Kep[1][4][1]:= Ord(c); Kep[1][5][1]:= Ord(a); Kep[1][6][1]:= Ord(l); End. A programban a képernyõ memória felépítésének megfelelõ tömböt deklaráltunk, majd az absolute direktíva segítségével a változót rádefiniáltuk a képernyõmemória kezdõcímére (B800h:0000h). Ezután már csak a tömb
elemeinek kell értéket adnunk, így közvetlenül írhatunk a szöveges képernyõ memóriájába. Mielõtt a szöveget kiírnánk a képernyõre, célszerû végrehajtani egy képernyõtörlést, így szebb lesz. A 80x86 processzor regiszterei: Az Assembly programok írása során közvetlenül a processzornak adjuk az utasításokat, a processzor (illetve az operációs rendszer) által definiált rutinokat - a megszakításokat - használjuk fel a program írása során. A megszakítások használatához a processzor belsõ tárolóhelyeit, a regisztereket kell felhasználnunk. A regisztereket a feladatuk alapján csoportosíthatjuk: Általános regiszterek: AX: Akkumlátor regiszter, osztásnál és szorzásnál van kitüntetett szerepe. BX: Bázis regiszter, indirekt és bázisregiszteres indirekt címzés során kap szerepet. CX: Számlálóregiszter, ciklusszervezés és stringmûveletek esetén alkalmazzuk. DX: Adatregiszter, szorzás, osztás és I/O utasítások során
fontos. Vezérlõ regiszterek: IP: Utasításmutató. Automatikusan a következõ utasítás címét tartalmazza Ugrások során fontos SP: Veremmutató. Automatikusan kap értéket, program hívásakor kap szerepet BP: Báziscím mutató. A Stack szegmens indirekt és bázisregiszteres címzésénél használjuk SI: Forrás index. Stringmûveletek során használjuk DI: Célindex. Stringmûveletek során használjuk Flags: Státusz regiszter, hiba esetén használható. Szegmens regiszterek: CS: Kódszegmens. DS: Adatszegmens. SS: Veremszegmens. ES: Másodlagos adatszegmens. FS: Másodlagos adatszegmens. GS: Másodlagos adatszegmens. A másodlagos adatszegmens regisztereket virtuális címzések során használjuk. A szegmensregiszterek közvetlenül nem kaphatnak értéket, csak egy másik regiszter tartalmát tölthetjük ide. A megszakítások: A megszakítások az operációs rendszer és a BIOS által elõre definiált rutinok, amelyeket a programozó felszanálhat a kívánt
tevékenység elvégzéséhez. A megszakítások használatához a regisztereket kell alkalmaznunk. Mivel a processzor közvetenül hajtja végre az utasításokat, a megszakítások adatait (I/O adatok) a regiszterekbe kell tölteni a megszakítás meghívása elõtt. A megszakítások a számítógép perifériáinak, erõforrásainak alacsony szintû kezelését hivatottak biztosítani. A különféle egységek kezeléséhez használható megszakítások hexadecimális sorszámokat kapnak, amelyek segítségével az adott megszakítás meghívható. A megszakításoknak általában vannak alfunkciói is Tehát mielõtt egy megszakítást használnánk a regisztereket fel kell tölteni a szükséges értékekkel: megszakítás száma, alfunció száma, input adatok. A megszakítás végrehajtása után szintén a regiszterek tartalmazzák a kimeneti információkat, a regiszterek tartalmát kell menteni, ha szeretnénk a késõbbiekben használni az output adatokat. Néhány
fontosabb megszakítás: Képernyõ kezelése: 10h megszakítás. (AX) Képernyõtörlés: 0h alfunkció (AH) Bemeneti információk: Karaterfelbontás: 1- 40x25 színes (AL) 3- 80x25 színes (AL) Kurzor beállítása: 1h alfunkció (AH) Bemeneti információk: Kezdõsor (CH) Zárósor (CL) Karakter kiírása a kurzor pozíciójába: 9h alfunkció Bemeneti információk: Karakter kódja (AL) Képernyõ oldal (BH) Attribútum (szín, háttér) (BL) Ismétlésszám (CX) Billentyûzet kezelése: 16h megszakítás (AX) Karakter olvasása: 0h alfunkció (AH) Kimeneti információk: Scan kód (AH) Karakter kód (AL) A címzési módok: Az Assembly programokból mind az adatterületet (adatszegmenst) mind a kódterületet (kódszegmenst) tudjuk címezni. A kódterület címzése tulajdonképpen a programban végrehajtott ugrást jelent, azon a címen fog folytatódni a program végrehajtása, amely címre ugrunk. A kódterület címzése: Az ugrás lehet feltétel nélküli, illetve
feltételes ugrás. A feltétel nélküli ugrás során a jmp utasítást használjuk. A feltétel nélküli ugrás fajtái: IP relatív címzés: Az utasításban talált adatot hozzáadja az IP regiszter által tárolt értékhez, itt folytatódik a program végrehajtása. Pld: jmp vissza Direkt címzés: Valamely regiszter tartalmazza a célcímet, a regiszter tartalmát használjuk fel az ugrás során. Pld: jmp bx Indirekt címzés: A regiszter címén van a kívát cím. Pld: jmp [bx] A feltételes ugrás során csak akkor történik meg a vezérlésátadás, ha a megfogalmazott feltétel teljesül. A feltételes ugrás során a jmp utasítás feltételes változatait használjuk fel a kritériumok meghatározásához. A feltételt az F (Flags) regiszter tartalmazza, az F regiszter feltöltéséhez célszerû felhasználni a cmp utasítást. Példa: ha ax regiszter értéke 5, akkor ugorjunk a vissza címkére: cmp ax,5 je vissza Tehát a feltételes ugrások két utasításból
épülnek fel, az elsõ utasítás során megfogalmazzuk a feltételt, a második utasítás során pedig megvizsgáljuk az értékét, és ennek megfelelõ ugrást hajtunk végre. A jmp utasítás feltételes változatai: JA címke: op1op2 JNA címke: op1<=op2 JAE címke: op1=op2 JNC címke: op1=op2 JB címke: op1<op2 JC címke: op1<op2 JNE címke: op1<op2 . Tehát a feltételes ugró utasítások variálható utasítások. Az elsõ betû kötelezõen a J, a második és harmadik betû a feltételt fogalmazza meg. Tehát: JNE címke J- Jump (ugrás) N- Not (tagadás) E- Equal (egyenlõ) A variációhoz felhasználható karakterek és jelentésük: N: Not (tagadás) L: Less (kevesebb) G: Greater (több) A: Above (felett) B: Bottom (alatt) C: Carry Z: Zero (nulla) E: Equal (egyenlõ) Nyilván az egymást kizáró karakterkombinációk nincsenek megengedve. Rossz példa: JAB címke. Ciklusok az Assembly programban: A ciklusok képzési is a feltételes ugrások
felhasználásával történhet. Pld: mov ax,1 @vissza: add ax,1 add bx,ax cmp ax,5 jne vissza A ciklusok szervezésekor van azonban egy kényelmesebb lehetõségünk is. A ciklusfeltétel vizsgálatához felhasználhatjuk a CX (utasításszámláló) regisztert. A CX regiszter használata esetén mindössze arra kell figyelni, hogy ennek a regiszterenek a tartalmát a ciklusmagban ne módosítsuk. A ciklus elkezdése elõtt a CX regiszterbe kell tölteni (mov cx, ) a ciklus ismétléseinek számát, és a ciklusmag végét a LOOP címke utasítással kell zárnunk. Az elõbbi ciklus ilyen módon: mov cx,5 mov ax,1 @vissza: add ax,1 add bx,ax loop vissza Az adatterület címzése: Az adatterület címzése során tulajdonképpen a direkt memóriakezelést valósítjuk meg. Az adatterületen tárolt változókhoz közvetlen módon férhetünk hozzá, értéküket közvetlenül módosíthatjuk. Az adatterület címzésének lehetõségei: Kódba épített adattal (közvetlen
címzés): A kívánt memóriacímet konstans értékként adjuk meg. Pld: mov ax, $B800 Regiszteres címzés: A kívánt memóriacímet egy regiszter tartalmazza. Figyelni kell arra, hogy a regiszterek azonos típusúak legyenek. Pld: mov ax, bx Közvetlen memória címzés: A második operandus egy változó, illetve egy változó címe. Pld: mov ax, adat Indexelt címzés: Pld: mov ax, [di+4] Regiszter indexelt címzés: A kívánt memóriacím offset részét a BX, SI, DI regiszterek egyike tartalmazza. Pld: mov ax, CS:[DI] Bázis relatív címzés: A memóriacím a BX tartalmához viszonyított cím. Pld: mov ax, [bx+4] Bázis indexelt címzés: Pld: mov ax, [bx][si+10] 16. tétel Fonstosabb Assembly utasítások (adatmozató, aritmetikai, vezérlésátadási) Az Assembly egy alacsony szintû programozási nyelv, filozófiája közel áll a gépi kódú programozáshoz. Ennek megfelelõen az Assembly utasítások egy-egy gépi kódú utasítás szimbolikus megfogalmazásai
(ún. 1-1 típusú nyelv, lásd Programozási nyelvek története) Az Assembly nyelven történõ programozás során a gépi logikát kell követnünk, a problémát a lehetõ legalapvetõbb lépésekre kell lebontanunk. Ennek megfelelõen alakították ki az Assembly nyelv utasításait is, amelyek a következõ csoportokra bonthatók (a teljesség igénye nélkül): - Adatmozgató utasítások - Aritmetikai utasítások - Vezérlésátadó utasítások Az adatmozgató utasítások: A magasszintû programozási nyelvek értékadó utasításához hasonló utasítások. A különbség, hogy a magasszintû nyelvek értékadó utasításai több gépi kódú utasítást reprezentálnak, lehetséges az áttételes értékadás, az Assembly adatmozgató utasítása csak közvetlen értékadásra alkalmas, ha közvetett értékadásra van szükségünk (pld egyes regiszterek esetén), akkor több utasítást kell alkalmaznunk. Az adatmozgató utasítás a mov utasítás Szintaxisa: MOV
op1, op2 Segítségével regiszterek és memóriaváltozó értékét változathatjuk meg. Az elsõ operandus határozza meg a regiszter, vagy a memóriaváltozó azonosítóját, a második operandus az értéket. Az elsõ operandus kötelezõen regiszter vagy memóriaváltozó kell legyen, soha nem lehet konstans értéket megadni (logikus). Az elsõ operandus által meghatározott változó vagy regiszter kapja meg a második operandus által meghatározott értéket. A második operandus lehet memóriaváltozó, regiszter, kifejezés vagy konstans érték egyaránt. Ez az operandus határozza meg az elsõ operandus értékét, tehát a második operandus által meghatározott értéket fogja kapni az elsõ operandus. Példa: Az AX regiszter értékét állítsuk be 10-re. mov ax, 10 A DI regiszterbe tegyük át az AX által tárolt értéket: mov di, ax Valt1 memoriaváltozó értékét változtassuk meg 30-ra: mov valt1, 30 A DX regiszter értékét állítsuk be 41-re egy
kifejezés segítségével: mov dx, 30+11 A második példát szándékosan fogalmaztam így, mivel az index regiszterek (DI, SI) csak közvetett módon kaphatnak értéket, a mov di,10 utasítás hibához vezet. A kifejezések alkalmazásakor figyelni kell arra, hogy a kifejezések értékét még fordítási idõben meg tudja határozni a fordító. Az aritmetikai utasítások: 13. tétel: A C program szerkezete, típusai, konstansok A C programok szerkezete sokkal kötetlenebb a Pascal programokhoz képest, de néhány fontos szabályt itt is be kell tartanunk. A C nyelv case-szenzitív, azaz megkülönbözteti a kis és nagy betûket. A C nyelv a Pascal-hoz hasonlóan viszoylag kevés utasítást tartalmaz, sokkal inkább a beépített alprogramokra támaszkodik. Az alprogramok közül csak a függvények használatát támogatja, az eljárások speciális függvények. Ennek következménye, hogy amikor egy eljárást (esetleg paraméter nélküli függvényt) használunk, a
függvény neve után ki kell tenni az üres zárójeleket. A C programok is blokkokból épülnek fel, a blokk kezdõ és zárószimbóluma: {}. Minden C programnak tartalmaznia kell egy main() nevû függvényt. Ez a függvény speciális szerepet kap a C programokban, hasonlóan mûködik, mint a Pascal esetén a fõprogramot tartalmazó blokk. A main() függvény határozza meg az operációs rendszer számára a program belépési pontját. A legegyszerûbb C program egyetlen sorban megírható: main() { } Mivel ebben az esetben a main() függvény törzse nem tartalmaz utasításokat, a program semmit nem csinál. A C programok szerkezete: Ezek után nézzünk meg egy még teljesebb példát: #include <stdio.h /* Elofordito utasitasok / #define OSZTO 2 int x, y; main() { int sum; /* Globalis deklaraciok / /* Foprogram / /* Lokalis deklaraciok / printf("X= "); /* Utasítások / scanf("%d", &x); printf("Y= "); scanf("%d", &y);
sum=x+y; printf("A ket szam osszege: %d ", sum); if (sum%OSZTO==0) printf("Az eredmeny paros szam "); else printf("Az eredmeny paratlan szam "); return 0; } A program beolvas két számot, kiszámolja az összegüket, majd kiírja, hogy az összeg osztható-e kettõvel. Ezen a programon már sokkal inkább meg lehet figyelni a C programok felépítését A C programok az ún. elõfordító utasításokkal kezdõdnek (#include #define ) Az #include utasítás a Pascal Uses utasításához hasonló. A #define utasítás ún makrok definiálására szolgál (ilyen a Pascal-ban nincs). A makrok a program során konstansokként használhatók fel, különbség a konstansokhoz képest, hogy a makrókat meg lehet szüntetni, ha már nincs rájuk szükségünk (#undef). A main() függvény törzse tartalmazza a scanf() és printf() függvény hívását. Az elsõ a billentyûzetrõl olvasást, a második a képernyõre írást valósítja meg Ezek a
függvények az stdio.h állományban van definiálva, ezért kellett az #include utasítással beépíteni a programban. A return utasítás a szabályos kilépést szolgálja. A Pascal függvényeinek is mindig volt visszatérési értékük, amelyet a FgvNev:= Kif; utasítással határoztunk meg. A C programok esetén a fõprogram maga is egy függvény (main()), amelynek szintén van visszatérési értéke. A C függvények visszatérési értékét a return utasítással határozhatjuk meg. (Érdekes, hogy a program akkor is fut, ha a return utasítást elhagyjuk, de ekkor a Borland C egy Warning-ot jelez, azaz nem szintaktikai, hanem szemantikai hibával állunk szemben.) A C program írása során elõször az elõfordító utasításokat kell megadnunk. Az elõfordító utasítások közül két fontosabb: Az elõfordító utasítások: #include: a szükséges Header file-ok beépítése a programba. A Header file-ok a Pascal unitokhoz hasonlítanak, de a Header file-ok
standard szöveges állományok, a függvények forráskódját tartalmazzák. Az #include utasítás lehetõvé teszi, hogy ezeket a függvényeket felhasználhassuk a programban. #define: makrók definiálása. A makrók a C nyelv speciális lehetõségei közé tartoznak A program elején definiáljuk õket, a programban konstans értékként használhatók fel. A konstansokhoz képest eltérés, hogy a makrókat a program futása során megsemmisíthatjük, ha nincs rá szükségünk, vagy a felhasznált névhez új értéket szeretnénk rendelni. Szintén fontos tulajdonság, hogy a makrók nemcsak értékek tárolására alkalmasak, segítségükkel egyszerû függvények is leírhatók, de ezzel a lehetõséggel nem foglalkozunk bõvebben. Általában a makrók azonosítóit nagy betûvel írjuk, hogy a program többi részétõl jól elkülöníthetõk legyenek. Globális deklarációk: A globális deklarációk a main() függvény törzsén kívül helyezkednek el. Ezek a
deklarációk akkor lehetnek fontosak, ha a saját függvényeket szeretnénk használni, és a velük való kommuninkációt globális változókon keresztül szeretnénk megvalósítani. A globális deklarációs rész tartalmazhat változókat, konstansokat, függvényeket, típusokat egyaránt. A main() függvény (fõprogram): A globális deklarációkat a main() függvény fejrésze, majd törzse követi. A main() függvény törzsén belül lehetõségünk van újabb deklarációk elvégzésére, ezek a lokális deklarációk lesznek. Az itt deklarált változók, konstansok, típusok, függvények csak a main() függvény törzsén belül használhatók fel, tehát csak azok a függvények használhatják õket, amelyek szintén a lokális dekalrációs részben szerepelnek. Lokális deklarációk: A lokális deklarációk után következnek a program utasításai, a program tevékenységét leíró rész (fõprogram). A program (majdnem) minden sorát pontosvesszõvel kell
lezárni (Szándékosan nem írtam utasítást!!!) A main() függvény törzsének utolsó utasítása általában a return utasítás, amely után meg kell határozni, hogy a függvény (program) milyen értéket (egész) adjon vissza az operációs rendszer felé. A main() függvénybõl visszaadott értéket felhasználhatjuk az ERRORLEVEL változó segítségével az operációs rendszer szintjén (pld. batch file-ok készítése során) A változók, típusok, konstansok deklarációja, a C nyelv típusai: A C nyelv deklarációinak formája jelentõsen eltérõ a Pascal deklarációktól. Változók deklarációja: típus azonosító; Például: int a; A típus meghatározása után egyszerre több azonosítót is felsorolhatunk. Pld: int a, b; A deklaráció során a változó azonnal kaphat értéket. Pld: int a=5, b=2; vagy int x=2, y; Konstansok deklarációja: const azonosító=érték; Például: const pi=3.14; A konstansok deklarációja a const foglalt szóval kezdõdik.
Meg kell adni a konstans azonosítóját, majd egyenlõségjel után a konstans értékét. Bármilyen konstans deklarálható ilyen módon. Pld: const szov="Egyszeru szoveg"; A C nyelv nem engedi meg a típusos konstansok használatát (amely szerintem felesleges is lenne a változók deklaráció során megadható kezdõértéke mellett). Típusok deklarációja: typedef típusleírás típusazonosító; Például: typedef double valos; A saját típusok deklarálása a typedef foglalt szóval lehetséges. Elsõsorban összetett típusok esetén alkalmazzuk, mivel a függvények C nyelvben sem engedik meg az összetett típusú paraméterek használatát. Egy bonyolultabb típusdeklaráció: typedef struct BOOK { char cim[40]; char szerzo[40]; int kiadasi ev; } konyv; Az alprogramok készítésével (függvények deklarációja) nem foglalkozunk. A C nyelv típusainak csoportosítása: A C nyelvben is deklarálni kell a változókat használatuk elõtt, a deklaráció
során megadott különbözõ tulajdonságok között az egyik legfontosabb a változó típusa. A Pascal nyelvhez hasonlóan a típus itt is meghatározza a változó által tárolható értékek jellegét, határait. A C nyelvben használható típusok csoportosítása: Egyszerû (skalár) típusok: Aritmetikai típusok: Egész jellegû típusok: char signed char unsigned char int long int short int unsigned int unsigned long int unsigned short int enum Lebegõpontos (valós) típusok: float double long double Mutató típusok Összetett típusok: Összeállított típusok: tömb típusok struktúra típusok Unió (union) típusok Az egyszerû típusok elõállítása során gyakran használunk ún. típusmódosítókat, amelyek egyegy alaptípus tulajdonságait módosítják, ilyen módon szinte egy új típust hoznak létre (Pld: int; long int) A típusok között az alaptípusok a következõk: char, int, float, double, enum, struct, union. Ezek közül a char, int, double típus
tulajdonságait módosítva kapjuk a többi felhasználható típust. Az alaptípusok jellemzõi: char: Egy karakter tárolására alkalmas típus, hossza 1 byte. Elõjel nélküli egész típus A C nyelvben a karaktereket azonos módon kezelhetjük a 0.255 közé esõ egész számokkal, amelyek tulajdonképpen a karakter ASCII kódját jelentik. Módosítható típus int: Egész számok tárolására használható elõjeles típus. Mérete általában megegyezik a gépi szó méretével, azaz hardverfüggõ. A szó általában 2 byte hosszú, de egyes C fordítók, és egyes processzorok esetében eltérés tapasztalható. Módosítható típus float: Lebegõpontos (valós számok tárolására alkalmas) elõjeles típus, mérete 4 byte. 6 tizedesjegyig pontos, egyszeres pontosságú lebegõpontos típus. Nem módosítható típus double: Dupla pontosságú lebegõpontos típus, mérete 8 byte. 15 tizedesjegyig pontos számolást tesz lehetõvé. Módosítható típus enum:
Felsorolt típus. Lehetséges értékei egy a felhasználó által megadott konstanshalmazból kerülnek ki. A felsorolt típus hosszúsága a felhasználó által felsorolt értékek számától függ, az értékek int típusú konstansként kerülnek értelmezésre. Pld.: enum valasz { igen, nem, talan }; A példában szereplõ változó 3x2, azaz 6 byte-ot foglal a memóriában, mivel a lehetséges értékek száma három, az int típusról pedig feltételezzük, hogy 16 biten van tárolva. struct: Az összetett típusok közé tartozik. Hasonló a Pascal nyelv record típusához Elsõsorban az adatállományok kezelése során használatos típus. A struktúra típusú változó méretét meghatározhatjuk, ha a struktúra tagjainak (mezõinek) méretét összeadjuk. Mindössze a deklarációban tér el a Pascal nyelvtõl. Példa: . struct szem { char nev[20]; char lakcim[40]; int eletkor; } Szemely; . A példában egyszerre deklaráltunk egy típust és egy változót. A
struktúra típusú változó a Szemely lesz, a struktúra típus a szem. union: Speciális, csak a C nyelv által támogatott típus. A struktúra adattipushoz áll legközelebb, de mûködése, és használatának jelentõsége merõben más. A union típus is eltérõ típusú mezõkbõl (tagokból) épül fel, de míg a struktúra esetén az egyes tagok a memóriában egymás után helyezkednek el, az unió tagjai közös kezdõcímet kapnak. Ezért az unió típusú változó mérete egyenlõ a leghosszabb tag méretével. Régebbi C változatok esetében a lehetõséget a memóriamegtakarítás miatt alkalmazták, ma speciális feladatok megoldása során használják. A típusmódosítók: A típusmódosítók segítségével az alaptípusok tulajdonságait (értéktartomány, tárolási méret) tudjuk módosítani. A típusmódosítók alkalmazásával tehát a meglévõ típusokból tudunk új típusokat elõállítani. A típusmódosítók néhány alaptípus esetén
használhatók: int, char, double A típusmódosítók: signed, unsigned, long, short. A típusmódosítók különbözõ kombinációit is alkalmazhatjuk, ha erre van szükség. A típusmódosítók segítségével elõállítható új típusok: signed char: Elõjeles egész szám, mérete 1 byte, értelmezési tartománya: -127.128 A char típus alapértelmezett jellemzõin nem módosít. unsigned char: Elõjel nélküli egész szám, mérete 1 byte, értelmezési tartománya: 0.255 long int: Elõjeles egész szám, mérete 4 byte, értelmezési tartománya: -2147483648.2147483647 short int: Elõjeles egész szám, mérete 1 byte, értelmezési tartománya: -32768.32767 Az int típus alapértelmezett jellemzõin nem módosít. signed int: Egyenlõ az int típussal, alapértelmezett jellemzõin nem módosít. unsigned int: Elõjel nélküli egész szám, mérete 2 byte, értelmezési tartománya: 0.65535 signed long int: Egyenlõ a long int típussal. unsigned long int: Elõjel
nélküli egész típus, mérete 4 byte, értelmezési tartománya: 0.4294967295 signed short int: Egyenlõ a short int típussal. unsigned short int: Elõjel nélküli egész szám, mérete 2 byte, értelmezési tartománya: 0.65535 long double: Kétszeres pontosságú lebegõpontos típus, mérete 10 byte, 19 tizedesjegyig pontos számolást tesz lehetõvé. A típusmódosítókat a deklarálás során használjuk fel, nem összetett típusokat képeznek, tehát a módosított típusok is az egyszerû típusok közé tartoznak (ez a függvények készítése során lehet fontos). 14. tétel: A C nyelv kifejezései, utasításai A C programok végrehajtandó része a program során elvégzendõ tevékenységek leírását, a szükséges utasításkat tartalmazza. A struktúrált programozási nyelvek sajátossága, hogy viszonylag kevés utasítást és sok beépített eljárást, függvényt tartalmaznak. Ezek a tulajdonságok C nyelv esetén is megfigyelhetõk, az utasítások
mindössze a struktúrált programozás eszözeit biztosítják a programozó számára. A C nyelv utasításainak csoportosítása: • Üres utasítás: • Összetett utasítás: • Szelekciós utasítások: • Vezérlésátadó utasítások: • Iterációs (ciklus) utasítások: • Kifejezés utasítások ; {} if, switch break, return for, while Üres utasítás: Olyan helyen alkalmazzuk, ahol szintaktikailag mindenképpen szerepelnie kell egy utasításnak, de semmilyen tevékenységet nem szeretnénk végezni. Nem tévesztendõ össze az utasításokat lezáró pontosvesszõvel. Összetett utasítás: A program blokkszerkezetének kialakítását teszi lehetõvé elsõsorban. Az összetett utasítás szintaktikailag egy utasításnak számít, de az utasítászárójelek között több utasítás is szerepelhet. Tehát célszerûen alkalmazhatjuk az összetett utasítást a program olyan részeinél, ahol egy utasítás állhat, de több utasítást szeretnénk
egyszerre elvégezni. Például: ciklusmagok, szelekciós utasítások. Szelekciós utasítások: Ezek az utasítások lehetõvé teszik a program feltételtõl függõ végrehajtását, két vagy több irányban. if utasítás: Az if utasítás feltételes végrehajtást, illetve kétirányú elágazást tesz lehetõvé. A két lehetõségnek megfelelõen két formában használhatjuk ezt az utasítást. Feltételes végrehajtás: if (feltétel) utasítás; Az utasítás csak akkor kerül végrehajtásra, ha a feltétel teljesül, egyébként a program az utasítást figyelmen kívül hagyja. A feltétel lehet egy változó vagy egy kifejezés A kifejezés típusa lehet logikai (értelemszerûen), és numerikus. A numerikus kifejezés esetén a 0-nál nagyobb érték jelenti a feltétel teljesülését, míg a 0 és a negatív érték hamis eredményt szolgáltat. Kétirányú elágazás: if (feltétel) utasítás1; else utasítás2; Amennyiben a feltétel igaz, az utasítás1,
egébként az utasítás2 kerül végrehajtásra. Az utasítás1 után mindig ki kell tenni a pontosvesszõt (Pascal-tól eltérõen). A feltétel ebben az esetben is egyaránt lehet változó vagy kifejezés. Az if utasítás mindkét formájára igaz, hogy a feltételt minden esetben zárójelek között kell megadni. switch utasítás: A switch utasítás segítségével tudjuk megoldani a program során az esetszétválasztást, azaz a kettõnél több irányú elágazást. Az esetszétválasztás során egy változó vagy egy kifejezés értékétõl függõen folytatódhat a program végrehajtása több (max 257) irányban. A változó, illetve kifejezés típusa mindenképpen egész típus kell legyen. A felvehetõ értékeket a switch utasításon belül konstansként kell meghatároznunk. switch (kifejezés) { case konstans1: utasítások; case konstans2: utasítások; . default: utasítások; } A konstansok elõtt szerepelnie kell a case utasításnak, a konstans értéktõl
kettõsponttal elválasztva írhatjuk le az utasításokat. Az esetkonstansok után több utasítást is megadhatunk utasítászárójel ( {} ) alkalmazása nélkül. A default utasítás után szereplõ utasítások akkor kerülnek végrehajtásra, ha a kifejezés (változó) értéke nem volt egyenlõ egyik konstanssal sem. A default utasítás elhagyható. Fontos figyelnünk arra, hogy miután egy konstanshoz tartozó utasítások végrehajtásra kerülnek, ki kell ugranunk a swith utasításból, ez ugyanis nem történi meg automatikusan (Pascal-lal ellentétben). A kiugrást célszerû a break utasítással végrehajtani Egy példa a swith utasítás alkalmazására: #include <stdio.h main() { char c; printf("A valasz [I/N]: "); c=getchar(); switch(c) { case I: case i: printf("A valasz: Igen "); break; case N: case n: printf("A valasz: Nem "); break; default: printf("Hibas valasz! "); } return 0; } A program beolvas egy betût a
billentyûzetrõl, majd kiértékeli, melyik betût ütöttük le. A case utasítás után csak egy konstans értéket adhatunk meg. Ha több érték esetén szeretnénk ugyanazt a tevékenységet végrehajtani, célszerû a fenti módszert alkalmazni. Minden esetkonstanshoz tartozó utasítássorozat végén célszerû a switch utasításból kiugrani, a break utasítás segítségével. Ha ezt nem tesszük akkor szemantikai hibát fogunk tapasztalni (a program nem úgy fog mûködni, ahogy szeretnénk. Hagyjuk el a break utasításokat, futtassuk a programot és meglátjuk mi fog történni, itt leírni hosszú lenne). Vezérlésátadó utasítások: Mint a nevükbõl is lehet következtetni, a program futása során a végrehajtás menetét befolyásoló utasítások. Hatásukra a program futásának folytonossága megszakad, a végrehajtás egy távolabbi utasításnál folytatódik. Azt, hogy hol fog folytatódni a végrehajtás az utasítás határozza meg break utasítás:
Hatására a program kilép az éppen végrehajtás alatt álló szelekciós vagy ciklusutasításból, és a szelekciós vagy ciklusutasítás után álló utasítással folytatja a program végrehajtását. Tehát egy szelekció vagy egy ciklus megszakítható a break utasítás segítségével. Ciklusok közül elsõsorban a while ciklusoknál alkalmazható, a for ciklus mûködésébe "nem illik" beavatkozni (de lehetséges!). return utasítás: Hatására a program kilép az éppen végrehajtás alatt álló függvény törzsébõl, és a függvényhívás után álló utasítással folytatódik a program végrehajtása. A return utasítás után meg kell adnunk a függvény visszatérési értékét. A return utasításnak speciális szerepe van a main() függény használata során. A main() függvényben szereplõ return utasítás hatására a program mûködése befejezõdik. Iterációs (ciklusszervező) utasítások: Minden magasszintû programozási nyelv
biztosítja a lehetõséget az ismétlõdõ tevékenységek ciklusban történõ végrehajtásához. (Bõvebben lásd 8 tétel) A C nyelv háromféle ciklusszervezési lehetõséget biztosít a programozó számára. for ciklus: Elöltesztelõ ciklus, az ismétlések szám elõre meg van határozva. Mûködés szempontjából hasonló a Pascal For ciklusához. for (ciklusváltozó=kezdõérék; feltétel; léptetés) utasítás; A for ciklus fejrészében együtt határozzuk meg a ciklus futásának összes feltételét. Az elsõ a ciklusváltozó kezdõértékének meghatározása, amely egy egyszerû értékadással történik. A feltétel határozza meg, hogy a ciklusváltozónak milyen értéket kell elérni a ciklus befejezéséhez. A léptetés során növeljük vagy csökkentjük a ciklusváltozó értékét. A ciklusváltozó int típusú változó. Példa: . for (i=1; i<=10; i++) printf("%d ", i); . A példaprogram kiírja a ciklusváltozó értékét a
ciklus minden egyes iterációja (végrehajtása) során. A for ciklus esetében a ciklusmag egyetlen utasítást tartalmazhat, ezért gyakran alkalmazzuk az összetett utasítást ciklusmagként. while ciklus: Elöltesztelõ ciklus, az ismétlések száma nincs elõre meghatározva, határozatlan ismétlésszámú ciklus. while (feltétel) utasítás; A feltételt itt is mindig zárójelek között kell megadni. A feltétel int vagy logikai típusú kifejezés, kiértékelése az if utasításánál leírtak szerint történik. A ciklusmag addig ismétlõdik, amíg a feltétel igaz. A ciklusmag itt is egy utasításból épül fel, gyakran használjuk az összetett utasítást Példa: Határozzuk meg az elsõ N természetes szám összegét. #include <stdio.h main() { int n, i=0, sum=0; printf("N= "); scanf("%d", &n); while (i<=n) { sum+=i; i++; } printf("Az elso %d termeszetes szam osszege: %d ", n, sum); return 0; } A ciklusmag
végrehajtása addig ismétlõdik, amíg i értéke el nem éri n értékét. A ciklusmagon belül magunknak kell változtatni az i változó értékét, a while ciklusnál a ciklusváltozó léptetése a ciklusmagon belül történik. do . while ciklus: Hátultesztelõ, határozatlan ismétlésszámú ciklus. do utasítás; while (feltétel); A ciklusmag végrehajtása addig ismétlõdik, amíg a feltétel igaz. Tulajdonképpen a while ciklus hátultesztelõ változatának tekinthetõ. Sokkal ritkábban alkalmazzuk, mint az elõzõ két ciklust A feltétel int vagy logikai típusú lehet. A ciklusmag egyetlen utasítást tartalmazhat, tehát ha több utasítást szeretnénk itt elhelyezni, akkor összetett utasítást kell alkalmaznunk. A ciklusváltozó értékének változtatását a ciklusmagon belül a programnak kell elvégezni, a léptetés nem automatikus. Az elõbbi feladat megoldása do while ciklus alkalmazásával: #include <stdio.h main() { int n, i=0, sum=0;
printf("N= "); scanf("%d", &n); do { sum+=i; i++; } while (i<=n); printf("Az elso %d termeszetes szam osszege: %d ", n, sum); return 0; } Kifejezések: A kifejezések operátorokból (mûveleti jelekbõl) és oprandusokból (változókból, konstansokból) felépülõ mûveletek. Az operátorok határozzák meg az elvégzésre kerülõ mûveletet, az operandusok adják az értéket, amivel mûveletet kell végezni. Egy kifejezésen belül elõírhatjuk több mûvelet elvégzését is: ezek az összetett kifejezések. Az összetett kifejezések kiértékelése során a mûveletek sorrendjét az operátorok határozzák meg, ezt nevezzük precedencia szabálynak. A precedencia szabály határozza meg, hogy az összetett kifejezések kiértékelése során milyen sorrendben kell elvégezni a mûveleteket. Egy másik fontos szabály az asszociativitás, amely meghatározza, hogy az összetett kifejezésen belül az azonos precedenciaszintû operátorok
milyen sorrendben kerülnek kiértékelésre. A C operátorok precendenciája és assziciativitása () [] . - balról jobbra ! ~ - ++ -- & * (típus) sizeof jobbról balra */% balról jobbra +- balról jobbra << balról jobbra < <= = balról jobbra == != balról jobbra & balról jobbra ^ balról jobbra | balról jobbra && balról jobbra || balról jobbra ?: jobbról balra = += -= *= /= %= <<= = &= |= ^= jobbról balra , balról jobbra A precedencia alapján csak akkor lehet meghatározni az összetett kifejezés kiértékelésének sorrendjét, ha a kifejezésben szereplõ mûveletek különbözõ precedenciájúak. Pld: e=a+3*b; A fenti kifejezés kiértékelése során elõször nyilván a szorzást végzi el a program, majd a szorzathoz adja hozzá a változó értékét. A precedencia szabályait felülbírálhatjuk zárójelek segítségével (hogy pontosan fogalmazzak a kifejezés precedenciáját
változtatjuk meg, hiszen a táblázatban látható, hogy a zárójel a legmagasabb precedenciával rendelkezõ operátor). e=(a+3)*b; A kifejezés kiértékelése során mostmár elõször az összeadás történik meg, aztán kerül sorra a szorzás. Ha egy kifejezésen belül azonos precedenciaszintû operátorok szerepelnek, azok kiértékelésének sorrendjét az asszociativitás határozza meg. Pld: . e=3; b=2; c=5; e+=b+7-2+c; . A példában az e változó értéke 15 lesz, mivel elõször az elsõ összeadás kerül kirtékelésre, majd a kapott eredménybõl kivonjuk a 2-t, hozzáadjuk a c változó értékét, és végül a kapott értékkel (12) megnöveljük az e változó értékét. A kifejezés kiértékelése során a precedencia mellett figyelembe kell venni az asszociativitás szabályait is, mivel az összeadás és a kivonás azonos precedenciájú mûveletek. Egy érdekes lehetõség a C nyelvben a többszörös értékadás. Az értékadás is a kifejezések
közé sorolható. Nézzünk egy példát: a=b=2; Ez a sor a C programban egy utasításként kerül értelmezésre, mivel bármely kifejezésbõl utasítást lehet elõállítani, ha a kifejezés után pontosvesszõt írunk. A kifejezés (utasítás) két azonos precedenciájú mûveletbõl épül fel: a=2, b=2. A két mûveletet egyetlen kifejezésbe is írhatjuk, mivel az értékadás mûvelet kiértékelése jobbról balra haladva történik, tehát elõször a kifejezés jobb oldalán álló konstans kerül értelmezésre, majd elõször a b, másodszor az a változó kapja meg a konstans értékét. Szintaktikailag helyes a következõ utasítás is, amelyet egy kifejezés segítségével írunk fel: x+2; A kifejezés valóban utasításként kerül kiértékelésre, de hatására semmi nem történik, tehát szintaktikailag helyes, de valójában nincs értelme ilyen utasításokat írni a C programba