A PASCAL

Blaise Pascal francia matematikus, filozófus, élt 1623-tót 1662-ig: Már 1642-ben, 19 éves korában elkészítette a világ valószínűleg első működő (mechanikus) számítógépét, az arithmométert, amelynek több eredeti példánya máig is működőképesen fennmaradt. Pascal így jogosan tekinthető a számítástechnika legelső úttörőjének: Később számos jelentős felfedezést tett a matematika és más tudományok területén. Méltán nevezték el Pascalról a korszerű számítástechnikának az egyik legtökéletesebb és talán legszebb nyelvét.

1. BEVEZETÉS - Miért a Pascal? 5. A beépített függvények és eljárások
2. A Pascal program Egész eredményt szolgáltató függvények
3. A deklarációs rész Matematikai függvények

A típusdeklarációk

A bevitellel és kivitellel kapcsolatos eljárások és függvények

Össtetett típusok

6. A Turbo-Pascal eljárásai és függvényei
4. A programszerkezetek A stringek és kezelésük

Az utasításrész

A dinamikus változókkal kapcsolatos eljárások és függvények

Az Értékadó utasítás

A képernyőkezelés; szöveges üzemmód

Az eljárás-utasítás

A billentyűzet kezelése

A ciklusszervező utasítások

Egyéb matematikai függvények

A FOR ... DO utasítás

Egyéb eljárások, függvények

A WHILE ... DO utasítás

7. A fordító direktívák

A REPEAT ... UNTIL utasítás

 

Az elágazó utasítások

 

Az üres utasítás

 

Eljárások és függvények

 

 

1. BEVEZETÉS - Miért a Pascal?
A számítástechnika és -tudomány a leggyorsabban fejlődő területe az emberi tevékenységnek. Már nem is évről évre, hanem szinte hónapról hónapra jelennek meg az újabb gépek, programok, és viharos gyorsasággal fejlődnek a programozási nyelvek is. Ennek a szédületes iramú fejlődésnek azonban megvan az árnyoldala is.
A terület harmonikus fejlődésének a menete az volna, ha a felhasználók megmondanák, hogy mire van szükségük; a programozók kifejtenék, hogy milyen szoftvereszközökkel tudnák megoldani ezeket a feladatokat; a rendszerprogramozók kitalálnák, hogy ehhez milyen eszközöket (operációs rendszereket, programnyelveket) kell kifejleszteni; végül a hardvergyártók megalkotnák az ehhez szükséges - és leginkább alkalmas - számítógépeket.
A valóságban ennek kezdettói fogva pont az ellenkezője zajlik: a hardvergyártók újabb és újabb gépeket hoznak létre, melyek mindegyike sokkal nagyobb teljesítményű, mint elődje, de szinte figyelembe sem veszik a felhasználóik tényleges igényeit. A rendszerprogramozók csonkig rágják a ceruzájukat, mire az egyre gyorsabb és nagyobb, de egyáltalán nem átgondolt szerkezetű gépekre a megfelelő rendszerprogramokat kifejlesztik. A kiinduláskor elkövetett hiba tovább gyűrűzik a felhasználóig.
Ennek eklatáns példája a FORTRAN programozási nyelv története. Senki sem vitatja el a nyelv érdemeit (egy generáció nőtt fel rajta, és feladatok tömkelegét oldották meg segítségével); azonban a nyelv illogikus, szedett-vedelt szabályaiból is kiviláglik, hogy az elsőrendű szempont nem az volt, hogy mennyire képes a programozót segíteni, hanem, hogy könnyű volt hozzá fordítóprogramot fabrikálni. A szakemberek hiába dolgozták ki ugyanabban az időben a logikusabb és a feladatmegoldásra sokkal alkalmasabb Algol-60 programnyelvet, mindenki a FORTRAN-t használta. Miért? Mert az futott a Nagy Kék, az IBM számítógépein.
A BASIC a Fortran kisöccse. Más előnye nincs, mint hogy könnyű megvalósítani, és hogy rendkívül elterjedt. Szokás sok-sok más előnyét is emlegetni. A szakember számára világos, hogy ezek az úgymond előnyök mind szakmai, mind pedagógiai szempontból valójában hátrányt jelentenek.
A számítástudomány pedig mindig időben létrehozta az adott kor ismeretei szerinti korszerű, jól használható programozási nyelveket. Ezek egyike a Pascal. A mi hibánk, ha nem használjuk ki előnyeit. Lássuk hát, hogy mit tud a Pascal, mondjuk, a BASIC-kel szemben.

Sebesség
A Pascal nyelven írt program a gépi kódéhoz hasonló - a BASIC után szinte fantasztikusnak tűnő - sebességgel fut. Ez abból következik, hogy a Pascal programot futtatás előtt a fordítóprogram gépi kódú programmá alakítja át. Ez néhány nagyságrenddel gyorsabb futást eredményez, mint a végrehajtás pillanatában értelmezett BASIC programé.
Ennek az érvnek van egy kis szépséghibája, mégpedig, hogy végső soron a BASIC program is lefordítható gépi kódra. A gyakorlatban azonban a BASIC fordítóprogramok nem nagyon terjedtek el: a BASIC tipikusan értelmezett, interpretált nyelv maradt. Az IS-BASIC ismert fordítóprogramja, a ZZZIP, túlságosan sok megkötést ír elő a lefordítani szánt programnál; tulajdonképpen nem azt a BASIC változatot valósítja meg, amelyiket a géppel gyárilag adott interpreter ismer. Sajnos, az IS-BASIC nyelvet pontosan egyik legrokonszenvesebb vonásától, a lokális változók használatának lehetőségétől fosztja meg. A ZZZIP fordítóprogramnak ugyanakkor a használata sem túlságosan praktikus, különösen lemezegység nélküli gépen.

A programszerkezetek
A programszerkezetek jelentősége tulajdonképpen nem igazán jelentkezik mindaddig, amíg az elkészítendő program mérete nem haladja meg a húsz-harminc sort. Egy ekkora program mindig áttekinthető marad, akármilyen össze-vissza módon is van szerkesztve. A probléma akkor jelentkezik, amikor nagyobb programot kell (ne adja Isten, határidőre, lehetőleg hibátlanul) készíteni. A program bonyolultsága - és ezzel együtt áttekinthetetlensége, a hibalehetőségek száma - a program méretével nem lineárisan, hanem ennél sokkal erősebben növekszik. A megoldás, ha kizárólag csak olyan jól definiált programszerkezeteket használunk, amelyek jól strukturált, könnyen áttekinthető, könnyen tesztelhető és javítható programot eredményeznek. A BASIC nagy hibája, hogy az egyetlen valódi programszerkezet, amit ismer, az utasítás vagy utasítássor. A Pascal által ismert programszerkezetek segítségével viszont szinte ideális tisztaságú programokat készíthetünk.

A program hossza és bonyolultsága közötti összefüggés katasztrófális hatását világítsuk meg egy példával. Tegyük fel, hogy a hossztól a bonyolultság négyzetesen függ. Ez azt jelenti, hogy a kétszer, háromszor hosszabb program négyszer, kilencszer bonyolultabb.
Most tételezzük fel, hogy egy százsoros program elkészítéséhez egy napra volt szükségünk. Egy komoly ügyviteli program vagy rendszerprogram néhányszor tízezer sorból áll. Elvállaltuk egy tízezer soros program megírását, mondjuk, négy hónapos határidővel, abból a hitből kiindulva, hogy a százszor akkora program megírásához elég a száz nap. Természetesen minden határidő módosítást folyamatosan túllépünk. Ez nem is meglepő, hiszen a négyzetes hossz-bonyolultság összefüggés alapján számolva a program bonyolultsága nem százszoros, hanem tízezerszeres. Ez azt jelenti, hogy a várható elkészítési idő tízezer nap, azaz mintegy 27 év... egy egész dolgos élet!
A valóságban a bonyolultság ennél még nagyobb is lehet.

A programszerkezetekhez tartozik a Pascal igen jól definiált és következetes eljárás- és függvénydeklarációja, illetve ezek hívása. Nagyon fontos a paraméterátadás (a beavatottabbaknak: az érték szerinti illetve a cím szerinti átadás), a lokális változók (általában a lokális objektumok), és itt említjük meg a rekurziót, mint bizonyos fajta feladatok egyetlen lehetséges, sok más feladatnak pedig egyszerűen csak célszerű megoldását.

Az adatszerkezetek
A BASIC példáján felnőtt programozó az adatszerkezetek fogalmát még kevésbé érti, mint a programszerkezetekét. Az iparszerű programozás-technikában pedig az adatszerkezeteknek a programszerkezetekkel azonos vagy talán még fontosabb szerepük van.
A BASIC csak néhány egészen elemi adatszerkezetet ismer, mégpedig az egész és a valós számokat (sok megvalósítás ezek közül is csak az egyiket), valamint a (karakter)stringet (magyarul füzért). Képes ezekből összetett adatszerkezeteket (tömböket) létrehozni. Ismeri még tulajdonképpen a file (a magyar neve állítólag állomány) fogalmát, de a BASIC felfogásában ez nem igazán adatszerkezet.
Ez a szűklátókörűség tisztán csak hagyomány: a számítógépeket legelőször csak numerikus alkalmazásokra szánták (mindenki tudja, hogy a számítógép a pontos célzás igényének köszönheti létét: az első számítógép a tüzérségnél használt lőelemképzőt akarta kiváltani). Ezeknél az első alkalmazásoknál ez a néhány szerkezet is elegendő volt.
Manapság az élet minden területén használják a számító- (helyesebben szólva: adatfeldolgozó) gépeket. Az alkalmazásoknak csak kis hányada dolgozik számokkal, a többségük mindenfajta egyéb objektumokon operál. A Pascal, bár eleve több adattípussal rendelkezik, azt is lehetővé teszi, hogy a programozó tetszés szerinti, új adattípusokat hozzon létre. így a program algoritmusa a feladat megoldásához legjobban alkalmazkodó adattípusokkal dolgozhat.

Gépfüggetlenség
Egy-egy program kifejlesztése igen sok eszközt és ráfordítást igényel. Áll ez a mi saját programjainkra is, de különösen a hatalmas méretű gyári programokra. Jogos a törekvés, hogy a sokszor több ember több évi munkáját követelő program ne csak egy géptípuson működjön. A számítógépek gyors generációváltása is indokolja, hogy az előző gépre fejlesztett program a következőn is használható legyen. A BASIC nyelvjárásai annyira szerteágazóak, hogy szinte bizonyosra vehető a kudarc, ha egy másik rendszeren fejlesztett BASIC programot próbálunk átvenni. Gondoljunk csak a valamennyire szabványosnak felfogható Microsoft BASIC-re, az IBM gépeken és utánzóikon futó GW-BASIC-re, vagy az ENTERPRISE rokonszenves, de egyáltalán nem szabványos alig-BASIC jére.
A Pascal nyelvet olyan gondosan tervezték, hogy a legtöbb fordítóprogramja szinte változtatás nélkül valósítja meg. Ugyanakkor igen magas szinten szabványosítják az idők folyamán mégis szükségessé váló módosításokat, kiegészítéseket. Ez garanciát ad arra, hogy a leporolt régi Pascal program változatlanul, vagy igen kicsiny, jól definiálható változtatással lefordul majd a legújabb fordítóprogrammal, és futni fog a következő, még gyorsabb, még többet nyújtó gépen is.

Egyéb
Sokáig lehetne még sorolni a Pascal feltétlen előnyeit a BASIC-szerű programozási nyelvekkel szemben. A teljességre törekvésre nem is gondolva itt most csak megemlítünk néhányat.
A Pascal eljárásai (ezek felelnek meg a BASIC-ben mellesleg nem túl ügyesen megvalósított szubrutin fogalmának) és függvényei paraméterezve hívhatók meg. Ennek elemi szükségességét minden programozó ismeri. További óriási előny, hogy az eljárásokon és függvényeken belül lokális (csak az adott eljárás vagy függvény határain belül értelmezett) változók létezhetnek, amelyek még névazonosság esetén sem léphetnek konfliktusba más eljárások vagy a főprogram változóival.
A Pascal nagy előnye még, hogy rendkívüli nyelvi gazdagsága ellenére könnyen fordítható, és megdöbbentően gyors fordítóprogram készíthető hozzá. Ennek legjobb példája a közismert Turbo-Pascal.
Még egy momentum: a Pascal program- és adatszerkezetei közel állnak a normális emberi gondolkodás fogalmaihoz. Teljesen jogos az az igény, hogy ne az embernek kelljen a számítógép korlátaihoz idomulnia, hanem a gépet emeljük fel az ember intellektuális szintjére. Ez ugyanakkor azt is jelenti, hogy a Pascalt megtanulni is könnyű. Ugyanakkor a tapasztalat azt mutatja, hogy akik a BASIC használatával kezdtek el programozni, később alig képesek megszabadulni a beléjük rögzült rossz programozási megoldásoktól. Pedig semmi akadálya nincs annak, hogy már a legelső pillanatban ne BASIC-ben, hanem Pascalban kezdjük tanulni a programozást.
Végül felmerül a kérdés: az ENTERPRISE felhasználói milyen módon próbálkozhatnak meg a Pascal nyelvű programozással? Nos, a csak magnóval rendelkező felhasználóknak elérhető a Spectrumosok által is ismert HiSoft-Pascal, amely egy elég teljes, többé-kevésbé jól sikerült megvalósítás. Saját, beépített editorral dolgozik, gyors, és a memóriában fordít, a program azonnal futtatható. Ugyanakkor a kész programból önállóan betölthető, futtatható kód állítható elő. A HiSoft-Pascal szépséghibája, hogy - mivel nem ENTERPRISE-ra készült - nem tudja közvetlenül kezelni a gép grafikai és egyéb lehetőségeit. Ez azonban senkit ne keserítsen el: készült ugyanis egy olyan kiterjesztés, amellyel az ENTRPRISE minden előnyös tulajdonsága kihasználható. Ezt a kiegészítést - mivel elég nagy terjedelmű, kényszerűen folytatásokban - közreadjuk.
Akik lemezegységgel is rendelkeznek, a Turbo-Pascal különböző verzióit használhatják. Ez a program megjelenése óta folyamatosan sztár mind az azóta gyakorlatilag már csak a hobbi-kategóriában megtalálható nyolcbites, mind pedig az egyre inkább mindenkinek elérhetővé váló IBM-kompatibilis számítógépeken programozók körében.

2. A Pascal program
A hosszúra nyúlt bevezetés után lássuk a Pascal jellemzőit.
A Pascalt ismertető könyvek általában a nyelv legalacsonyabb szintű építőelemeivel kezdik a tárgyalást, ezekből építik fel az egyre bonyolultabb szerkezeteket. Ebben a sorozatban ellenkező úton járunk: a legmagasabb szintről kezdve haladunk a legalacsonyabbak felé. Ez egyébként megfelel a korszerű programtervezési módszereknél alkalmazott top-down, azaz felülről lefelé haladó tervezési eljárásnak. Ugyanakkor azt is lehetővé teszi, hogy in medias res - azonnal a dolgok közepébe vágva - érdekesebbé és olvashatóbbá tegyük a leírást.
Ahhoz, hogy a menet közben példákat is megadhassunk, előfordulhat, hogy időnként még nem definiált dolgokra is hivatkoznunk kell; ezeknek az értelmezését vagy az olvasó józan belátására bízzuk, vagy ideiglenesen megmagyarázzuk.

Az első, amivel meg kell ismerkednünk, a program felépítése. Míg a Basic-ben nincs különös megkötés a program fogalmára - programnak számít minden, amiben Basic utasítások vannak -, addig a Pascal szigorúbb szerkezete meghatározza a program fogalmát. A Pascal program egy programfejből és egy programtörzsből (a terminológia szerint blokkból) áll. Ezeket a megfelelő elválasztójellel kell egymástól (és a külvilágtól) elválasztani.
A programfej jelzi a program kezdetét és azonosítja a programot. A pontosvessző a - kötelező - elválasztójel. A blokk vagy programtörzs végez minden hasznos tevékenységet. A blokk után álló pont a program végét jelzi. Ne hagyjuk el!
Maga a programfej a PROGRAM kulcsszóból és a program nevéből áll. A legtöbb fordítónak mindegy, hogy nagy- vagy kisbetűvel írjuk-e. A program neve egy tetszőleges azonosító, de célszerű a program tényleges nevét megadni.
A blokk két fő részből áll: a deklarációs részből és az utasításrészből.
A deklarációs rész egyrészt meghatározza az adatszerkezeteket, amelyekkel a program dolgozni fog, másrészt - az adatszerkezetekhez hasonlóan - meghatározza a program által használni kívánt absztrakt eljárásokat. Az utasításrész - más programnyelvek terminológiája szerint a "főprogram" - végzi a tulajdonképpeni műveleteket. Mind a deklarációs rész, mind az utasításrész lehet üres is.
A deklarációs rész a konstansdefiníciókat, típusdefiníciókat, változódefiníciókat, valamint az eljárás- és függvénydefiníciókat tartalmazza. Ezekre hamarosan kitérünk. Az utasításrészt a BEGIN és az END kulcsszó határolja. Ezeket úgy kell felfogni, mint egyfajta zárójeleket; a nevük is utasítás-zárójel. Minden, ami az utasítászárójel-páron belül van, együtt kezelendő, egyetlen utasításnak számít (lásd az utasítássorozat fogalmát a bevezető részben). Az eljárási rész után egy pont áll, ami az egész programot lezárja. A legegyszerűbb Pascal program tehát így néz ki:

PROGRAM programnév;
BEGIN
END.

Ez a program az égvilágon semmit nem csinál, viszont kipróbálható vele a fordítóprogram működése. A programnév helyére egy tetszőleges azonosítót - célszerűen a program nevét - kell beírni. Az azonosító - mint a legtöbb programnyelvben - belűvel kezdődő és betűvel vagy számmal folytatódó karaktersorozat. A hosszát egyes fordítóprogramok 8 vagy esetleg 16 karakterben korlátozhatják, vagy csak az első a karakterét tekintik érvényesnek; mások tetszőleges hosszúságig elfogadják. Akármit is enged meg a fordítónk, ne vigyük túlzásba! Egyikünk sem képes első látásra megkülönböztetni olyan azonosítókat, mint XXXXXXCXABBABABABBA vagy XXXXXXCXXABBABBABABA.
Azoknak, akik elsőre ennél többet akarnak, a program eljárási részét kibővítjük egy utasítással, amely a később tárgyalandó WRITELN eljárást használja; mivel ez már nem minta, hanem konkrét, működő program, a programnév helyére egy konkrét nevet írtunk be.

PROGRAM elso_programom;
BEGIN
  WRITELN('Tessek, itt vagyok!');
END.

A program, elvárásunknak megfelelően, az aposztrófok közötti szöveget írja majd ki a képernyőre.

3. A deklarációs rész
A Pascal program deklarációs része határozza meg, mint már említettük, azokat az adat- és eljárási szerkezeteket, amelyeket a program később használni fog. A deklarációs rész elemei (ebben sorrendben):

Az összes felsorolt elem el is hagyható. Megjegyezzük még, hogy egyes Pascal-implementációk, Így a Turbo-Pascal is, nem ragaszkodnak a deklarációk itt meghatározott kötött sorrendjéhez, az egyes deklarációk keverve is előfordulhatnak.
A deklarációs rész tulajdonképpen a címkedeklarációkkal kezdődne. A címke szerepe, hogy GOTO utasítással ráugorhassunk; mivel azonban nincs olyan program, amelyik ne lenne ugrás nélkül is megírható, a sorozatban mind a címkéket, mind a GOTO utasítást elfelejtjük. Aki ragaszkodik az ugráláshoz, a számtalan szakkönyv egyikében utánanézhet, hogyan teheti programját áttekinthetetlenné és hibákra érzékennyé.
A Basic nyelvben a konstans tulajdonképpen egy érték, amelyre felírásával hivatkozunk; ez az úgynevezett alaki konstans. Számos esetben azonban a konstansok értéke a program fejlesztése során változhat. Például menet közben rájövünk, hogy más méretű képernyőt vagy ablakot kell használnunk, mint amilyent eredetileg akartunk. Ilyenkor, ha a képernyő vagy ablak vízszintes és függőleges méretét, illetve az ettől függő egyéb állandókat számértékként (alaki konstansként) adtuk meg, a program módosítása emberfeletti nehézségekbe ütközhet: meg kell keresni az összes konstans összes előfordulási helyét és átírni az új értékre. Más esetben igen kellemetlen lehet a konstans értékét állandóan kikeresni és a kellő pontossággal beírni a kifejezésekbe. Az előrelátó programozó ilyen esetekben a konstans helyett változót használ, amit a program elején feltölt a megfelelő értékkel. Egy megváltozott értéket ilyenkor csak egyetlen egy helyen kell módosítani. A hibalehetőség itt az, hogy elfelejtjük a kezdeti értékadást; illetve valahol a programban tévedésből megváltoztatjuk a "konstans" értékét. A Pascal bevezeti a név szerinti konstans fogalmát. A konstans ekkor egy ugyanolyan azonosító, mint a változók azonosítója, de értéke állandó. Ugyanúgy szerepelhet kifejezésekben, mint a változók, de új értéket nem kaphat. A konstansdeklarációk rész felsorolja az ilyen név szerinti konstansokat, meghatározva értéküket. A deklarációkat a CONST kulcsszó vezeti be, ezt követik az egyes konkrét konstansdefiníciók. A példában - a Pascal szabályainak megfelelően - kapcsos zárójelben álló megjegyzés magyarázza az egyes konstansok szerepét.

CONST
  X MAX=1329; { a max. vízszintes felbontás }
  Y_MAX=759;  { a max. függőleges felbontás }
  UZENET='*** Hibas ertek! ***'; { hibaüzenet }
  SZOKOZ=' '; { a szokoz }
  URES_HELY=SZOKOZ; { egy konstans egy másikra hivatkozik }
  PI=3.14159265358;

Látható, hogy az összes ismert típussal megadható konstans (a fordítóprogram a konstans alakjából következtet az alkalmazandó típusra). Az X_MAX és az Y_MAX konstans így egész típusú lesz; az UZENET sztring típusú, míg a SZOKOZ (és a rajta keresztül definiált URES HELY) karakter típusú. A PI konstans természetesen valós típusú lesz. (A Pascal implementációk - így a Turbo-Pascal is - a Pi értékét egy beépített argumentum nélküli függvénnyel teszik hozzáférhetővé; ilyen esetben nem szükséges a PI konstanst deklarálnunk.) A Pascal egyes továbbfejlesztései további lehetőségeket engednek meg, úgymint (fordítási időben kiszámítható eredményű) kifejezések alkalmazása a konstansok definíciójában, illetve a tipizált konstans; ezeket majd később tárgyaljuk.

A típusdeklarációk
A Pascal nyelv igazi gazdagsága a felhasználó által definiálható típusoknál mutatkozik meg igazán. A típusdeklarációk segítségével a mindenkori feladatnak legjobban megfelelő adattípusok hozhatók létre. Ez a programozási feladat bonyolultságát több nagyságrenddel is csökkentheti. A saját típusok létrehozásának két módja van: új típus vagy egy meglévő (szabványos vagy korábban a felhasználó által definiált) típus szűkítésével hozható létre, vagy pedig ugyanilyen meglévő típusok kombinálásával. Először gyorsan tekintsük át a nyelvben már meglévő, szabványos típusokat, röviden kitérve gépi reprezentációjukra is.

Az egész típus
Az egész (INTEGER) típus az egész számok egy részhalmazát veheti fel értékként. Mivel a gépi megvalósításánál (a gyorsaság miatt) rendszerint két bájton ábrázolják, a szokásos értéktartománya a kettes komplemensű aritmetika szabályainak megfelelően -32768-tól +32767-ig terjed. Ez elég kevésnek tűnik, de a gyakorlat azt mutatja, hogy az egész típust rendszerint csak ciklusszervezésre, számlálásra, és még néhány egyszerűbb feladatra használják, erre pedig általában ez az értéktartomány elegendő. Ha nagyobb értékekkel kell dolgoznunk, használjuk a valós típust. Az adott Pascal implemetációban a használható legnagyobb egész értéket a MAXINT (argumentum nélküli) függvény adja meg.
Egyes Pascal implementációk ismerik még az egy bájt hosszúságú egész típust is, ennek neve BYTE, értéktartománya 0-tól 255-ig terjed. A bájt típus mind az egész, mind pedig a (később ismertetendő) karakter típussal kompatibilis, azaz értéket adhat át azoknak vagy vehet át ezektől típuskonverzió nélkül. Egyes implementációk ugyancsak ismerik a szó (WORD) típust; ezt szintén két bájton ábrázolják, de előjel nélküli számként értelmezve. Az értéktartomány így 0-tól 65535-ig terjed. Segítségével nagyobb ciklusok szervezhetők, mint az egész típussal, és a 16 bites címeknél a teljes címtartomány átfogható.

A valós típus
A valós (REAL) típus a valós számok egy részhalmazát veheti fel értékként. Az egyes gépi megvalósításokban csak az a közös, hogy minden esetben lebegőpontos számként ábrázolják; egyébként jelentős eltérések lehetnek mind a maximális értékben, mind az elérhető (relatív) pontosságban. A Turbo-Pascal például a valós típust 6 bájton ábrázolja: egy bájton az exponenst kettes komplemens alakban, öt bájton pedig a mantisszát, ugyancsak kettes komplemens alakban. Az értéktartomány így a -9.9999999999E+37 értéktől a +9.9999999999E+37 értékig terjed, míg az exponens értéktartománya -38-tól +37-ig érvényes. Az értékes tizedesjegyek száma 11. Más rendszerekben a megoldás más lehet.

A logikai típus
A logikai (BOOLEAN) típus mindössze két értékkel rendelkezik: igaz (TRUE) és hamis (FALSE). Az összehasonlítások (relációk), feltételvizsgálatok eredménye logikai érték.

A karakter típus
A karakter (CHAR) típus egyetlen karakter tárolására alkalmas. Gépi megvalósításához többnyire egyetlen bájtot használnak fel.
A felsorolt négy típust - tehát az egész, a valós, a logikai és a karakter típust - összefoglaló néven skalár típusként emlegetik, mivel mindegyikük csak egy-egy értéket vehet fel. A valós típust kivéve, mindegyiküket sorszámozott típusnak is nevezik, mivel lehetséges értékeik halmaza megszámlálható.

A string típus
A szabványos Pascal - sajnos - nem ismeri az igen sokoldalú string (magyarul füzér) típust, helyette a karaktertömböt lehet használni. A különböző implementációk némileg eltérő módon, de általában mégis megvalósítják a string típust.

A résztartomány típus
Nem minden alkalommal van szükségünk egy-egy sorszámozott típus teljes értéktartományára. Egy tömbindex például nem vehet fel tetszőleges értékeket, csak azokat, amelyekhez tömbelem tartozik, különben használatával "túlindexelünk" a tömb határain, ami esetleg beláthatatlan következményekkel járhat. A hónap napjait leíró egész érték sohasem lehet nagyobb 31-nél. A csak nagybetűk tárolására szánt karakterváltozónak nem kell tudnia kisbetűket fogadnia. Sokszor kimondottan hasznos, ha az értéktartomány túllépésére a fordítóprogram figyelmeztet. Erre a célra vezették be a résztartomány típust. Legjobb, ha pár példán át nézzük:

CONST
  TOMBMERET=1000;
TYPE
  HONAPOK=1..12; { a hónapok }
  NAPOK=1..31;   { a hónap napjai }
  INDEX=0..TOMBMERET; { a TOMBMERET-et korábban konstansként deklaráltuk! }
  NAGYBETU='A'..Z';
  KISBETU='a'..'z';
  SZAMJEGY='0'..'9';

Ha most egy változónk, amelyet NAPOK típusúnak deklaráltunk, tévedésből 0, 13 vagy -49 értéket kapna, a Pascal rendszer hibát jelezne.

A felsorolási típus
Sok esetben használunk olyan típust, amely néhány diszkrét értéket vehet fel. A BASIC-ben ilyenkor az egész (vagy annak híján a valós) típust kell használni. A Pascal megengedi, hogy felsoroljuk a típus lehetséges értékeit, és később ezeket az értékeket használjuk. Itt is legjobb a példa:

TYPE
  HET_NAPJA=( Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap );
  HONAP_NEVE=( Januar, Februar, Marcius, Aprilis, Majus, Junius, Julius, Augusztus, Szeptember, Oktober, November, December );

Ha a NAP változót HET_NAPJA típusúnak deklaráltunk, később a programban jogos a
NAP:= Kedd;
értékadás. Természetesen a felsorolási típusból is képezhetünk résztartomány típust:

TYPE
  HEKOZNAP=Hetfo..Pentek;
  NYARI_HONAP=Junius..Augusztus;

Összetett Típusok

A tömb
Az összetett típusok egyik fajtája a tömb (ARRAY), ez adott mennyiségű azonos típusú elemből áll. A kétdimenziós tömb felfogható úgy is, mint egy egydimenziós tömbökből álló tömb. A tömbindex indulhat 1-től, 0-tól, de indulhat tetszőleges pozitív vagy negatív számtól is. Ennek jelentősége később nyilvánvaló lesz.
Figyeljük meg, hogy a tömb mérete meghatározható akár a megfelelő résztartomány típus megadásával, akár pedig az alsó és a felső indexhatár megnevezésével (azaz a példabeli egész és valós tömb azonos méretű lesz). Az is lényeges, hogy a tömbök dimenziójának száma gyakorlatilag tetszőleges lehet, azt csak a memória mérete korlátozza.

TYPE
  INDEX=0..TOMBMERET;
  EGESZ_TOMB=ARRAY[INDEX] OF INTEGER;
  VALOS_TOMB=ARRAY[0..TOMBMERET] OF REAL;
  SOR=ARRAY[1..80] OF CHAR;     {egy képernyősor}
  KEPERNYO=ARRAY[1..24] OF SOR; {a képernyő 24 sora}
  PIXEL=0..MAX_SZIN;            { a szín-módtól függ }
  KEP=ARRAY[0..1279] OF ARRAY[0..719] OF PIXEL;

Az utolsó sor felírható egyszerűbben is:

KEP=ARRAY[0..1279,0..719] OF PIXEL;

(bár egy ekkora tömb nem valószínű, hogy elfér a memóriában.)

A rekord
Sok objektum nem írható le egyetlen mennyiséggel. A dátum évből, hónapból és napból áll, egy képpontot a koordinátái és a színe jellemezhet. A Pascal lehetőséget ad arra, hogy az ilyen összetett jellemzőkhöz önálló típust hozzunk létre összetevőiból. Ez a rekord típus.

TYPE
  DATUM=RECORD
      EV:1900..2100;
      HO:HONAPOK; {Korábban deklaráltuk}
      NAP:NAPOK; {mindkettőt}
  END;

  NEVTIPUS:ARRAY[1..25] OF CHAR;

  SZEMELYNEV=RECORD
      VEZETEKNEV:NEVTIPUS;
      KERESZTNEV:NEVTIPUS;
  END;

  CIM=RECORD
      VAROS:ARRAY[1..20] OF CHAR;
      UTCA:ARRAY[1..32] OF CHAR;
      HAZSZAM:ARRAY[1..16] OF CHAR;
      IR_SZAM:1000..9999;
  END;

  SZEMELYI_KARTON=RECORD
      NEV:SZEMELYNEV;
      SZULETETT:DATUM;
      LAKCIM:CIM;
      MEGJEGYZES:ARRAY[1..4,1..80] OF CHAR;
  END {of RECORD};

  NYILVANTARTAS=ARRAY[1..1000] OF SZEMELYI_KARTON;

Láthatjuk, hogy a rekord típusból is létrehozhatunk tömböket, ugyanúgy, mint a skalár típusból. A rekord típussal sok művelet (értékadás, kiírás fájlba és visszaolvasás stb.) közvetlenül végezhető; más műveletek (pl. összehasonlítás) az egyes elemein egyenként. Ha egy ilyen nyilvántartást Basic-ben akarnánk megvalósítani, vagy külön-külön tömbben kellene kezelni a rekord típus egyes elemeit, ekkor egy elem kiemelése a tömbből esetleg féltucatnyi vagy még sokkal több értékadást jelentene; vagy az egész adatszerkezetet össze kellene "csomagolni" egy nagy stringbe, akkor állandó gond lenne a ki- és becsomagolás. Nem is beszélve arról, hogy ha később meg kell változtatnunk a rekordszerkezetet (mondjuk, utólag ki akarjuk egészíteni az adatokat a foglalkozással), akkor szinte az egész programot át kellene írnunk, míg itt csak egy deklaráción múlik az egész.
A rekord típus további trükköket is megenged. Előfordulhat, hogy a rekordban rögzítendő adatok szerkezete eltérő. Erre az esetre vezették be a változó rekord fogalmát. Ebben egy bizonyos elem határozza meg, hogy az utána következő rekordrész milyen szerkezetű legyen.

TYPE
  ... {itt szerepel a CIM típus deklarációja az előbbi példából}
  NYILV_SZAM=ARRAY[NY_NY_SZ_H] OF CHAR;
    {a nyugdíjnyilvántartó szám hosszúságát (NY_NY_SZ_H)
          konstansként kellett megadni}
  MUNKAHELY=RECORD
      VALLALAT:ARRAY[1..40] OF CHAR;
      V_CIM:CÍM;
  END;

  KARTON=RECORD
      NEV:NEVTIPUS;
      CASE FOGLALKOZAS:ARRAY[1..32] OF CHAR
      OF
         'Nyugdijas':(NYIL_SZAM:ARRAY[1..12] OF CHAR);
         'Tanulo':(ISKOLA:CIM);
         'Egyeb':(MH:MUNKAHELY);
  END; {RECORD}

Itt a KARTON típusú rekord FOGLALKOZAS eleme mondja meg, hogy milyen legyen a rekord további szerkezete: nyugdíjasoknál a nyugdíjnyilvántartó számot (ez egy adott hosszúságú karaktersorozat), tanuló esetén az iskola címét (ez egy korábban deklarált típusú rekord), egyéb esetben a munkahely nevét és címét tároljuk ugyanabban a rekordelemben. A fordítóprogram ilyen esetben az előforduló leghosszabb változó rekordelemnek foglal le helyet, az összes többi eset így garantáltan elfér.

A halmaz (set) típus
A matematikában a halmaz valamilyen szempontból azonos jellemzőjű objektumok összessége. A Pascal nyelvben is bevezették a halmaz típust ez egy adott - megszámlálható - típushoz tartozó értékek együttesét jelenti.
Miért van erre szükség? Mindenki, aki készített már komolyabb programot, találkozott azzal a problémával, hogy meg kell állapítani egy érték hovatartozását. Numerikus értékeknél legtöbbször nincs is gond, egyszerűen megvizsgáljuk, hogy az érték beleesik-e az elbírt tartományba:

IF ( ERTEK>=ALSO_HATAR ) AND (ERTEK<=FELSO_HATAR ) THEN

Egy fordítóprogram készítésekor azonban olyan feltételeket kell vizsgálni, hogy az adott karakter pl. betű-e vagy szám, esetleg írásjel. Szerencsére az ASCII kódok esetében a betűk egy (a kis- és nagybetűket külön kezelve két) folyamatos kódtartományba tartoznak, így a vizsgálat megoldható a fenti módszer kiterjesztésével. Eláruljuk, hogy ez nem minden kódrendszerben van így - az IBM nagygépeken használt EBCDIC (ejtsd "ebszidik") kódkészlet sem ilyen ; az Írásjelek viszont valóban össze-vissza helyezkednek el a kódtartományban.
A Pascal az ú.n. halmazgenerátor segítségével generálni tud az adattípusának - egy megszámlálható típusnak - az elemeiből egy halmazt (a halmazgenerátor programbeli alakja egy szögleteszárójel-pár, amelyben felsoroljuk vagy folytonos tartományként megadjuk az alaptípusnak azokat az elemeit, amelyeket az adott halmazhoz akarunk rendelni). lássunk erre először pár példát. Itt most halmazokat generálunk egy fordítóprogram számára:

CONST
  TAB=CHR(9); {HiSoft Pascal; TURBO Pascalnál TAB=#9};
  PONT='.';
  VESSZO=',';
  PONTOSVESSZO=';'

VAR
  KISBETU,  {ezeket}
  NAGYBETU, {a változókat}
  BETU,     {karakter-}
  SZAMJEGY, {halmaznak}
  HEXA_SZAMJEGY, {deklaráltuk}
  ELVALASZTOJEL:SET OF CHAR;
...
BEGIN
  KISBETU:=['a'..'z'];    {a kisbetűk halmaza a-tól z-ig}
  NAGYBETU:=['A'..'Z'];   {a nagybetűk halmaza A-tól Z-ig}
  BETU:=KISBETU+NAGYBETU; {a két előző halmaz uniója}
  SZAMJEGY:=['0'..'9'];
  HEXA_SZAMJEGY:=SZAMJEGY+['A'..'F'];
  ELVALASZTOJEL:=[PONT,VESSZO,PONTOSVESSZO,TAB];
END;

Ezután gyerekjáték megállapítani, hogy egy karakter beletartozik-e valamelyik halmazba:

IF C IN BETU THEN ...
IF C IN ( BETU+SZAMJEGY ) THEN ...

Az IN halmazművelet, értelemszerűen igaz eredményt ad, ha egy érték beletartozik az adott halmazba. A második programsorban a betűk és a számjegyek közös halmazába tartozást vizsgáltuk. Természetesen konstansként is definiálhattuk volna ugyanezeket a halmazokat:
CONST
KISBETU = ['a'..'z'];

stb. A halmazok között megengedett az értékadás; ha A és B halmaz típusú akkor ez
   A:=B
alakban írható. A természetesen változó, B tetszőleges - halmaz típusú - kifejezés lehet. Az
   A+B
kifejezés, mint már láttuk, a két halmaz unióját - közös halmazát - jelenti (vagyis azokat az elemeket, amelyek akár az A, akár a B halmazba tartoznak); az
   A*B
a két halmaz közös részét - interszekcióját - (vagyis azokat az elemeket, amelyek egyidejűleg mind a két halmazhoz tartoznak); az
   A-B
a két halmaz különbségét (A-nak azt a részhalmazát, amelyik nem tartozik ugyanakkor B-hez is). Vizsgálhatjuk két halmaz egyenlőségét, azaz, hogy ugyanazokat és csak ugyanazokat az elemeket tartalmazzák-e:
   A=B
két halmaz egyenlőtlenségét, azaz, hogy nem tartalmaznak azonos elemet:
   A<>B
és azt, hogy az egyik halmaz részhalmaza-e a másiknak:
   A<=B
vagy megfordítva, a másik az egyiknek
   A>=B
Írjuk még fel az üres halmazt:
   []
Ez nyilván egy olyan halmaz, amelynek nincs egyetlen eleme sem. Szóljunk pár szót a halmaz típus gépi ábrázolásáról. Egy adott alaptípusból képzett összes lehetséges halmazban az összes elemnek csak egy tulajdonságát használjuk ki, mégpedig, hogy beletartozik-e az adott halmazba vagy sem; így az elemek ábrázolásához elegendő egy-egy bit. Például, a karakter alaptípusból képzett összes halmaz egy 256 bites (32 bájtos) mezővel ábrázolható, ahol az egyes bitpozíciókon 1 jelenti, hogy az adott karakter eleme a halmaznak, és 0, ha nem eleme. Így az üres karakterhalmaz gépi ábrázolása:

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 ... (még 24 x 8 nulla)

ahol a bal szélső nulla a 00 hexa kódú karaktert (pontosabban annak hiányát) jelenti, az utolsó - a példában már nem ábrázolt - nulla pedig az FF hexa kódú karakterhez tartozik. A csak a számjegyeket tartalmazó halmaz gépi ábrázolása pedig

00000000 00000000 00000000 00000000 00000000 11111111 11000000 ... (itt az első egyes a 0, az utolsó a 9 számjegyhez tartozik).

Az elválasztójeleket tartalmazó halmaz ábrázolása pedig (lásd a fenti példát)

00000000 01000000 00000000 00000000 00000000 00001010 00000000 00010000 ...

az összes karaktert tartalmazó halmazé pedig

11111111 11111111 11111111 11111111 11111111 11111111 11111111 11111111...

Mindezzel persze nem kell törődnünk, a fordítóprogram megcsinálja helyettünk a leképezést.

A file magyarul állomány típus
A fájl típus ugyanúgy azonos típusú elemekből áll, mint a tömb típus, azonban azzal ellentétben az elemeinek száma nem meghatározott. Az elemek sorban egymás után következnek, és egyszerre csak egy elemhez férünk hozzá. Ezután mar nem meglepő, hogy a fájl típus megvalósítása a köznapi fájl fogalommal azonos módon történik, azaz a fájl típus valamilyen háttértárolón (mágnesszalag, mágneslemez) vagy periférián (terminál, modem) megjelenő adatok kezelésére szolgál. A file típus elemei tetszőleges típusúak lehetnek, azaz pl. karakter, egész vagy valós, rekord vagy tömb. A karakter-fájl (FILE OF CHAR) mellett a Pascal kényelmi okokból megkülönböztet ú.n. szövegfájlt (TEXT), amely ugyan karakterekből áll, de benne a karakterek szövegsorokat alkotnak.
Ha deklarálunk egy fájl típusú változót:

VAR F:FILE OF INTEGER;

akkor automatikusan létrejön egy F^ változó is, az adott fájl puffer-változója. Ez, mint egy ablak, mindig megmutatja a fájl valamelyik elemét. Az "ablak" végig tud lépkedni a fájl elemein, így tudunk ezekhez hozzáférni. Ha a fájl utolsó elemén is túlmentünk, a puffer-változó értéke meghatározatlan.
Minden fájlhoz tartozik még egy logikai típusú függvény is, az
EOF(F)
függvény (end of file), amely igaz értéket ad, ha a puffer-változó a fájl végén túlra került.
A fájl típus kezeléséhez négy alapvető eljárást használunk:
RESET(F) - Egy létező fájl elejére állítja a puffer-változót, elkezdhetjük (újrakezdhetjük) a fájl olvasását;
REWRITE(F) - Egy nem létező fájlt létrehoz, vagy egy létező fájlt töröl, és az elejére állítja a puffer-változót, elkezdhetjük a fájl írását;
GET(F) - Egy elemmel továbblépteti a puffer-változót a fajiban, a következő elemet tudjuk olvasni:
PUT(F) - Egy elemmel továbblépteti a puffer-változót a fájlban, a következő elemet tudjuk írni.

Maga az írás és az olvasás a puffer-változó segítségével végezhető, azaz pl.
   F^:=34; PUT( F );
írás esetén, és
   V:=F^; GET( F );
olvasás esetén (itt a V változót korábban ugyanolyan típusúnak kellett deklarálna, mint az F file elemeinek típusa).
Bár ezekkel az eljárásokkal mindenfajta fájlművelet elvégezhető, a Pascal - megint csak kényelmi okokból - újabb két eljárást vezet be:

READ(F,V) - amely a V változóba teszi a puffer-változó értékét, majd továbblép a fájlban:
WRITE(F,E) - amely az E kifejezés értékét a fájl végére írja, majd ugyancsak továbblép a fájlban.
További kénvermet nyújt az, hogy a READ és a WRITE eljárásnak több paramétere Is lehet, így egyetlen eljáráshívással több értéket is olvashatunk vagy írhatunk.
   READ(F,V1,V2,... Vn)
   WRITE(F,K1,K2, ... Kn)

A szövegfájlok
Bár a szöveget, azaz karakterek sorozatát tartalmazó fájlt definiáltuk:FILE OF CHAR alakban, a Pascal mégis bevezetett a speciális szövegfájlt (TEXT) fogalmát. Ez abban különbözik a FILE OF CHAR típustól, hogy feltételezi azt, hogy a szöveg sororra tagolódik. Ezt a sorokra tagolódást a Pascal helyettünk kezeli, így sokkal egyszerűbb a szövegsorok feldolgozása. Ennek megvalósításához a Pascal bevezet két új eljárást és egy függvényt:
READLN(F) - A beolvasás alatt lévő fájl következő sorára pozícionálja a pufter-változót:
WRITELN(F) - Lezárja az eddig irt szövegsort. Az ezután kiír karakterek új sorba kerülnek majd.
Kényelmi okokból a READLN és a WRITELN eljárásnak paraméterei is lehetnek, ezekre ugyanaz étvényes, mint a READ és WRITE eljárás paramétereire; így a beolvasás (kiírás) és az új sorra állas egyetlen utasítással elvégezhető. A sorváltás mindig az összes paraméter beolvasása (kiírása) után történik.
Az EOLN(F) (end of line) függvény TRUE értéket ad, ha a puffer-változó a sor végére mutat a beolvasott fájlban.

A mutató (pointer) típus
Az összes eddig tárgyalt adatszerkezet közös vonása, hogy statikusak: valahol, egy blokkban deklaráljuk őket, ezzel "létrejönnek", és mindaddig "élnek", amíg a programvégrehajtás ki nem lép az adott blokkból. Szükség lehet azonban olyan adatokra, amelyeknek a mennyiségét, struktúráját nem ismerjük a program írása idején; ezeket menet közben, a program végrehajtása során kell létrehozni és esetleg megszüntetni. Ezek a dinamikus változók.
A dinamikus változókra nem lehet név szerint hivatkozni, hiszen nem szerepelnek semmilyen deklarációban. Kezelésükhöz a Pascal a mutató (angolul pointer) típust használja. A mutató típusú változó, értelemszerűen, "rámutat" a dinamikus változóra: maga a dinamikus változó tetszőleges típusú lehet. A

TYPE P=^T

deklaráció a P típust olyan mutató típusnak deklarálja, amelyik valamilyen T típusú (dinamikus) változóra mutat.
Deklaráljunk most egy ilyen P típusú változót, tehát egy mutatót:

   VAR MUTATO:P

Ha MUTATO értéket kapott, akkor valamelyik T típusú elemre mutat. Lehetséges még egy olyan eset, hogy a mutató éppen nem mutat semmire: értéke ekkor a NIL (magyarul semmi) nevű speciális konstans (ez gyakorlatilag ugyanaz, mint az egész vagy valós változó 0 értéke).
Hogyan tudunk most a mutatott, a dinamikus változóra hivatkozni? A mutatón keresztül:

MUTATO^

jelentése az a (T típusú) változó, amelyre MUTATO éppen most mutat.
A dinamikus változók létrehozására a NEW eljárás szolgál; a

NEW(MUTATO)

eljáráshívás létrehozza a T típusú dinamikus változót (helyet foglal számára a memóriában), és a MUTATO változónak az új dinamikus változóra mutató értéket ad. A

DISPOSE(MUTATO)

eljárás megszünteti azt a dinamikus változót, amelyikre a MUTATO az adott pillanatban éppen mutat.
Ha az A és B változó - a dinamikus változó típusával egyezően T típusú, akkor az

A:=MUTATO^

értékadás A-ba tölti a dinamikus, mutatott, változó pillanatnyi értékét, a

MUTATO^:= B

értékadás pedig a dinamikus változóba tölti B értékét.
A dinamikus változókkal számtalan érdekes dolgot lehet csinálni. Az egyik ilyen az adatrekordok "felfűzése". Mivel a dinamikus változó tetszőleges típusú lehet, senki sem akadályozhat meg bennünket abban, hogy a mutató egy olyan rekordra mutasson, amelyik önmaga is egy (vagy több) ugyanilyen mutatót tartalmaz. Ha mindegyik rekordban a mutató valamelyik másik rekordra mutat, a mutatókon "lépkedve" akkor is végigmehetünk a rekordok láncolatán, ha azok fizikailag nem sorrendben, hanem össze-vissza helyezkednek el.
Az utolsó rekord mutatója nem mutat sehová (értéke NIL), hiszen nincs több elem a láncban. Ez azt jelenti, hogy ha egy új rekordot hozunk létre egy rendezett lancban, akkor azt úgy tudjuk a "helyére" tenni, hogy nem kell összes rekordot átrendezni, az új elemet egyszerűen "beláncoljuk" a megfelelő helyre: az előző elem mutatóját ráállítjuk a beszúrt elemre, a beszúrt elem mutatóját pedig a következő elemre.
A másik érdekes lehetőség, ha a rekord több mutatót is tartalmaz. Ekkor ugyanazon a rekordokon többféle láncot követve mehetünk. végig; pl. egy könyvtári adatbázisban az egyik lánc mentén a szerző neve szerint, a másik lánc mentén a cím szerint, egy harmadikon a kiadás éve szerint lehet °rendezve" ugyanaz az adattömeg.
Ilyen pointeres, muratós adatszerkezettel lehet ábrázolni a számítástechnikában, de más területen is közkedvelt fa-struktúrákat, vagy a matematikusok által szeretett gráfokat. Valós folyamatok modellezésénél szinte elkerülhetetlen a dinamikus adatszerkezetek alkalmazása.

4. A programszerkezetek
Az adatszerkezetek - igen kimerítő- áttekintése után rátérhetünk a programszerkezetek tanulmányozására. Míg az adatszerkezet alkalmas megválasztásával egy átlagos alkalmazás esetén megtakaríthatjuk programsoraink mintegy 75 százalékát, a jó programszerkezetek használatával megtakaríthatjuk a programfejlesztésre és -belövésre fordított idő 75 százalékát.
Mielőtt a dolgok közepébe vágnánk, emlékeztetem az olvasókat a Pacal nyelv néhány felírástechnikai jellegzetességére.
Míg a Basic nem tesz különbséget az értékadás jele és az egyenlő relációs jel között, mindkettőre az egyenlőségjelet alkalmazza, addig a Pascal két különböző jelet használ a két merőben eltérő funkció felírásához. Az egyenlőségjelet meghagyja az egyenlőség vizsgálata számára, míg az értékadás jeleként beveteti a BASIC-en felnőtt programozók számára szokatlannak tűnő := jelet. A megkülönböztetés pedig jogos; nézzük az alábbi programrészletet:

VAR
   L:BOOLEAN;
   A, B : INTEGER;
...
A:=1;B:=2;
...
L:=A=B;

Az utolsó értékadásban a logikai típusúnak deklarált L változó akkor kapna TRUE, azaz igaz értéket, ha A és B értéke egyenlő volna; mivel ez nem igaz, L értéke FALSE, azaz hamis.
A másik sajátosság a zárójelek használatában van. Míg a BASIC csat a hagyományos kerek zárójelet használja, addig a Pascal igényt tart a szögletes és a kapcsos zárójelre is (sőt, még egy igen különös fajta zárójelre is, az utasítás-zárójelre, lásd később):
( ) [ ] { }
A kerek zárójel egyrészt a kifejezésekben szerepelhet a matematikában megszokott módon, a kiértékelés sorrendjének befolyásolására, másrészt a függvények és eljárások paramétereit határolják. Emellett, mint láttuk, a felsorolási típus felírásában van szerepe a deklarációban. A tömbelemek indexelésére és a deklarációra a tömb méretének megadására viszont a Pascal a szögletes zárójelet használja. Mint láttuk korábban, a szögletes zárójel másik alkalmazása halmazgenerálásnál van. A kapcsos zárójel a programban megjegyzést jelöl. A megjegyzés a nyitó kapcsos zárójeltől a csukó kapcsos zárójelig terjed, így természetese több sorra is kiterjedhet. Egyes régi klaviatúrákon nem szerepel a kapcsos zárójel, ezért a megjegyzés a
(* *)
összetett zárójelpárral is megadható. Másik jellegzetesség a tól -ig határok jelölése. Ilyen a BASIC-ben nincs, mivel ott a tömb elemei mindig a 0 indexel kezdődnek (az IS-BASIC ebben is egyedülálló! Ott megadhatjuk az alsó indexhatárt is). A Pascal, mint korábban már megfigyelhettük, megengedi tetszőleges kezdő- és záróindex használatát:

VAR
  A:ARRAY[-5..5] OF INTEGER;
  B:ARRAY[-28..-13] OF REAL;
  C:ARRAY[1-..0,0..1] OF BOOLEAN;

A deklarációban az alsó és felső határt választja el egymástól a két pont. (Csak megjegyzem, hogy tetszőleges indextartomány megengedése egyáltalán nem felesleges gesztus; mint majd látni fogjuk, sokszor igen hasznos lehet.) Ugyancsak két pont választja el a résztartomány típus alsó és felső határának értékét. Ez nem véletlen: az index mindig résztartomány típusú:

TYPE
  SZINTIP=( VOROS,NARANCS,SARGA,ZOLD,KEK,IBOLYA);
VAR
  INTENZITAS:ARRAY[SZINTIP] OF REAL;
  LUMINOZITAS:ARRAY[VOROS..IBOLYA] OF REAL;

Még egy jelöléstechnikai dolog, és ezzel már bele is vágtunk a programszerkezetek tárgyalásába: a Pascalban az utasításokat pontosvessző választja el egymástól. Ez kényelmes programtagolást tesz lehetővé, mivel nem kell (nem is lehet!) az utasítást egy sorba szorongatni; viszont veszélyt is rejt, hiszen egy esetleges szintaktikai hiba sem korlátozza a hatását egy fizikai sorra, hanem szinte bárhol kibukhat.

Az utasításrész
Nos, mint már korábban láttuk, a Pascal program egy fejrészből és egy blokkból áll; a blokk egyik összetevőjét, a deklarációs részt már megismertük; most nézzük meg részletesebben a másik összetevőt, az utasításrészt.
Az utasításrész egy ú.n. összetett utasítás. Ennek jellemzője, hogy egy BEGIN és egy END kulcsszó határolja, szinte zárójelként: nem csoda, hogy e két kulcsszót utasítás-zárójelnek nevezik. Az utasítás-zárójelben tetszőleges számú utasítás állhat. Ezek lehetnek egyszerűutasítások, vagy újabb összetett utasítások, bár ez utóbbinak önmagában nincs sok értelme. Az utasítás-zárójelpár lehet üres is; ennek látszólag szintén nincs semmi értelme, a valóság persze bonyolultabb (ha egy készülő programban a még el nem készült eljárásokat és függvényeket üres utasításrésszel felírjuk, akkor a fordítóprogram hajlandó elfogadni a programot, és ellenőrizhető a már elkészült részek helyessége).

Az Értékadó utasítás
Az egyik egyszerű utasítás az értékadó utasítás. Ennek szintaktikája nem mutat semmi meglepőt: az utasítás bal oldalán egy változó áll, ez kap majd értétet; a jobb oldalon pedig egy kifejezés, ez határozza meg a változónak adandó értéket. A két oldal az értékadás operátora, a már ismert := jelkombináció választja el egymástól.
Mivel a Pascal számos (pontosabban: számtalan) típust ismer, nagyon fontos, hogy a baloldali és a jobboldali típusnak egy kifejezésen belül azonosnak vagy egymással kompatibilisnek kell lennie. A változó típusát mi határozzuk meg a deklaráláskor; a kifejezés típusát az őt alkotó elemek típusa és a rajtuk végrehajtott műveletek együttesen határozzák meg. Egy valós változó mindig megkaphatja egy egész kifejezés értékét, ezért a

VALOS_VALTOZO:=EGESZ_KIFEJEZES

Alakú értékadás helyes; egy egész változó viszont definíciója szerint szűkebb értéktartományt fed le, mint egy valós kifejezés, ezért az ellenkező kombináció nem megengedett, a fordítóprogram visszadobja. Ez a fajta korlátozás csak első pillanatra ellenszenves: sokkal jobb, ha azonnal kiderül a típuskeveredés, mintha futás közben jelentkeznének a megmagyarázhatatlan hibák. A típus-összeférhetetlenség okozta konfliktusok feloldására szerencsére léteznek a Pascalban ú.n. típuskonverziós függvények, amelyek az egyik típusból a másikba alakítanak át egy értéket, ha ez lehetséges. Ezeket a függvényeket majd később nézzük át.
A Pascal értékadása sokkal hatékonyabb mint a BASIC hasonló funkciója. Most látjuk csak a sok-sok típus előnyeit: sok száz elemű rekordok és sokdimenziós, sok ezer elemű tömbök kaphatnak értéket egyetlen értékadó utasítással!
Anélkül, hogy a kifejezés pontos szintaktikájának taglalásába (az érdeklődők megtalálhatják valamelyik Pascal könyvben), úgy lehetne összefoglalni: a kifejezést változók, konstansok és függvényhívások, illetve operátorok alkotják, a kiértékelés sorrendjét szükség esetén zárójelezéssel adják meg.
A változó meghatározását ebben a pillanatban az olvasó intuíciójára bízzuk, induláskor ez elegendő. A konstans lehet alaki konstans, amikor is a kifejezésben a konstans alaki értéke szerepel, és lehet deklarált konstans, ha a kifejezésben korábban konstansnak deklarált azonosító szerepel.
A kifejezés kiértékelése a program által balról jobbra haladva történik, de befolyásolva az egyes operátorok ú.n. precedenciájától. Ez a BASIC-ben is igen hasonló módon történik, csak a lehetséges operátorok száma kisebb.

Ha az operátorok precedenciája által meghatározott kiértékelési sorrend az adott esetben nem megfelelő, a sorrendet a matematikában megszokott módon zárójelek alkalmazásával megváltoztathatjuk.


Az eljárás-utasítás
A másik egyszerű utasítás az eljárás-utasítás. Ez egy általunk definiált, vagy a Pascal rendszer által készen adott eljárásra való hivatkozás. Az eljárás-utasítás végrehajtásakor a program végrehajtja az eljárás törzsében foglalt utasításokat. Ez nagy vonalakban a BASIC GOSUB utasítására emlékeztet, de egy sokkal korrektabb formában. (Érdemes megjegyezni, hogy az IS-BASIC talán az egyetlen BASIC nyelvjárás kortársai körében, ami lehetőséget engedd eljárások és függvények definiálására, használatára!) Az eljárás-urasítás a meghívott eljárásnak paramétereket adhat át. Az egész kérdéskört majd részletesen áttanulmányozzuk az eljárások és függvények tárgyalásánál.

A ciklusszervező utasítások
A hagyományos Basic csak egyetlen ciklusszervező szerkezetet ismer, ez a FOR ... NEXT utasításpárral valósítja meg (Az IS-BASIC "természetesen" nem minősül hagyományos BASIC-nek...). A Pascalban is létezik hasonló szerkezet, ugyanakkor egyéb ciklusszervező szerkezetek is léteznek.

A FOR ... DO utasítás
A FOR ... DO utasítást akkor használjuk ciklusszervezésre, ha előre tudjuk, hány alkalommal kell a ciklusmagot végrehajtanunk. Az utasítás szintaktikája a következő:

FOR valtozo:=also_hatar TO felso_hatar DO
   Utasítás

Illetve:

FOR valtozo:=felso_hatar DOWNTO also_hatar DO
   utasítás

Mint látjuk, a szintaktika hasonló a Basic-ben megszokotthoz A változó a ciklusváltozó, ez veszi majd fel sorban az összes értéket az alsó határ és a felső határ között a TO kulcsszavú változatnál növekvő, a DOWNTO kulcsszavú változatnál csökkenő sorrendben. A DO kulcsszó szerepe csak az elválasztás. A ciklus minden egyes lefutásakor végrehajtódik az utasítás, amely természetesen lehet összetett utasítás is, ekkor a BEGIN ... END utasítás-zárójelpárt kell alkalmaznunk:

FOR valtozo:=also_hatar TO felso_hatar DO
BEGIN
   Utasítás_1;
   Utasítás_2;
   ...
   Utasítás_n
END

Az utasítás lehet újabb FOR ... DO utasítás is, pl.:

FOR I:=1 TO IMAX DO
  FOR J:=1 TO JMAX DO
    FOR K:=1 TO KMAX DO
      A[I,J,K]:=B[K,J,I];

Mint látjuk, a Pascal-fék FOR ... DO utasítás nem ismeri a BASIC által megengedett STEP kulcsszót, így a ciklusváltozó értéke minden lefutáskor eggyel (illetve mínusz eggyel) változik. Ez nem jelent problémát, a S'TEP-es alak egyszerű módon átalakítható a STEP nélküli alakba.
Itt meg kell jegyeznem néhány fontos szabályt. A ciklusváltozó csak egész típusú (vagy annak résztartománya), valamint felsorolási típus lehet. Ez megint csak egy ésszerű korlátozás, valós típust akkor sem szabad ciklusváltozónak használni, ha a programnyelv azt megengedi. Ennek az az oka, hogy valós számokat a számítógép véges pontossággal ábrázolja, így egyrészt nem tudjuk, hogy a kezdő- és a végfeltételnél hogyan fog viselkedni a ciklus, másrészt a ciklus többszöri végrehajtása során az egyébként általában kicsiny értékű kerekítési hiba összegződhet, így egészen rossz eredményt kaphatunk.
A másik fontos szabály, hogy a ciklusváltozónak csak lokális változót használjunk, azaz a változót abban az eljárásban vagy függvényben deklaráljuk, amelyikben a ciklus van. Ellenkező esetben váratlan és nehezen felderíthető hibákra számíthatunk. Egyes Pascal fordítók figyelmeztetést adnak, ha a ciklusváltozó nem lokális.
A harmadik szabály -nemcsak a Pascalban- a ciklusba beugrás és a ciklusból kiugrás tilalma. Minden ciklusba csak az elején szabad belépni és csak a végén szabad kilépni. Ha ugyanis beugrunk a ciklusba, a ciklusváltozó nem kapott kezdőértéket, így a továbbiakban értéke meghatározatlan. A ciklusból kiugrás pedig áttekinthetetlenné teszi a program szerkezetét. Ha mindenképpen ki kell ugranunk a ciklusból idő előtt, inkább használjuk a WHILE ... DO vagy a REPEAT ... UNTIL. szerkezetet. Végső, nem igazán elegáns, de még megengedhető meg oldásként adjunk a ciklusváltozónak a végértéknél nagyobb (A DOWNTO változat esetében kisebb) értéket, így a ciklusmag végén megtörténik a kilépés.
A negyedik szabály, hogy a ciklusváltozót a ciklusmagban nem szabad megváltoztatni, Ez alól hallgatólagos kivételt teszünk az előző szabálynál megengedett esetben.

A WHILE ... DO utasítás
Az utasítást akkor használjuk, ha előre nem tudjuk, hogy a ciklusmagot hányszor akarjuk végrehajtani. Az utasítás alakja a következő:

WHILE feltétel DO
   utasítás

A végrehajtás során a gép megvizsgálja, hogy teljesül-e a feltétel. Ha igen, végrehajtja az utasítást, majd újra megvizsgálja a feltételt. Az utasítást így mindig végrehajtja, amíg a feltétel fennáll. Az alábbi programszerkezet mindaddig olvas az F file-ból a C változóba, amíg a file végére nem értünk:

WHILE NOT EOF(F) DO
   READ(F,C);

Az EOF(F) függvény TRUE (igaz) értéket ad vissza, ha az F file-ban már nincs több adat. A NOT EOF(F) kifejezés értéke így TRUE mindaddig, amíg nem értünk a file végére.
Az utasítás itt is lehet összetett utasítás is, pl. újabb WHILE ... DO utasítás.
A WHILE ... DO utasítás használatánál nagyon fontos, hogy a ciklusmag úgy működjön, hogy előbb-utóbb a feltétel értéke FALSE (hamis) legyen, különben a program soha nem lép ki a ciklusból. A

WHILE TRUE DO;

Utasítás pl. garantáltan végtelen ciklust eredményez. Egyes rendszerekben ilyenkor azért billentyűzetről megszakítható a program; vigyázzunk, nem mindenütt! Arra is figyelnünk kell, hogy ha a feltétel értéke belépéskor FALSE, a ciklusmag egyszer sem hajtódik végre.

A REPEAT ... UNTIL utasítás
Ezt az utasítást is akkor használjuk, ha nem ismeretes előre, hány alkalommal kell a ciklusmagnak lefutnia. Az utasítás alakja:

REPEAT
   Utasítás_1;
   Utasítás_2;
   ...
   Utasítás_n
UNTIL feltétel

Belépéskor először végrehajtódik a ciklusmagot alkotó utasítássorozat, majd a gép kiértékeli a feltételt. Ha ez TRUE (igaz), a végrehajtás kilép a ciklusból, ellenkező esetben a ciklusmag ismételt végrehajtásávalfolytatódik. Látjuk tehát, hogy a feltétel hatása itt pontosan az ellenkezője annak, amit a WHILE ... DO utasításnál láttunk. Ügyeljünk arra tehát, hogy a REPEAT ...UNTIL szerkezetben a ciklusmag legalább egyszer minden esetben végrehajtódik. Ez a szerkezet használható akkor, ha a FOR ... DO ciklusból való kiugrást el akarjuk kerülni:

I:= 0;
REPEAT
  I:=I+1;
UNTIL MEGVAN(I) OR (I=IMAX);

Az általunk íri - BOOLEAN típusú - MEGVAN függvény vizsgálja, hogy az I-ik elem-e a keresett.

Az elágazó utasítások
Az IF ... THEN és az IF ... THEN ... ELSE utasítás
A programvégrehajtás feltételtől függő elágaztatását az IF ... THEN ... ELSE utasítással valósíthatjuk meg, ahol az ELSE ág elmaradhat. Az utasítás szintaktikája a kővetkező:

IF feltétel THEN utasítás

Illetve:

IF feltétel THEN utasítás_1
ELSE
Utasítás_2

Az első változatnál, ha a feltétel teljesül, végrehajtódik az utasítás; ellenkező esetben a végrehajtás a következő utasítással folytatódik. A második változatnál a feltétel teljesülése esetén az utasítás_1, ellenkező esetben az utasítás_2 hajtódik végre; mindkét esetben a végrehajtás a következő utasítással folytatódik. Mindegyik alaknál az utasítás lehet összetett utasítás is, akár újabb IF ... THEN vagy IF ... THEN ... ELSE utasítás. Ilyenkor félreértésre adhat alkalmat, hogyan értelmezzük pl. az alábbi utasítást:

IF feltétel_1 THEN
   IF feltétel_2 THEN
      utashás_1
   ELSE
      utasítás 2

vagy

IF feltétel_1 THEN
   IF feltétel_2 THEN
      utasítás_1
   ELSE
      utasítás 2

azaz az ELSE utasítás_2 ág melyik IF utasításhoz tartozik (a bekezdéssel való utalást csak a programozó nézi, a fordítóprogram nem). Megállapodás, hogy kétértelműség esetén az ELSE ág mindig a legutolsó IF utasításhoz tartozik, tehát a példabeli első változat a helyes. Ha a második változattal sugallt megoldást akarjuk a programba írni, ezt utasítás-zárójelekkel kell jelezni:

IF feftétel_1 THEN
BEGIN
   IF feltétel_2 THEN
   utasítás_1
END
ELSE
   utasítás_2

Nem különböztetjük meg külön az IS-BASIC-ból ismert ELSE-IF szerkezetet, mivel azt lefedi az IF ... THEN ... ELSE utasítás szintaktikája. Ugyanakkor érdemes a szerkezetet a tagolással az érthetőség kedvéért kihangsúlyozni.

A CASE utasítás
A program többirányú elágaztatására szolgál a CASE utasítás. Alakja a következő:

CASE változó OF
   Lista_1 : utasítás_1
   Lista_2 : utasítás_2
   ...
   Lista_n : utasítás_n
ELSE
   utasítás_n+1
END

Végrehajtáskor a gép megvizsgálja, hogy a változó értéke melyik listában szerepel lista_1 ... lista_n között. Ha talál egyezést, végrehajtja az ahhoz a listához tartozó utasítást; ellenkező esetben az ELSE ágban lévő utasítást. A végrehajtás ezután a következő utasítással folytatódik. Az ELSE ág el is maradhat. Lista_1 ... lista_n vesszővel elválasztott konstansok vagy résztartományok sorozata lehet.
Be kell vallanom, hogy itt nem a szabvány Pascal definíciónak megfelelő szintaktikát adtam meg, hanem a szinte mindenki által használt Turbo-Pascal szerintit. Az eredeti definíció szerint a CASE utasításnak ugyanis nincs ELSE ága; az alternatívák felsorolásakor pedig résztartomány nem adható meg, csak konstansok vesszővel elválasztott sorozata. Ha valaki ilyen, a szabványos Pascalt megvalósító rendszeren dolgozik, jó, ha erre emlékezik; mi, többiek viszont nyugodtan használhatjuk ezt a hasznos bővítést.

Az üres utasítás
Az üres utasítás, mint a neve is mutatja, üres, nem csinál semmit. Mielőtt felelőtlen kijelentéseket tennénk az ilyen utasítás használhatóságára vonatkozóan, nézzünk egy pár példát. A

REPEAT
UNTIL KEYPRESSED;

Utasítás mindaddig zárt ciklusban várakozik, amíg a KEYPRESSED függvény azt nem jelzi, hogy a felhasználó le nem ütött egy billentyűt. Az üres utasítás itt a REPEAT és az UNTIL kulcsszó között van (illetve nincs, felfogás kérdése). Hasonlóan, a

WHILE VALASZ<>'y' DO;

utasítás mindaddig várakozik, amíg a - felhasználó által írt, és nyilván valamilyen kérdésre valamilyen választ beolvasó - VALASZ függvény a YES választ jelentó y értéket nem adja vissza. Az üres utasítás itt a DO kulcsszó és a pontosvessző között van.

Ezzel a néhány utasításfajtával minden programozási feladat megoldható. Amint a típusdeklarációk tárgyalásánál szándékosan kihagytuk a címkét, most kihagyjuk a GOTO utasítást: nem lesz rá szűkségünk. Az utasítások tetszőleges mélységben egymásba ágyazhatók, így nem csoda, hogy nincs szükség ide-oda ugrálásra. Ugyanakkor, mint korábban már többször is láttuk, több utasítás is beilleszthető oda, ahol a Pascal szintaxisa csak egy utasítást enged meg: az utasítássorozatot a BEGIN ... END utasítás-zárójelpárral egyetlen összetett utasítássá alakfajuk át.
Kicsit korábban már volt szó az eljárásokról, amely előre definiált eljárást vagy a programozó által definiált eljárást hív meg. Még korábban, az értékadó utasítás tárgyalásánál említést tettünk a függvényekről, amelyek szintén lehetnek előre definiáltak, vagy a programozó által készítettek. A szabványos Pascal nyelv előre definiált eljárásait és függvényeit majd sorban megismerjük; kitérünk majd a Turbo-Pascal sajátos eljárásaira és függvényeire is, és említést teszünk a Hisoft-Pascal sajátosságairól is.

Eljárások és függvények
Nem szükséges ecsetelni, hogy mennyire megkönnyíti a programozást az eljárások és függvények alkalmazása. A Pascal egészen odáig elment, hogy a beviteli-kiviteti funkciókat is teljes egészükben eljárások valósítják meg; a bevitelt és kivitelt előíró kulcsszavak így nem is részei a nyelv alapszókészletének. A felhasználó által definiált eljárások és függvények nélkül a nagyobb programok szerkezete követhetetlen volna, és számtalan ismétlésre és felesleges műveletre kényszerülne a programozó.
Egészen nagy programok pedig, amelyek egy darabban nem kezelhetők a fordítóprogrammal, vagy amelyeket több darabból kell összerakni, mert több programozó készítette azokat vagy különböző programnyelveken íródtak, nem lennének megvalósíthatók. Az eljárásokat és függvényeket, mint tudjuk, a program deklarációs részében kell deklarálni, az - általunk kihagyott címkedeklarációkat -, a konstansdefiníciókat, a típusdefiníciókat és a változódeklarációkat követően (a Turbo-Pascal, szerencsére, megengedi a deklarációk sorrendjének megváltoztatását, sőt, megbontását, tehát egy változódeklaráció után jöhet egy konstansdefiníció, majd újra változódefiníció).
Mind az eljárás, mind a függvény egy-egy programrészletet tesz egyetlen hivatkozással - az eljárás vagy függvény nevével - elérhetővé, végrehajthatóvá. Az eljárás vagy függvény deklarációja így nagyon hasonlít a teljes program "deklarációjára": a programfej helyett az eljárás vagy függvény fejéből áll, amit - pontosvessző - után egy blokk vagy egy direktíva követ. Ezek mindegyikét alaposan megvizsgáljuk.

Az eljárások
Az eljárásfej a PROCEDURE kukaszóval kezdődik, amit az eljárás azonosítója - neve - követ. Az azonosító után következik - zárójelek között - a formális paraméterek listája ami persze elmaradhat, ha az eljárásnak nincs paramétere.
A formális paraméterek listája tartalmazza tulajdonképpen azoknak a változóknak a deklarációját, amelyek majd kívülről, az eljárás meghívásakor kapnak értéket. A listában egy-egy változót vagy vesszővel elválasztott változólistát kettőspont után követi a típusazonosító. Ha egynél több típuslista van, pontosvessző után megadható a következő. Itt csak megjegyezzük, hogy a formális paraméter lehet eljárás- vagy függvény azonosító is; ezzel az esettel majd később foglalkozunk.
Amikor a korábban megismert eljárás-utasítás a program valamelyik pontján meghívja az azonos nevű eljárást, az eljárás-utasításban megadott aktuális paraméterek (azok a változók vagy konstansok, amelyekkel az eljárást az adott esetben le akarjuk futtatni) sorban hozzárendelődnek az eljárás-deklaráció formális paramétereihez: az első formális paraméterbe behelyettesítődik az első aktuális paraméter, a másodikba a második és így tovább. Az eljárás végrehajtása ez után kezdődik csak meg. A paraméterek átadása kétféle módon történhet: érték szerint vagy név szerint:

Az alapeset az érték szerinti hívás; a név szerinti híváshoz az adott aktuális paramétert a listában a VAR kulcsszónak kell megelőznie. A fordítóprogram "lelki életére" is kíváncsiaknak elárulom, hogy az érték szerinti paraméterátadásnál az eljárás az aktuális paraméterek ténylegesen az értékét kapja meg; név szerinti paraméterátadásnál viszont a címét, így érthető, hogy a megfelelő változó "magával viszi" az eljárásban kapott értéket az eljáráson kívülre is. Itt az ideje, hogy lássunk pár példát:

PROCEDURE ELJARAS(A,B:REAL;VAR C:REAL);
BEGIN
   C:=A+B;
   A:=B;
END { procedure eljaras };

Itt a valós típusúnak deklarált A és B változó érték szerinti paraméterátadást vár, míg a C változó név szerintit. Ez azt jelenti, hogy az eljárás hívásakor az A és a B aktuális paraméter lehet kifejezés, konstans vagy változó; a C paraméter viszont csak változó! Ezért az
ELJARAS(1,2,M);
hívás helyes, és N értéke az eljárásból visszatérve 3 lesz; az
ELJARAS( X, X+2,55);
viszont természetesen hibás, hiszen az 55 konstans nem kaphat értéket az eljárásban. Nézzünk most egy bonyolultabb esetet a hívásra a teljes program néhány részletével:

VAR
   A,B,X:REAL;
...
BEGIN
   ...
   A:=1; B:=2;
   ELJARAS(A,B,X);
   ELJARAS(A,2*A,X);
   ELJARAS(X,3*X,X);
   ...
END.

Igaz ugyan, hogy az eljárás első utasítása az A formális paraméternek 4-et ad értékül, a második utasítás azonban ezt átírja B értékére, az pedig az eljárás meghívásakor 3 volt. Az eljárás befejezésekor így az A formális paraméter ezt a végső értéket adja vissza a programbeli A változónak, azaz az aktuális paraméternek.
Az itt látottak arra figyelmeztetnek, hogy a változóparaméterekkel óvatosan kell bánnunk. Ugyancsak óvatosnak kell lennünk, ha az eljárás megváltoztat globális változókat, azaz olyan változókat, amelyek az adott eljáráson kívül vannak deklarálva. Ezekkel az úgynevezett mellékhatásoknak a szakkönyvek sokszor egész fejezetet szentelnek. Mi itt csak az óvatosságot tudjuk újra említeni.
Mivel az eljárás törzse a definíció szerint blokk, így az kezdődhet deklarációkkal. Ez azt jelenti, hogy az eljárásnak saját, lokális konstansai, típusai és változói lehetnek. Sőt, saját belső eljárás- és függvény-deklarációkat is tartalmazhat, ez utóbbiak használata azonban nagyon elbonyolítja a program szerkezetét. Ezeket csak akkor használjuk, ha ez feltétlenül szükséges.
Az eljáráson belül deklarált változók - és a formális paraméterek is ide tartoznak - csak az eljárás törzsén belül használhatók, csak ott vannak értelmezve. Az eljárásból való kilépéskor ezek megszűnnek létezni. A minden titkok tudójának címére jelöltek számára eláruljuk, hogy a paramétereket az eljárás rendszerint a vermen keresztül kapja meg. A program az eljárás hívásakor a verembe nyomja be a paraméterek értékét vagy címét, mint ahogy a végrehajtás folytatásához szükséges visszatérési címet is - köztudottan - oda teszi. Az eljárás programkódja ugyanitt hozza létre az eljárás belső változóit is. Az eljárás befejezésekor a verem kiürül, a paraméterek és a lokális változók megsemmisülnek, a processzor a gépi kódú programozók számára ismert módon a veremből veszi a visszatérési címet a hívás helyéhez való visszataláláshoz.
Ez a megoldás rendkívüli lehetőségeket villant fel előttünk, a rekurzív eljáráshívásét. Mi van akkor, ha egy eljárás úgy van megírva, hogy egy ponton önmagát hívja meg? Mivel a második híváskor - és a további hívásokkor - a verem a memória másik részét használja, a második hívásnál fizikailag is más helyen vannak az eljárás formális paraméterei és lokális változói, így azok nincsenek konfliktusban az előző híváskor keletkezett ugyanilyen vállazókkal.
Ha az eljárás egy ponton majd visszatér a hívás helyére - és a rekurzív eljárást mindig úgy kell megírni, hogy ez valamikor garantáltan bekövetkezzen -, a második hívás változói megsemmisülnek, de az elsőé tovább "élnek", tehát a program végrehajtása probléma nélkül folytatódhat. A rekurzív hívások határtalan lehetőségeket nyújtanak a programozónak, és a felkészült programozó él is ezekkel a lehetőségekkel. Mint a bevezetőben már említettem, bizonyos algoritmusok csak rekurzív technikával oldhatók meg.
Csak egy példa: ha arra gondolunk, hogy a Pascal fordítóprogram egy eljárás-deklarációban egy újabb, belső eljárás-deklarációt találhat, egy IF ... THEN utasítás belsejében egy másik IF ... THEN utasítást, és így tovább, gyakorlatilag korlátlanul, akkor rögtön világos lesz számunkra, hogy a fordítót csakis rekurziót használó programtechnikával lehet megvalósítani.

A függvények
A függvény egy olyan eljárás, amelyiknek saját értéke van. Ezt az értéket a nevén - azonosítóján - keresztül hordozza. Ez azt jelenti, hogy a függvényazonosító bármilyen kifejezésbe beírható (természetesen a típus-összeférhetőség figyelembe vételével), és ott úgy viselkedik, mint egy változó. Az eltérés az, hogy a változó mindaddig megtartja a korábban felvett értékét, amíg újat nem adunk neki, a függvény értéke viszont minden egyes hivatkozásnál újra kiszámítódik. A másik eltérés, hogy a függvénynek paraméterei (argumentumai) lehetnek, ugyanúgy, mint a normál (PROCEDURE) eljárásnak.
A Pascal nyelv számos beépített függvénnyel rendelkezik, ezeket majd később részletesen megismerjük. Most csak néhány példát. A valós típusú
SIN(X)
függvény a valós argumentum szinuszát számítja ki. Az
ORD(X)
függvény bármilyen megszámlálható típusú argumentum típuson belüli sorszámát adja meg. Így ha X karakter, ORD(X) a karakterkódot adja vissza. Az argumentum nélküli
MAXINT
függvény az ábrázolható legnagyobb egész számot adja eredményül.

Saját fülvények
Természetesen saját függvényeink is lehetnek, ezeket a program (vagy más blokk) deklarációs részében kell meghatározni; a függvénydeklarációk az eljárás-deklarációkkal vegyesen szerepelhetnek. (Mint tudjuk, a Turbo-Pascalban a deklarációk egészen szabadon keveredhetnek egymással.)
A függvény deklarációját a FUNCTION kulcsaszó vezeti be. A továbbiakban a függvény deklarációjára ugyanaz vonatkozik, mint az eljáráséra, tehát a formális paraméterek behelyettesítése az aktuális paraméterekkel ugyanúgy megtörténik majd híváskor, mint az eljárások esetében. Mivel a függvénynek értéke is van, a típusát is deklarálni kell, ezt a deklaráció végén kettőspont után megadott típusazonosítóval érhetjük el.
Egyetlen fontos szabály van még: a függvény törzsében a függvénynek értéket kell adni, azaz a függvény nevét szerepeltetni kell egy (esetleg több) értékadás bal oldalán. Ha ezt elmulasztjuk, a függvény a hívásakor értelmetlen értéket ad majd át. Lássunk egy példát függvénydeklarációra:

FUNCTION SZINUSZNEGYZET(X:REAL):REAL;
BEGIN
  SZINUSZNEGYZET:=SIN(X)*SIN(X);
END {SZINUSZNEGYZET};

és a hívásra:

S:=SZINUSZNEGYZET(PI/2)+KOSZINUSZNEGYZET(PI/2);

(feltételezve, hogy KOSZINUSZNEGYZET megírása SZINUSZNEGYZET ismeretében nem okozhat gondot). Itt sommásan látjuk az elmondottakat: a függvényt, amelynek argumentuma és saját értéke is valós, az értékadást a függvényazonosítóra és a hívást egy konkrét esetben. Egy másik:

FUNCTION TURESEN_BELUL(X,Y,EPS:REAL) BOOLEAN;
BEGIN
  IF ABS(X-Y)<= EPS THEN
     TURESEN_BELUL:=TRUE
  ELSE
     TURESEN_BELUL:=FALSE {END IF};
END {TURESEN BELUL};

Ez a függvény igaz (TRUE) értéket ad vissza, ha az X és az Y valós változó különbsége kisebb, mint az EPS változóval megadott tűrés, és hamis (FALSE) értéket ellenkező esetben. (A példabeli vizsgálat numerikus integrálás és egyéb iteratív számítások esetén fordul elő.)
(A Pascal persze lehetőséget ad ennek jóval egyszerűbb kiszámítására: a BEGIN ... END utasítászárójel-pár közé elég annyit beírni, hogy
TURESEN BELUL:=(ABS(X-Y)<=EPS);
hiszen a zárójelben álló kifejezés típusa logikai (BOOLEAN), és ez a szintén logikai típusú TURESEN_BELUL függvényazonosítónak értékül adható.)
Egyvalamivel óvatosnak kell lennünk: ha a függvény azonosítója a függvény törzsében értékadás jobb oldalán szerepel, ez rekurzív hívást eredményez, hiszen a függvényérték kiszámításához a függvényt ismételten meg kell hívni. Ez nem feltétlenül hiba, csak akkor, ha nem megfelelően alkalmazzuk. Próbáljuk meg egy korábban deklarált tömb elemeit összeadni az OSSZEG függvénnyel!

TYPE ARRAYTYPE=ARRAY[1 ..100] OF INTEGER;
...
FUNCTION OSSZEG(X:ARRAYTYPE; N:INTEGER):INTEGER;
VAR I : INTEGER; {CIKLUSSZÁMLÁLÓ}
BEGIN
  OSSZEG:=0;
  FOR I:=1 TO N DO
    OSSZEG:=OSSZEG+X[I] {END FOR I};
END {OSSZEG};

A fordító szerencsére "kiakad" azon, hogy az értékadás jobb oldalán az OSSZEG azonosítónál hiányoznak a paraméterek. A fordító ugyanis "tudja", hogy OSSZEG nem változó, hanem függvényazonosító. Ellenkező esetben a függvény újra és újra meghívná önmagát, amíg a verem meg nem telik, és a program vagy elszáll, vagy valamilyen hibaüzenetet kapunk. A feladat megoldása helyesen:

FUNCTION OSSZEG(X:ARRAYTYPE;N:INTEGER):INTEGER;
VAR I,SZUMMA:INTEGER;
BEGIN
  FOR I:=1 TO N DO
    SZUMMA:=SZUMMA+X[I] {END FOR I};
  OSSZEG:=SZUMMA;
END {OSSZEG};

Amint látjuk, be kellett vezetni egy ideiglenes változót (SZUMMA), amelyben az ősszegzést elvégezzük, és csak a számítás legvégén adjuk ezt az összeget értékül a függvényazonosítónak. OSSZEG így csak a bal oldalon szerepel értékadó utasításban, így nem lesz rekurzív hívás.
Ugyancsak rekurzív hívást eredményez, ha egy függvény ugyan nem önmagát hiuja, hanem egy másikat, az viszont újra meghívja az elsőt. Tegyük fel, hogy egy grafikai programhoz gyors egész típusú szögfüggvényekre van szükségünk (a Pascal beépített sin és cos függvénye logikusan - valós típusú). Matekóráinkról emlékszünk, hogy
SIN(alfa) =SQRT(1-COS ^2(alfa))
és
COS(alfa)=SQRT(1-SIN^2(alfa))
Gyorsan megírjuk a két függvényt (feltételezve, hogy az egész típusú INTSQRT függvény már létezik):

FUNCTION INT_SIN(X:INTEGER):INTEGER;
BEGIN
  INT_SIN:=INT_SQRT(1-INT_COS(X)*INT_COS(X));
END { INT_SIN };
FUNCTION INT_COS(X:INTEGER):INTEGER;
BEGIN
  INT_COS:=INT_SQRT(1-INT_SIN(X)*INT_SIN(X));
END { INT_COS };

A program (a mindjárt ismertetendő kiegészítéssel) lefordul, de futtatáskor elszáll, hiszen a két függvény vég nélkül hívogatja egymást. Az ENTERPRISE-on is futó Turbo-Pascal 3.01 esetén például majdnem 3000 oda-vissza hívás történik, mielőtt a program belehalna. (Csak megjegyezzük, hogy ez a példa nem csak ebből az egy sebből vérzik, matematikailag is hibás.)
Persze, az ilyen típusú rekurzió is megengedett, ha megfelelően alkalmazzuk (gondoskodunk arról, hogy a hívások lánca valamikor megszakadjon). Ügyeljünk arra, hogy bár a Pascal nyelv leírása tetszőleges típusazonosítót megenged a függvény típusának deklarációjában, a fordítók általában korlátozzák a megengedett típusokat. A Turbo-Pascal például csak skalár jellegű típust enged meg, így itt sem rekord, sem tömb nem használható. (A STRING skalárisak számít, az ARRAY OF CHAR viszont már tömb!)
Mit tegyünk, ha mindenképpen tömböt vagy rekordot kell visszaadnunk eredményül? Nagyon egyszerű: használjunk függvény helyett eljárást, az eredményt pedig változóparaméterként kaphatjuk meg. Ugyanezt tehetjük, ha a függvénynek egynél több paramétert kell visszaadnia: A fájlokkal végzett műveletekhez a FILE (és az ezzel rokon T'EXT) változót név szerint kell paraméterként átadni, hiszen a művelet során változik az értéke:

TYPE
  NAME_TYPE=STRING;
  ACCESS_TYPE=(FOR_READ,FOR_WRITE);
FUNCTION OPENFILE(VAR F:TEXT;FILE_NAME:NAME_TYPE;ACCESS:ACCESS_TYPE):BOOLEAN;
BEGIN
  ...
END {OPENFILE};

Itt az OPENFILE függvény (nem részletezett módon) megpróbálja megnyitni a fájlt, és ha ez sikerül, a függvényérték TRUE tesz, ellenkező esetben FALSE. A függvény hívása (pl. egy listázó programrészben) ilyen lehet:

IF OPENFILE(DATAFILE, 'DATA.DAT', FOR_READ) THEN BEGIN
   WHILE NOT EOF(DATAFILE) DO BEGIN
      READLN( DATAFILE, SZOVEGSOR );
      WRITELN(SZOVEGSOR);
   END { WHILE };
   CLOSE(DATAFILE);
END ELSE BEGIN
   WRITELN;
   WRITE('*** Nem tudom megnyitni az adatfile-t!');
   READLN;
END {IF};

A példa egyúttal ízelítőt ad a helyes programozási stílusból (megjegyezve, hogy nem feltétlenül ez az egyedül üdvözítő megoldás); SZOVEGSOR valamilyen stringként van deklarálva, lehetőleg a példát tartalmazó eljárásban lokális változóként.

Az előzetes deklaráció
A két egymást hívogató függvény néhány példával korábbról úgy, ahogy van, nem fordítható le. A Pascal ugyanis minden esetben csak már deklarált objektumokra való hivatkozást fogad el. Ez érthető is, hiszen a fordító nem tud mit kezdeni egy azonosítóval, ha azt sem tudja, hogy az konstans, típus, változó vagy függvény, sem pedig, hogy milyen típusú. Így az INT_SIN függvény fordításakor az INT_COS azonosítót még nem ismeri, így hibát jelez. Mivel azonban ilyen jellegű rekurzióra szűkség lehet, a probléma megkerülhető az ú.n. előzetes (FORWARD) deklaráció segítségével. Ennek a módja az, hogy a később definiálandó függvény vagy eljárás fejét felírjuk és pontosvesszővel elválasztva utána írjuk a FORWARD kulcsszót. Ekkor már hivatkozhatunk a nevére, magát a függvényt vagy eljárást ráérünk később definiálni. A példa ennek fényében helyesen így néz ki:

FUNCTION INT_COS(X:INTEGER) INTEGER;FORWARD;
FUNCTION INT_SIN(X :INTEGER):INTEGER;
BEGIN
   INT_SIN:=INT_SQRT(1-INT_COS(X)*INT_COS(X));
END {INT_SIN};
FUNCTION INT_COS;
BEGIN
   INT_COS:=INT_SQRT(1-INT_SIN(X)*INT_SIN(X));
END {INT_COS};

Figyeljük meg, hogy a függvény tényleges definíciójában már nem adtunk meg sem típust, sem pedig a formális paraméterek listáját, hiszen mindkettő már ismert a fordító számára az előzetes deklarációból. Ügyeljünk arra, hogy az egyes megvalósításokban a FORWARD deklaráció szintakszisa eltérő lehet!

Végül pedig lássunk egy példát a "hasznos" rekurzióra! Minden tankönyv a faktoriális számítását hozza fel példának a rekurzióra, hozzátéve, hogy egyáltalán nem ez a számítás egyetlen lehetséges (és legegyszerűbb!) módja; mi sem tesszük másként. Mint tudjuk, n! az n szám faktoriálisát, azaz az összes természetes szám szorzatát jelenti 1-tőt n-ig. Így
1! = 1
2! = 1*2 = 2
3! = 1*2*3 = 6
4! = 1*2*3*4 = 24

stb., hozzátéve, hogy megállapodás szerint
0! = 1
Bár ennek alapján a faktoriális értéke egy 1-től n-ig menő ciklussal igen egyszerűen kiszámítható, a rekurzív függvényhívás menetének megértéséhez mégis az alábbi függvényt írjuk meg:

FUNCTION FACT(N:INTEGER):INTEGER;
BEGIN
   IF N<>0 THEN BEGIN
      FACT:=N*FACT(N-1);
   END ELSE BEGIN
      FACT:=1;
   END {IF};
END {FACT};

A módszer azon alapszik, hogy a definíció alapján bármely a értéknél n!=n*(n-1)! azaz bármelyik szám faktoriálisa kiszámítható a nála 1-gyel kisebb szám faktoriálisának és önmagának a szorzataként.
Hívjuk meg a függvényt a

WRITELN(FACT(3));

utasítással! A híváskor N felveszi a 3 értéket, így a függvény törzsében a feltételvizsgálat eredménye IGAZ; a függvény ki akarja számolni a 3*2! szorzatot, de ehhez szüksége van 2! értékére. Meghívja tehát újra a FACT függvényt, de most már N=2 argumentummal. Az új híváskor a függvény adatterülete újra generálódik, így N előző híváskori értéke nem vész el. A függvény most újra végrehajtja a feltételvizsgálatot, és mivel N értéke (2) nagyobb nullánál, megint a szorzást hajtaná végre. Ehhez szüksége van 1! értékére, ehhez meghívja a FALT függvényt N=1 argumentummal.
Az újabb hívásnál az előzőek szerint játszódik le minden, a függvény harmadszor is meghívja önmagát, most N=0 argumentummal. Végre történik valami: a feltétet nem teljesül, az ELSE ág hajtódik végre, a függvény a FACT = 1 értéket kapja, és a vezérlés visszaadódik a hívás helyének. A hívó azonban ugyanez a FACT függvény volt, ahol is N értéke 1. Végrehajtódik a FACT:=1*1 értékadás, a vezérlés - és a függvényérték - visszaadódik a hívónak. A hívó a FACT függvény, de most N értéke 2. Végrehajtódik a FACT:= 2*1 értékadás, a hívó megint megkapja a függvényértéket. Itt N értéke 3, a szorzás eredménye átadódik a legelső hívónak, ez a főprogrambeli WRITELN utasítás, amely ki is írja a kapott 6 értéket. Ennyi az egész.
A rekurzív technika alkalmazásánál azért ügyeljünk arra, hogy bár elvileg a rekurzió tetszőleges mélységű lehet, gyakorlatilag mindig van egy határ, ez pedig a verem mélysége. Ne felejtsük el, hogy minden újabb rekurzív hívásnál a verembe kerül a visszatérési cím és a paraméterek, és ugyanide kerülnek az eljárás vagy függvény lokális változói. Amint láttuk, a Turbo-Pascal az ENTERPRISE-on néhány ezres szintet enged meg. Ha túl sok paramétert vagy változót használunk, a verem jóval hamarabb megtelik. Ha semmiképp nem elegendő a memória, fordítsuk le lemezre a programot, azaz készítsünk .COM fájlt és azt futtassuk (HiSoft Pascal esetén fordítsuk magnóra, azaz készítsünk betölthető programot), ekkor jóval több memóriát kapunk.

5. A beépített függvények és eljárások
A függvények egy része a bemenő paraméterével azonos típusú eredményt ad, sok függvény különböző típusú paramétereket is elfogad. Más függvények típusa eltér a bemenő paraméter típusától; sőt, egyes függvények célja kimondottan a konverzió egyik típusból a másikba. Ez megnehezíti a tárgyalásukat, osztályozásukat. Megpróbáljuk a lehetetlent, a lehető leglogikusabb csoportosítást.

Jelölések
A függvények és (eljárások) paramétere általában konstans, változó vagy kifejezés lehet. Egyes esetekben a paraméter csak változó lehet, ezt mindig jelezzük. A paramétert helyettesítő kisbetű jelzi a paraméter típusát:
i : egész (integer)
r : valós (real)
c : karakter (char)
s : string
b : logikai (boolean)
f : file (vagy text)
x : a paraméter többféle típusú lehet.

Egész eredményt szolgáltató függvények

ABS(i) - az i egész paraméter abszolút értéke
SQR(i) - az i egész paraméter négyzete (nem négyzetgyöke!)
TRUNC(r) - az r valós paraméter egész része, azaz a törtrész elhagyásával keletkező egész szám
ROUND(r) - az r valós paraméter kerekítésével keletkező egész szám (0-tól 0.5-ig lefelé, onnantól felfelé kerekít).

Példa:
   ABS(1) = 1;
   ABS(-2) = 2
   SQR(3) = 9
   TRUNC(3.9) = 3
   TRUNC(-3.9) = -3
   ROUND(3.9) = 4
   ROUND(-3.9) = -4

Az utóbbi két függvény jelentőségét az adja, hogy míg minden egész számnak megfeleltethető egy vele azonos matematikai értéket képviselő valós szám, addig ugyanez visszafelé már nem igaz a valós számok csak kis töredékének felel meg valamilyen egész szám, még akkor is, ha a valós érték egyébként az ábrázolható egész számok tartományán belül van. Ez azt jelenti, hogy egy egész kifejezést mindig értékül adhatunk egy valós változónak (az ehhez szükséges típuskonverziót a fordítóprogram önmagától előállítja). Viszont a fordított esetben, tehát ha egy valós kifejezést akarunk értékűt adni egész változónak, abban az esetben, ha egyáltalán az értékadás végrehajtható, minden esetben meg kell mondanunk, hogy milyen konverziót kívánunk végrehajtani: a kifejezés egész (vagy csonkított) értékére, vagy pedig kerekített értékére van-e szükségünk. Tipikus példa lehet egy rajzoló program, ahol a görbe pontjainak koordinátáit nagy valószínűséggel valós értékekkel számoljuk, a rajzoláshoz viszont egész képpont-koordinátákat kell megadni. Ha itt csonkított értéket használunk, a görbe jobban torzul, mintha kerekítést alkalmaznánk.
Egész típusú paraméterrel is használható a következő két függvéní, amelynek bemenő paramétert bármilyen sorszámozott (azaz egész, résztartomány vagy felsorolási, vagy karakter) is lehet:

SUCC(x) - eredménye a sorozat következő eleme
PRED(x) - eredménye a sorozat előző eleme

Példa:
   SUCC(3) = 4;
   PRED(3) = 2
   SUCC('A') = 'B'

és, feltételezve a TYPE NAP = (hétfő, kedd, szerda, csütörtök, péntek, szombat, vasárnap) deklarációt,
   SUCC( kedd ) = szerda
   PRED( kedd ) = hétfő
viszont SUCC(vasárnap) és PRED(hétfő) eredménye meghatározatlan.

Tetszőleges felsorolási típusú, illetve karakteres típusú érték típuson belüli sorszámát adja vissza az ORD függvény. Karakter típusú paraméterrel így a BASIC ASC függvényével egyenértékű, de sokkal általánosabban használható.
ASCII kódkészletet feltételezve:
   ORD('A') = 65
   ORD( 'a' ) = 97

és az előző deklaráció érvényességén belül
   ORD(hétfő) = 0,
   ORD(vasárnap) = 6.

Kizárólag karakter típusú eredmény esetén az ORD függvény inverze a CHR függvény:
   CHR(65) = 'A'
és így CHR( ORD='A')) = 'A'.

Az ODD függvény eredménye logikai (BOOLEAN) típusú: ha egész paramétere páratlan, a függvény értéke TRUE (igaz), páros szám vagy nulla esetén pudig FALSE (hamis).

Matematikai függvények
A függvények egy külön csoportját alkotják a matematikai függvények. Ezek paramétere és értéke is valós.
Már ismerjük az egész típusú ABS függvényt. Nos, az ABS(r) függvény valós paraméter esetén valós értéket szolgáltat. Ugyanez érvényes az SQR függvényre is. A valós paraméter négyzetgyökét az SQRT(r) függvény adja.
A SIN(r) függvény a valós paraméter szinuszát,
a COS(r) függvény a valós paraméter koszinuszát,
az ARCTAN(r) függvétny a valós paraméter arkusz-tangensét adja meg. Ezek a leggyakrabban használt trigonometrikus függvények; a többi értékét szükség esetén ezekből már kiszámíthatjuk.
Az EXP(r) függvény az e alapú exponenciális függvény ("e az r-edik hatványon megfelelője).
Az LN(r) függvény ennek inverze, tehát az e alapú logaritmus függvénye.

A bevitellel és kivitellel kapcsolatos eljárások és függvények
A most következők, sajnos, nem vonatkoznak a HISOFT-PASCAL implementációra, mivel az - a SPECTRUM-tól örökölve - nem tartalmaz file-kezelést. A Turbo-Pascal viszont teljes mértékben megfelel a leírásnak, sőt, további szolgáltatásokkal teszi még könnyebbé a file-kezelést.
A bevitel és kivitel tárgyalásakor röviden áttekintettük a két alapvető (és igen ritkán használt) beviteli-kiviteli eljárást, a GET(f) és a PUT(f) eljárást. Említettük az inkább használatos READ és WRITE eljárást is. Ezek már változó számú paramétert is elfogadnak, így használatuk igen kényelmes. Általános alakjuk:
   READ( f, pl, P2 ... pn )
   WRITE( f, pl, p2 ... pn ).

A file-kezeléshez tartozik még néhány eljárás és függvény:
Az ASSIGN(f,s) eljárás az f file-hoz az s stringben megadott file-nevet rendeli hozzá. A file ezután már megnyitható írásra: REWRITE(f) vagy - feltéve, hogy a file létezik - olvasásra: RESET( f ).
Az olvasásra megnyitott file-t nem kell lezárni, de az írásra megnyitott file lezárását nem szabad elmulasztani, hiszen ekkor kerül ki ténylegesen a háttértárra vagy perifériára a file utolsó darabja, illetve ekkor íródik felül a file katalógusbejegyzése a merevlemezen vagy a floppyn. A lezárást - Turbo-Pascalban - a CLOSE(f) eljárás végzi.
Olvasáskor a file végét jelzi a logikai (boolean) típusa EOF(f) függvén, ez igaz (TRUE) értéket ad, ha a következő elem már nem beolvasható, mert elértük a fele végét. Ennek alapján egy file másolását a következő programvázlat mutatja:

TYPE
  ALAPTIPUS:CHAR {vagy tetszőleges más típus};
  F1,F2:FILE OF ALAPTIPUS;
VAR
  PUFFER:ALAPTIPUS;
  FNAME1,FNAME2:STRING[40];
...
  FNAME1:='F1.DAT'; FNAME2:='F2.DAT'; {vagy más módos adott nevek}
  ASSIGN(F1,FNAME1); RESET(F1); {megnyitjuk olvasásra}
  ASSIGN(F2,FNAME2); REWRTTE(F2); {megnyitjuk írásra}
  WHILE NOT EOF(F1) DO BEGIN
    READ(F1,PUFFER ); WRITE( F2,PUFFER );
  END {WHILE};
  CLOSE(F2);

Bár a szöveges információ a FILE OF CHAR deklaráció segítségével is kezelhető lenne, a Pascal bevezet egy teljesen új típust a szöveges file-ok feldolgozásához. Ez a típus a TEXT. Például:

TYPE T:TEXT;

A TEXT típus nem azonos a FILE OF CHAR típussal. A különbség az, hogy a TEXT típus, amellett, hogy természetesen karakterekből áll, nagyobb egységekre, szövegsorokra osztható. Tudjuk, hogy fizikailag a szövegsorok végét CR és LF karakterpáros jelzi. A TEXT típus használatával azonban ezt a CR-LF karakterpárost a programozó nem látja, a nyelv más módon, sokkal kényelmesebben jelzi a sorok végét. Egyrészt a TEXT típus esetén a READ és a WRITE eljárás mellett használható a csak erre a típusra érvényes READLN és WRITELN eljárás is. Ezek teljes alakja:

READLN(f,p1,p2 ... pn)
WRITELN(f,p1,p2 ... pn)

A READLN eljárás beolvassa a paraméterlistájában megadott elemeket, majd az aktuális sor végét átugorva, új sor elejére áll (azaz átugorja a következő CR-LF karakterpárost). A WRITELN eljárás kiírja a paraméterlistájában szereplő elemeket, majd új sorra ugrik (azaz a file-ba ír egy CR-LF karakterpárost).
A sorok kezelését könnyíti az EOLN(f) függvény, ez igaz ('I'RUE) értéket ad, ha az olvasott file-ban éppen sor végéhez érkeztünk.
A szöveg-file tagolását segíti a PAGE(f) eljárás, amely új lapot kezd a file-ban, azaz a további kiírás új lapra kerül. Ennek elsősorban nyilván nyomtatásra szánt szöveg esetén van jelentősége. Fizikailag ilyenkor a file-ba általában egy FF (form feed) karakter kerül, ez a nyomtatónál lapdobást vált ki.
A Pascal, a főbbi programozni nyelvhez hasonlóan, a perifériás készülékeket - a billentyűzetet, a képernyőt, a nyomtatót stb. - szintén file-ként kezeli. A Turbo-Pascal például a kővetkező készülékneveket fogadja el:

CON: - A rendszerkonzol, általában a képernyő és a billentyűzet
KBD: - A billentyűzet, csak bemeneti eszköz.
TRM: - Terminál (képernyő), csak kimeneti eszköz.
LST: - Valamilyen listázó eszköz, általában nyomtató.
AUX: - Valamilyen külső eszköz, pl. lyukszalag-olvasó vagy -lyukasztó.
USR: - A felhasználó által definiált eszköz.

A CON: estköz pufferelt és echózott be- és kivitelt valósít meg, azaz a billentyűzeten beírt karakterek megjelennek a képernyőn, és mindaddig nem kerülnek be a befogadásukra rendelt változóba vagy változókba, amíg meg nem nyomjuk az ENTER billentyűt. Ez azt jelenti, hogy addig javíthatjuk az esetleg hibásan bevitt szöveget.
A KBD: eszközről ténylegesen karakterről karakterre tudjuk az információt bevinni, és a beírt karakterek nem jelennek meg a képernyőn. Ezeket a készülékneveket ugyanegy használhatjuk, mint bármilyen file-nevet:

ASSIGN(F1,'TRM:'); REWRITE(F1);
ASSIGN(F2,'KBD:'); RESET(F2);

Van azonban egy még kényelmesebb lehetőség a készülékek használatára. A Turbo-Pascal mindegyik készülékhez hozzárendel egy előre definiált - TEXT típusú - változót. Ha ezeket használjuk, nincs szűkség sem az ASSIGN, sem pedig a REWRITE vagy a RESET eljárás használatára, ezek a file-ok automatikusan megnyitódnak a program indításakor. Ezek a fele-azonosítók a következők:
CON, KBD, TRM, LST, AUX, USR
és értelemszerűen mindegyik a hasonló neve készülékhez van hozzárendelve.
A szabványos Pascalban - és a Turbo-Pascalban is - létezik még két előre definiált file, az INPUT és az OUTPUT lile. Mindkettő általában a rendszerkonzolhoz, tehát a billentyűzet-képernyő együtteshez van hozzárendelve. Ez a két file jelenti a rendszer szabványos bemenetét és kimenetét; ez annyira így van, hogy ezt a két file-t használva a nevek maguk elhagyhatók. Ennek értelmében

READ(X) megfelelője READ(INPUT,X)
READLN(X) megfelelője READLN(INPUT,X)
WRITE(X) megfelelője WRITE(OUTPUT,X)
WRITELN(X) megfelelője WRITELN(OUTPUT,X)

Ez rendkívül leegyszerűsíti az alapszintű be- és kivitelt.
A TEXT típus használata esetén a Pascal egy óriási segítséget ad, ez az automatikus konverzió. A READ, READLN, WRITE és WRITELN eljárás ugyanis nemcsak karaktereket tud olvasni vagy írni, hanem numerikus (egész és valós) értékeket, sőt, kiíráskor logikai (boolean) típust is használhatunk. A numerikus-karakter konverzió és ellentettje olyan természetes számunkra, hogy meg sem fordul a fejünkben, hogy ez bizony valóban konverzió: a 123 egész érték kiírásakor a fiie-ba a '123' karaktersorozat kerül; a file-ban található '1.2' karaktersorozat beolvasásakor a READ (vagy READLN) eljárásban megadott valós változóba az 1,2. valós érték (pontosabban, egy ehhez igen közeli érték) kerül.
A WRITE és a WRITELN eljárás további lehetőséget ad ennek a konverziónak a befolyásolásához, ez a formátumvezérlés. A Pascal formátumvezérlése rendkívül egyszerű, ugyanakkor hatékony. A kiírandó kifejezést (változót, konstanst) kettősponttal elválasztva egy pozitív egész eredményt adó másik kifejezés (változó, konstans) követi. A második érték neve mezőszélesség, és nevéhez híven a kiírási mező szélességét adja meg, azaz azt, hogy a kiírás hány karakter szélességű legyen. Ez a formátumvezérlés alkalmazható a numerikus egész, a karakter, a logikai és a string értékek kiírásához, valamint a valós értékekhez is, ha megfelel a lebegőpontos (exponenciális, normálalakú) megjelenítési forma.
Ha nem adunk meg formátumvezérlő információt, a Pascal minden értéket annyi karakterrel ír ki, amennyi ehhez minimálisan szűkségen. A valós értékeket ekkor lebegőpontos alakban írja ki, az értékes jegyek számát az adott gépi reprezentáció határozza meg. Az ilyen "szabadon hagyott" kiírásnak az a hibája, hogy az egyes értékek egymásba folynak, nem lehet azokat egymástól megkülönböztetni. Például a
   WRITELN(1,'2',3,TRUE) eljáráshívás eredménye
   123TRUE
ami nem túl szerencsés. Javíthatunk az eredményen így: WRITELN( 1,' ','2',' ',3, ' ',TRUE )
vagy pedig így:
   WRITELN(1,' 2',' 3',' ',TRUE)
mindkét megoldás eredménye
   1 2 3 TRUE
A formátumvezérlést alkalmazva azonban a különböző hosszúságú eredmények is oszlopokba rendezhetők.
  WRITELN(1:4,'2':4,3:4,TRUE:6);
  WRITELN(11:4,'22':4,33:4,FALSE:6);
  WRITELN(111:4,'222':4,333:4,TRUE:6);

hatására a három számjegy mindegyike egy-egy négykarakteres mező jobb szélére igazítva jelenik meg.
A valós értékek kiírása, mint már említettük, exponenciális formában történik, így
  WRITELN(3.14159265358:8) eredménye
   3.14E+00
Ha inkább fixpontos formában szeretnénk Látni a valós értéket, akkor alkalmazzuk a formátumvezérlés másik alakját, amelyik egy további értéket ad meg, a mezőszélességtől ugyancsak kettősponttat elválasztva. Ez az érték a kiírandó tört jegyek számát adja meg:
  WRITELN(3.14159265358:8:4) eredménye
   3.1415
Ha csak az egész legyekre vagyunk kíváncsiak, egyszerűen 0-t adunk meg.
Ha a bármelyik érték nem fér el a számára ily módon előírt helyen, különböző Pascal implementációk eltérően viselkedhetnek. Míg a nyelv eredeti leírása szerint ilyenkor az értékből csak annyi jelenik meg, amennyi a számára előirt helyen elfér, nem törődve azzal, hogy így esetleg téves értéket kapunk, a Turbo-Pascal ilyen esetben kitör a korlátok közül, és önkényesen annyi pozíciót használ fel a kiírásra, amennyi ehhez minimálisan szükséges; azaz, mintha nem is adtunk volna meg formátumspecifikációt.

6. A Turbo-Pascal eljárásai és függvényei
Eddig nagyon röviden áttekintettük a szabványos Pascal nyelv eljárásait és függvényeit, itt-ott utalva az egyik vagy másik megvalósítás sajátosságaira. A közismert és méltán népszerű TurboPascal sok-sok új lehetőséget ad a programozónak, nagyrészt a beépített eljárások és a függvények listájának kibővítésével. Most ezeket a lehetőségeket tekintjük át röviden. Azok se keseredjenek el, akiknek - lemezegységgel nem rendelkezvén - be kell érniük a HiSoft Pascal szolgáltatásaival; a bemutatott megoldások egy része, kisebb-nagyobb módosításokkal, a HiSoft Pascalban is megvalósítható egy kis kiegészítés úján. Sorozatunkban igyekszünk majd közölni egy ilyen nyelvi bővítést.

A stringek és kezelésük
A stringek (magyarul füzérek) - a szót sokan stringnek, sokan viszont sztringnek ejtik - igen kényelmes és általánosan elterjedt módját adják a karakteres információ kezelésének. A szabványos Pascal, sajnos, nem tartalmazza a stringet mint nyelvi elemet, helyette az ARRAY OF CHAR típust vezeti be; ez egy tetszőleges, de előre meghatározott hosszúságúra deklarált karaktertömb. Ennek kezelése nem olyan rugalmas, mint a többi nyelv, pl. a BASIC string-kezelése. A Pascal ugyanis csak azonos hosszúságú karaktertömbök között enged meg műveleteket, pl. értékadást. Az is korlátot jelent a karaktertömbök használatánál, hogy a függvények visszatérő értéke csak skalár lehet, így karaktertömb eredményt nem lehet függvénnyel visszaadni.
Ennek a nyilvánvaló hiányosságnak a kiküszöbölésére vezették be a Turbo-Pascalban a STRING típust. A korábbi Turbo-Pascal verziókban - így az ENTERPRISE gépeken is használható 3.01-es verzióban - a stringek hosszát (ez persze mindig a maximális hossz) deklarálni kell:

VAR S:STRING[80];

Az így deklarált S változó tetszőleges karaktersorozatot kaphat értékül, a sorozat hossza 0 és 80 kőzött lehet. Ugyan most is csak azonos hosszúságú stringek között lehet műveleteket végezni, de ez az ellenőrzés szűkség esetén kikapcsolható a {$V-} fordításvezérlő opcióval (ezekről az opciókról majd később lesz részletesen szó).
A Turbo-Pascal későbbi verziói megengedik mind a fenti, deklarált hosszúságú, mind pedig a deklarálatlan hosszúságú stringek használatát:

TYPE
  STRING80=STRING[80];
  STRING2=STRING[2];
VAR
  S1:STRING80;
  S2:STRING2;
  S3:STRING;

Az S3 string persze nem lehet akármilyen hosszt, a gépi megvalósítás határt szab a hosszúságnak, mégpedig 255 karakterben. Ez csak látszólag jelent korlátot: valójában ki akar több mint három képernyősornyi stringekkel dolgozni? A 255 karakteres hosszkorlátnak az oka egyébként a gépi megvalósításban rejlik. A Turbo-Pascal - sok más nyelvhez hasonlóan - egy hosszkóddal megfejelve tárolja a stringet alkotó karaktereket. Mivel a hosszkód egy bájtos, a string hossza 0-tól 255 bájtig terjedhet.
Lássuk a stringeken végezhető műveleteket. Nyilvánvaló művelet az értékadás. Esetleg kevésbé nyilvánvaló, de igen hasznos a relációs műveletek lehetősége. Az összehasonlításkor az a string a kisebb, amelyikben - természetesen az adott kódtáblázatban - előbb adódik alacsonyabb kódú karakter, vagy amelyik rövidebb. A Turbo-Pascal beépített relációs operátorai így kiválóan alkalmasak angol szavak ábécébe rendezésére, de csak valamilyem trükköt alkalmazva lehet ékezetes, tehát mondjuk, magyar szavakat sorbaszedni velük. Megjegyezzük még, hogy a karakter (CHAR) típus kompatibilis az egységnyi hoszszúságú stringgel; a stringet alkotó karakterek pedig egyszerű indexeléssel emelhetők ki, mintha a string karaktertömb volna.
A stringekkel való egyéb műveletekhez a Turbo-Pascal egy sor eljárást és függvényt ad. A tárgyalásnál a stringet, sc string-konstanst, sv string-változót jelent, az esetleg használt indexszel; a numerikus értéket, mégpedig nc konstanst, nv változót, ne kifejezést jelent. A jobb érthetőség kedvéért bevezetünk még két jelölést: l hosszúságot jelöl, k pedig egy - később részletezendő - hibakódot; mindkettő egész érték. Az sv string-változó helyén - az elébb említett kompatibilitás miatt - karakter-konstans is állhat.

A LENGTH függvény a paraméteréül adott string hosszát adja meg; alakja:
   LENGTH(s)
A függvény értéke a string tényleges hossza:
   WRITELN(LENGTH('negy'));
eredménye 4, de
   WRITELN(LENGTH('ot'));
eredménye nyilván 2.

A CONCAT függvény stringek összefűzésére szolgál, alakja:
   CONCAT( s1, s2 ... sn );
A függvény az s1, s2 ... sn stringet a felírás sorrendjében egymáshoz fűzi, és a kapott stringet adja vissza függvényértékként. Az eredmény nem lehet hosszabb 255 karakternél, különben hibajelzést kapunk. Ha az eredmény a string deklarált hosszánál nagyobb hosszúságú, a "felesleg" elvész. Megjegyezzük, hogy az függvény helyett használható a + operátor is, az alábbi alakban:
sv:=s1+s2+...sn

Az INSERT eljárás egy string beszarását végzi egy másik stringbe; alakja:
   INSERT(s,sv,n)
Az eljárás az s stringet beszarja az sv stringbe, annak n-ik karakterpozíciójától kezdődően. Ha az eredményül adódó string túl hosszú, a "kilógó" rész elvész.
   S:='abcdefgh';
   INSERT('----',S,5);
   WRITELN(S);

eredménye
   abcd----efgh

A DELETE eljárás stringek egy darabjának a törlésére használható; alakja:
   DELETE(sv,n,I)
Az eljárás az sv stringváltozóból törli az n-ik pozíciótól kezdődő, l karakter hosszúságú darabot (rész-síringet). Ha a síring nem létező darabját akarjuk törölni, nem történik semmi; ha a törlendő darab "kilóg" a síringből, értelemszerűen csak a string létező, törölhető 'vége" törlődik.
   S:='abcdefgh';
   DELETE( S, 5, 8)

eredménye
   'abcd'

A COPY függvény stringek egy darabjának kiemelésére szolgál, alakja:
   COPY(s,n,I)
A függvény az s stringből, annak n-ik pozíciójától kezdve kiemel egy l karakter hosszúságú rész-stringet, és ez lesz a függvény visszatérési értéke.

A POS függvény egy string előfordulását keresi meg egy másik síringben. Alakja:
   POS(Smit, Smiben)
A függvény az Smit string előfordulását keresi az Smiben stringben. Ha ez utóbbi tartalmazza az előbbit, a függvény visszatérési értéke az előfordulás kezdő pozíciója lesz; ellenkező esetben a függvény értéke nulla.

A fennmaradó két függvény a stringek és numerikus értékek közötti konverziót valósítja meg mindkét irányban.
A VAL eljárás egy string-alakban megadott számot konvertál (egész vagy valós) numerikus értékké:
   VAL(s,nv,k)
Az eljárás az s stringben lévő számjegy- és egyéb karaktereket megpróbálja egész vagy valós számmá átalakítani, és ezt az értéket teszi az nv változóba. A változó értéke nem használható minden esetben: ha ugyanis az átalakítás sikeres volt, a k hibakód értéke 0, ellenkező esetben annak a karakternek a sorszámát adja meg a stringben, ahol a konverziót nem lehetett tovább folytatni.

VAL('1284',N,K);
WRITELN(N:8,K:4);

eredménye
   1234 0
míg
   VAL('12A45678',N,K);
esetén K értéke 3 lesz, mutatva, hogy hol "bicsaklott ki" a konverzió, N értéke pedig meghatározatlan.

Egyszerűbb az eset a fordított konverzió esetében (egy szám mindig kiírható számjegyeivel...). Az STR függvény, amelynek alakja
   STR(ne,sv)
az ne numerikus kifejezés aktuális értékét teszi az sv string-változóba. Ennek értelmében
   STR(2*2,S); WRITELN(S);
eredménye 4 lesz.
A VAL eljárást akkor érdemes használni, ha teljes egészében magunk akarjuk kezelni a numerikus bevitelt, azaz "hülyeség ellen védett" programot szeretnénk írni. A Turbo-Pascal ugyanis hibajelzéssel kilép, ha a READ vagy a READLN eljárás konvertálhatatlan adatot kap. Ha viszont csak szókőzőket adunk meg, mindaddig vár a program, amíg valami "ehetőt" nem kap.
Az STR eljárást például akkor használjuk, amikor grafikus képernyőre kell számértékeket írni (a Turbo-Pascal grafikus lehetőségeivel csak szöveget tudunk közvetlenül kiírni).

A dinamikus változókkal kapcsolatos eljárások és függvények
Az adattípusok tárgyalásánál megismerkedtünk a dinamikus változó fogalmával. A dinamikus változót az különbözteti meg a statikus változótól, hogy míg ez utóbbi a program elindulásakor, illetve - ha egy eljárás (függvény) lokális változójáról van szó - az eljárás (függvény) meghívásakor létrejön, és egészen a program befejezéséig vagy az eljárás (függvény) végrehajtásának befejezéséig létezik, addig az előbbi csak akkor jön létre, ha ezt a program explicit módon előírja. A statikus változónak neve van, a dinamikus változót viszont csak egy mutatón - pointeren - keresztül lehet elérni. Az alábbi deklaráció

TYPE
  ALAPTIPUS={itt tetszőleges (statikus) típusdeklaráció állhat};
  MUTATOTIPUS=^ALAPTIPUS;
VAR
  MUTATO:MUTATOTIPUS;

létrehoz egy tetszőleges alaptípust, a MUTATOTIPUS típus pedig erre az alaptípusra mutató mutatót (sic!). A MUTATO változó mutat majd az ALAPTIPUS típusú dinamikus változóra, ha azt - később létrehozzuk.
Ennek az első olvasásra talán körmönfontnak tőnő megoldásnak a gyakorlati programozói munkában igen sok előnye van. A legfontosabb, hogy csak ilyen módon tudunk olyan táblázatokkal, listákkal dolgozni, amelyeknek a méretét nem tudhatjuk előre.
A BASIC például csak a DIM kulcsszóban megtestesülő vektort vagy mátrixot adja lehetőségként. Ezeknek a méretét előre definiálni kell. Nincs annál dühítőbb, amikor a program leáll, mert az egyik táblázatunk betelt, pedig a másik még szinte üres, de hiába, mert a rendszer nem tud "ellopni" az egyik táblázat számára lefoglalt memóriából, hogy odaadja azt a másiknak.
Ha viszont dinamikus adatszerkezetekkel dolgozunk, a különböző táblázatokhoz csak egy-egy mutatót kell előre lefoglalnunk, a menet közben létrehozott dinamikus változók ugyanazt a tárterületet kezdik betölteni. A program csak akkor áll le, ha valóban minden rendelkezésre álló memória elfogyott.
A szabványos Pascalban a NEW eljárás hozza létre a dinamikus változót. Az előző példát folytatva, a

NEW(MUTATO)

eljárás létrehozza a tár erre kijelölt szabad területén az ALAPTIPUS típusú változót, a változó címét pedig elhelyezi a MUTATO nevű - statikus - változóban. Magát a dinamikus - tehát az ALAPTIPUS típusú - változót csak a mutatóján, a MUTATO változón keresztül érhetjük el. Ha például az ALAPTIPUS egyszerűen INTEGER, akkor a dinamikus változónak a

MUTATO^:= 0;

utasítással adhatunk értéket; itt a ^ karakter jelzi, hogy nem a MUTATO kap értéket, hanem az általa mutatott változó kap értéket. Ha az ALAPTIPUS viszont string volt, akkor az értékadás így nézhet ki:

MUTATO^:='Ez lesz a dinamikus valtozo tartalma';

Ha újra meghívjuk a NEW eljárást, újabb, azonos típusú dinamikus változó jön létre, és most ennek a címe kerül a MUTATO mutatóba. Ha a mutatott változó, amely természetesen egy rekord is lehet, tartalmaz egy vagy több mutatót is, határtalan lehetőségek nyílnak meg előttünk. A dinamikus változó "testében" lévé mutató ugyanis egy másik, ugyanilyen dinamikus változóra mutathat, az egy következőre, és ,így a (majdnem) végtelenségig. A dinamikus változók-ekkor az egymásra mutogató-mutatók segítségével tulajdonképpen "fel vannak fűzve", a program a változókon "végiglépkedhet", ha ismeri az első elem címét.
A dinamikus változó persze nem csak születhet, hanem meg is halhat a program futása során. Ha valahány dinamikus változó feleslegessé vált, és szükségünk van a memóriaterületre, a RELEASE eljárással felszabadíthatjuk a memória egy részét. A

RELEASE(MUTATO)

eljárás törli a MUTATO által mutatott dinamikus változót, valamint az összes olyan dinamikus változót, amelyet az ez után a változó után hoztunk létre.
Ha a program futása közben valamikor végrehajtjuk a

MARK(MUTATO)

eljárást, a Pascal-rendszer a MUTATO változóban tárolja a dinamikus változók által még éppen nem lefoglalt első szabad memóriacímet, Így mintegy megjelöljük, hogy a későbbi RELEASE eljárás honnantól szabadítsa fel a memóriát.
Törölhetünk egy dinamikus változót a DISPOSE(MUTATO) eljárással is, ekkor azonban csak egyetlen változó helye szabadul fel, azaz a memóriában egy "lyuk" keletkezik. Később, egy újabb NEW eljárással végrehajtásakor az így keletkezett lyuk esetleg "betömődik"; ebből kikövetkeztethető, hogy a MARK-RELEASE páros és a DISPOSE eljárás között választanunk kell, egy programon belül a kettőt vegyesen használva sok kellemetlenséget okozhatunk magunknak.

Az eddig leírtak a szabványos Pascalban érvényesek. A Turbo-Pascalban a dinamikus memóriakezelés további lehetőségekkel bővül. A tárgyalásnál p mutatót (pointer) jelöl, s pedig memóriaméretet bájtban, ez utóbbi egész kifejezés lehet.

A GETMEM eljárás segítségével a dinamikus változók számára fenntartott memóriaterületen, a heap-ben (magyar neve halom) tetszőleges méretű memóriaterületet foglalhatunk le, és használhatjuk kedvünk szerint. A
GETMEM(p,s)
eljáráshívás s bájt memóriát foglal le, a kezdőcímet pedig a p mutató típusú változóba teszi. Ehhez p-t előzőleg az általánosított POINTER típusúnak kell deklarálni:

VAR P:POINTER;

A GETMEM eljáráshívással lefoglalt memóriaterület felszabadítását végzi a FREEMEM eljárás. A
   FREEMEM(p,s)
eljáráshívás felszabadítja a p mutató által mutatott, s bájt méretű memóriaszakaszt. A létrehozáskori és a felszabadításkori méretnek értelemszerűen meg kell egyezni egymással.

Ha nem akarjuk, hogy a program váratlanul hibajelzéssel leálljon, ha a heap megtelik, érdemes ellenőrizni, van-e elegendő memória a lézerhozandó dinamikus változó vagy a lefoglalandó terület számára. A MEMAVAIL - paraméter nélküli - függvény a heap számára még rendelkezésre álló szabad memóriaterület méretét adja vissza, függetlenül attól, hogy az egy darabban vagy szabdalva érhető-e el. Akkor használjuk, ha a MARK-RELEASE párossal szabadítjuk fel a memóriát.

A MAXAVAIL függvény a legnagyobb egybefüggő memóriaterület méretét adja vissza. Akkor használjuk, ha a DISPOSE eljárással szabadítjuk fel a memóriát, vagy ha a GETMEM-FREEMEM párossal dolgozunk.

A SIZEOF függvény árulja el egy tetszőleges típus vagy változó memóriabeli méretét. Ha tehát nem ismerjük dinamikus változóink nagyságát, vagy bonyolult lenne azt kiszámolni, ne essünk kétségbe.

WRITELN(SIZEOF(INTEGER):6 );

eredménye 2; ugyanezt az eredményt megkaphatjuk kissé bonyolultabban is:

VAR N:INTEGER;
BEGIN
   WRITELN(SIZEOF(N):6);
END.

Továbbá:
   SIZEOF(REAL) = 4,
   SIZEOF(CHAR) = 1,
   SIZEOF(STRING[80]) = 81
(miért?)
   SIZEOF(MUTATO) =2 (8-bites gépeken)

A képernyőkezelés; szöveges üzemmód
A "szabványos" Turbo-Pascal TEXTMODE eljárása, amely a képernyő szöveges üzemmódra állítását lenne hivatott elvégezni, az ENTERPRISE gépen nincs megvalósítva. Ennek oka igen hétköznapi: a nyelv grafikus elemei itt nincsenek megvalósítva, így nincs is grafikus üzemmód, amelyről át lehetne kapcsolni szövegesre. Emiatt ennek a fejezetnek sincs meg a párja, amelynek címe A képernyőkezelés; grafikus üzemmód lenne...
Ha azonban a szöveges üzemmódot használjuk (ez ugye gyakorlatilag elkerülhetetlen), hasznos és szükséges segítség a képernyőtörlés, amit a Turbo-Pascalban a CLRSCR eljárás valósit meg. Az eljárás végrehajtása során a kurzor szokás szerint a bal felső sarokba kerül, ennek a pozíciónak a koordinátái (1,1).

Nem foglalkozunk a terminál installálásával kapcsolatos CRTINIT és CRTEXIT eljárással, akit ez érdekel, utánanézhet a kézikönyvben. A képernyő törlése után a következő legfontosabb funkció a képernyő címzése. A Turbo-Pascal szabványos eljárása erre a feladatra a GOTOXY névre hallgat, szintakszisa
   GOTOXY(oszlop, sor)
Ez egy parányit megzavarhatja az IS-Basic PRINT AT utasításának - talán kicsit logikusabbnak tűnő - sor, oszlop sorrendű paramétermegadósához szokott felhasználót. Ez a fordított sorrend talán a grafikus üzemmód paraméterezésével való párhuzam miatt jött létre, a grafikus eljárásoknál ugyanis x, y a koordináták sorrendje, a koordinátageometria pedig vízszintesen az x, függőlegesen az y koordinátát szereti felrajzolni. Aki nem tud kibékülni ezzel a sorrenddel, az nyugodtan definiálhat magának egy áthidaló eljárást, amit azután minden programjába beszerkeszthet:

PROCEDURE AT(SOR,OSZLOP:INTEGER);
BEGIN
  GOTOXY(OSZLOP,SOR);
END {AT};

Ha tehát a képernyő közepére szeretnénk kiírni egy üzenetet, akkor azt a következő kis eljárással ill. hívással tehetjük meg (a STRING80 típust a főprogramban kell definiálni, célszerűen 80 elemű stringként):

PROCEDURE KOZEPRE(SOR:INTEGER;SZOVEG:STRING80);
BEGIN
   GOTOXY((80-LENGTH(SZOVEG)) DIV 2, SOR);
   WRITE(SZOVEG);
END {KOZEPRE};
...
   KOZEPRE( 12, 'Itt a világ közepe!');

Ha nem tudjuk, hova is tettük a kurzort, megtudhatjuk a pozícióját a WHEREX és a WHEREY függvény meghívásával.
Nem ér senkit meglepetésként, hogy az első függvény a kurzorpozíció oszlopkoordinátáját, a második függvény a kurzor sorkoordinátáját adja meg, mindkettő értelemszerűen egész érték. Ennek megfelelten a
   GOTOXY(WHEREX,WHEREY);
programrészlet ugyanoda teszi a kurzori, ahol az eddig is volt, azaz nem csinál semmit.

A CLREOL eljárás (ennek a nevét már segítünk megfejteni: clear to end of line) a kurzor sorának a jobboldali végét törli a kurzor pozíciójától a sor végéig. Akkor lehet hasznos, ha a képernyő adott pozícióira írunk, és nem szeretnénk, ha az előzőleg kiírt szöveg hosszúra sikeredett vége ott maradna, ugyanakkor nem akarjuk újraírni a szöveg elejét. A sorrend mindegy: pozícionálunk, törlünk, írunk, vagy pedig pozícionálunk, írunk, törlünk (a többi kombináció nem nyer!). Készülő szövegszerkesztő programunk képernyő-újraíró eljárása nem nélkülözheti ezt az eljárást.

Ha az egész sort akarjuk törölni, a DELLINE eljárást érdemes használni. Ez meg is szünteti a sort, azaz a képernyő további sorait eggyel feljebb ugrasztja. Ha csak a sor tartalmát akarjuk törölni, de a helyét meg akarjuk tartani, használjuk a CLREOL eljárást, miután a kurzort a sor elejére pozícionáltuk. (Ha nem tudjuk, melyik sorban vagyunk, használjuk a WHEREY függvényt! A DELLINE eljárás párja az INSLINE, ez teszti egy üres sort, ehhez a kurzor sorát és az alatta levő sorokat lejjebb tolja.

Ha kedvünk van, játszhatunk a szöveg színével, persze, csak a megengedett lehetőségeken belül. A LOWVIDEO eljárás a gyenge szövegtónust kapcsolja be, a NORMVIDEO eljárás a normális tónust, a HIGHVIDEO eljárás pedig az erős tónust. A normális tónus használható a mezei kiírásra, az erős tónus a szöveg kiemelésére. A gyenge tónus jelölheti például egy menürendszerben az adott pillanatban valamilyen okból éppen nem használható menüpontokat.
A három eljárás által ténylegesen beállított szín természetesen az adott rendszertől függ. Az ENTERPRISE esetében mindez az IS-DOS által beállított palettának megfelelően fog működni. Sajnos, az IS-DOS elég "keményen" osztja ki a színeket, nehéz rajtuk változtatni. Fekete-fehér képernyőn, ha a monitorkimenetet használjuk, nincs különbség a háromféle kiírás között. Ez elég baj, és persze nem a Turbo-Pascal hibája (ugyanez a beépített hardverhiba okozza, hogy a kurzor és a szöveg azonos tónust a fekete-fehér képernyőn). Egy kis szoftveres barkácsolással megoldhatjuk a problémát, sót, az INVVIDEO eljárást bevezetve inverz (világos alapon sötét) karaktereket is kiírhatunk.
Ha sikerül felélesztenünk az inverz kiírást, kiíráskor ügyeljünk arra, hogy még a WRITELN utasítás kiadása előtt állítsuk vissza a norrmális üzemmódot, különben a sor üresen maradó vége is inverzre változik!

A billentyűzet kezelése
A képernyő után foglalkozzunk egy kicsit a billentyűzettel is! A billentyűzetet kétféle módon szokás kezelni. Az egyik, amikor bevitelre várunk, és mindaddig nem megyünk tovább, amíg az meg nem történik. A READ és a READLN eljárás Így működik.
Sokszor azonban a programnak folyamatosan mennie kell, és csak akkor kell valami eltérőt cselekedni, ha a felhasználó hozzányúlt a billentyűzethez. Ez lehet egy játék során, de pl. folyamatszabályozásnál, szimulációnál és még sok más esetben. Ehhez nyújt segítséget a KEYPRESSED függvény, amely logikai (boolean) típust. A függvény mindaddig FALSE, azaz hamis értéket ad vissza, amíg a billentyűzethez nem nyúlunk, és TRUE, azaz igaz értéket ad, amikor egy billentyűt lenyomunk. A
   REPEAT UNTIL KEYPRESSED;
programrészlet mindaddig vár, amíg egy tetszőleges billentyűt le nem ütünk. Kis kiegészítéssel felhasználhatjuk reflexeink gyorsaságának mérésére, vagy pedig egy véletlenszám-sorozat kezdőértékezésére:

S:=0;
REPEAT
  S:=S+1;
UNTIL KEYPRESSED;

Természetesen meg is tudhatjuk, hogy melyik billentyűről van szó:

REPEAT
  {itt folyamatosan történik valami}
  IF KEYPRESSED THEN BEGIN
      READ(KBD,C);
      CASE C OF
         {itt sokfelé ágazunk}
      END {CASE};
  END {IF};
UNTIL VEGE;

Mivel a KEYPRESSED függvény konfliktusba kerül az operációs rendszer billentyűzetkezelő szolgáltatásaival, használata esetén a C fordítási opciónak kikapcsolt állapotban kell lennie. Az opciókkal majd később foglalkozunk részletesen, most csak annyit előzetesen, hogy a programban valahol helyezzünk el egy {$C-} megjegyzést (így, ahogy van, szóközök nélkül). Vigyázzunk, mert ilyenkor az elindított programot nem tudjuk leállítani sem a STOP gombbal, sem pedig a CTRL-C billentyűkombinációval! Ha viszont a programot a RESET gombbal állítjuk meg, kikerülünk az IS-DOS szintjére, Így a programunk elvész, ha nem mentettük el. Futtatás előtt mindig mentsünk!

Egyéb matematikai függvények
Mint korábban láttuk, a szabványos Pascal a matematikai függvények közül csak viszonylag keveset valósít meg. A hiányzó függvények a megfelelő matematikai összefüggések ismeretében a meglévőkből előállíthatók. A Turbo-Pascal némiképpen bővíti a megvalósított matematikai függvények körét.

A FRAC(x) függvény, amelynek paramétere egész vagy valós lehet, valós eredményt ad, mégpedig a paraméter töri részét. Hasonlóan, az INT(x) függvény az egész vagy valós paraméter egész részét adja vissza, de valós típusaként! Nem használhatjuk tehát valós-egész típuskonverzióra (erre a célra a ROUND függvény alkalmas). Azt gondolhatnánk, hogy az INT függvény "házilag" is előállítható. Gondolatmenetünk mindaddig helyes, amíg a valós szám nem haladja meg az ábrázolható legnagyobb (negatív szám esetén legkisebb) egész számot, akkor ugyanis az eljárásunk már nem működik.

Ha dobókockát szimulálunk, molekulák ütközését vagy légörvények hatását modellezzük, elengedhetetlen a pszeudo-véletlenszámok generálása. A RANDOM függvény, ha paraméter nélküli változatát hívjuk meg, egy valós véletlenszámot ad vissza a 0..1 intervallumban. Az intervallum elölről zárt, hátulról nyitott; ez azt jelenti, hogy a 0 része az intervallumnak, az 1 viszont nem. Nullát adhat vissza a függvény, 1-et azonban nem, csak olyan számot, amelyik tetszőlegesen megközelíti 1-et, de el nem éri. Ha a függvény paraméteres változatát hívjuk meg, amely RANDOM(i) alakú, akkor az egy egész típusú véletlenszámot ad vissza a 0..i intervallumban; az intervallum most is elölről zárt, hátulról nyitott. Egész számok esetén ez átfogalmazható: a függvény a 0..i-1 tartományban ad eredményt. A kockadobás szimulálása tehát így nézhet ki:

WRITELN(RANDOM(6)+1);

(a RANDOM(6) hívás a 0..5 számsorozatból ad meg egy számot, az 1 hozzáadásával ezt az 1..6 intervallumra korrigáljuk). Ha a képernyő véletlenszerűen kiválasztott pozíciójába akarunk egy csillagot kiírni, akkor ezt így tehetjük meg:

GOTOXY(RANDOM(80)+1,RANDOM(24)+1);
WRITE('*');

Ha a csillag helyett egy véletlenszerűen kiválasztott nagybetűt akarunk kiírni, akkor a megoldás ez lehet:

WRITE(CHR(RANDOM(ORD('Z')-ORD('A')+1)+ORD('A')));

Az ORD('Z')-ORD('A')+1 kifejezés egy 0 ... 25 közé eső véletlenszámot állíttat elő a RANDOM függvénnyel. Ha ehhez hozzáadjuk 'A' karakterkódját, akkor az 'A'..'Z' közé eső kódokat kapjuk. A megoldás megfogalmazásának előnye, hogy tetszőleges kódtáblázattal működik, ha ott a nagybetűk folyamatos kódtartományt foglalnak el, és így nem is kellett megírásához kódtáblázatot elővennünk.
A RANDOM függvény a program minden indításakor ugyanazzal a pszeudo-véletlenszámmal kezdi a generált sorozatot; ez előnyös lehet a program belövésénél, viszont kevésbé volna tisztességes egy szerencsejátékban így használni. Ha azonban végrehajtjuk a RANDOMIZE eljárást, a véletlenszáni-generátor egy előre nem ismert kezdőértéket kap, így a véletlenszám-sorozat nem fürkészhető ki előre.

Egyéb eljárások, függvények
Van még néhány eljárás és függvény, amely hasznos lehet a programozási munkában.
A DELAY(i) eljárás várakozást iktat be a programba. Az i paraméter ms-ban, azaz ezredmásodpercben értendő. Sajnálatos, hogy az ENTERPRISE géphez ez az eljárás nincs következetesen illesztve, ezért a tényleges késleltetést kísérletileg kell megállapítani. Ha a ms-ben számított késleltetést elosztjuk 1.4-de1 és ezt az értéket adjuk át, nagyjából jó eredményt kapunk.

Sajnos, a Turbo-Pascal SOUND és NOSOUND eljárása itt nem működik, így zenei hangot csak barkácsolással tudunk a programunkból kicsalni. Azért ne keseredjünk el, valami hangot csak ki lehet hozni a gépből. A CTRL-G vagy BELL (azaz csengő) (08H kódú) vezérlőkarakter rendeltetésszerűen működik, egy furcsa pendülésszerű hangot ad ki:

CONST
   BELL=CHR(8);
...
   WRITE(BELL);

Az emelkedett programozóst stílusnak gyakorlatilag elengedhetetlen kelléke az éppen végrehajtás alatt lévő blokkból való kiugrást eredményező EXIT eljárás. Aki visszaemlékszik a sorozat bevezető részére, az tudja, hogy egy programszerkezetnek csak egy belépési és csak egy kilépési pontja lehet. Az EXIT eljárás sok esetben jelentősen le tudja egyszerűsíteni a program szerkezetét anélkül, hogy ezt az alapszabályt fel kéne rúgni.
AHALT eljárás befejezi a program futását, a vezérlés visszaadódik a Turbo-Pascal fejlesztői környezetnek vagy az operációs rendszernek.

Az UPCASE(c) függvény, amely paraméteréhez hasonlóan karakter típusú, a paraméterben adott karaktert nagybetűsíti. Ha az nagybetű vagy nem betű, változatlanul adja vissza. Ha egy egész stringet akarunk nagybetűsíteni, akkor egy eljárást kell írnunk (a Turbo-Pascal későbbi verzióiban ugyanezt már függvénnyel is megoldhatjuk):

PROCEDURE UPPERCASE(VAR S:STRING[255]);
VAR I:INTEGER;
BEGIN
  FOR I:=1 TO LENGTH(S) DO BEGIN
      S[I]:=UPCASE(S[I]);
  END {FOR I};
END {UPPERCASE};

Az eljárás a Turbo-Pascal string-kezelésének azt a nem mindenütt dokumentált tulajdonságát használja ki, hogy a string tulajdonképpen egy sajátos karaktertömb, amelyik a nulladik elemében tartalmazza a string hosszát. A fordítóprogram megengedi, hogy a string karaktereit indexelve, egyenként elérjük. Ha ez nem volna, akkor egy ilyen nagybetűsítést a COPY és a CONCAT függvény sokszorosan ismételt használatával. érhetnénk csak el, ami sokkal lassabb volna, és felírni is kényelmetlenebb.

Ugyancsak a gyorsabb végrehajtást és az egyszerűbb programozást segíti a MOVE eljárás. Szintakszisa:
MOVE(forrás, cél, hossz)
forrás és cél tetszőleges típusú változó lehet, míg hossz nyilván egész. Az eljárás a forrás változóval kezdődő tárterület tartalmát másolja át a cél változóval kezdődő területre, a harmadik paraméter a másolt szakasz hosszát adja meg bájtban. Az eljárás használható egyrészt egy nagy adatszerkezet kisebb darabjának az átviteléhez, de fordítva is, sok-sok (de csak szomszédos) adat egy menetben történő áthelyezéséhez. Mind a két esetben gyorsabb, mintha felsorolnánk az áthelyezendő elemeket egy-egy értékadásban. Az átviendő adatmennyiség meghatározásánál hasznunkra lehet a korábban megismert SIZEOF függvény, amelyik megadja az adatelem vagy -szerkezet hosszát.
Mivel forrás és cél eltérő típust változó is lehet, elképesztő galibákat tudunk okozni az eljárás meggondolatlan használatával. Akkor is érhet meglepetés, ha a másolandó terület belelóg a fogadó területbe, hiszen ekkor egy ponton túl a már felülírt területet másoljuk tovább. Legyünk hát óvatosak!

Még néhány függvény azoknak, akik egészen gépi szinten akarnak programozni. A HI(i) illetve a LO(i) függvény az egész paraméter felső illetve alsó bájtját adja vissza, egész értékként. A SWAP(i) függvény ugyanezt a két bájtot egymással felcserélve adja vissza. Akik foglalkoztak gépi kódú programozással, azok tudják, hogy erre miért van szükség.

Egyéb megoldások
Ugyan nem függvény, nem is eljárás a most következő néhány megoldás, mégis itt érdemes megemlíteni őket. A memória és a bemeneti-kimeneti portok közvetlen elérését a Pascal nem eljárásszerűen, hanem ú.n. előre definiált tömbök útján oldja meg.
A MEM tömb úgy viselkedik, mintha azt a felhasználó
   VAR ARRAY[0..$FFFF] OF BYTE;
deklarációval hozta volna létre. Ráadásul a tömb első eleme a memória 0. bájtjára illeszkedik, így a tömb elemei éppen a memória bájtjaira illeszkednek. (A beavatottak tudják, hogy a MEM tömb valóban létrehozható az
   ARRAY[0.. $FFFF] OF BYTE ABSOLUTE 0
deklarációval; ez a programozó által kiválasztott abszolút címtől kezdődően helyezi el a tömböt.)
A tömb írható és olvasható, tehát a memória (egészen pontosan, az IS-DOS alatt látszó memória) teljes tartományát közvetlenül kezelni tudjuk. Ezzel megint csak hatalmas baklövéseket lehet elkövetni, ugyanis óvatlanul belenyúlhatunk akár a saját programunk szövegébe vagy kódjába, akót a Turbo-Pascal rendszerbe, akár az operációs rendszerbe, és ekkor beláthatatlan dolgok történhetnek. Ha viszont ügyesen kezeljük, működés közben, in vivo boncolhatjuk fel az operációs rendszert vagy bármi mást a memóriában, vagy akár turbósított (azaz a memóriában dolgozó) fordítóprogramot készíthetünk. Sajnos, a teljes memóriatartomány kezelését megnehezíti az, hogy a az ENTERPRISE gépeken futó Turbo-Pascalból még hiányzik a WORD típus, amely az egész számokat előjelesen, a -32768.. +32767 tartományban kezelő INTEGER típussal szemben azokat a 0..65535 tartományban tudja kezelni.
Ha az éppen nem elérhető memórialapokra is kíváncsiak vagyunk, meg kell oldanunk a belapozásukat, ami nem könnyű, mert a TurboPascal és az IS-DOS alul-felül elfoglalja a memóriát, középtájon pedig a mi programunk van; így normál esetben nem találunk szabad szegmenst: bármelyikhez szegmenshez is nyúlunk, valamit mindig "kilapozunk magunk alól". Segít, ha a Turbo-Pascal indításakor nem töltjük be a hibaüzeneteket, vagy ha nem a fejlesztői környezetben futtatjuk a programot, hanem .COM fájllá lefordítva, közvetlenül IS-DOS-ból. A Turbo-Pascal megadja, hogy az egyes adatterületek hol kezdődnek, fordítás után pedig a hosszukat is elárulja. Figyeljünk ezekre az adatokra!

A belapozást végző eljárásokhoz, meg persze sok egyéb trükkhöz használhatjuk a szintén előre definiált PORT tömböt. Amint a neve elárulja, ez a bemeneti-kimeneti portok kezelését teszi lehetővé, mindkét irányban, tehát írásra és olvasásra is. A PORT tömb deklarációja - a Z-80 mikroprocesszor környezetében - mintha
   ARRAY[0..255] OF BYTE
lenne. A 0. lapregiszter tartalmát így olvashatjuk be:

CONST
   PAGE0=$B0;
VAR
   B:BYTE;
...
   B:=PORT[PAGE0];

Ha csak olvassuk a portokat, nem történhet baj, de ha írunk is rájuk, könnyen elszállhat a rendszer. Ha viszont ügyesek vagyunk, a DAVE chip közvetlen kezelésével akár zenei szintetizátort is csinálhatunk.

7. A fordító direktívák
Az elmúlt néhány folytatásban sokat foglalkoztunk a TurboPascallal, kissé elhanyagolva azoknak az olvasóinknak a lelkivilágát, akiknek be kell érniük a HiSoft Pascallal. Ez nem valamiféle szándékos diszkrimináció volt, egészen egyszerűen a Turbo-Pascal egy teljesebb, gazdagabb implementáció, több lehetőséget nyújt a felhasználónak, mint akaratlan vetélytársa, amelyet eredetileg egy egyszerűbb környezetre fejlesztettek ki; így többet lehet - és kell is - vele foglalkozni. A Turbo-Pascal lehetőségeinek nagy részét ismertettük; ami kimaradt, azt kisebb részben most pótoljuk, a maradékot az avatott felhasználó amúgy is csak egy részletesebb leírás birtokában tudja használni.
Amivel még mindenképpen adósok vagyunk, az a fordító direktívák rövid összefoglalása. Miért is van ezekre szükség, és egyáltalán, mik is ezek? Nos, egy programnyelv - legalábbis napjainkban - úgy készül, hogy minél nagyobb mértékben független legyen egy adott géptípus, modell, konfiguráció sajátosságaitól. Azonban a lefordított programnak egy konkrét gépen (azaz típuson, modellen, konfiguráción) kell futnia. Vannak más sajátosságai is a program futásának, az ember-gép kapcsolatnak stb., amit nem lehet nyelvi szinten elintézni. Itt lépnek be a fordító direktívák. Ezek olyan utasítások, amelyek a fordítóprogramnak szólnak, és előírják számára a fordítás során előállítandó program valamilyen tulajdonságát. A direktívák tehát nem programutasítások, nem keletkezik belőlük gépi kód. Nagy vonalakban: a forrásprogram, a programszöveg a fordítóprogram által feldolgozott nyersanyag, a direktívák pedig egyfajta utasítások, hogy hogyan is dolgozzon ezzel a nyersanyaggal...
A direktívák igen sajátos módon adhatók meg. A nyelv eredeti leírása csak egyetlen helyen engedélyezi tetszőleges információ megadását, mégpedig a megjegyzésekben. Ezért a TurboPascal (és más Pascal-implementációk is) egy speciális megjegyzést vezet be a direktívák megadásához. Tudjuk, hogy a Pascalban a megjegyzés a kapcsos zárójelben álló tetszőleges szöveg, amely akármilyen hosszú lehet (alternatív megoldás a (* és a *) jelkombináció alkalmazása, például olyan környezetben, ahol a terminál vagy a nyomtató nem ismeri a kapcsos zárójelet). A fordító direktíva olyan megjegyzés, amely dollárjellel kezdődik; ezt követi a direktíva törzse. Ez minimális korlátozást jelent csak a megjegyzések használatára.
A direktíva törzsének fő eleme a direktívát azonosító egybetűs kód, ezt követi a direktíva paramétere. A legtöbb direktíva valamilyen jellemző be- vagy kikapcsolt állapotával kapcsolatos, ezért a direktívák többségének paramétere a + vagy - jel, ezek jelentése értelemszerű. Más esetben egy fájlnév vagy egy érték lehet a paraméter. Ennek megfelelően a direktívák valahogy így néznek ki a program szövegében:
   {$A+}
   (*$IFILENAME.EXT*)
   {$W8}

Vigyázzunk, hogy a nyitó kapcsos (vagy összetett) zárójel és a dollárjel, illetve ez utóbbi és a direktíva betűkódja között ne álljon szóköz.
Minden direktívának van egy alapértelmezés szerinti értéke. Ha nem tüntetjük fel a direktívát, akkor ez az alapérték lesz érvényes. Egyes direktívák lokálisak, azaz csak a bekapcsolásuk és kikapcsolásuk közötti programszövegre érvényesek. Más direktívák globálisak, azaz az egész programszövegre kifejtik hatásukat; ezeket csak egyszer lehet megadni. Mindegyik direktívánál megadjuk az alapértelmezés szerinti értéket, és feltüntetjük a direktíva lokális vagy globális jellegét.

Az A direktíva: abszolút kód
Az A direktíva a rekurzív hívások használatát szabályozza. Ha a direktíva aktív, akkor a program abszolút kódot generál, azaz rekurzív hívás nem használható. A direktíva passzív állapotában használhatunk rekurzív hívást. Az alapértelmezés szerinti érték az aktív állapot. Ha tehát rekurzív programot akarunk írni, a program elején adjuk ki a
   {$A-}
direktívát. Készüljünk fel arra, hogy ilyenkor a program kódja valamivel hosszabb lesz, a végrehajtás pedig lassabb. Eltérés lesz a verem használatában is, a rekurzív program sokkal jobban igénybe veszi a vermet.
Akit érdekel ennek mechanizmusa, elmondhatjuk, hogy mindaddig, amíg a programban nincs rekurzív hívás, az eljárások és függvények saját, belső változóit tulajdonképpen bárhová teheti a fordítóprogram, akár statikusan is kijelölheti azok helyét ugyanügy, ahogy a (fő)program - globális - változóival teszi. Ezek címzése viszonylag egyszerű és gyors. Ha azonban rekurzív hívást akarunk végrehajtani, a lokális változókat csak a veremben foglalhatjuk le. Csak így érhető el, hogy minden egyes hívásnál a változókból egy új készlet jöjjön létre, és a visszatéréskor ezek a változók megszűnjenek létezni. Ekkor azonban ezeket a változókat a processzor csak relatív címzéssel érheti el, ez egy kicsit tovább tart.
Ha már szóba került a tárkezelés rekurzió esetén, újra szólnunk kell arról, hogy a hívások egymásba ágyazásának van egy fizikai korlátja, mégpedig a verem számára rendelkezésre álló memória. Ha ugyanis túl sok hívást ágyazunk egymásba, a verem számára kijelölt memória túlcsordul, és ez a program elszállását okozhatja. Ha ez a veszély fenyeget, használjuk a verem ellenőrzését előíró K fordító direktívát (lásd később).

A B direktíva: logikai eszköz kijelölése
A B direktíva a standard bevitelhez és kivitelhez hozzárendelt eszközt választja ki. Alapértelmezés szerinti, azaz aktív állapotában a CON: logikai eszközt, passzív állapotában a TRM: logikai eszközt választja ki. Talán még emlékszünk rá, mindkettő ugyanazt a fizikai eszközt, a képernyő-billentyűzet kombinációt jelenti. A különbség az, hogy a CON: logikai eszköz pufferelt bemenetet ad, azaz a beírt információt szerkeszthetjük, törölhetjük mindaddig, amíg az ENTER billentyűvel le nem zártuk; a TRM: logikai eszköz viszont a leütött billentyű kódját azonnal továbbítja a programnak. Ha tehát mi magunk akarjuk a bevitelt irányítani a standard input-output fájlon, ki kell adnunk a
   {$B-}
direktívát (a direktíva globális).

A C direktíva a Ctrl-C és a Ctrl-S billentyűkombináció hatását szabályozza.
Mint tudjuk, a, CP/M operációs rendszerben - és így az IS-DOS-ban is - a Ctrl-C billentyűkombinációval általában leállítható - megszakítható - a program, a Ctrl-S kombinációval pedig a túl gyors vagy túl hosszú képernyőkiírás állítható meg és indítható le újra. A Turbo-Pascalra ez úgy érvényes, hogy a program megszakítása READ illetve READLN utasítás végrehajtása közben történhet meg. Ha a C direktíva aktív (és ez az alapértelmezés szerinti állapot), a leírt két funkció működik; a direktíva passzív állapotában nem. Ha azt akarjuk, hogy a programunkat ne lehessen ilyen módon leállítani, adjuk ki a - globális
   {$C-}
direktívát. Ezt azonban csak a hibátlan, belőtt programmal tegyük meg, különben saját magunkkal is kitolhatunk.

Az I direktíva: hibakezelés, beszúrt fájlok
Az I direktíva kétféle célra is használható. Egyik esetben a beviteli-kiviteli hibakezelés vezérlésére használhatjuk. Ilyenkor a direktíva kapcsoló jellegű. Alapértelmezés szerint, azaz bekapcsolt állapotában a program maga ellenőrzi a bevitel-kivitel helyességét. Ilyenkor tehát hiba esetén a program futása félbeszakad, és kapunk egy hibaüzenetet a hiba - számmal jelölt - kódjával.
Saját célra így is megfelel a program, ha azonban másokat is szerencséltetni akarunk a művünk feletti gyönyör érzésében, az összes lehetséges, előre látható hibát a programban le kell kezelnünk. Ezt teszi lehetővé az I direktíva kikapcsolt állapota, amikor is hiba esetén a program futása folytatódik, mintha mi sem történt volna. Valami azért történik: az IORESULT függvény meghívásával mindig ellenőrizhetjük, hogy nem volt-e hiba az utoljára használt beviteli vagy kiviteli utasítás végrehajtása során. Idézzük fel: az IORESULT függvény értéke 0, ha a művelet hibátlan volt, és ettől eltérő értéket ad vissza, ha valamilyen hiba volt.
Mindezekből az következik, hogy egy jól megírt programban egy fájl-másolás kezdete valahogy így fog kinézni:

VAR
  FF, { forrás-fájl }
  CF {cél-fájl}
     :FILE;
  FFN, { fájlnevek }
  CFN
     :STRING[14];
...
BEGIN
  WRITE( 'Kerem a forras-fajl nevet:');
  READLN(FFN);
  ASSIGN(FF,FFN);
  {$I-} RESET(FF); {$I+} {Itt vesszük kézbe a sorsunkat}
  IF IORESULT<>0 THEN BEGIN
     WRITE('Sajnos, nem tudom megnyitni a fajlt!');
     READLN;
     WRITELN('Vege...' );
     HALT;
  END {IF};
  ...
END.

A fájl-megnyitás idejére "átvesszük" a hibakezelést a Pascal rendszertől saját kezünkbe. Ugyanezt kell tenni cél-fájl esetében is, és legalább ez utóbbinak a lezárásakor. Ha következetesek vagyunk, akkor a beolvasást és a kiírást is ugyanígy lekezeljük, hiszen bármikor előfordulhat egy lemezhiba, vagy csak a szabad hely fogy el a lemezen. Arra ügyeljünk, hogy az IORESULT függvény értéke a hívás után mindig nullára áll, azaz nem lehet a hibakódot így felderíteni:

IF IORESULT<>0 THEN BEGIN
   WRITELN('*** Hiba! A hibakod: ',IORESULT:4);
END { IF };

ugyanis ekkor mindig nullát kapunk. A helyes megoldás:

VAR IORES:INTEGER; {Ideiglenes változó a hibakódnak}
...
   {$I-} {Itt van a kritikus művelet} {$I+}
   IORES:=IORESULT;
   IF IORES<>0 THEN BEGIN
      WRITELN('*** Hiba! A hibakod: ',IORES:4);
   END {IF};

Az I direktíva másik alkalmazása az ú.n. include fájl, azaz beszúrt fájl megadása. Ha olyan hosszú a forrásszöveg, hogy a Turbo-Pascal már nem tudja kezelni, szétszabdalhatjuk több önálló darabra, ekkor mindig csak egy fájl van a memóriában. Egy másik lehetőség, hogy bizonyos feladatokhoz kész eljárás és függvénykönyvtárral rendelkezünk, és ezt érintetlenül akarjuk a programunkhoz fűzni. A programunkhoz változatlanul hozzáfordítandó forrásszöveg az inlcude-fájl. A fordítóprogram ott fogja az include-fájlt beszerkeszteni a programunkba, ahol az I direktíva áll, Legyen a könyvtár neve LIBRARY.LIB, a program már elkészült és tesztelt másik részének pedig PROGI.PAS; ekkor így fűzhetjük össze a három részt a fordítás idejére:

PROGRAM PROG;
{Itt állhatnak a saját deklarációk}
{$I LIBRARY.LIB}
{$I PROG1 }
{ Itt következik a most szerkesztett programszöveg }

A programrészek ilyen módon végzett beszúrását segíti a Turbo-Pascalnak az a könnyítése az eredeti Pascal specifikációhoz képest, hogy a konstans-, típus-, változó-, eljárás- és függvénydeklarációk tetszőleges sorrendben, keverve is állhatnak a programban. Ez azt jelenti, hogy a beszúrt fájlok minden korlátozás nélkül deklarálhatják saját adat- és programszerkezeteiket.

A K direktíva: a verem ellenőrzése
A K direktíva, mint korábban utaltunk rá, a verem ellenőrzését írja elő. Az alapértelmezés szerinti, aktív állapotban a program minden hívás előtt ellenőrzi, hogy van-e elég hely a veremben a hívott eljárás vagy függvény lokális változói számára. Ha kevés a hely, a hibajelzést kapunk és a program leáll. A direktíva passzív állapotában nincs ellenőrzés. Ha tehát már belőttük a programot, és vagy nem használunk rekurzív hívást, vagy pedig biztosan tudjuk, hogy nem fogjuk túlterhelni a vermet, kikapcsolhatjuk az ellenőrzést a
   {$K-}
direktíva megadásával; a program futása így valamivel gyorsabb lesz.

Az R direktíva: az érvényességi tartományok ellenőrzése
Az R direktíva az indexek érvényességi tartományának, valamint a skalár- és résztartomány típus értelmezési tartományának ellenőrzését engedélyezi. Alapértelmezésben a direktíva passzív, azaz ilyenkor programhiba esetén "túlindexelhetünk" a tömb határain. Ha csak olvassuk a tömböt, ez nem okoz túl nagy problémát (leszámítva, persze, azt a kellemetlen élményt, hogy a program hibás eredményt ad). Ha azonban írunk is a tömbbe, elronthatjuk létfontosságú változók értékét, sót akár a program kódrészébe vagy valamilyen rendszerterületre piszkíthatunk, ekkor a program nagy valószínűséggel elszáll. A résztartomány vagy felsorolási típus esetén ez a súlyosabb hiba nem fenyeget, de részben elveszítjük az ilyen változók használatát indokoló előnyöket. A program belövésének idejére tehát érdemes a direktívát bekapcsolni:
   {$R+}
Ha már fut a program, és nem várható, hogy a felhasználó a, saját adataival esetleg újra kiakaszthatja (a bevitt adatok érvényességét, ha az kritikus, amúgy is mindig ellenőrizni kell), akkor kikapcsolhatjuk a direktívát.

Az U direktíva: életveszély!
Az U direktíva rendeltetése az volna, hogy engedélyezze a program megszakítását bárhol (azaz nem csak READ és READLN utasítás végrehajtásakor; lásd a C direktívát). Hasznos segítséget jelenthetne programbelövéskor egy véletlenül beépített végtelen - vagy csak nagyon hosszú - hurok esetén. Sajnos, a direktíva bekapcsolt állapotában az általunk használt TurboPascal (3.01-es verzió) Enterprise-on elszáll.

A V direktíva: a string-paraméterek típusellenőrzése
A V direktíva a string változó-paraméter típusellenőrzését végzi. A direktíva alapértelmezés szerinti, aktív állapotában az eljárásnak vagy függvénynek paraméterként átadott string és a formális paraméter hosszának meg kell egyeznie. A direktíva passzív állapotában nincs ellenőrzés, ekkor eltérő hosszúságú paraméter is átadható. Mivel általában ez a kedvezőbb állapot, tiltsuk meg a típusellenőrzést:
   {$V-}

A W direktíva: a WITH utasítások mélysége
A W direktíva a WITH utasítások egymásba skatulyázásának maximális mélységét adja meg. Alapértelmezésben ez az érték 2; a direktívával a mélységet 1 és 9 között állíthatjuk be.
A WITH utasítás ismertetését annak idején elmulasztottuk; így azt most röviden pótoljuk. A rekord típusú változók használatakor, mint tudjuk, az egyes elemekre hivatkozáskor a rekord nevét is meg kell adni. Ez sokszor kényelmetlen, különösen, ha többszörösen egymásba skatulyázott rekordokról van szó, és feleslegesnek tűnik, ha például ugyanannak a rekordnak sok-sok elemét kell felsorolni. Hogy ez a dolog elkerülhető legyen, az érintett utasításokat egy ú.n. WITH blokkba zárhatjuk, és ekkor a blokkon belül a rekord nevét nem kell megadni, csak az egyes elemekét. Az alábbi deklaráció esetén:

VAR
   REKORD:RECORD OF
      A,B,C:INTEGER;
   END { RECORD };

így néz ki egy értékadás a WITH utasítás nélkül:

REKORD.A:=REKORD.B+ REKORD.C

és így, ha használjuk a WITH utasítást:

WITH REKORD DO BEGIN
   A:=B+C;
END { WITH };

Az X direktíva: a tömbök optimalizálása
Az X direktíva alapértelmezés szerinti, aktív állapotában a tömbkezelés futási idő szerinti optimalizálását váltja ki, míg passzív állapotában a fordítóprogram a memóriafoglalást igyekszik a minimumra szorítani.

-UL-

Vissza az Újságokhoz
Viszsa a felhasználói programokhoz