A BASIC nyelvű programozás alapjai Kedves Lóri! Ebben az irományban megpróbálok neked némi segítséget nyújtani abban, hogy el tudd kezdeni a programozást. Előrebocsátom, hogy ez a dolog egyáltalán nem könnyű, így eleinte túlságosan gyors előrehaladásra nem lehet számítani. Az olyan okosan kialakított programnyelvek, mint amilyen a Rapid-Q Basic, nagyon sok dolgot megkönnyítenek a programozó számára, de ezek a könnyítések csak akkor használhatók ki, ha az ember már tisztában van az alapokkal. Nagyon egyszerű például megterveznünk azt az ablakot, amelyben a programunk fut majd, de magát a programot nekünk kell megírni a működtető elemek „mögé”. Könnyen, egy-két kattintással helyezhetünk el az ablakban mindenféle hasznos elemet: képmezőt, gombokat, szövegbeolvasó ablakocskákat, stb. – de amikor a programot lefordítjuk, ezek az elemek csak ott vannak, maguktól semmit sem fognak csinálni. Az ablakocskákba csakugyan írhatunk szövegeket, a gombokat nyomkodhatjuk, de ha nem közöltük a programmal, hogy mit kezdjen a szöveggel, mit csináljon gombnyomás esetén, magától sohasem fog rájönni. Ezért nem is ugrom rögtön neki a Rapid-Q Basicnek, hanem egy hagyományosabb BASIC-változattal indítok. Az átküldött fájlok között mellékelem ezt a „gyakorlónyelvet” is. Némi történeti áttekintés (Unalmas, de nem bánnám, ha elolvasnád) Mielőtt azonban bármiféle nyelvbe belekapnék, jó, ha tisztázunk bizonyos alapfogalmakat, amelyek a programnyelvektől függetlenül szükségesek a számítógép működésének megértéséhez. A valódi alapokkal, nevezetesen, hogy ez a rakás alkatrész hogyan tudja megcsinálni mindazt, amit majd elmondok róla, nem foglalkozom, sok esetben magam sem tudom, a programozás szempontjából nem is érdekes. Elsőként szokás megemlíteni, hogy a gép, legalábbis emberi értelemben, nem ért semmit a feladatból. A legfontosabb része – a processzor, vagyis a műveletvégző egység – egy bonyolult szerkezet, amely sokféle egészen egyszerű feladat elvégzésére képes. Azt, hogy mit csináljon, úgy határozhatjuk meg, hogy elküldünk neki egy számot. Ha ez a szám mondjuk a 283, akkor a processzor a számtalan lehetőség közül a kettőszáznyolvanharmadik műveletet végzi el. Gyakran kábítják a hozzá nem értőket azzal, hogy a számítógép tulajdonképpen csak kétféle számot ismer (a jelöléstől függően 0 vagy 1, igaz vagy hamis, stb.), és ebből épül fel az egész rendszer, de ez csak félig igaz. Azon például senki sem csodálkozik, hogy mindössze tíz számjeggyel be lehet számozni egy hosszú utcát: Igaz ugyan, hogy csak a 0,1,2,3,4,5,6,7,8,9 számjegyet használjuk, de amikor ezek elfogynak, nem áll meg a tudomány, hanem ezeket többször írjuk egymás mellé, keverjük, így akármilyen nagy számokat ábrázolhatunk velük. A kettes számrendszer csak két számjegyet ismer, de ugyanúgy használhat sok helyiértéket, mint a tízes, így semmi sem akadályozza a nagy számok használatát. A processzor tehát nem 0 vagy 1 értéket kap, hanem egyszerre egy nagy, nullákból és egyesekből álló számot, így sokezer utasítás közül lehet választani. Mit jelent az, hogy ezeket a számokat elküldjük a processzornak? Rendszerint azt, hogy a számokat a számítógép valamely erre alkalmas helyén tároljuk, és valami módon közöljük a géppel, hogy az értékeket éppen onnan töltse be a processzorba. A processzor azután végrehajtja a kapott utasítást, azután beolvassa a következőt, azt is elvégzi, és így tovább. Így, a sok egyszerű kis utasításból bonyolult, összetett műveletsorok állíthatók össze, amelyek együtt már komolyabb feladatot is elvégezhetnek. Az ilyen műveletsorokat, illetve az azokból összefűzött, még bonyolultabb rendszereket nevezzük programnak. Persze, a dolog nem mehet a végtelenségig, valahol minden programnak vége kell, hogy legyen, ennek jelzésére is vannak megfelelő utasítások. Mi történik, ha egy program véget ér? Ugyebár, tapasztalatból tudjuk, hogy a gép nem áll le, hanem várakozik, és új programokat indíthatunk. Eltekintve attól, hogy az egésznek a megvalósítása a számunkra elképesztően bonyolult, az alapja jól elképzelhető: Van egy hatalmas program, amely a számítógép bekapcsolásakor elindul, és utána körbe-körbe jár. Amíg megy, sorban ellenőrzi, hogy talál-e elvégezhető feladatot – például leütöttek-e egy billentyűt, vagy a CD-lejátszó jelzi-e, hogy beleraktak egy lemezt –, és ha igen, elindít egy olyan programot, amely a feladatot megoldja. Ha az
ellenőrzés végére ér, kezdi az egészet elölről, mindaddig, amíg egy általunk megadott jel át nem irányítja a befejezésre. Ekkor egy újabb utasítássort kezd beolvasni, amely olyan műveletekből áll, amelyek felkészítik a gépet a kikapcsolásra. Ennek a programrészletnek a vége egy olyan szám, amelynek hatására a processzor a tényleges kikapcsolást hajtja végre: jelet küld a kapcsolónak, és az megszünteti az áramellátást. Amikor mi programot indítunk, ez a központi program az általunk megadott címről kezdi olvasni az utasításokat, és amikor a programunk végére ér, visszatér a főprogram végrehajtásához. A dolog valójában sokkal bonyolultabb, ugyanis a processzor időnként abbahagyja a mi programunk olvasását, hogy közben más fontos dolgokkal is foglalkozzon, de ez a mi szempontunkból nem érdekes, mivel magától elvégzi. Ezt a bizonyos nagy, központi programot nevezik operációs rendszernek. Manapság a legismertebb efféle a Windows, de sok egyéb is létezik, mint például a Unix (kiejtése angolosan junix, ezért csak „a” a névelő) alapú rendszerek (köztük a Linux), az AIX, a BSD, Irix, Minix, Oberon, Macintosh, DOS, ésatöbbi. Ezeknek a szervezése teljesen eltér egymástól, így ugyanazok a számsorok általában nem ugyanazt jelentik a számukra – ennek megfelelően az egyik rendszer számára megírt programok nem működnek a másikban. Fontos még, hogy a számítógép nemcsak utasításokat tárol, hanem olyan adatokat is, amelyeknek egészen más lehet a feladatuk, például képet, hangot, szöveget vagy a munkához szükséges számokat. Ezeket a gép ugyanolyan számsorok formájában tudja csak kezelni, mint az utasításokat. Hogy a kétféle dolog ne keveredjen egymással, a programnak azt is közölnie kell a processzorral, hogy a megfelelő helyen lévő számokat ne próbálja meg végrehajtani, csak használja fel a műveleteihez. Elvileg azonban rá lehet irányítani a programot mondjuk egy képet leíró adatcsoportra, ami persze rendszerint végzetes következményekkel jár: Először is, a gép elkezd értelmetlen műveleteket végrehajtani, hiszen nem valódi programrészt talál, hanem olyan számokat, amelyek véletlenül megegyeznek ezzel vagy azzal az utasítással. Végül azután eljut valami olyan számhoz, amelynek mondjuk az a jelentése, hogy „ugorj vissza ötöt”, akkor öttel hátrább ugorva újra olvasni kezdi, megint eljut a számig, megint visszaugrik, és így tovább, a végtelenségig... Ettől fogva a gép semmi értelmeset nem csinál, csak ezen a néhány számon lépked, így nem olvassa a billentyűket sem, tehát le sem lehet állítani. Ezt nevezik úgy, hogy a gép „lefagy”. Bonyolultabb programok esetén a programozók figyelmetlenségéből is előadódhat, hogy a vezérlés efféle nemkívánatos helyre kerül, ezért a mai rendszereket igyekeznek úgy kialakítani, hogy bizonyos jelek (például a Ctrl+Alt+Delete gombok egyszerre történő lenyomása) még az ilyen reménytelen kátyúból is kirántsák az operációs rendszert, anélkül, hogy a nagy kikapcsológombot meg kellene nyomni – az erőszakos kikapcsolás ugyanis, amikor a főprogram nem tudja elvégezni a leállítás műveletét, károsíthatja a gépet. Ha idáig eljutottál, lehet, hogy azt gondolod, hogy az egész programozás olyan zűrös, hogy nem is érdemes megpróbálni, de várj egy kicsit, jönnek a jó hírek! Az, hogy a gépnek az ember közvetlenül számokat küldözgessen, az 50-60 évvel ezelőtti ősidők módszere. Ahogy a gépek és az operációs rendszerek bonyolódtak, egyre kevésbé lehetett hibamentesen megcsinálni. Ezért a puszta számokból álló gépi kód helyett kitalálták a programnyelveket. Első volt az assembly nyelv: Minden, a processzor számára érthető számkódot egy-egy többé-kevésbé megjegyezhető angol rövidítéssel helyettesítettek, és írtak egy olyan programot (assembler), amely ezt a forráskódot vissza tudta fordítani gépi kódra, tehát a rövidítések alapján az azoknak megfelelő számot írta a program megfelelő helyére. Ez még mindig nem könnyű mesterség, hiszen a processzor utasításkészlete nem tartalmaz olyanféle összetett dolgokat, hogy „rajzolj ablakot a képernyőre”, hanem csak ilyesmiket: „olvass be egy számot” vagy „döntsd el, hogy az egyik beolvasott szám nagyobb-e, mint a másik”. Az is igaz viszont, hogy ezzel az icipici részekre bontott programnyelvvel tökéletesen pontosan meg lehet mondani a processzornak, hogy milyen utasításokat hajtson végre, így az assembly nyelvet a mai napig használják olyan esetekben, amikor fontos a hatékonyság, például nagyon gyorsan kell sok adatot feldolgozni vagy a képernyőre vinni. A legtöbb esetben azonban nem kell a folyamatot ilyen pontosan ellenőrizni. Tételezzük fel, hogy már jól kidolgozták, hogy hogyan lehet egy szöveget a képernyőre írni hatvan gépi utasítás segítségével. Ki az a bolond, aki ezt a hatvan utasítást minden alkalommal a programba akarja
gépelni, valahányszor szükség van rá? Ráadásul a programszöveg áttekinthetetlenül hosszú is lenne, jóformán javíthatatlan, ha valami mégsem stimmel. Mennyivel egyszerűbb, ha a programozónak csak annyit kell írnia: print "kakuk" vagy write('hű, de kafa!'), a program pedig szépen beírja helyette azt a bizonyos hatvan számot a megfelelő helyre! Az efféle megoldásokból jöttek létre a magas szintű programnyelvek, amelyekben a programozó a tényleges feladatra koncentrálhat, nem kell a gép számára olvasható formával foglalkoznia – azt a fordítóprogram elvégzi helyette. Ennek a megoldásnak van még egy előnye. Említettem, hogy a különféle operációs rendszerek gépi kódú utasítássorai törvényszerűen eltérnek egymástól. Ha tehát ugyanazt a feladatot egy másik rendszerben is meg kellett oldani, az egész programot újra kellett írni másképpen. Ha viszont több rendszerre is elkészítik egy magas szintű nyelv fordítóprogramját, akkor a programozónak nem kell azzal foglalkoznia, hogy éppen melyiken akarja futtatni a programját. Lefordítás után ugyan minden esetben más gépi kód kerül a számítógépre, de a programozó – maradva az előző példánál – csak mondjuk azt írja a forráskódba, hogy write('tessék parancsolni!'), amikor ezt a szöveget akarja látni a képernyőn. Amíg a számítógépek nagyok, drágák, és nehezen kezelhetők voltak, lehetett mondani, hogy a programozás módja a programozók magánügye. Amikor Laci nagypapa a Tákiban dolgozott, az ottani számítógép egy egész szobát elfoglalt, minden indításkor végig kellett ellenőrizni, mert tele volt villanykörtékre emlékeztető elektroncsövekkel, amelyek könnyen kiégtek. Ennek megfelelően a működtetése is olyan drága volt, hogy külön össze kellett gyűjteni a sok számolást igénylő fontos feladatokat, szakemberek által lefordítani, és egyszerre elvégeztetni, amikor végre eljött a beindítás ideje. Azonban a gépek gyorsan fejlődtek, kicsik, biztos működésűek és olcsóbbak lettek, így egyre több olyan ember is hozzájuk férhetett, aki nem számítástechnikus volt ugyan, de a munkájában hasznukat látta. Aki segítséget vár a géptől, joggal bosszankodik, ha az nem hajlandó működni olyasmik miatt, mint hogy véletlenül kisbetűvel írt valamit a programszövegben, vagy elfelejtett külön helyet készíteni valamelyik számnak, amelyet használni akar. Ezért a programozás többek között a nem szakértők számára is érthető programnyelvek irányában fejlődött tovább. Ezek próbáltak a természetes angol nyelvhez igazodni, és minél kevesebb szigorú szabályt alkalmazni, ha az a feladat szempontjából szükségtelen volt. Az egyik ilyen programnyelv a BASIC. A név egy hosszabb angol kifejezés rövidítése (nem emlékszem rá, hogy mi is az), egyszerűen kiolvasva viszont „alapszintű” a jelentése. Az olcsóbb számítógépekbe rendszerint be is építették valamelyik változatát, és ez nagyban hozzájárult a számítástechnika népszerűségéhez. Könnyen tanulható, az egyszerűbb feladatok jól megoldhatók benne, ezért a mai napig általában ez az a nyelv, amellyel a programozást elkezdik az érdeklődők. A dolognak van egy kis szépséghibája. Amikor a BASIC megszületett, a számítógépek korántsem voltak olyan okosak, mint manapság. Bekapcsoláskor általában egy fekete képernyő jelent meg, villogó fehér kurzorral, és már lehetett is szöveget beírni – közvetlen parancsokat, programkódot, akár írógép helyett is használható volt. Mindenféle hasznos programokat is indíthatott a tulajdonos, amelyek bonyolult számítások elvégzésében vagy egyébben segítették. Arra azonban álmában sem gondolt senki, hogy egy-két évtized múlva a zenehallgatástól a filmvetítésen át a könyvtervezésig az égegyadta világon mindenre használni fogják... Csakhamar azonban szükségessé vált, hogy legalább egyszerűbb ábrák rajzolására alkalmas legyen, azután az Apple-Macintosh cég kitalálta az ablakozó rendszert, amelyben egy képernyőn több program is futtathatóvá vált. Ezt azután a híresneves Bill Gates nem egészen tiszta módon lekoppintotta, és Microsoft nevű vállalkozásán keresztül az egész világon elterjesztette – a ma már nem egészen ismeretlen Windows néven. A BASIC követte a fejlődést, de a sokféle gép- és monitortípusnak megfelelően az eredeti, viszonylag egységes programnyelv számtalan változatra, „tájszólásra” bomlott. A nyelv alapjai általában megmaradtak, de az újabb igények kielégítésére sokféle eltérő megoldás született. A Windows kezdetben a DOS operációs rendszer alprogramja volt, így aki BASIC-ben programozott, megtehette, hogy a Windows elindítása nélkül, a szokott betűs képernyőn írhatta meg a programját. A fejlődés azonban úgy hozta, hogy a rendszert „kifordították”: Ma a Windows az alaprendszer, amely a régi programok futtatása érdekében még képes a DOS-képernyőhöz hasonló ablakot nyitni. Ezért a hagyományos BASIC-programozással való ismerkedés bonyodalmasabb.
Bevezetés A Windows-programozás látható része a képernyőre rajzolandó elemek kezelése. A valódi működtető rész azonban maga a programnyelv, amelynek megismerésekor az úgynevezett grafikus felület csak zavaró tényező. Először tehát hasznosabbnak látom, ha egy olyan basic-nyelvet mutatok be, amely a szokott képernyőelemeket nem használja. Ennek a nyelvnek csak egy részével foglalkozom, azzal, amelyik vagy még a mostani programozásban is érvényes, vagy divatjamúlt ugyan, de a régies programhoz elengedhetetlen. Csak mellékesen jegyzem meg, hogy bár a BASIC voltaképpen kezdőbetűkből összerakott mozaikszó, manapság már senki nem törődik az eredeti jelentésével, ezért egyszerűen kisbetűvel szokás írni – a továbbiakban én is így használom. Interpreterek és compilerek A sokféle programnyelv egyik lehetséges csoportosítása az interpreter és a compiler típusú nyelvekre való felosztás. Az interpreter-nyelvek esetén tulajdonképpen a nyelv lefordítására alkalmas programot futtatjuk, és abba töltjük be a programszöveget. Ezek után a program beolvassa a programsorokat, a maga számára lefordítja, és végrehajtja a kapott utasításokat. A compilerek a program megírása után úgy fordítanak, hogy létrehozzák a gépet közvetlenül működtető kódot, és ez a továbbiakban már önállóan futtatható, attól függetlenül, hogy a létrehozó program jelen van-e a gépen. A két típus nem mindig válik el élesen egymástól: Van olyan fordítóprogram, amely kipróbáláskor a saját rendszerén belül futtatja a programunkat, de amikor már elégedettek vagyunk a működésével, önálló kódot is készíttethetünk vele. Az interpreterek tehát egyrészt lassúbbak, hiszen menet közben fordítanak, másrészt igénylik, hogy a futtatáskor jelen legyen az általában tekintélyes méretű fordítóprogram. Ezért én például jobban szeretem a compilereket, hiszen a kész program gyors, és nem kell a futtatásához egyéb segítség. Az a helyzet azonban, hogy az egyre gyorsabb gépek megjelenésével és a tárolóhely olcsóbbá válásával az interpreterek újra divatba jöttek: A viszonylagos lassúság nem jelent igazi hátrányt, hiszen így is milliószor gyorsabb, mint az ember, és ugyanaz a programszöveg többféle operációs rendszeren is futtatható. Igaz ugyan, hogy a fordítóprogram nagy méretű, de cserébe a programszövegek sokkal kisebb helyet foglalnak el, mint egy önálló program – annak ugyanis tartalmaznia kell a teljes működtető részt. Ha száz programot készítünk, akkor százszor, míg az interpreterbe olvasandó programok mind ugyanazt az egy programot használják. Efféle modern interpreterek például a Java vagy a Python programnyelv, amelyek amúgy sem árt, ha a gépünkön vannak, hiszen sok profi program is ki tudja aknázni a jelenlétüket. A Rapid-Q Basic compiler típusú, azonban a tanuláshoz hasznosabb egy olyan interpreter, amelyet a DOS-ablakban úgy tudunk futtatni, hogy a programunk lefutása után az ablak nem záródik be azonnal, hanem nyitva marad, így szemügyre vehetjük az eredményt. Ezért telepítünk egy basic interpretert. Ha ez már a gépünkön van, a futtatásával belépünk a basic-rendszerbe. A Blassic interpreter telepítése Első dolgunk tehát, hogy telepítsük a basic-interpretert. Annak ellenére, hogy az imént jajgattam az interpreterek nagy mérete miatt (a Pythonra, Javára ez bizony igaz), a közönséges, szöveges basic esetén nincs okunk panaszra. Az általam kiválasztott Blassic alig több, mint 1 megabájt. A Blassic név a „basic classic” vagyis klasszikus basic kifejezés összevonása. Amikor kipróbáltam, egészen meghatódtam a régi emlékektől, olyan bámulatosan utánozza a hajdani kisgépek basicjét. A „telepítése” több, mint egyszerű: Készítesz egy mappát a számára, azután belemásolod a ZIPfájlban található egyetlen, blassic.exe nevű fájlt. A futtatására a legegyszerűbb módszer, ha küldesz egy parancsikont az asztalra (Jobb egérgomb => Küldés => Asztal(parancsikon létrehozása)), és a továbbiakban onnét indítod. A megírandó programok majd ugyanabba a mappába kerülnek, mellesleg ez is jól felidézi a régi gépek világát, amikor még nem volt mindenféle bonyolult könyvtárrendszer, egyszerre úgyis csak egy program lehetett a gépben.
Az első program Elindítjuk a Blassic programot. A képernyőn megjelenik a DOS-ablak, amelyet ma inkább konzol névvel illetnek, és a program várja, hogy begépeljünk valamit, amit végre tud hajtani. Írd be a basic egyik leggyakoribb utasítását: PRINT. (Említettem, hogy a nyelv ezen a téren nagyon engedékeny, így mindegy, hogy kis- vagy nagybetűvel írod, én csak azért nagybetűsítem, hogy jól felismerhető legyen a programszöveg.) Amikor a sor végén megnyomod az Enter gombot, az interpreter nyomban végre is hajtja a műveletet. Látszólag azonban nem történt semmi, csak egy sorral lejjebb kerültünk, és egy Ok üzenet jelzi, hogy minden rendben, ezért a dolog némi magyarázatra szorul. A PRINT utasítás azt jelenti: nyomtass, tehát a basic kinyomtatja a szöveget, amelyet kértünk tőle, egyéb pontosítás nélkül egyszerűen a képernyőre. Mit is kértünk? Semmit! Az üres PRINT parancs tehát a nagy semmit írja ki, azután annak rendje-módja szerint lezárja a sort, ugrik a következőre, és várja az újabb teendőket. Ha valamit tényleg ki akarunk íratni a képernyőre, akkor közölnünk kell, hogy mi legyen az, tehát adatot kell átadnunk a programunknak. Föntebb említettem, hogy a gépnek voltaképpen mindegy, hogy mit irkálunk bele, mindent számok formájában tárol, akkor is, ha mi betűket vagy képet látunk. Emiatt az esetünkben külön jeleznünk kell, hogy a PRINT utasítás után nem egyéb, mint egy meghatározott betűsor következik. Ezt a basicben úgy oldjuk meg, hogy a szöveget két felső idézőjel közé fogjuk: PRINT "galagonya" Ha ezt a sort beírod, és utána megnyomod az Entert, a képernyőn csakugyan megjelenik a galagonya felirat. Ezen a ponton illendő gratulálnom, ugyanis megírtad az első olyan működőképes programodat, amely csinál is valami látható dolgot. Na jó, a dolog még nem mérhető Lara Croft kalandozásaihoz, de egy régi kínai közmondás szerint az ezer mérföldes út is egyetlen kicsi lépéssel kezdődik... Lépjünk még egyet. Az iménti sort nagyvonalúan programnak tituláltam, de ez valójában pontatlan meghatározás. Igaz, a program akárhány sorból állhat, tehát miért is ne lehetne egyetlen az az akárhány? Az ilyen azonnal végrehajtandó és elfelejtendő sorokat azonban parancsoknak nevezik. Ahhoz, hogy kiérdemeljék a program nevet, az kell, hogy a gép meg is jegyezze őket, és kérésre újra meg újra el tudja végezni a kért feladatot. A hagyományos basic nyelvben a valódi programsorokat vagyis utasításokat az különbözteti meg a rögtön végrehajtandó parancsoktól, hogy sorszámmal kezdődnek, így: 10 PRINT "galagonya" Írd csak be! Az Enter leütése után máris fölfigyelhetsz a különbségre: A basic nem írta ki a galagonyát, csak az Ok-t. A magyarázat mindjárt következik, csak előbb szót ejtek a sorszámokról. A számozás tetszés szerinti értékről indulhat, és nem kell, hogy egyesével kövessék egymást a számok. Sőt, a régi típusú basicekben ajánlatos jó nagy különbséget hagyni a számok között: Ez biztosítja, hogy a program fejlesztése során újabb sorokat tudjunk beszúrni a már meglévők közé. Később megoldották, hogy a program utólag átszámozható legyen, ha már nem fér be több sor a rosszul megválasztott sorszámok közé, és a modern változatokban már egyáltalán nincs szükség sorszámokra, de a Blassic még ragaszkodik a hagyományhoz. A sorszámmal jeleztük az interpreternek, hogy most nem parancsról, hanem programsorról van szó. Ezért azután nem is hajtotta végre, hanem csak megjegyezte. Futtatni csak akkor fogja, amikor majd megkérjük rá. Mindezt akár becsszóra is elhihetnéd, de nem árt, ha a tanulóban van egy kis egészséges kétkedés. Később ráadásul még bonyolódhat a program, és mondjuk 5 sor után ki a csuda emlékszik rá, hogy minek is kéne pontosan a gépben lennie? Nem ártana megnézni, hogy tényleg ott van-e valahol az állítólagos program... Ezt a LIST paranccsal (listázz) teheted meg. Ha beírod, hogy LIST, a basic a sorok számozása szerint kiírja a program szövegét, esetünkben az egyetlen 10 PRINT "galagonya" sort. Minthogy tudjuk, hogy ez a sor tulajdonképpen egy működő programszöveg, akár ki is próbálhatjuk, de hogyan? Erre való a RUN (fuss) parancs. Írd be, és Enter után megjelenik a galagonya. Most azonban a basic nem felejti el, mint az imént, hanem akárhányszor beírod a RUN-t, mindig elő tudja szedni a PRINT "galagonya" utasítást, hogy végrehajtsa.
Építsük tovább a programot: Most már egy helyett több utasítássort helyezzünk el benne: 20 PRINT "kakuk" 30 PRINT "kalap" 40 PRINT "ragacs" 25 PRINT "szerda" Két dologra hívnám fel a figyelmedet. Az egyik, hogy tartózkodtam az ékezetek használatától. Bizonyos okok miatt az ékezetes betűk várhatólag nem úgy jelennek meg, ahogyan beírjuk őket. Te ettől függetlenül nyugodtan kipróbálhatod, semmi baj nem származik belőle. A másik dolog, hogy az utolsó sorba 25-ös sorszámot írtam, mintha utólag jutott volna eszembe, hogy ezt a sort a 20-as és a 30-as közé szúrjam be. Természetesen egy ilyen rövid programot akár teljesen átírhattam volna a megfelelő sorrend érdekében, de jóval hosszabb forráskód esetében ez már nem menne egykönnyen. Listázzuk ki a programot: LIST 10 PRINT "galagonya" 20 PRINT "kakuk" 25 PRINT "szerda" 30 PRINT "kalap" 40 PRINT "ragacs" Ok A program tehát emelkedő számsorrendbe rendezte a sorokat, és tud az előzőleg beírt 10-es számú sorról is. Futtatás: RUN galagonya kakuk szerda kalap ragacs Ok Ez idáig nagyszerű. Van azonban egy bökkenő, ami idáig nem zavart bennünket. Az interpreter megjegyezte ugyan a programunkat, de ez mit jelent? Ha most bezárjuk, akkor mindent elfelejt, és legközelebb kezdhetjük a gépelést elölről? A dolog bizony így áll. A program most a gép memóriájában van, és törlődik, amint az interpretert kikapcsoljuk. Ahhoz, hogy később is használni tudjuk, valami maradandóbb tárolásra van szükség, például a merevlemezre kell mentenünk. Ez a SAVE (mentsd) parancssal végezhető el. Írd be a következőt: SAVE "szavak.bas" Mint látható, ennek a parancsnak szintén egy szöveget (a számítástechnikában inkább úgy mondják, hogy karakterláncot vagy karakterfüzért) adtunk át, ez határozza meg, hogy mi legyen az elmentendő fájl neve. A régi számítógépeken a fájlnév legföljebb nyolc karakter lehetett, és nem tartalmazhatott szóközt. Én itt tartottam magam ehhez a szabályhoz, de a Blassic már nem követeli meg. A .bas a basic-programok hagyományos kiterjesztése, de ehelyett is lehet akármit írni. Az ékezetek viszont itt is problémásak, úgyhogy azoktól érdemes őrizkedni. Enter megnyomása után az adatok a lemezre íródnak, tehát nem vesznek el a számunkra. Legközelebb a LOAD (töltsd) utasítással olvashatjuk be a programot: LOAD "szavak.bas" Érdekesség, hogy a programszöveg megírható a Jegyzettömbbel is, a Blassic úgy is be tudja olvasni. Ha viszont a Blassicből mentett fájlt nézed meg a Jegyzettömbbel, láthatod, hogy az nem ugyanolyan formában ment, emiatt azután már nehézkes lenne kívülről továbbfejleszteni. Ez annak köszönhető, hogy a Blassic a saját maga számára legkényelmesebb formába rendezve menti el az adatokat. Az ismertetőjét lapozgatva azonban megtudható, hogy ha a mentősor végére egy vesszővel leválasztott A betűt írunk, az arra utasítja az interpretert, hogy közönséges szövegként mentsen. Így már minden jól látszik, remekül használható a kényelmesebb Jegyzettömb:
SAVE "szavak.bas", A Búcsúzóul érdemes megemlíteni, hogy az interpreter nem hagyja magát csak úgy bezárni: Windows-hibaüzenet jelzi, hogy még futna tovább. Természetesen választhatjuk az Erőszakos bezárás lehetőségét, de a tisztességesebb megoldás, ahogy látom, a következő: Az interpreterbe beírod az STOP parancsot (vége), utána pedig az EXIT parancsot (kilépés), mindkettőt Enterrel lezárva. Ez normálisan befejezi a program futását. Megjegyzés: A fenti program az úgynevezett „Helló, világ!” programocskának egy változata. A programozásban kialakult játékos hagyomány szerint egy programnyelvvel való ismerkedéskor első próbaként a „Hello word!” szöveget kiíratni a képernyőre. Gondolom, ezt már a segítségem nélkül is meg tudod tenni... Újabb adalékok Tekintsük át, hogy eddig mit tudunk. Először is megtanultál néhány parancsot, amely a basicprogram általános működtetéséhez szükséges: A begépelt programot ki tudod listázni (LIST), el tudod indítani (RUN), elraktározhatod (SAVE), és újra betöltheted (LOAD). Tudod, hogy az utasítások sorrendjét a sor elejére írt sorszámmal határozhatod meg, illetve, hogy a sorszám közli a basic-interpreterrel, hogy egyáltalán programsorról, nem pedig azonnal végrehajtandó parancsról van szó. Másodszor: Megírtál egy kis programot, amely szavakat ír a képernyőre. Erre a PRINT utasítást használtad, amelynek idézőjelek közé fogott karakterláncot adtál át adatként. Mehetünk tovább. A program fejlesztése állandó változtatgatást jelent. Ezt külső szerkesztőben, például a Jegyzettömbben könnyű megtenni, de láttuk, hogy a Blassic által mentett szöveg ott már nemigen módosítható, például azért, mert nem látszanak a sorszámok. Bemutattam, hogy a kellően ritkán számozott sorok közé milyen egyszerűen szúrhatunk be újabbakat, közéjük eső sorszámmal. De mi történjen, ha egy már meglévő sort szeretnél módosítani? Mondjuk, ha a „galagonya” helyett a „vadkan” szót íratnád ki? Írd be: 10 PRINT "vadkan" Listázás után láthatóvá válik, hogy az azonos számmal beírt új sor felülírta a régit. Megeshet viszont, hogy a sor teljes egészében fölöslegessé válik, törölni szeretnéd. Ekkor csak ennyit írj: 10 Az egyéb szöveg nélkül megadott sorszám törli az adott sort, tehát a programban nem fognak üres sorok kuksolni, hanem csakis azokat tekinti létezőnek, amelyek valamit tartalmaznak. Ez hasznos, viszont egy egyelőre még nem feltűnő kellemetlenségre vezet. Amikor már alaposan felhizlaltunk egy programot, a mázsaszámra begépelt szöveg egyre áttekinthetetlenebbé válik. Szükségünk lesz arra, hogy részekre tagoljuk, így jobban elkülönülnek egymástól a program különböző feladatai. Továbbá, amíg dolgozunk, nagyjából tudjuk, hogy mire való a programunk, de fél év múlva már vakarhatjuk a fejünket, hogy mi is ez a nagy kupac művelet voltaképpen. Ezt valahogyan fel kellene tüntetni a programban, de ha csak úgy beírunk valamit, az interpreter azonnal hibát jelez. Nem csoda: Beolvassa ugyan, hogy Lóri programja, de hiába, nem tudja lefordítani, mert ilyen utasítások nem léteznek. Utasításként ugyanis nem írhatunk be bármit. A basic megalkotói a programozás céljára összeállítottak egy szókészletet, és a feladatok leírására csak ezeket a szavakat használhatjuk. Nincs ez egyébként másképpen a természetes nyelvekben sem: Akármilyen tisztán, szépen hangsúlyozva mondom, hogy „prunc”, senki sem fogja megérteni – ilyen magyar szó nem létezik. A programnyelvek persze jóval kevesebb szót használnak, így végképp nem várható, hogy minden általunk beírt dologgal kezdjenek valamit. A problémára a REM utasítás a megoldás. A REM a remark (megjegyzés) szó rövidítése, a programba írva pedig azt jelenti: „Amit ide írtam, csak megjegyzés, nem a program része, ne foglalkozz vele”. Így használható: 1 REM szokiiro program 2 REM (a megadott szavakat print utasitasokkal a kepernyore irja) 3 REM 10 PRINT "galagonya" 20 PRINT "kakuk"
24 REM a kovetkezo sort utolag szurtam be: 25 PRINT "szerda" 30 PRINT "kalap" 40 PRINT "ragacs" Futtatás: RUN galagonya kakuk szerda kalap ragacs Ok A program tehát a szokott módon lefut, anélkül, hogy törődne a számára ismeretlen szavakkal. Fölhívnám a figyelmedet a 3. sorra, amikor a REM után nem írtam semmit. Ez nem tartalmaz útmutatást, egyszerűen csak egy üres sor, amellyel jobban elkülöníthetem a programszöveg részeit. A REM szó begépelése azonban mégiscsak munka, sőt a program megértését is nehezíti, hogy elolvassuk, ezért aposztróffal helyettesíthető. Így még jobban át lehet tekinteni a szöveget: 1 ' szokiiro program 2 ' (a megadott szavakat print utasitasokkal a kepernyore irja) 3 ' 10 PRINT "galagonya" 20 PRINT "kakuk" 24 ' a kovetkezo sort utolag szurtam be: 25 PRINT "szerda" 30 PRINT "kalap" 40 PRINT "ragacs" Néhány szó a memóriáról. Az imént már szóba került a memória, de akkor még nem foglalkoztam vele, mert nem volt különösebb jelentősége. A későbbiek megértéséhez azonban szükség lesz rá, ezért most vázlatosan megmagyarázom, mi az. A szó eredetileg emlékezetet jelent, és némely hasonlóság miatt így nevezték el a számítógépnek azt a részét, amelyben ideiglenesen adatokat helyezhetünk el. Ebből a szempontból adatnak tekinthető a program is, hiszen ugyanúgy számértékek sorozata, mint minden egyéb, amivel a gép dolgozik. A memória adatainak tárolása, változtatása vagy törlése tisztán elektromos folyamatok formájában történik, mint ahogy maguk az itt kezelt adatok is villamos jelek. Ezért a memória működése sokkal gyorsabb az egyéb tárolókénál, ahol valamely maradandó változást kell előidézni a jelek elraktározásakor, és ez az áram sebességéhez mérten nagyon sok időt vesz igénybe. Azonban éppen ezért, amint az elektromosság megszűnik benne – például a gép kikapcsolásakor, vagy áramszünetkor –, a memóriában lévő minden adat eltűnik. De amikor egy programot bezárunk, általában az operációs rendszer is törli a program által elhelyezett adatokat, hogy helyet csináljon más programok adatainak. Ezért fontos tehát, hogy legkésőbb a munka végén mindenképpen elmentsük a fontos dolgokat, vagyis maradandó jelekké alakítva tároljuk, például a gép merevlemezén. A memória valódi felépítése sokféle lehet, de ezzel itt nem érdemes foglalkoznunk – nem mintha egyáltalán értenék hozzá. A számítástechnika tele van a hétköznapi elme számára felfoghatatlan dolgokkal, amelyek működésének lényegét sokkal jobban megértjük egy leegyszerűsített és hamis, de jól elképzelhető hasonlat alapján, mint az igaz, de érthetetlen magyarázatból. A memória például úgy képzelhető el szemléletesen, mint valami hatalmas iratszekrény, rengeteg beszámozott fiókkal. Minden fiókban egy szám lehet. Amikor a programnak szüksége van egy adatra, megnézi, hogy hányadik fiókból kell azt elővennie. A fiókok számozását nevezik memóriacímnek. Amikor még a régi gépeken csak egy program lehetett a memóriában, egészen pontosan lehetett tudni, hogy az melyik címen volt megtalálható, így az akkori programozásban jelentős szerepe volt a PEEK és POKE utasításoknak, amelyekkel közvetlenül
lehetett kiolvasni és beírni a memória számértékeit. Így nagyon trükkös módszerekkel lehetett beavatkozni a program lefutásába. Bonyolultabb módon ugyan, de a mai gépeken is meg lehet tudni a memóriacímeket – sőt, bizonyos célokra a Blassicban is használhatók a PEEK és POKE utasítások –, ám a mi szintünkön semmi szükségünk nem lesz ilyesmire. A második program (játék a konstansokkal) Az elmondottak mind hasznos ismeretek, de a program még valójában nem fejlődött semmit. Arról még nem esett szó, hogy mi is az a „galagonya” vagy egyéb, amit a PRINT utasítás kezel. Azt már tudod, hogy adat, mégpedig szöveges adat, más néven karakterlánc. Említsük meg, hogy azért nem betűkről szokás beszélni, mert a szöveg tartalmazhat egyebet is – számjegyeket, írásjeleket – amelyek hagyományos értelemben nem betűk, ezért a külföldi szakirodalomból átvett összefoglaló kifejezéssel karaktereknek nevezzük őket. Amikor a programot beolvassa az interpreter, keres a szövegeknek a memóriában egy alkalmas helyet, és futtatáskor az itt található adatok alapján írja a betűket a képernyőre. Nem véletlenül kerülöm azt a pongyola kifejezést, hogy „az itt található adatokat írja a képernyőre”. Mint tudjuk, a memóriában számok találhatók, nem pedig karakterek. Hogy lehet, hogy a program számokat kap, és betűket ír ki? Úgy, hogy valahol a gépen van egy táblázat, ahol egy bizonyos sorrendben föl vannak sorolva a karakterek. Ha a program „tudja”, hogy betűt kell írnia, akkor a memóriából szerzett szám mutatja meg a számára, hogy hányadik karaktert kell kiválasztania a táblázatból. Ha például a 65-ös számot találja, akkor a hatvanötödik karaktert írja ki, amely történetesen a nagy A betű. Itt a kiírt szövegeknek arra a tulajdonságára szeretném felhívni a figyelmedet, hogy a program során nem változnak meg. Az efféle változatlan adatokat állandóknak, más néven konstansoknak mondjuk. Éppen azért, mert nem változnak, túl sok izgalmas dolgot nem várhatunk tőlük. Ugyan, mit kezdhet az ember néhány egymás alá írt szöveggel? Nos, például rajzolhat velük:
Ezen a kis ábrán nem feltűnő, de éppenséggel látható, hogy a különböző betűk nem egyformán fedik a képernyőt: a pont csak alig, a bonyolultabb rajzolatú X, M vagy W viszont erősebben látszik. Megfelelően kiválasztott betűkkel egészen tűrhető árnyékolást lehet megvalósítani. Amikor az ilyesféle mókák divatban voltak, a programozók jobb ügyhöz méltó buzgalommal készítettek mindenféle képet, népszerű színészek portréjától a pucér nőcis rajzokig. A szövegeket össze is lehet adni, ekkor a karakterfüzérek egybekapcsolódnak: PRINT "kard"+"hal" kardhal Ok PRINT "csavar"+"anya" csavaranya Ok Nemcsak szöveges konstansok léteznek. Beírhatunk a parancsba számokat is, ennek már igazi számolás lesz az eredménye:
PRINT 100+25 125 OK Számok esetén az összeadáson kívül egyéb műveletek is elvégezhetők. A szorzás és az osztás jele különbözik attól, amit a számtanórákon megszoktunk: A szorzást pont helyett csillag (*), az osztást kettőspont helyett jobbra dőlő ferde vonal (/) jelöli. Például tízet osztunk kettővel, beszorozzuk hárommal, és kivonunk belőle kettőt: PRINT 10/2*3-2 13 Ok Most húszból kivonunk ötöt, és megszorozzuk hárommal. Ez, ugyebár, 45 lesz: PRINT 20-5*3 5 Ok Hoppá, dehogy oké! Mi történt? Elromlott a számítógép? Nem, csak éppen van egy különleges szabály, amit elfelejtettem megemlíteni... A program bizonyos műveleteket fontosabbnak ítél, ezért azokat a beírás sorrendjétől függetlenül előbb végzi el. A szorzást és az osztást például előbbre valónak tartja, mint az összeadást és a kivonást. Ezt úgy is mondják, hogy nagyobb a precedenciájuk. Hogy ez miért van így, most közömbös, hasznosabb, ha a helyzet megoldását közlöm. Az egybe tartozó kifejezést zárójelek közé kell fogni, akkor a program azt számolja ki előbb: PRINT (20-5)*3 45 Ok Így már stimmel. Erre a szabályra (precedenciaszabály) mindig gondolni kell, amikor programnyelven fogalmazunk meg egy matematikai műveletet, különben nehezen földeríthető hibák forrása lehet. Még egy lehetőség a melléfogásra: PRINT "100"+"200" 100200 Ok Ejnye! Nem háromszáznak kellett volna kijönnie véletlenül? Nem bizony, ugyanis a látszat ellenére nem számokat adtunk meg! Az idézőjelek azt jelzik a programnak, hogy karakterfüzéreket kell összeadnia. Ezek, mint föntebb szó volt róla, nemcsak betűket, hanem bármiféle a gép által leírható dolgot tartalmazhatnak, itt történetesen csupa számjegyet. Ennek megfelelően a számoknak nem az értékét, hanem csak a leírt képét adjuk össze, mint amikor betűkből álló szavakat kapcsoltunk egybe. Úgy lehet elképzelni, mintha a két számot felírtuk volna két cetlire, utána egymáshoz ragasztottuk volna. Játék a változókkal A konstansokkal való kísérletezésnek hamar a végére értünk. Az, hogy szövegeket írunk ki, esetleg ragasztunk egymáshoz, végső soron eléggé sekélyes mulatság. A számokkal végzett műveletek valamivel hasznosabbak, csak éppen mindig át kell írni a programot, ha újabb számokkal dolgoznánk – a példákat én nem is raktam programba, csak parancsok formájában mutattam be. Tulajdonképpen miért is olyan változtathatatlan a konstans? Hiszen a gép könnyedén beírja a memóriába, és éppolyan egyszerűen törli is! A fő gond az, hogy nem ismerjük az adatok memóriacímét. Ha megtudhatnánk, és valahogy más értékeket tudnánk beírni, érdekes dolgokat művelhetnénk. Szerencsére a megoldás nem ilyen bonyodalmas, mivel a leggyakoribb ügyek egyikéről van szó. Nincs szükségünk arra, hogy memóriacímeket bújjunk, azt a program elvégzi helyettünk. Mi csak kérünk magunknak némi helyet a memóriában, ezt a helyet elnevezzük, és e névre hivatkozva tehetünk oda adatokat, kedvünk szerint. Ha a memóriát továbbra is fiókos szekrénynek gondoljuk el, arra kérjük az operációs rendszert, hogy biztosítson a számunkra annyi fiókot, amennyire
szükségünk van, és címkézze fel az általunk megadott névvel. Innentől fogva édesmindegy a számunkra, hogy az a fiók végül is hányadik a sorban. Elég, ha mondjuk, azt kérjük, hogy az ADAT nevű fiókba rakjon valamit, az operációs rendszer tudni fogja, hogy teszemazt a 116834. fiók jelölésére használta ezt a nevet. Mivel az így elhelyezett adatokat bármikor megváltoztathatjuk, ezeket a tárolóhelyeket változóknak nevezzük. Érdemes megemlíteni, hogy a változók elnevezésekor tartanunk kell magunkat bizonyos szabályokhoz. A változónév nem tartalmazhat szóközt, ékezetet, és az elejére nem kerülhet számjegy. Leghelyesebb csak az angol ABC betűit és (ugyebár nem első karakterként) számjegyeket alkalmazni. Ezen kívül fontos szerepe van néhány különleges karakternek, amelyek közül itt csak a dollárjelet ($) fogjuk használni, mert a modern basicben ez a dolog elveszítette a jelentőségét. A nehézkes magyarázat után térjünk rá az üdítően egyszerű gyakorlatra. Noha sorrendben végrehajtva parancsként is működnének a következő dolgok (vagyis a változókat akkor is megjegyzi az interpreter), írjunk rögtön rendes, elmenthető programot Változókat úgy hozhatunk létre, hogy egyszerűen hivatkozunk rájuk: 40 PRINT SZAMLALO Így, külön jelzés nélkül, a basic az integer, vagyis egész típusú számok tárolására biztosít helyet. Hogy ez ténylegesen mennyit foglal le a memóriából, az most közömbös, egyelőre elég annyit tudnunk, hogy egy viszonylag nagy pozitív vagy negatív, de feltétlenül egész számot tudunk beletenni (egy memóriarekeszbe egyébként csak 256 különféle számérték tehető, ha ennél többre van szükség, több helyet foglal le a program). A fenti utasításban hivatkoztunk a SZAMLALO nevű változóra, mivel azt mondtuk az interpreternek, hogy van egy ilyen nevű kiírható szám – ha tehát eddig nem volt ilyen, most kaptunk egy számnak való helyet. Nézzük meg, mi van benne: RUN 0 Ok A program tehát nullát rakott a változónkba. Ez a basicben így történik, de fontos tudni, hogy más programnyelvek általában sokkal szigorúbbak. Ott általában egyáltalán nem hivatkozhatunk nem létező változókra, de ha mégis, megeshet, hogy a megcélzott memóriacímeken valami véletlenül ott maradt érték lesz, ezért érdemes megszokni, hogy használat előtt először mindig adjuk meg a változó értékét, a basicben egyszerűen így: 30 SZAMLALO=1 Az az igazság, hogy ez is lezser megoldás, mivel a jelentése: SZAMLALO legyen 5, vagyis ez egy értékadás, és hamarosan látni fogjuk, hogy ugyanígy írjuk azt az állítást is, hogy SZAMLALO értéke 5, ami azután vizsgálható, hogy igaz-e vagy sem. Ha pontosak akarunk lenni, az értékadásra létezik is egy utasítás, a LET, amely ilyenformán használható: LET SZAMLALO=5, de ezt el lehet hagyni, mert az interpreter mindig el tudja dönteni, hogy értékadásról vagy állításról van-e szó. Listázással és futtatással ellenőrizzük, mit végeztünk: LIST 30 SZAMLALO=1 40 PRINT SZAMLALO Ok RUN 1 Ok Tehát a SZAMLALO nevű változó kezdőértékét csakugyan 1-re tudtuk állítani, s a következő utasítás már ezt az értéket írta ki. Változtassuk meg menet közben az értékét! Egészítsd ki a programot a következő sorokkal: 50 SZAMLALO=2 60 PRINT SZAMLALO 70 SZAMLALO=3 80 PRINT SZAMLALO Vagyis új értékeket adunk a SZAMLALO-nak, így ugyanaz a PRINT SZAMLALO utasítás mindig
más értéket ír ki. Lássuk: RUN 1 2 3 Ok Ez már egészen formás kis program, érdemes lesz elmenteni, de adjuk meg a módját. Írjunk neki címet is: 10 ' 1. SZAMLALOPROGRAM 20 ' Lehet menteni: SAVE "SZAMLALO1.BAS", A Mivel egyre hosszabb futási eredményekre számíthatunk, most vegyük elejét annak, hogy a programunk fél oldalakat használjon el néhány szám kinyomtatására. Ehhez új dolgot tudunk meg a PRINT utasításról. Ha a sor végére nem írunk semmit, az történik, amit láttunk, a következő sorra ugrik, és a következő PRINT onnét indul. Ha azonban a sort vesszővel zárjuk, érdekes dolog történik. Írd át a következő sorokat így: 40 PRINT SZAMLALO, 60 PRINT SZAMLALO, Hadd fusson: RUN 1 2 3 Ok A program tehát némi helyet hagyva a számok közt, de egy sorba írta őket. Természetesen, ha elérnénk a sor végére, új sort indítana. A nyolcvanas sor végét azért nem írtam át, hogy az Ok felirat, amely nem tartozik a programhoz, mégiscsak eggyel lejjebb kerüljön. Most írj a vesszők helyére pontosvesszőt! 40 PRINT SZAMLALO; 60 PRINT SZAMLALO; Futtatás: RUN 123 Ok A számok most kihagyás nélkül, közvetlenül egymás mellé kerültek. Így már jóval több szám elfér egy sorban. Szükségünk is lesz rá. Mielőtt tovább mennénk szeretném felhívni a figyelmedet valamire, ami megkönnyíti a program szerkesztését. Ha nem akarsz minduntalan a Jegyzettömbre átugrálni, hanem a Blassic ismertetett szövegszerkesztési módszereit alkalmazod, már ebben a kis szövegben is eléggé idegesítő lehetett, hogy teljes sorokat újra kellett gépelned egyetlen vacak írásjel miatt. Azonban, ha megnyomod a felfelé-nyíl billentyűt, látod, hogy megjelenik az előzőleg beírt sor, így visszalépkedhetsz a megváltoztatni kívánt utasításig, amelyben a jobbra-balra nyilakkal a változtatás helyére tudsz állni. Itt beírod, ami szükséges, és Enterrel már érvényesítheted is a módosítást. Ha visszalépkedéskor túlszaladtál, lefelé-nyíllal az ellenkező irányba is mehetsz. Ha mindezt szegényes segítségnek tartod, vigasztald magad azzal, hogy a régi programozóknak még ennyi sem jutott... Ez az a pont, ahonnét felpörögnek az események. Az eddigi program eredményét jóval rövidebben, egyetlen PRINT 123 paranccsal is produkálhattuk volna. Természetesen nem ez a gyengécske produkció a célunk, hanem a tanulás, és a továbbiak megértéséhez való átvezetésként írtuk ilyen hosszadalmas formában. Ami most jön, azt már csak a változókkal csinálhatjuk meg. Kezdetben majdnem az iménti kódot írjuk be:
10 ' 2. SZAMLALOPROGRAM 20 ' 30 SZAMLALO=1 40 PRINT SZAMLALO; 50 SZAMLALO=SZAMLALO+1 60 PRINT SZAMLALO; 70 SZAMLALO=SZAMLALO+1 80 PRINT SZAMLALO A futtatás eredménye ugyanaz, mint az előbb, de nem is ez az érdekes, hanem az ötvenes és hetvenes sorba írt rejtélyes SZAMLALO=SZAMLALO+1 utasítás. Hogyan lehet egy szám egyenlő egy saját magánál nagyobb számmal? Csakis úgy, hogy ez tulajdonképpen egy csonka LET SZAMLALO=SZAMLALO+1 utasítás, vagyis értékadás, nem pedig állítás! A jelentése tehát: A SZAMLALO változó értéke legyen az eddigi értéknél eggyel nagyobb. A dolog eredményeképpen az 50-60-as utasítások tökéletesen megegyeznek a 70-80-assal. Ez azt jelenti, hogy a hetvenes és nyolcvanas sor fölösleges, ha rá tudjuk venni a gépet, hogy az előző kettőt megismételje! Ha mondjuk a hatvanas sor után azt kérjük, hogy ugorjon vissza az ötvenesre, akárhányszor kiírhatunk mindig eggyel nagyobb számot. Ennek semmi akadálya, csak van egy kis bibi. Ha csak ennyit tudunk, az „akárhány” végtelen sokat fog jelenteni, mivel a program sohasem jut el a végéig, hanem állandóan visszalép az ötvenes sorra. Mielőtt tehát bolond módon nekiugranánk, foglalkozzunk azzal, hogy hogyan ellenőrizzük azt, kell-e még növelni a számot. Tételezzük fel, hogy mondjuk tízig akarunk elszámolni, ha tehát a szám ezt eléri, már nem ismételgetjük a növelést és kiírást, hanem befejezzük a programot. Ezt magyarul például így fogalmazhatjuk meg: Hogyha SZAMLALO kisebb, mint 10, menj az 50-es sorra. Azt, hogy a SZAMLALO kisebb, mint 10, feltételnek nevezzük. Ha ez a feltétel teljesül, az 50-es sorra ugrunk, ha nem, akkor lépünk tovább, esetünkben egyszerűen elérjük a program végét. Mi azonban nem magyarul írjuk a programot, hanem a félig-meddig angol basic nyelven. Készítsük elő a fordítást: A kisebb, mint matematikai jelölése <, tehát a feltételünket így írjuk: SZAMLALO<10. A hogyha angolul IF, az akkor szó (legalábbis ebben az értelmében) pedig THEN. Ez a kettő lesz a kívánt utasításpár. Azért pár, ugyebár, mert csak együtt van értelmük. A menj valahova angolul GO TO, de mivel egy utasítást szerencsésebb egy szóval megoldani, basic nyelven GOTO válik belőle. A teljes sor tehát a következő lesz: 70 IF SZAMLALO<10 THEN GOTO 50 A nyolcvanas sorba elég egy üres PRINT, a soremeléshez. Nézzük, hogy fest most a program: LIST 10 ' 2. SZAMLALOPROGRAM 20 ' 30 SZAMLALO=1 40 PRINT SZAMLALO; 50 SZAMLALO=SZAMLALO+1 60 PRINT SZAMLALO; 70 IF SZAMLALO<10 THEN GOTO 50 80 PRINT Ok Futtatás: RUN 12345678910 Ok A program prímán működik, de a jó programozó sosem elégedett magával. Mindig az a jó, ha a program a lehető legegyszerűbb, hiszen úgy a legkevesebb a hibalehetőség, és általában a futása is akkor a leggyorsabb. Némi vizsgálódás után észreveheted, hogy a negyvenes sor fölösleges. Ha
törlöd, a program rendben lefut – igaz, hogy a számolás 2-ről indul, de miért is? Mert előzőleg egyet állítottunk be kezdőértéknek, és az ismétlődő programrész rögtön egy növeléssel indul. Adjunk meg tehát nullát a harmincas sorban: 30 SZAMLALO=0 A futtatást már ki sem írom... lehet menteni. Az efféle ismétlődő programrészeket ciklusoknak nevezzük. Azt talán már nem is kéne hangsúlyoznom, hogy nemcsak eggyel növelhetünk, hanem más számmal is, mint ahogy az IF feltétel határértéke is tetszés szerint megváltoztatható, például 28947-ig is számolhatsz négyszáztizenhármasával, természetesen nem várható, hogy az utolsó szám mindig éppen a határérték lesz – általában valamivel túllépi, mivel a vizsgálat előtt növeltük. A feltételvizsgálat nagyon fontos része a programozásnak, nem csoda hát, hogy sokféle megoldása lehet. Ennek megfelelően vizsgálhatjuk, hogy valami nagyobb-e (>) a másiknál, éppen egyenlő-e vele (=), de azt is hogy hátha nem egyenlő (<>, tehát vagy kisebb vagy nagyobb, de nem ugyanaz). Az utóbbi kettővel csínján kell bánni, csak akkor használható, ha egészen biztos vagy benne, hogy a feltétel bekövetkezik. Az előbbi program működik akkor is, ha a 70-es sorát így írtuk volna: 70 IF SZAMLALO<>10 THEN GOTO 50 Tehát, ha SZAMLALO nem egyenlő tízzel, lépj vissza. Mivel egyesével növelünk, előbb-utóbb okvetlenül belebotlunk a 10-be. Ha azonban, mondjuk, hármasával akarunk elszámolni százig, pórul járunk, mert a számláló nem talál bele a százasba, hanem átlép rajta (...96, 99, 102...), és értelemszerűen utána sem lehet vele sohasem egyenlő, tehát ugyanolyan végtelen ciklus alakul, ki, mintha az egész feltételvizsgálatot bele sem építettük volna a programba. Ezért a pontosan meghatározott érték vizsgálata helyett, amikor csak lehet, mindig tartományt vizsgálunk – például, hogy kisebb vagy nagyobb-e egyik a másikánál. Sőt, mi több, a fentieken túl lehet kisebb vagy egyenlő(<=, más szóval nem nagyobb) és nagyobb vagy egyenlő (>=, tehát nem kisebb) is. Ennek kapcsán érdemes megemlíteni, hogy a programozás során előadódó hibákat két nagy csoportra szokás osztani. Az első a szintaktikai hibák csoportja, amikor valami olyat írsz a programba, amit az interpreter nem ért meg. Ezt a fordító hagyományosan a SYNTAX ERROR (nyelvi hiba) hibaüzenettel jelzi (a Blassic SYNTAX HORROR-t, vagyis „nyelvi rémséget” ír, nyilván viccből). Ezekkel a hibákkal van a kisebbik baj, hiszen olyannyira észrevehetők, hogy a programot általában nem is tudjuk futtatni, míg ki nem javítjuk. A másik csoport a logikai hibáké. Ezen az értendő, amikor programnyelvileg ugyan tökéletesen fogalmazunk meg valamit, csak rosszul mérjük fel a következményeit. A fenti példa tipikus logikai hiba: Az interpreter érti, amit kérünk, kifogástalanul végre is hajtja, csak a feltételt olyan ügyetlenül szabtuk meg, hogy a vizsgálata semmit sem ér, így a program használhatatlan. A logikai hibák sokszor nagyon nehezen találhatók meg, főleg azért, mert éppen a saját gondolkodásunknak attól az irányától kell eltérnünk, amely előidézte őket. Nem is mindig vezetnek végzetes következményekhez, esetleg csak meghamisítanak egy eredményt, vagy nem jutunk el bizonyos programrészekhez. Az is előfordulhat, hogy a hiba csak különleges esetekben bukkan fel, amelyre a programozó nem is gondol. Bonyolultabb program közzétételét ezért mindig próbaváltozatok előzik meg, amelyeken a felhasználók is kipróbálhatják annak képességeit. Mire a program eljut a „stabil” tehát megbízhatóan használható változatig, általában több tucat oldalnyi hibát javítanak ki benne. Ezt csak biztatásul mondom, hogy ne kámpicsorodj el, ha beüt valami gikszer, és nem azt csinálja a program, mint amit szeretnél. A kitérő után térjünk vissza a feltételvizsgálathoz. Egy feladatot többféleképpen is meg lehet oldani, így a feltételek is átfogalmazhatók. Az előbb az IF..THEN utasításpárról beszéltem, de valójában ez kiegészíthető az ELSE (egyébként) harmadik taggal. Az így kialakított szerkezet jelentése: HA a feltétel teljesül, AKKOR csináld ezt, EGYÉBKÉNT amazt. Amíg feltételt nem szabunk, a program fut a maga útján, a választáskor viszont több út is lehetséges, ezért az efféle programrészeket feltételes elágazásoknak mondják. Példaként bemutatom az előbbi program ELSE taggal módosított változatát:
10 ' 3. SZAMLALOPROGRAM 20 ' 30 SZAMLALO=0 50 SZAMLALO=SZAMLALO+1 60 PRINT SZAMLALO; 70 IF SZAMLALO>9 THEN PRINT " Befejeztem." ELSE GOTO 50 80 PRINT Futtatás: RUN 12345678910 Befejeztem. Ok Itt tehát azt vizsgáltam, hogy a számláló nagyobb-e kilencnél (ha igen, akkor nyilván elérte a tízet). Ha igen, akkor nem tesz mást, csak kiírja, hogy „Befejeztem” (egy szóköz után, hogy ne tapadjon a számokra), majd a feltételvizsgálat utáni részre fog lépni, ami most a program vége. Minden más esetben, vagyis, amikor még nem haladta meg a kilencet, visszaugrik az ötvenes sorra, növelve és kiírva SZAMLALO értékét. Ebben az esetben ez a megoldás semmit sem javított a programon, csak azt kívántam bemutatni, hogy ugyanaz a feladat egy kissé más logikával is megoldható. Egyéb helyzetekben viszont sok múlhat azon, ha ki tudjuk választani a legszerencsésebb módszert. Említettem, hogy olyan lépésköz esetén, amely nem érkezik pontosan a határértékre, az utolsónak leírt szám mindig nagyobb lesz annál, mert a program először végrehajtja a növelést és a kiírást, és csak azután ellenőrzi, mi is történt. Ezt a szerkezetet, mivel az esemény után vizsgál, hátultesztelős ciklusnak hívják. Általában azonban azért szabunk meg egy határértéket, mert nem szeretnénk, ha a program átlépné azt. Mi a megoldás? Az elöltesztelős ciklus, vagyis, hogy előbb megvizsgáljuk a változás eredményét, és attól tesszük függővé, hogy mit műveljen a program. Esetünkben kétféle akció közül választhatunk: Vagy növelünk és kiírunk, vagy befejezzük a programot, attól függően, hogy meghaladtuk-e a határértéket. Először is válasszunk egy olyan lépésközt és határértéket, amelyek garantáltan nem találkoznak, azután próbáljuk meg átrendezni a programot a következőképpen: 10 '4. SZAMLALOPROGRAM 20 ' 30 SZAMLALO=0 40 SZAMLALO=SZAMLALO+7 50 IF SZAMLALO>40 THEN PRINT " Befejeztem." ELSE PRINT SZAMLALO; GOTO 40 60 PRINT Keletkezett ugyebár egy gyanúsan hosszú sor, amely annyira zavarosnak tűnik, hogy alighanem meggyűlik vele a bajunk, de csak futtassunk: RUN Syntax horror in 50 Ok Na ugye! Ez a bizonyos ötvenedik sor béli rémség nem egyéb, mint az, hogy nem írhatunk egymás után több utasítást. Az eddigi szerkezetben a THEN és ELSE után is egy-egy utasítás következett, így nem volt velük baj. Most azonban az egyik esetben többet is végre kellene hajtanunk. Erre azonban ez az ős-basic nem képes, tehát más megoldás kell, mondjuk ilyenformán: 50 IF SZAMLALO>40 THEN GOTO 80 60 PRINT SZAMLALO, 70 GOTO 40 80 PRINT 90 PRINT " Befejeztem." Lám, az ELSE nem is kell, elég egyetlen THEN: Amikor SZAMLALO átlépi a határt, átugorja a következő három sort, addig viszont végrehajtja, és rendben visszakerül a negyvenesre.
Megjegyzem, a programozók azonban kissé berzenkednek a GOTO utasításoktól. Az egyik ok a régi basic-programozás esetlenségéből adódott: Ha a programot át kellett sorszámozni, nagyon könnyen benne maradhatott egy régi sorszámra mutató GOTO, amelyet azután verejtékfakasztó munka volt megtalálni. A modern basic az egész sorszámozást a szemétre vetette, és sokkal biztosabb módon határozza meg a GOTO célját, így ez a probléma megszűnt. A másik ok viszont ma is érvényes: Az ide-oda ugrálás áttekinthetetlenné teszi a programokat. Ezért azután mindig arra törekedtek, hogy ezt az utasítást kiküszöböljék – mára ezt is sikerült megoldani. Egy darabig vitáztak is arról, egyáltalán benne hagyják-e a nyelvben, végül a régi programok futtathatósága érdekében nem bántották. Nem is baj, mert néha ez a legjobb megoldás. Ugye, most is segített? Később az IF elágazásnak megjelent egy ügyesebb több soros formája is, ezért voltaképpen nem örülök, hogy a Blassic esetleg rákapat a GOTO használatára. Az elöltesztelős ciklust viszont mindenképpen be akartam mutatni, mert a következő utasítás is ilyen lesz. Nézzük tehát egészében programot: LIST 10 '4. szamlaloprogram 20 ' 30 SZAMLALO=0 40 SZAMLALO=SZAMLALO+7 50 IF SZAMLALO>40 THEN GOTO 80 60 PRINT SZAMLALO, 70 GOTO 40 80 PRINT 90 PRINT " Befejeztem." Ok Mint láthatod, a 60-as sor végére vesszőt tettem, hogy jól elváljanak egymástól a kétjegyű számok. RUN 7 14 21 28 35 Befejeztem. Ok A feltételvizsgálatokra még vissza-visszatérek majd, de nem árt, ha egyéb dolgokkal is foglalkozunk közben. Végül is azt ígértem, hogy most a változókkal foglalkozunk... Már szó volt róla, hogy a programozók lusták gépelni, és a sűrűn előadódó programrészleteket igyekeznek tömörebben megfogalmazni. Az előző program feladata – hogy valamettől valameddig elszámoljon – olyan gyakori, hogy külön utasítás készült rá. Ez a FOR..NEXT ciklus (talán tól-ig ciklusnak lehetne magyarítani). Először is kell egy változó, amelynek az értékét módosítani fogjuk a ciklusban. Mivel a szerepe szorosan összefügg a ciklus működésével, ciklusváltozónak szokás nevezni. A programozásban ugyan hasznos megoldás a beszélő nevek használata (vagyis, hogy olyan nevet válasszunk egy programelemnek, amelyből rögtön látjuk, mi a szerepe), de itt most nincs különösebb jelentősége, ezért megelégszem a szikárabb C elnevezéssel, hogy lásd, nem csak értelmes szó lehet a változónév. A ciklussal az iménti programot fogom utánozni, tehát 7-től 40-ig számolok, hetes lépésközzel: FOR C=7 TO 40 STEP 7 PRINT C, NEXT C Mielőtt rendes programot írnánk belőle, megmagyarázom a szerkezetét. A FOR jelenti, hogy a következő értéktől számolunk, ezután a szokott módon be is állítjuk a ciklusváltozó kezdőértékét. A TO után adjuk meg, hogy meddig számoljon a program, a STEP (lépés) pedig a lépésköz. Ezután kiírjuk a ciklusváltozó értékét. Itt azonban csak ebben a programban áll egyetlen utasítás, általános esetben viszont bármennyi következhet. Mivel azonban a programnak tudnia kell, hogy végül is hány sort akarunk megismételni, valahogyan jeleznünk kell a ciklus végét. Erre szolgál az utasítást lezáró NEXT (következő) kulcsszó, amely után a ciklusváltozó nevét kell írnunk. Ebből tudja az
interpreter, hogy ennek a változónak az értékét kell a megadott lépésközzel megnövelnie, és miután ezt megtette, visszaugrik a ciklus elejére, mindaddig, amíg csak el nem éri a határértéket. A NEXT ilyenfajta értelmezésével van egy kis probléma. A ciklus közepében – úgy mondjuk, hogy a ciklusmagban – bármiféle utasítások lehetnek, többek között akár egy másik ciklus. Ebben az esetben tehát a ciklus például így épül fel: FOR EGYIK=1 TO 10 STEP 10 FOR MASIK= 5 TO 25 NEXT MASIK NEXT EGYIK Ezt a ciklusok egymásba ágyazásának szokás nevezni. Természetesen mindkét cikluson belül lehetnek még egyéb utasítások is, csak nem akartam elvonni a figyelmedet a lényegről. A lényeg pedig az, hogy az első cikluson belül indított másik ciklust teljes egészében be kell tenni az elsőbe, vagyis még az első cikluson belül le kell zárni. Tételezzük fel, hogy egy figyelmetlen vagy urambocsá' komisz programozó a következőt írja be: FOR EGYIK=1 TO 10 STEP 10 FOR MASIK= 5 TO 25 NEXT EGYIK NEXT MASIK Vagyis a külső ciklust előbb zárja le, mint a belsőt. Mi van ilyenkor? Baj. Az interpreter nem tudja értelmezni, hogy melyik ciklust mikor kell lezárnia. Ezért a modern basicben már nincs szükség a NEXT utáni ciklusváltozóra, elég csak magában hagyni. Éppenséggel beírhatod, ha úgy szoktad meg, csak éppen az interpreter nem veszi figyelembe, hanem egyszerűen a legutóbb indított ciklust zárja le. Ha tehát nem a Blassicben programozol, jó eséllyel számíthatsz rá, hogy elég az üres NEXT, de ha azt írod, hogy NEXT NYENYERE, az sem okoz galibát. Itt azonban ajánlatos mindig odafigyelned, ha egymásba ágyazott ciklusokat alkalmazol. Három dolog még megemlítendő. Ha a lépésköz csak egy, akkor a STEP elem elhagyható: FOR A=3 TO 444 ugyanazt jelenti, mint FOR A=3 TO 444 STEP 1. Továbbá, visszafelé is számolhatsz, ha a STEP értéke mínusz szám: FOR VALAMI=80 TO 12 STEP -2. Végül, ha a kezdőérték már eleve eléri a határértéket, a ciklus egyszer sem hajtódik végre. Például: FOR A=100 TO 20 Ez a példa nagyon butának tűnhet, hiszen ki az a mamlasz, aki fölfelé számlálással kisebb értékhez akar eljutni? Azonban a ciklus értékei maguk is lehetnek változók: FOR A=B TO C STEP D Ilyen esetben nem mindig tudjuk, hogy a máshonnan (például egy számtani művelet eredményeképpen) kapott értékek nem vezetnek-e valami hasonlóra, ezért jó, hogy az interpreter erre is föl van készítve. Jöjjön a számlálóprogram új változata: 10 '5. szamlaloprogram (for..next ciklussal) 20 ' FOR C=7 TO 40 STEP 7 PRINT C, NEXT C 80 PRINT 90 PRINT " Befejeztem." Ok Elegánsabb, ráadásul a változót nullázni sem kellett a ciklus előtt, mint az imént. RUN 7 14 21 28 35 Befejeztem. Ok Egy fontos dologról még nem ejtettem szót, mégpedig a ciklusváltozó kitüntetett szerepéről. A
ciklus ugyanis ennek az értékéből tudja megállapítani, hogy hol tart. A ciklusváltozó a ciklusmagban ugyanúgy megváltoztatható, mint a többi, és ez néha rafinált programozói trükközésre ad lehetőséget, de jobb nem babrálni. Elvileg például megtehetjük ezt: 10 FOR I=10 TO 20 STEP 2 20 I=I-2 30 NEXT I Máris kész az undok végtelen ciklus. A ciklusmagban levonunk kettőt, a NEXT hozzáad ugyanennyit, tehát a ciklusváltozó egy tapodtat sem mozdul. A hasonló helyzetek elkerülése végett megjegyzendő ökörszabály: A ciklusváltozót nem illik módosítani. A fenti programocskában még mindig a ciklusváltozó értékét írattuk ki, ezért úgy tűnhet, hogy a ciklusmagban okvetlenül foglalkoznunk kell vele. Erről azonban szó sincs. A ciklusváltozó egyszerűen egy számláló, amely a programnak mutatja, hogy hányadik ismétlésnél tart. A ciklusmag pedig bármi lehet, amit ismételni akarsz. Példának okáért egy régivágású tanár rád parancsol, hogy büntetésből írd le százszor: „Nevelőimnek mindig előre köszönök, és nem huzigálom a lányok haját matematika órán.” A feladat basic nyelvű megoldása a következő: 10 FOR SOKSZOR=1 TO 100 20 PRINT "Nevelőimnek mindig előre köszönök, és" 30 PRINT "nem huzigálom a lányok haját matematika órán." 40 PRINT 50 NEXT SOKSZOR Futtatás: RUN
(stb.) Ok Ha nem a Jegyzettömböt használod a szerkesztéshez, a kiírás a valóságban nem fog így sikerülni azok miatt az átok ékezetek miatt... A következőkben erre találunk majd megoldást.
Jöjjenek hát az ékezetes betűk. Ha a programot nem a Jegyzettömbben, hanem a Blassicen belül írtuk meg, hiába gépeltük be tökéletesen a 20-30-as sorokat, már a listázáskor kiderül a disznóság:
Ha pedig abban reménykednénk, hogy majd a futáskor helyrezökken a világ rendje, csalódunk: Elnézést, hogy nem közlöm mind a száz mondatot, az összes ilyen... A program tehát nem a beírt szöveget jeleníti meg, hanem önkényesen megváltoztatja az ékezetes betűket, mégpedig láthatólag értelmesen, vagyis nem valamiféle szemetet, hanem létező jeleket írva a képernyőre, és következetesen, vagyis az azonos ékezetes betűk helyére mindig ugyanazt a jelet teszi, például minden á betűt a fura német s-sel, a ß karakterrel helyettesít. A dolognak az lehet a magyarázata, hogy a Blassic végül is Windows-program, így beíráskor a Windows karakterkészletét használja, értelmezéskor viszont a sajátját, amely alkalmasint valamely régi szabványhoz igazodik. Az is lehet, hogy ugyan a Windows valamelyik készletét használja, de más számozással. Ez a magyarázat egyszerű és rövid, de a számodra nyilván nem egészen kerek... Legfőképpen: Mi a búbánat az a karakterkészlet? Nos, ez az a táblázat, amelyről már szóltam. Az, amelyben le van írva, hogy melyik szám hatására melyik betűt rajzolja ki a gép. Úgy lehet elképzelni, hogy valahol a gépen egy csomó apró képen le vannak rajzolva a betűk, és a táblázat alapján a gép mindig előhúzza a memóriában talált szám által kijelölt képet. Azonban a gépen nemcsak egyetlen ilyen képsorozat van, hanem sokféle, aszerint, hogy valaki milyen nyelven akar írni. Amikor a billentyűzetet átállítod angolra, azt kéred az operációs rendszertől, hogy a továbbiakban ne a magyar, hanem az angol betűk képsorát használja. Így lehet elérni, hogy ugyanazzal a billentyűzettel orosz, japán vagy tamil írásjegyeket rajzoljunk a képernyőre. Bár, ha meggondoljuk, a mi írásunk nagyon hasonlít az egyéb latin betűs írásokhoz, így joggal merül fel a kérdés: Azt az egy-két eltérő betűt miért nem csapták még hozzá a készlethez? Erre is van magyarázat, de úgy döntöttem, hogy a terjengősebb ismertetéseket ezentúl függelékben közlöm, amit vagy elolvasol, vagy sem, de itt inkább a ne szakítsuk meg állandóan a gondolatmenetet. Ha érdekel, ott nézz utána! El kell tehát fogadnunk, hogy az ékezetes betűk helyén mindenféle furcsa dolgok jelennek meg. Én a DOS-os időkben sokat játszadoztam a betűk átrajzolásával, de ott tudni lehetett a karakterek mintájának a pontos memóriacímét, meg nem is basicet használtam, hanem valami gépközelibb nyelvet, úgyhogy most ne erőltessük ezt a megoldást. Viszont az tudható, hogy a nyugati nyelvekben használatos karakterek között is vannak, amelyek megegyeznek a magyar ékezetes betűkkel, vagy legalább hasonlítanak hozzájuk. Ha ezeket a képernyőre tudnánk csalogatni, máris beljebb volnánk. Különleges karaktereket ugyan kapunk az AltGr billentyű hatására, de a printelés ugyanúgy eltér a beírt szövegtől, mint eddig, úgyhogy nem ez a megoldás. Bár előre megmondom, ha sikerül megtalálnunk a módszert, az aligha lesz valami kényelmes, úgyhogy nagyregények írására inkább mást válassz majd... A vizsgálódáshoz nem árt, ha van egy olyan szövegünk, amely az összes magyar ékezetes betűt tartalmazza. Persze, írhatnánk azt, hogy „áéíóöőúüű”, de szórakoztatóbb, ha keresünk valami szavakból álló kifejezést, ezt megjegyezni is egyszerű, és így nem felejtünk ki egy ékezetet sem. Ha az ékezet nélküli magánhangzókat el akarjuk hagyni, az ékezetesek közt pedig nem tűrjük a fölösleges ismétlést, a feladat nem is olyan egyszerű, viszont annál jópofább szóösszetételekre vezet. Karakterkészletek kipróbálására széltében ismert tesztkifejezés például az „árvíztűrő tükörfúrógép”, de énnekem tetszik az általam gyártott „hűs füvön túrázó színésznő”, „félúton tülkölő fűnyírógép” vagy az „üvöltő szűzlányt kínzó kényúr” is. Írjunk be valami hasonlót: PRINT "rémítő üstökű kútásó" rÚmÝt§ Řst÷kű k˙tßsˇ
Ok Az én számomra kissé furcsa, hogy éppen a legzűrösebbnek vélt hosszú ű stimmel, de örüljünk neki. Egy gonddal kevesebb. A többi közül csak egy-kettőt tudunk megtalálni a billentyűzet AltGr gombos végignyomkodásával, meg őszintén szólva, nem is ez a célom, így hát mást próbálunk ki. Megtudjuk, melyik számérték milyen betűt ír le, és azokból összeállítjuk a szöveget. Mivel ugyebár még mindig a változókkal foglalkozunk, csináljunk a szövegünkből is egy változót, amelyben majd összeadhatjuk a megtalált betűket: 30 SZOVEG="rémítő üstökű kútásó" RUN Type mismatch in 30 Ok Most meg mi van? Pedig nem felejtettük el az idézőjeleket, ezért nem hiheti utasításnak a beírt szavakat. A hibaüzenet persze elárulja, hogy nem is erről van szó: „típuskeveredés a 30-as sorban”. Nincs különösebb baj, csak eddig még nem ismerjük a szöveges változókat. Azt ugye, tudjuk, hogy valójában a szöveg is egy számsor. Ha az interpreter csak úgy hagyná, hogy egy közönséges változóba beírjuk, akkor a memóriából való kiolvasáskor valami számot kapnánk, amit a szokott számméretre használt értékekből rakna össze a rendszer (azzal, hogy a memória maximum 256 értékű rekeszeiből hogyan állít össze nagyobb számokat a gép, itt nem foglalkozom). Ezért valamivel jeleznünk kell a számára, hogy ebben az esetben szövegről van szó. Az idézőjel itt nem segítene, mert az maga is egy számmal leírt karakter, és egy számváltozó miért ne egyezhetne meg vele? A memóriába való beíráskor azonban még észreveszi az idézőjelet, ezért leáll, ha egy közönséges számváltozóba (pontosabban integer változóba, lásd jóval fentebb) szöveget próbálunk betenni. A basic nyelv ezt úgy oldja meg, hogy már a változó elnevezésekor jelezzük, hogy nem akármit, hanem szöveget akarunk benne tárolni, mégpedig a végére írt dollárjel segítségével: 30 SZOVEG$="rémítő üstökű kútásó" Ilyenkor az interpreter megjegyzi, hogy arra a helyre szöveget pakolt, és helyesen tudja majd kiolvasni. Tulajdonképpen az integer típusnak is van megkülönböztető jele, a százalékjel (%), de mivel alapesetben ezt a típust hozza létre, alig van szükség rá, hogy külön hangsúlyozzuk a fajtáját, így a nyelv szabványa megengedi, hogy elhagyjuk. Még többféle jelölés létezik különböző típusokra, de egyelőre ezeket nem használjuk, a modern basic pedig másképp oldja meg a kérdést, és a Rapid-Q-ban majd aszerint dolgozunk. Futtatáskor ugyan még semmit sem látunk, de az interpreter nem jelez hibát, tehát a változónkat elfogadta. Mentsd el a programot valami néven, és most egy kicsit félretesszük, hogy egy másik programmal megnézzük a karakterkészletet. Ehhez azonban megint tanulnunk kell valami újat. Kellene egy olyan módszer, amellyel megtudhatnánk, hogy az emlegetett táblázatnak hányadik helyén mi jelenik meg. Ezt egy eddig nem ismertetett fajta programelem, a CHR$() függvény teszi lehetővé számunkra. A neve a character szó rövidítése. Hogy a függvény az iskolában pontosan mit jelent, az nem ide tartozik – a programozásban olyan programrészleteket jelöl, amelyeknek adatot adhatunk át (a basicben ezt a függvénynév utáni zárójelben kell megadnunk), és ő attól függően ad vissza nekünk valami más adatot. Némely függvényeknek csak egy adatot adunk át, másoknak többet, a visszakapott adatok típusa is változhat. A lényeg, hogy a visszakapott adat lesz a függvény értéke, amelyet azután ugyanúgy használhatunk fel, mintha közvetlenül adtuk volna meg. Használat közben ez mindjárt világosabb lesz, csak még a CHR$() név végén ékeskedő dollárjelre szeretném fölhívni a figyelmedet. Mivel a függvény meghívása egy érték megszerzését jelenti, ismernünk kell az érték típusát is, hogy használni tudjuk. Ennek a függvénynek a nevéből rögtön tudjuk, hogy egy szöveges típusú értéket ad vissza, magyarán: szövegként lehet kezelni, például egy szöveges változóba beolvasni: BETU$=CHR$(65) A CHR$() feladata pontosan az, amire szükségünk van: A zárójelek között megadunk egy 0-255 közötti számot, mire ő felveszi a számnak megfelelő karakter értékét, amely 65 esetén a nagy A betű. A fenti sor tehát ugyanazt jelenti, mintha BETU$="A" parancsot írtunk volna be, azzal a
különbséggel, hogy a zárójelek között megadott adat (úgy mondják, hogy a függvény argumentuma) változó is lehet! Például a következő programocska az ABCDEF szöveget írja ki: 10 FOR SZAM=65 TO 70 20 BETU$=CHR$(SZAM) 30 PRINT BETU$; 40 NEXT SZAM 50 PRINT Észreveheted, hogy a BETU$=CHR$(SZAM) értékadásra valójában nincs szükség, CHR$(SZAM) értékét azon nyomban ki lehetett volna íratni egy PRINT CHR$(SZAM) utasítással, de a későbbiekben valami hasonlót fogunk csinálni, érdemes begyakorolni. A következő változatnál azonban már szükség lesz az értékadásra. Azt aknázzuk ki, hogy a szövegek is összeadhatók: 10 BETU$="" 20 FOR SZAM=65 TO 70 30 BETU$=BETU$+CHR$(SZAM) 40 NEXT SZAM 50 PRINT BETU$ Magyarázatot kíván a BETU$="" értékadás. Ha szükségünk van egy szöveges változóra, de azt szeretnénk, hogy egyelőre ne legyen benne semmi, akkor az idézőjelek közé semmit sem írunk. Úgy lehet tekinteni, hogy ez a szöveges változók „nullázása”. Az eredetileg üres BETU$ változóhoz azután a ciklusmagban mindig hozzáadjuk a ciklusváltozó által meghatározott sorszámú betűt, és csak a program legvégén nyomtatjuk ki az így összerakott szöveget. Mindez szép, de esetleg eltöprenghettél azon, hogy miféle varázslatos dolog folytán képes a függvény egy egész számot „átalakítani” betűvé. Hogy lásd, mennyire nincsen benne csoda, megmutatom, hogy az eddigi fogyatékos tudásunkkal is megtehetünk valami hasonlót: 10 BETU$="" 20 FOR SZAM=65 TO 70 30 IF SZAM=65 THEN BETU$=BETU$+"A" 30 IF SZAM=66 THEN BETU$=BETU$+"B" 30 IF SZAM=67 THEN BETU$=BETU$+"C" 30 IF SZAM=68 THEN BETU$=BETU$+"D" 30 IF SZAM=69 THEN BETU$=BETU$+"E" 30 IF SZAM=70 THEN BETU$=BETU$+"F" 40 NEXT SZAM 50 PRINT BETU$ Egy programozó persze a haját tépné, és joggal, ha ilyesmit látna, de a lényeg, hogy mi is könnyedén képesek voltunk egy számérték alapján betűket hozzáadni a szöveghez. Valamikor mi is írunk valódi saját függvényeket, de azt inkább a modernebb változatban mutatom majd be, Blassicben maradjunk a basic beépített függvényeinél. Az ezelőtti program szövegösszeadásával egyébként már a távolabbi jövőbe kacsingatunk, a most készülőnek a váza már a kettővel előbbiben megvan, csak a kezdő- és határértékeket kell megváltoztatnunk: 10 FOR SZAM=0 TO 255 20 BETU$=CHR$(SZAM) 30 PRINT BETU$; 40 NEXT SZAM 50 PRINT Futtatáskor fura dolgok is történnek. Próbáld ki!
A legfeltűnőbb az, ami itt a képen nem reprodukálható, hogy csúnyán dudálgat a gép, mintha valami komoly baja lenne. Másodszor: néhány karakter után új sorban folytatódik a kiírás. Harmadszor: a
sok jel elején feltűnnek a franciakártya színei, a kőr, káró, treff – hát a pikk hol marad? Negyedszer: bizonyos karakterek láthatólag ismétlődnek, ami nyilván nem igazodik a helytakarékosság szép eszméjéhez. Az utóbbi talán a több tárhelyen raktározott szám csonkításából ered (lásd a függelékben), így nem tudunk rajta változtatni, a többire mindjárt rátérek. Örömmel állapíthatjuk meg viszont, hogy az idegen betűk közt hiánytalanul megvannak a magyar ékezetesek is, így nincs más dolgunk, mint figyelmesen leszámolni, hogy melyik betű hanyadiknak jelent meg, és máris ki tudjuk nyomtatni a CHR$() függvény segítségével. Azt is észreveheted, hogy amikor nagybetűt írsz, akkor a gép nem a kisbetűt „nagyítja meg” valami rejtelmes módon, hanem egyszerűen külön ábrákon vannak rögzítve a nagybetűk. Ezt azért jó tudni, mert a basic nyelvben (és a Windows kezelésében általában) közömbös, hogy kis- vagy nagybetűt használsz, így olyan érzésed alakulhat ki, mintha a két forma azonos lenne. Most azonban láthatod, hogy valójában egészen más sorszámok jelzik őket – a Linux például figyel a különbségre, ott nem szabad összetéveszteni a kettőt, de bizonyos programnyelvek (mondjuk a C) szintén szigorúan megkülönböztetik, Windows alatt is. Ha csakugyan elszánjuk magunkat a számolásra, érdemes először mindjárt az összes karaktert megszámlálni – és mindjárt láthatjuk, hogy hiba van a kréta körül: Akárhogy számolgatjuk, nem lesz meg a 256 karakter. Jóval kevesebbet találunk, ha jól számolom, csak 244-et. Mivel nem tudhatjuk, hogy honnan hiányoznak az eltűnt karakterek, nyilvánvaló, hogy nem reménykedhetünk megbízható eredményben, és az ujjunkon való számlálgatást más módszerrel kell helyettesítenünk. Először azonban meg kell értenünk, hogy miből származnak a rendellenességek. A számítógép nem magától találja ki, hogy egy bizonyos számra egy kis ábrát jelenítsen meg. Ezt a programozók írták neki elő. A programozók azonban fortélyos emberek. Úgy gondolták, hogy hasznosabb, ha a táblázat némely értéke hatására a gép más feladatot végez el. A sor elejére vagy a következő sorba ugrik, visszatöröl egy karaktert, vagy éppen figyelmeztető hangot ad ki. Mindez nyilván egyéb módon is megoldható volna, de mennyivel egyszerűbb, ha ez a sokféle feladat ugyanazzal a PRINT utasítással elvégezhető! Mondjuk, hogy a program futtatója egy szöveget gépel be: A billentyűk leütésekor általában megjelenik a kívánt betű rajza, de ha az Enter gombot nyomja meg, kiírás nincs, viszont a kurzor a következő sor elejére ugrik. Vagy megjelenik a szöveg: „Folytatja? I/N”, és erre a felhasználó lenyomja a T gombot. A program pedig se nem folytatja, se be nem fejezi, hanem sípol egyet, jelezvén, hogy nem érti. Az efféle feladatokat mind meg tudjuk oldani azzal, hogy egy szöveges változóba olvassuk a megfelelő értéket, és egy PRINT CHR$(valami) utasítást hajtunk végre. Ily módon sokféle különböző út helyett egyetlen, általánosabb megoldást találunk a leggyakoribb helyzetekre. Megint csak a takarékosság... Ügyes, nem? Az efféle, nem megjeleníthető, hanem egyéb feladatot ellátó karaktereket vezérlőkaraktereknek hívjuk. A képernyőn azért nem tudunk 256 karaktert megszámlálni, mert a vezérlőkarakterek nem láthatók, sőt, van, amely még az előző karaktert is törli azzal, hogy visszaugrik egyet a sorban (ahá, a hiányzó pikk!). A CHR$(0), ha jól emlékszem, éppenséggel semmit sem csinál – néha éppen erre van szükség a programban. Mivel több sípolást is hallottunk, valószínű, hogy a sípszó-karakter (hivatalosan a CHR$(7)) többször is szerepel a táblázatban, mint ez látható karakterek esetén is megesett. A vezérlőkarakterekkel hamarosan még próbát tehetünk, de térjünk vissza az eredeti feladatunkhoz. A kiíráskor meglehetősen zavaró, hogy már nem férünk el egyetlen sorban. Az sem közömbös, hogy a zsúfoltság miatt néha nehéz eldönteni, hogy melyik ábra melyik karakterhez tartozik. Például van egy szokatlan dupla felkiáltójel: ez most egy karakter vagy kettő? Jobb lenne valami átláthatóbb formába rendezni a táblázatot. Jó megoldás lenne, ha egy sorba csak annyi karaktert írnánk, amennyi kényelmesen elfér, azután egy PRINT utasítással a következő sorba ugranánk. Így mindig pontosan tudnánk, hogy egy sorban hány karaktert találunk, könnyebb lenne a számolás is. Vajon hány karaktert írjunk egy sorba? Mivel azt szeretnénk, hogy ne tapadjanak egymáshoz, ezentúl nem pontosvesszőt, hanem csak vesszőt írunk a BETU$ változó után, azt már tudjuk, hogy így valamennyi helyet hagy a következő kiírás után. Eddig azonban nem volt szó arról, hogy pontosan mennyi az a valamennyi. Nos, úgy lehet elképzelni, mintha a képernyő 8 karakter széles oszlopokra lenne felosztva, és a következő kiírás mindig a következő oszlop elején indulna. Ha tehát a
szövegünk csak egy betűből áll, a következő kiírás 7 betűnyi távolságra lesz, 2 betű esetén 6-ra és így tovább. Nyolc betűs szavak között nincs kihagyás, ha ennél hosszabb a szó, akkor nem a következő, hanem az utána jövő oszlopra ugrik a program. A mi szövegeink egyelőre 1 betűsek, tehát minden oszlopba kényelmesen írhatunk. A képernyő szélessége 80 karakter, kínálkozik tehát, hogy az így kialakuló 10 oszlopnak megfelelően 10 karaktert írjunk egy sorba. A 256 azonban nem osztható maradék nélkül tízzel, így a végén egy csonka sor maradna, amit külön kell kezelnünk: minden sort végig tudunk írni egy 1-től 10-ig tartó ciklussal, csak éppen az utolsóhoz kéne egy rövidebb. A 256 azonban nem egy légből kapott szám, hanem a számítástechnika egyik „bűvös” alapszáma, amely a kettes számrendszer törvényszerűségeiből adódik (nem merülök bele a részletekbe), ezért bizonyosan el lehet rendezni csonka sorok nélkül. Voltaképpen 16 2, vagyis 16 második hatványa, egyszerűbben szólva 16*16. Tizenhat karaktert viszont már nem tehetünk a sorba, tehát fele olyan hosszú sorokból csinálunk kétszer olyan hosszú táblázatot, magyarán 8*32 méretű táblázatban helyezzük el a karaktereket. Egy sort nagyon könnyen elkészíthetnénk a ciklusváltozó használatával. Minthogy a karakterek száma 0-ról indul, mi sem az átláthatóbb FOR=1 TO 8, hanem az ugyanennyi lépést eredményező FOR=0 to 7 beállítást használhatnánk. Csakhogy mi lenne a következő lépés? Ugyanezt a sort egyszerűen megismételhetnénk harminckétszer, ha a ciklusunkat egy másikba beágyaznánk, ekkor mondjuk ezt a programot kapnánk: 10 FOR KULSO=1 to 32 20 FOR BELSO=0 TO 7 30 PRINT CHR$(BELSO), 40 NEXT BELSO 50 PRINT 60 NEXT KULSO Sietek megjegyezni, hogy ez hülyeség, hiszen nem az a cél, hogy mindig ugyanazt írjuk le 32-ször. Azonban, ha megadjuk a módját, a hülyeségeinkből is tanulhatunk. Itt például fölfigyelhettél arra, hogy nem minden sort kezdek a sor elején. Erről eddig nem volt szó, de ahogyan a programjaink bonyolódnak, úgy lesz egyre nagyobb szükség arra, hogy jól átláthassuk őket. Ez a fogás is erre való. A számítógépnek mindegy, hogy talál-e fölösleges szóközöket, nem veszi őket figyelembe, nekünk azonban segítség, hogy így első ránézésre látjuk: A külső ciklus ciklusmagja a belső ciklusból és egy üres PRINT utasításból, a belsőé pedig egy olyan PRINT utasításból áll, amely a belső ciklusváltozó éppen érvényes értékének megfelelő karaktert írja ki. Értelmezzük emberi nyelven. Összesen harminckétszer hajtjuk végre a következőket: A 0. számútól a 7. számúig egy sorba kiírjuk a karaktereket, és a következő sorra ugrunk. Hát ez nem is olyan bonyolult. Az egyetlen probléma, hogy a belső ciklus minden indításánál nulláról indul. Erre többféle orvoslat létezhet, maradjunk a legegyszerűbbnél: Felejtsük el, hogy a ciklusváltozóval akarjunk dolgozni! Először mégiscsak alakítsuk vissza a belső ciklus beállításait, nem, mintha számítana, de így jobban látható, hogy meddig számolunk egy sorban: 20 FOR BELSO=1 TO 8 Ugyebár, a továbbiakban úgysem ezt fogjuk kiíratni. De akkor mit is? Szúrjunk be az elejére egy 0 értékű változót, ez lesz a számláló: 5 SZAM=0 Ez írja majd ki a karaktert a 30-as utasításban, tehát: 30 PRINT CHR$(SZAM), A program tökéletes lenne, ha mindig a nulladik karaktert akarnánk kiíratni. A SZAM változó értéke azonban magától nem fog megváltozni, hiszen nincs FOR..NEXT ciklushoz kötve, mint a ciklusváltozók, ezért nekünk kell gondoskodnunk róla, hogy minden egyes léptetéskor (tehát a belső ciklusmagban) növekedjen az értéke: 35 SZAM=SZAM+1 Készen is vagyunk, de futtatás előtt még megint gondoljuk végig, hogy mit csinál a program. A külső ciklus harminckétszer kiír egy-egy sort, utána a következőre ugrik. A kiírandó sorok úgy készülnek el, hogy a belső ciklus nyolcszor egymás után írja a számláló által mutatott karaktert, és
megnöveli a számlálót, tehát a táblázat nyolc egymás utáni karakterét írja ki. Amikor a belső ciklus egy újabb sor összeállításába kezd, a ciklusszámlálója nullázódik, de ez nem befolyásolja a mi számlálónk működését, az továbbra is az előző értékről növekedik. Nézzük meg egyben a programot: LIST 5 SZAM=0 10 FOR KULSO=1 to 32 20 FOR BELSO=1 TO 8 30 PRINT CHR$(SZAM), 35 SZAM=SZAM+1 40 NEXT BELSO 50 PRINT 60 NEXT KULSO OK Futtatás:
Majdnem tökéletes, de mielőtt számlálgatni kezdünk, még néhány jelentéktelen csinosítást megejtünk rajta. Először is: Hol van a beígért BETU$ változó? Jó-jó, semmi szükségünk nem volt rá, de ha már előbb használtam, majdcsak találunk neki valami értelmes feladatot... Engem például meglehetősen idegesít a sok sípolás, pláne, hogy az éjszaka közepén a szomszédokra is tekintettel kell lennem... Jó volna elébe vágni a dolognak, és csak akkor kiíratni, ha SZAM értéke nem egyezik meg a sípszót jelentő kóddal. Ezt az előbbi programban is megtehettük volna egy feltételes elágazással, ilyenformán: 30 IF SZAM<>sípszó THEN PRINT CHR$(SZAM) ELSE PRINT "duda" Ha tudjuk, hogy az ideiglenesen használt sípszó helyére milyen számot kell írnunk, a program éppen azt csinálja, amit szeretnénk: Ha ehhez a számhoz érkezik, sípolás helyett csak a duda szót írja a képernyőre, egyébként pedig a számnak megfelelő karaktert. Ezek után azonban könnyen vérszemet kaphatunk, és esetleg más vezérlőkaraktereket is hasonlóan helyettesítenénk. Ekkor azonban nemcsak két lehetőségünk van, így a kényelmes ELSE helyett valamivel összetettebb szervezésre van szükségünk. Megpróbálom úgy beírni, hogy az előző helyen elférjen: 21 IF SZAM<>sípszó THEN GOTO 24 22 PRINT "duda", 23 GOTO 35 24 IF SZAM<>vissza THEN GOTO 27 25 PRINT "törlés", 26 GOTO 35 27 IF SZAM<>soremelés THEN GOTO 30 28 PRINT "ugrik",
29 GOTO 35 30 IF SZAM<>soreleje THEN GOTO 34 32 PRINT "eleje", 33 GOTO 35 34 PRINT CHR$(SZAM), 35 SZAM=SZAM+1 Hát, éppen csak hogy be tudtam nyomni! Hogy érthető legyen: Sorban megvizsgáljuk, hogy a karakter megegyezik-e valamelyik vezérlőkóddal. Ha nem, ugrunk a következő vizsgálatra. Ha igen, kiírjuk a jelentését, utána mindent átugorva a SZAM változó növelésére lépünk, stb. Ha egyetlen vezérlőkaraktert sem találtunk, megérkezünk a 34-es sorba, ahol közvetlenül kiíratjuk a megfelelő karaktert, és ez után is a növelés jön. Mindez egyébként hibátlanul működne, és akár így is megírhatjuk a programot, ha nem vagyunk az áttekinthetőség és a takarékosság megszállottjai. Mi azonban figyelembe vesszük, hogy nem vezet jóra, ha ide-oda ugrálunk, továbbá, hogy a PRINT utasítás tulajdonképpen egy viszonylag bonyolult gépi kód beillesztését illeszti, míg az egyszerű értékadás sokkal rövidebb és gyorsabb. Ezért úgy intézzük a dolgot, hogy minden esetben a BETU$ változónak adunk valami értéket, és a kiírást egyetlen PRINT-tel intézzük el. Nézzük tehát az elvi felépítést: Írjuk be tehát: 6 BETU$="" 25 BETU$=CHR$(SZAM) 26 IF SZAM=sípszó THEN BETU$="duda" 27 IF SZAM=vissza THEN BETU$="vissza" 28 IF SZAM=soremelés THEN BETU$="ugrik" 29 IF SZAM=soreleje THEN BETU$="eleje" 30 PRINT BETU$, Valamivel rövidebb és egyszerűbb... Tehát: Először beállítjuk BETU$ értékét a számértéknek megfelelőre, ha azonban később kiderül, hogy vezérlőkarakter, megváltoztatjuk a PRINT utasítás előtt. Az az igazság, hogy a megoldás nem egészen precíz. Amikor egy vezérlőkaraktert találunk, rögtön ugornunk kéne a nyomtatásra – így teljesen fölöslegesen még megvizsgáljuk a többi lehetőséget is, amelyek pedig nyilvánvalóan nem következhetnek be, és ez lassítja a programot. Esetünkben azonban ez elhanyagolható apróság az egyszerűség előnyeihez képest. Ezután már hajlandó vagyok elárulni a kódok tényleges értékét: sípszó=7, egy lépés vissza=8, soremelés=10, ugrás a sor elejére=13. Listázzunk: LIST 5 SZAM=0 6 BETU$="" 10 FOR KULSO=1 TO 32 20 FOR BELSO=1 TO 8 25 BETU$=CHR$(SZAM) 26 IF SZAM=7 THEN BETU$="duda" 27 IF SZAM=8 THEN BETU$="vissza" 28 IF SZAM=10 THEN BETU$="ugrik" 29 IF SZAM=13 THEN BETU$="eleje" 30 PRINT BETU$, 35 SZAM=SZAM+1 40 NEXT BELSO 50 PRINT 60 NEXT KULSO Ok Futtatás:
No, elég szép, mindenesetre történt egy furcsaság: a második sor végén látunk valami bogárkát. Némi vizsgálódás után észrevesszük, hogy az eltolódást egy elfelejtett vezérlőkarakter okozza: CHR$(9) ugyanis egy oszloppal továbbküldi a kurzort. Mi sem egyszerűbb! Beszúrunk mondjuk egy IF SZAM=9 THEN BETU$="előre" sort... De hova is? Igen, a 25-30 sorok közé kéne valahová, csakhogy oda már nem fér több! Rémes! Át kell számoznunk a programot. Ez persze most nem túl nagy munka, de mi volna, ha nyolcvan oldalnyi szövegben történik mindez? Szerencsére közben olvasgatom ám a dokumentációt, és abból kiderül, hogy a Blassic nem olyan kezdetleges, mint feltételeztem, mégiscsak ismeri az átszámozás lehetőségét. Ez a RENUM parancs: RENUM új számozás eleje, számozás kezdete a régi programszövegben, lépésköz Mi az egyszerűség kedvéért az egészet (vagyis 5-től kezdve) átszámozzuk: RENUM 10, 5, 10 Most már be tudjuk szúrni: 75 IF SZAM=9 THEN BETU$="előre" Akár újra átszámozhatnánk, hogy szép szabályos legyen, de még nem vagyunk készen. Futtatáskor persze látjuk, hogy minden stimmel, eltekintve a továbbra is felharsanó dudaszótól: Lehet, hogy ez nem is az, aminek gondoltam, hanem csak a program jelzi, hogy az adott karaktereket nem tudja kinyomtatni. Majd mindjárt kikerüljük ezeket is, most tegyük félre a kérdést. Lehet számolni. De végül is mi a csudának fárasszuk magunkat? Számoljon a számítógép, az a dolga! A számlálónk már készen van, csak ki kell íratnunk az értékét. A mostani változatban éppen a századik sorban nyomtatjuk ki a karaktert, de előtte van még helyünk, ahova beírhatnánk: 98 PRINT SZAM; 99 PRINT "="; Mivel a pontosvessző egymás után rakja a kiírandó dolgokat, ilyesforma kiírást kapnánk: 65=A Ugye, kényelmesnek ígérkezik? Ráadásul a basic egymás utáni PRINT utasítások esetén nem ragaszkodik a külön sorokhoz, megengedi a következő tömörebb formát: 100 PRINT SZAM; "="; BETU$, Futtatáskor persze látjuk, hogy a második sor megint túl hosszú – a szavakkal való leírás túlnyúlt egy-egy oszlopon. Csakhogy már mi vagyunk nyeregben: megtudtuk a karakterek kódját, így a szavak helyett ábrácskákat írhatunk a vezérlőkarakterek helyére, mondjuk egy utána írt felkiáltójellel utalva arra, hogy ez csak magyarázat, nem a karakter képe, például: 75 IF SZAM=9 THEN BETU$=CHR$(16)+"!" 80 IF SZAM=10 THEN BETU$=CHR$(20)+"!" 90 IF SZAM=13 THEN BETU$=CHR$(27)+CHR$(27)+CHR$(27)+"!" A sípolás megszüntetésére pedig PRINT CHR$(beírt szám) parancsokkal kipróbálhatjuk a gyanús karaktereket, ahol semmit sem látunk megjelenni: PRINT CHR$(32) Ez téves próbálkozás, mivel a szóközt jelenti, de a 133, 149, 183 számú karakterekkel nagyobb
szerencsénk van. Itt, mivel nem tudjuk, hogy mi idézte elő a sípolást, nem írjuk ki a hangjegyet, csak három felkiáltójellel figyelmeztetjük magunkat arra, hogy ezeket nem használhatjuk. Ez ugyebár a következő utasításokat jelentené: IF SZAM=133 THEN BETU$="!!!" IF SZAM=149 THEN BETU$="!!!" IF SZAM=183 THEN BETU$="!!!" Azonban a basic képes arra, hogy a kifejezések igazságtartalmát összevonva vizsgálja. Erre több művelet használatos, leggyakrabban a nem, és, vagy összefüggések, amelyeknek basic megfelelői a NOT, AND, OR kulcsszavak. Ezek voltaképpen matematikai műveletek, de az értelmük jobban elképzelhető hétköznapi példák alapján: Ha NEM esik az eső, fölösleges az ernyő. Ha nyitva a bolt ÉS van rá pénzem, veszek újságot. Ha fel tudom hívni a barátomat, VAGY összefutok vele a koncerten, megbeszélem vele a teendőket. A számtani igazságok hasonlóan értékelhetők ki, esetünkben: Ha SZAM=133 vagy SZAM=149 vagy SZAM=183, akkor BETU$ legyen "!!!". Basicül: IF SZAM=133 OR SZAM=149 OR SZAM=183 THEN BETU$="!!!". Megjegyzendő, hogy ezeknek a műveleteknek magas a precedenciájuk, így összetettebb kifejezések esetén mindig érdemes zárójelezni, hogy a gép tudja a helyes sorrendet: IF (A*24>80) AND (B+2=17) OR (PUFI>=100) THEN PRINT "miskulancia" Egy szó mint száz, a beszúrt sorunk a következő lesz: 95 IF SZAM=133 or SZAM=149 or SZAM=183 THEN BETU$="!!!" Végül még egy picit szépítünk a táblázaton azzal, hogy a sorok közé még egy üres printet berakunk: 135 PRINT Futtatás előtt újra átszámozzuk, és megnézzük. Mivel az első sorunk most 10, elhagyhatjuk a RENUM parancs paramétereit, ilyenkor ugyanis a 10, 10, 10 értékeket állítja be magának: RENUM Ok LIST 10 SZAM=0 20 BETU$="" 30 FOR KULSO=1 TO 32 40 FOR BELSO=1 TO 8 50 BETU$=CHR$(SZAM) 60 IF SZAM=7 THEN BETU$=CHR$(14)+"!" 70 IF SZAM=8 THEN BETU$=CHR$(17)+"!" 80 IF SZAM=9 THEN BETU$=CHR$(16)+"!" 90 IF SZAM=10 THEN BETU$=CHR$(20)+"!" 100 IF SZAM=13 THEN BETU$=CHR$(27)+CHR$(27)+CHR$(27)+"!" 110 IF SZAM=133 OR SZAM=149 OR SZAM=183 THEN BETU$="!!!" 120 PRINT SZAM; "="; BETU$, 130 SZAM=SZAM+1 140 NEXT BELSO 150 PRINT 160 PRINT 170 NEXT KULSO 100 IF SZAM=13 THEN BETU$=CHR$(27)+CHR$(27)+CHR$(27)+"!" 110 IF SZAM=133 OR SZAM=149 OR SZAM=183 THEN BETU$="!!!" 120 PRINT SZAM; "="; BETU$, 130 SZAM=SZAM+1
140 NEXT BELSO 150 PRINT 160 PRINT 170 NEXT KULSO Ok És végre-valahára jöjjön a kész program futtatása:
Természetesen te alkalmazhatsz más, neked jobban megfelelő jeleket a vezérlőkarakterek helyett, a sorok elejére még beszúrhatsz szóközöket, hogy ne tapadjon a táblázat az ablak széléhez, de a lényeg megvan. És most térhetünk vissza a kútásóhoz: 30 SZOVEG$=" r"+CHR$(233)+"m"+CHR$(237)+"t"+CHR$(245)+" "+CHR$(252)+"st"+CHR$(246)+"k"+CHR$(251)+" k"+CHR$(250)+"t"+CHR$(225)+"s"+CHR$(243) 40 PRINT SZOVEG$
Hát igen, rendes betűmérettel már nem fért volna ki egy sorban, de az interpreter elfogadja, ha szép folyamatosan beírod. Lehet futtatni.
Én mondtam, hogy nem lesz kényelmes módszer... De remélem, tanulságos volt. Most már a Blassicből elmentve és visszatöltve is ugyanez lesz az eredmény. Azért az szerencsére nem túl gyakori eset, hogy a „csábítón dülöngélő műút”, „sárízű túrón ülő döglégy”, „lúdlábért vívó szörnyű hüllők”, „őrült színű új sövényvágó” vagy a „rücskös bőrű vérszívó búvár” kifejezéseket jelenítjük meg a képernyőn, inkább csak efféléket: „Melyiket választod?” vagy „Írj be egy számot 1-10-ig”. Ha csak egy-két ékezetre van szükség, akkor viszont nem olyan elviselhetetlen a dolog, és szebb a program, ha tisztességes magyar szövegeket ír ki. Adatok bevitele Arra, hogy a változóink csakugyan változzanak, eleddig egyetlen módszert ismertünk: a programon belüli értékadást. Ahhoz viszont, hogy a program ne mindig ugyanazokat a változtatásokat hajtsa végre, minduntalan át kellett írnunk a forrást. Nem, mintha ez olyan nehéz dolog lenne, de hát azt szeretnénk, hogy a program önmagában is alkalmas legyen valamely feladat végrehajtására, akkor is, ha a programszöveghez nem nyúlunk. Végső célunk amúgy is az önállóan futó programok készítése, amelyek akkor is működnek, ha semmiféle interpreter vagy egyéb támogatás nincs a gépen. Ha pedig valóban nincs, ezekbe a programokba már akkor sem tudunk belenyúlni, ha akarunk. De az interpreter-nyelvek esetén is gondolnunk kell arra, hogy nem okvetlenül mi fogjuk használni a programot, a másik jámbor felhasználó pedig esetleg egyáltalán nem is ért a programozáshoz. Gondoskodnunk kell tehát arról, hogy futás közben is be lehessen vinni adatokat a programba. A bevitel angol és basic neve INPUT. Ennek több változata létezik, mi itt csak az alapesettel foglalkozunk, amely a változó értékének begépelését jelenti. Így használható: INPUT VALTOZO Mindjárt írjunk is egy kis programot, amely két számot ad össze: 10 INPUT A 20 INPUT B 30 PRINT A+B Vagyis beolvassuk először A, majd B változó értékét, azután kiíratjuk az összegüket. A program „kezelőfelülete” módfelett letisztult, ezért nem árt némi útmutatás. Futtatáskor először egy kérdőjel jelenik meg, ez mutatja, hogy várja az első számot. Begépeled tehát, majd leütöd az Entert, ezzel jelzed a bevitel végét. Újabb kérdőjel, újabb szám, és Enter után megjelenik az összeg. A kezelés tehát igen egyszerű, mindamellett számíthatunk rá, hogy a kevésbé tájékozott felhasználó csak báván mered majd a képernyőre, ha szembesül a művünkkel. A programot „fel kell öltöztetnünk”, hogy beépített magyarázatot is adjunk a működésére. A következő változatban bőséges szövegezéssel biztosítjuk az útmutatást: 10 ' osszeado program INPUT utasitasokkal 20 ' 30 PRINT "***************************" 40 PRINT "K";CHR$(201);"T SZ";CHR$(193);"MOT ";CHR$(214);"SSZEAD";CHR$(211);" PROGRAM"
50 PRINT "***************************" 60 PRINT 70 PRINT CHR$(205);"rd be az els";CHR$(245);" sz";CHR$(225);"mot: ";
80 INPUT A
90 PRINT CHR$(205);"rd be a m";CHR$(225);"sodik sz";CHR$(225);"mot: ";
100 INPUT B 110 PRINT "A megadott sz";CHR$(225);"mok ";CHR$(246);"sszege: ";A+B
Tehát először is, a magunk számára írunk egy fejlécet, hogy a későbbi fejlesztés során tudjuk, hogy mire szolgál a program. Utána kezdődik a tényleges kód:
A karaktertáblából ellesett CHR$()-értékekkel jól láthatóan a képernyőre írjuk a program feladatát. Az INPUT utasítások előtt közöljük, hogy milyen adatra számítunk. A végeredmény kiírása előtt megmondjuk, hogy miféle szám következik. A hosszú utasításokat itt is kisebbel szedtem, hogy kiférjenek egy sorban, de ennek az igazi forrásszövegben sem szerepe, sem lehetősége nincs, egyszerűen csak írd be. Most tehát a program nem egy előre megadott összeadást végez el, hanem azokat a számokat adja össze, amelyeket tőlünk kap. Egyelőre csak egy összeadást tud, azután véget is ér, de nyilván ügyesebb, ha leállás nélkül annyiszor számoltathatunk vele, amennyiszer a kedvünk tartja. Ehhez a végrehajtás után megint a beolvasások elé ugrunk, azaz ciklust szervezünk. A FOR..NEXT típusú ciklus értelemszerűen nem jöhet szóba, hiszen nem tudjuk előre, hogy a felhasználó hány számolást akar elvégeztetni, ezért maradunk a legelőször megismert GOTO-s módszernél: 120 GOTO 60 Hát ez nem volt bonyolult... Ha most futtatod a programot, a végtelenségig adogathatsz vele össze számpárokat. Persze nem kell ezentúl napjaid fogytáig összeadással foglalkoznod: elég, ha a bekért számérték helyére bármilyen nem-szám adatot írsz be, a típuskeveredés miatt azonnal leáll a program. Ez azonban nem elegáns megoldás, pláne, hogy egy másik felhasználó esetleg nem olyan okos, mint te, és őszbe csavarodik a szakálla, mire végre eltéveszti egyszer a számok beírását. Az tehát a tisztességes, ha egy feltétellel biztosítjuk a számára a szabályos kilépés lehetőségét. Eddig a feltételeket mindig a programon belülről kaptuk, itt viszont a felhasználó döntésétől kell függnie. Ez megoldható például úgy, hogy minden számolás végén megkérdezzük, akarja-e folytatni: 115 PRINT "Folytassuk"; 116 INPUT A$ 120 IF A$="i" GOTO 60 ELSE PRINT "Vége. A viszontlátásra!” Mint látható, nem küzdök tovább a CHR$() függvénnyel, mert nagyon meghosszítja a sorokat, már úgyis tudod, hogyan kell használni illetve a Jegyzettömbbel kikerülni. Mivel a felhasználó számtalan módon adhat meg elfogadhatatlan értéket, a gyakorlatban jobban ki kellene dolgozni a feltételt. Hogy hogyan, azt egyelőre nem közlöm, mert a cél az, hogy mielőbb áttérjünk a Rapid-Q-ra, ott pedig gyökeresen másképpen intézzük a kilépést. Most mindenesetre feltételezzük, hogy a felhasználó tudja: a folytatáshoz kis i betűt, majd Entert kell lenyomnia. Ha mégis érdekelne egy tisztességesebb megoldás, írd meg, és akkor azt is megmutatom. A fenti sorokat nem használjuk majd, de két tanulság miatt érdemes volt kipróbálni. Egyrészt látható, hogy megfelelő fogalmazással úgy is intézhetjük, hogy az INPUT kérdőjele értelmet kapjon, vagyis tényleg egy kérdés végére kerüljön. A programban is írhattuk volna így: 70 PRINT "Mi legyen az első szám"; 90 PRINT "Mi legyen a második szám";
Ízlés dolga. Másrészt észreveheted, hogy a szöveges változónak lehet ugyanaz a neve, mint egy számváltozónak (esetünkben A), mivel a végére írt dollárjel egyértelműen megkülönbözteti tőle. Tehát a típusjelzés maga is a név része, vagyis a két név mégsem azonos – az egyik A, a másik A$. Hosszú távon ez az állandó rákérdezgetés kissé idegesítő, ezért más megoldáshoz is folyamodhatunk. Nem kérünk külön útmutatást, csak a bevitt számértékeket figyeljük, de azokat megvizsgáljuk, hogy nem egy bizonyos, jelzésként meghatározott műveletet kaptunk-e. Ha igen, kilépünk. Például: 120 IF A<>3000 AND B<>5999 THEN GOTO 60 ELSE PRINT "Vége. A viszontlátásra!”
Ebben az esetben a felhasználó úgy tudja befejezni a programot ha első számként háromezret, másodikként ötezerkilencszázkilencvenkilencet ad meg, minden más esetén újra kezdi. Ha csak az egyik azonos, akkor még nincs vége, erről az AND (és, tehát, ha mindkét feltétel igaz) művelet gondoskodik. Mivel erre a módszerre magától nehezen jön rá a felhasználó, legalább a program elején illik róla tájékoztatni... Valójában ilyen hasraütéssel választott feltételt nem érdemes szabni. Mi van, ha az illető nem akarja befejezni, de pont ezt a két számot adná össze? Ezért válasszunk olyan feltételt, ami kevesebb problémával jár. Bármilyen számhoz nullát hozzáadni nem túl értelmes dolog, de ha szükséges, akkor sem kell hozzá a számítógép – ezt a műveletet fenntarthatjuk programvége-jelzésnek. Lehet
tehát az a feltétel, hogyha legalább az egyik összeadandó nulla, akkor lépjünk ki. Itt nem egyszerre kell teljesülnie a kettős feltételnek, hanem bármelyik fele a program végét jelenti, tehát az OR műveletet használjuk: 120 IF A<>0 OR B<>0 THEN GOTO 60 ELSE PRINT "Vége. A viszontlátásra!"
Mivel egy szám nullával való szorzata nulla, a feltételt a következőképpen is megfogalmazhatjuk: 120 IF A*B<>0 THEN GOTO 60 ELSE PRINT "Vége. A viszontlátásra!" Ez egyébként valamivel lassúbb, de itt nincs jelentősége, hogy az utasítás mondjuk 25 vagy csak 4 ezredmásodpercig tart. A feltétel meg is fordítható, például ilyenformán: 120 IF A*0 THEN GOTO 140 130 GOTO 60 140 PRINT "Vége. A viszontlátásra!" Az értelme ugyanaz. Nézzük egyben az egészet: 10 ' osszeado program INPUT utasitasokkal 20 ' 30 PRINT "***************************" 40 PRINT "KÉT SZÁMOT ÖSSZEADó PROGRAM" 45 PRINT "A program bármelyik összeadandó" 46 PRINT "0 értéke esetén fejeződik be." 50 PRINT "***************************" 60 PRINT 70 PRINT "Írd be az első számot: "; 80 INPUT A 90 PRINT "Írd be a második számot: "; 100 INPUT B 110 PRINT "A megadott számok összege: ";A+B 120 IF A*B=0 THEN GOTO 140 130 GOTO 60 140 PRINT "Vége. A viszontlátásra!" A program – eltekintve az ékezetproblémáktól – IS-Basicben csak az aposztrófok (helyettesítsd REM-mel) és a 110-es utasítás végére csapott A+B miatt nem fut, a PRINT eltérő szabályozása miatt. Ha kettészeded a sort, már jó: 110 PRINT "A megadott számok összege:"; 120 PRINT A+B Azt is megteheted, hogy A+B értékét karakterlánccá alakítod, akkor maradhat egy sorban: 110 PRINT "A megadott számok összege: "&STR$(A+B) Az IS-Basic azonban különbet is tud. Ha megfigyeled a program szerkezetét, a végrehajtó rész leegyszerűsítve valahogy így néz ki: ELEJE utasítások VÉGE: feltétel szerint az elejére vagy tovább Ez, mint már tudjuk, egy ciklus, mégpedig feltételes ciklus, mivel a végén található feltételtől tesszük függővé, hogy ismétlünk, vagy abbahagyjuk, és továbbmegyünk a programban. Látható, hogy a szerkezete voltaképpen nagyon hasonló a FOR..NEXT cikluséhoz, azzal a különbséggel, hogy nem egy előre megadott ciklusváltozóhoz értékéhez kötjük a lefutását. A modernebb basic nyelv ezért – meg a GOTO kiküszöbölésére – ennek a ciklusfajtának is külön utasításokat adott. A két kulcsszó a ciklus elejét jelző DO (csináld) és a végét mutató LOOP (ismételd), így: DO utasítások LOOP Ez végtelen ciklus, mivel feltételt nem adtunk meg. Ez is megtehető azonban a LOOP-hoz kapcsolt UNTIL (mígnem) kiegészítéssel: LOOP UNTIL feltétel
Így a ciklus addig fut, mígnem egyszer csak bekövetkezik a feltétel igaz állapota. Arról természetesen nekünk kell gondoskodnunk, hogy a feltétel valamikor teljesüljön, egyébként tovább zakatol a végtelenségig. Mielőtt az ennek megfelelően átalakított programot bemutatnám, még egy apróság következik. Az inputok előtt – helyesen – egy PRINT utasítással mindig megmagyaráztuk, hogy mit kérünk: 70 PRINT "Írd be az első számot: "; 80 INPUT A Ez annyira gyakori fordulat, hogy az ENTERPRISE basicje hasznosnak tartotta egy különleges INPUTváltozat bevezetését, amellyel egyszerre intézhetjük a kiírást és a beolvasást: 70 INPUT PROMPT "Írd be az első számot: ":A Hála és köszönet az IS-Basic megalkotóinak, amiért egyúttal az INPUT eddig kötelezően megjelenő, idétlen kérdőjelét is elhagyták! Beíráskor ügyelj a PROMPT szóra, még az eredeti ENTERPRISE-kézikönyvben is több helyütt PROMT-ot írnak helyette. Akkor most nézzük a módosított programot: 100 ' összeado program INPUT utasitásokkal 110 ' 120 PRINT "***************************" 130 PRINT "KéT SZÁMOT ÖSSZEADO PROGRAM" 140 PRINT "A program bármelyik összeadando" 150 PRINT "0 értéke esetén fejezödik be." 160 PRINT "***************************" 170 DO 180 PRINT 190 INPUT PROMPT "Ird be az elsö számot: ":A 200 INPUT PROMPT "Ird be a második számot: ":B 210 PRINT "A megadott számok összege: "&STR$(A+B) 220 LOOP UNTIL A*B=0 230 PRINT "Vége. A viszontlátásra!" A DO..LOOP ciklus ez esetben, mint megfigyelhetted, hátultesztelős. Az elöltesztelős változat hasonló, csak a ciklus elejére tennénk a feltételt: 170 DO UNTIL A*B=0 ekkor a 220-as sorban nem kéne a feltétel, csak egy LOOP. A két változat között ugyebár az a különbség, hogy most az utasítások előtt vizsgáljuk a feltételt, így, ha az teljesül, a ciklus egyszer sem fog lefutni. Ebben a programunkban például éppen ezért marhaság volna az elöltesztelős változat, hiszen induláskor a létrejövő A és B értéke egyformán nulla, így a feltétel rögtön igaz lenne. Az UNTIL-nak van egy hasonló testvérkéje, a WHILE (amíg). Ez ugyanolyan módon használható, de nem azt vizsgálja, hogy a feltétel mikor következik be, hanem éppen ellenkezőleg, hogy meddig érvényes, más szóval, hogy mikor válik hamissá. Ennek megfelelően a fenti ciklust így is szervezhettük volna: 170 DO ... 220 LOOP WHILE A*B<>0 Vagyis addig ismétlünk, amíg A*B nem nulla. Ha ez a feltétel nem teljesül, vége a ciklusnak. Itt egyszerűen meg tudtuk fordítani a feltételt, más programban esetleg okkal döntünk egyik vagy másik megoldás mellett. A tömbök Noha a gyors haladás érdekében a basic nyelvnek sok igen fontos elemét említetlenül hagyom, a változókkal való ismerkedésből nem maradhat ki a tömbök bemutatása. Térjünk vissza a legelejére, a szókiíró programunkhoz. Ebben, mint emlékezhetsz, a galagonya, kakuk, szerda, kalap és ragacs szavakat írtuk ki a képernyőre, akkor egyszerűen szövegkonstansok formájában. Most, hogy már
tudunk egyet-mást a változókról, kipróbálhatjuk, hogy ez esetben is tudjuk-e őket használni valamire. Megtehetjük például, hogy bevezetünk egy szövegváltozót, és annak az értékét módosítgatva íratjuk ki a szavakat: 1 ' szokiiro program egy valtozoval 2 ' (a megadott szavakat print utasitasokkal a kepernyore irja) 3 ' 5 SZO$="galagonya" 10 PRINT SZO$ 15 SZO$="kakuk" 20 PRINT SZO$ 25 SZO$="szerda" 30 PRINT SZO$ 35 SZO$="kalap" 40 PRINT SZO$ 45 SZO$="ragacs" 50 PRINT SZO$ Megfigyelheted, hogy a dolog teljesen haszontalan, a konstansokat ugyanolyan módon használjuk, csak fölöslegesen megbonyolítottuk az egészet. Ez tehát nem vezetett értelmes eredményre. Az állandóan visszatérő PRINT SZO$ utasítás azonban azzal kecsegtet, hogy ciklusba szervezhetnénk a kiíratást, ha a konstansok bevitelét másképpen tudnánk megoldani. Ha például valahol sorban el tudnánk helyezni a szavakat, és mindig a soron következőt olvasnánk be, akkor a kiíró rész a következő FOR..NEXT ciklusra egyszerűsödne: FOR I=1 to 5 SZO$=következő szó PRINT SZO$ NEXT I A dolgot meg tudjuk csinálni! Éppen a fent elmondottakat oldja meg a DATA (adat) utasítás. Ha egy sor ezzel kezdődik, az azt jelenti, hogy ott nem végrehajtható kódot találunk, hanem a program futtatásához szükséges adatokat. Ha ilyen úgynevezett DATA-blokkot talál a fordító, akkor azt átlépi, mintha REM utasítás volna ott. A különbség csak annyi, hogy amikor viszont szükségünk van rájuk, ezeket az adatokat az erre szolgáló READ utasítással el tudjuk érni. A DATA-sorok adatait vesszővel választjuk el egymástól. A mostani esetben ez így néz ki: DATA "galagonya", "kakuk", "szerda", "kalap", "ragacs" Látható, hogy rendesen idézőjelek közé fogtam a szavakat, minthogy szöveges adatok, egyébként így jelezhető az interpreternek, ha szövegen belüli vesszőt használunk, nem pedig az adatokat elválasztóról van szó. Ugyanígy tárolhatnék számokat, sőt matematikai kifejezéseket is, de a dologban nem szeretnék elmerülni, ugyanis a DATA fölött eljárt az idő. A modern basic már nem használja, a Rapid-Q például már nem is ismeri. Itt még az adatok betöltéséhez jól használható, de holt tudásnak tekinthető az ismerete. Az így tárolt adatok kezelése szokatlan. Akárhová írjuk őket a programban, mindig a legelsőnél kezdi az olvasásukat, és a következővel folytatja. Emiatt alaposan át kell gondolnunk, hogy hogyan szervezzük meg őket: Ha például egy szövegváltozó után számváltozót akarunk beolvasni, a következő adatnak számnak kell lennie, különben leáll a program. Belátható, hogy sok adat esetén könnyen tévedhetünk, nem csoda, hogy a későbbiekben megszabadultak ettől a nehézkes megoldástól. Ez a sorban való hozzáférés egyébként meg is nehezíti az adatok elérését. Ha egy bizonyos adatra van szükségünk, akkor az egyetlen lehetőségünk, hogy az adatok elejére ugrunk – erre a RESTORE utasítás szolgál –, és az adatokat egymás után kiolvassuk, amíg a kívánt bejegyzéshez nem érünk. Ez, mivel a szükségtelen adatokat semmire sem használjuk, igencsak gazdaságtalan és lassú. Az ENTERPRISE basicje ezen annyit finomított, hogy a RESTORE utasítás után kiadott sorszámmal meghatározhatjuk, hogy melyik sorban kezdje az újraolvasást. Így nem kell elölről kezdeni, hanem épp a kívánt blokkra ugorhatunk, ami rendkívül nagy segítség az eredeti kínosan suta megoldáshoz képest.
Noha a DATA-blokkok bárhol lehetnek a programban, hagyományosan a program legvégén szokás összegyűjteni őket. Nézzük tehát a program mostani állását: 1 ' szokiiro program egy valtozoval 2 ' 10 FOR I=1 TO 5 20 READ SZO$ 30 PRINT SZO$ 40 NEXT I 50 DATA "galagonya", "kakuk", "szerda", "kalap", "ragacs" Ez kétségtelenül szebb, mint az iménti változat, de még mindig semmire sem használható. Ha már változót alkalmaztunk, az egész akkor kapna értelmet, ha a beállított értékével valamit kezdhetnénk. Most viszont a kiírás után rögtön meg is változtatjuk az értékét – ha pedig azt nem őrizzük meg, semmivel sem tartunk előbbre, mint a konstansos verzióban. Olvassuk be inkább mindegyik szót külön változóba, akkor megmarad az értéke, és azt csinálhatunk vele, amit akarunk, például azt íratjuk ki közülük, amelyiket a felhasználó szeretné. Ezt az eddigi tudásunk alapján például így oldhatnánk meg: 1 ' szokiiro program tobb valtozoval 2 ' 10 READ SZO0$ 20 READ SZO1$ 30 READ SZO2$ 40 READ SZO3$ 50 READ SZO4$ 60 PRINT "Az adattáramban a következő szavak vannak" 70 PRINT "0: ";SZO0$ 80 PRINT "1: ";SZO1$ 90 PRINT "2: ";SZO2$ 100 PRINT "3: ";SZO3$ 110 PRINT "4: ";SZO4$ 120 PRINT "Gépeld be annak a szónak a számát," 130 PRINT "amelyet szeretnél újra kiírni:" 140 INPUT SZAM 150 IF SZAM=0 THEN PRINT SZO0$ 160 IF SZAM=1 THEN PRINT SZO1$ 170 IF SZAM=2 THEN PRINT SZO2$ 180 IF SZAM=3 THEN PRINT SZO3$ 190 IF SZAM=4 THEN PRINT SZO4$ 200 DATA "galagonya", "kakuk", "szerda", "kalap", "ragacs" Már említettem, hogy ez a feltételkezelés olyan messze van a tökéletestől, mint Makó Jeruzsálemtől. Ennek ellenére most békén hagyjuk (pontosabban: a Függelékben majd ejtek szót a helyesebb megoldásokról, ha jut rá időm), mert egészen máshová szeretnék kilyukadni. Láthatod, hogy az újabb változók bevezetése teljesen szétzilálta a program már-már összeálló szerkezetét. Miért is nem lehet a változókra is sorszámmal hivatkozni a névadás helyett! – akkor mindent ciklusokkal lehetne intézni: Az első változó kapná az első adat értékét, a második a másodikét és így tovább. Persze, sejthetted, hogy a sok hasznavehetetlen, béna próbálkozás irányult valamire... Nos, pontosan erre. Ugyanis azonos típusú változókból csakugyan létrehozható az áhított adatszerkezet. Egy ilyen változócsoporton belül csak meg kell mondanunk, hogy hányadik elemet kezeljük, és az interpreter, tudván, hogy egy elem mekkora helyet foglal el a memóriában, könnyedén a kért változóra áll. Tételezzük fel, hogy egy szám tárolásához 4 memóriarekeszre van szükségünk, és az ötödik változóba akarunk írni. Ekkor az interpreter kiszámolja, hogy a változócsoport kezdetétől számítva a huszadik (5*4) memóriacímen kezdődő értékkel kell foglalkoznia. Az efféle csoportokat nevezzük tömböknek. A tömbök kezelése különleges feladat. Magányos változót bármikor létrehozhatunk a programban, az interpreter keres a memóriában egy
akkora szabad helyet, ahová befér, és kész is van. A tömbhöz viszont nem adogathatunk elemeket ötletszerűen, mert lehet, hogy az új elem már nem férne el az eddigiek mellé, ha pedig máshová írnánk, nem lehetne a címét a fenti módon kiszámolni. Ezért a tömbnek minden elemét egyszerre kell létrehoznunk. Ez úgy történik, hogy megadjuk a tömb nevét (a végén a szokásos módon különleges karakterrel jelezvén a típusát), azután zárójelben utána írjuk, hogy hány elemből áll. Így az interpreter tudja, hogy a teljes tömbnek mekkora lesz a kiterjedése, és akkora helyet biztosít a számára a memóriában. A kiterjedést idegen szóval dimenziónak hívjuk (angolul dimension), ennek rövidítése a basic DIM kulcsszava, amellyel a tömbváltozók helyigényét közöljük. Úgy is mondjuk, hogy a tömböt dimenzionáljuk. Itt egy öt szövegváltozóból álló tömbre van szükségünk, tehát ezt írjuk: DIM SZO$(5) Ez a gyakorlatban azt jelenti, hogy a tömb a következő öt elemből áll össze: SZO$(0), SZO$(1), SZO$(2), SZO$(3), SZO$(4) Tehát a számozás nulláról indul, az előző programban én is ezért neveztem SZO0$-nak az első elemet, noha ott bármi lehetett volna a neve. A számozás ennek megfelelően a megadott határérték előtt eggyel fejeződik be, vagyis SZO$(5) változó nincs a tömbben! Ez a köznapi gondolkodás számára kissé zavaró hiszen az elsőtől az ötödik tömbelemig terjedő tartományban dolgozunk. Ezért más basicek megengedik a következő fajta dimenzionálást is: DIM SZO$(1 TO 5) Sőt, ha valami miatt úgy hasznos, akár DIM(387 TO 401) vagy hasonló is lehet. A Blassic még nem ismeri ezt a módszert. Amikor a basic létrehoz egy tömböt, az elemeknek rögtön értéket is ad: Számtömbök esetén nulla, szövegtömbök esetén "", vagyis üres string lesz az elemek kezdőértéke. Más programnyelvekben ez nem feltétlenül van így, ott a létrehozott tömböket külön fel kell töltenünk a kívánt kezdőértékkel, más szóval inicializálni kell őket. A tömbelemek zárójelben megadott sorszámát indexnek, vagyis mutatónak nevezzük. Az indexet nemcsak számkonstanssal, hanem változóval is megadhatjuk – például, hogy „hazabeszéljek” egy kissé, egy ciklusváltozó is lehet. A ciklusváltozók ilyenfajta használata is indokolhatja azt a hagyományt, hogy a C (counter, vagyis számláló) mellett az I rövidítés (index) is gyakran használt ciklusváltozónév. Most pedig nézzük, hogy a programunk milyen elegánsan oldható meg tömbhasználattal. 1 ' szokiiro program tombvaltozoval 2 ' 10 DIM SZO$(5) 20 PRINT "Az adattáramban a következő szavak vannak" 30 FOR I=0 TO 4 40 READ SZO$(I) 50 PRINT I;": ";SZO$(I) 60 NEXT I 70 PRINT "Gépeld be annak a szónak a számát," 80 PRINT "amelyet szeretnél újra kiírni:" 90 INPUT I 100 PRINT SZO$(I) 110 DATA "galagonya", "kakuk", "szerda", "kalap", "ragacs" A 90-es sor INPUT I utasítása mellé még kívánkozik egy megjegyzés. Az előző programban itt egy SZAM nevű változóba kértük az adatot, de az I voltaképpen egy közönséges változó. Mivel ciklusváltozóként véget ért a szerepe, bátran felhasználhatjuk a bevitelre is, nincs szükség újabbra. Utána ugyanúgy indexelhetünk vele, mint a ciklusban. Mint említettem, az átlagos gondolkodás számára némiképp idegen a nulladik sorszám használata. Ezt elrejtheted a felhasználó elől, ha az 50-es utasításban a gép által használt értéknél eggyel nagyobbat íratsz ki: 50 PRINT I+1;": ";SZO$(I)
Ekkor viszont a felhasználó is nagyobb számot ír be, tehát az indexből le kell egyet vonni, hogy igaz legyen: 100 PRINT SZO$(I-1) Minden működik, de még adós vagyok az ENTERPRISE-változattal. Íme: 1 ' szokiiro program tombvaltozoval 2 ' 10 DIM SZO$(5) 20 PRINT "Az adattáramban a következö szavak vannak" 30 FOR I=0 TO 4 40 READ SZO$(I) 50 PRINT STR$(I-1)&": "&SZO$(I) 60 NEXT 70 PRINT "Gépeld be annak a szonak a számát," 80 PRINT "amelyet szeretnél újra kiirni:" 90 INPUT I 100 PRINT SZO$(I-1) 110 DATA "galagonya", "kakuk", "szerda", "kalap", "ragacs" Ha a fentieket jól megértetted, szemedbe ötölhet egy furcsaság: Amikor kötött hosszúságú számokat helyezünk el egy tömbben, könnyű elképzelni, hogy a program egyszerűen megtalálja egy-egy elem kezdetét. De a szövegek hossza különbözik. Honnan tudja a program, hogy a „galagonya” elejétől kilenc tárcímet kell átugornia a következő elemig, a „kalap”-étól meg csak ötöt? A Blassic válasza egyszerű: nem tudja. A régi basicben egy szöveges elem hossza 256 bájt lehetett (az elején egy bájt mutatta, hogy hány karakter a megjelenítendő szöveg, utána maximum 255 karakter következett). A basic minden szövegváltozó részére lefoglalta ezt a hosszt, attól függetlenül, hogy végül milyen hosszú szöveg került bele. Így mindig 256 bájtot kellett ugornia a következő elemig. A modern basicben megszűnt ez a korlátozás, gyakorlatilag bármilyen hosszú szöveg kerülhet egy tömbelembe. Viszont így már nem volna túl gazdaságos, ha az egyik tömbelem ugyanakkora lenne, mint a másik. Képzeld csak el, ha mondjuk egy kételemű tömbben az egyik elembe a Háború és béke nagyregény kerül, a másikba meg csak annyi, hogy „hé!”. Világos, hogy kár lenne annak is ugyanakkora memóriaterületet lefoglalni. Ezért az új basic egészen másképpen szervezi meg az adatok tárolását – hogy pontosan hogyan, ne firtassuk, mindenesetre a szöveghosszhoz igazodva. Amikor a SZO$ változóit együttesen tömbnek nevezem, nem vagyok egészen pontos. Ha a változókat szemléletesen kis dobozokként képzelem el, akkor úgy tekinthetem, mintha ezek a
Persze, nem szabad elfelejteni, hogy valódi, gépbe szerelt memória szerkezetéről úgysem tudunk semmit, úgyhogy csupán a szándékunk dönti el, hogy milyen irányúnak tekintjük a sort: Valójában nincs vízszintes, függőleges vagy ferde, az egymásutániság a lényeg. A kijelölt hármas számú dobozban esetünkben a „kalap” szó van. Az egy sorban elhelyezett adatokat változókat pongyolábban egydimenziós tömböknek, hivatalosan vektoroknak nevezik. Az adatok azonban nemcsak egy sorban rendezhetők el, hanem táblázatban is. Hogy a basic ezt a saját maga számára hogyan oldja meg az egymást sorban követő memóriacímeken, azzal ne foglalkozzunk, mi úgy képzelhetjük el, hogy egymás alá írt sorokban és oszlopokban rendeződnek el az adatok. Ekkor viszont nem elég egy kiterjedést megadnunk, hanem vesszővel elválasztva, mind a két irányban meghatározzuk, hogy hány adat méretű legyen a tömb. Egy 4*5 kockás táblázatot például így adhatunk meg: DIM TABLAZAT$(4,5) Itt négy sorban elhelyezett öt-öt adatot tárolhatunk. Az voltaképpen mindegy, hogy melyik méretet melyik kiterjedésnek tekintjük (tehát jelenthetne öt négyoszlopos sort is), csak az adatok
kezelésekor már következetesen kell tartanunk magunkat az általunk megadott rendszerhez, tehát bármelyik irányban csak akkora számmal címezhetünk, amekkora a dimenzionáláskor megadott értékbe belefér. A fenti tömb például hiába 20 elemű, nem hivatkozhatunk például a TABLAZAT$(4,2) helyre. Ez ugyan akár egy kisebb tömbben is benne lehetne, de a TABLAZAT$ tömbben nincs négyes számú sor (csak nullától háromig, lásd fentebb). A fenti kétdimenziós tömböt így képzelhetjük el:
Feltöltésére két egymásba ágyazott ciklust használhatunk. Figyeld meg, hogy az adatokat a tömb szerkezetének megfelelően rendezem el a DATA-sorokban. Ez nem kötelező, ömlesztve is lehetne a húsz szó, de így jobban áttekinhető: 1 ' ketdimenzios tomb hasznalata 2 ' 10 DIM TABLAZAT$(4,5) 20 FOR SOR=0 TO 3 30 FOR OSZLOP=0 TO 4 40 READ TABLAZAT$(SOR,OSZLOP) 50 NEXT OSZLOP 60 NEXT SOR 70 PRINT TABLAZAT$(2,1) 80 DATA "Ancsa","Bella","Cili","Dorka","Elli" 90 DATA "Friderika","Gabi","Hédi","Juli","Kata" 100 DATA "Lola","Manci","Nóra","Olga","Petra" 110 DATA "Rozi","Sári","Tamara","Vera","Zsófi" A program tehát először feltölti a tömböt a nevekkel, azután a hetvenes sor kiírja az ábrán is jelölt TABLAZAT$(2,1) tömbelem értékét, amely a Manci név. Hasonlóan hozhatunk létre háromdimenziós tömböt is, például: DIM HAZ(10,3,3) Ezt úgy is képzelheted, mintha három 3*10 méretű táblázatot tettél volna egymás mögé. Ez jelentheti mondjuk egy nagy ház lakásait. Most a harmadik kiterjedés a mélységet jelöli. Megfigyelheted, hogy a kockák száma most fölfelé növekszik. Ezt nem azért rajzoltam így, mert így van, hanem éppen azt akartam jelezni, hogy a változók tartalmának értelmezése tőlünk függ. Ha egy ház adatait tároljuk bennük, a tömb magasabb számú lapjai (itt az első index) emeleteket jelenthetnek, talajrétegek esetén pedig egyre mélyebb szinteket, ekkor a tömböt lefelé növekvőnek képzelhetjük el. Természetesen címzéskor is három adatot kell megadni, mint a képen kijelölt elemen látható: PRINT HAZ(5,0,1) Az utasítás a tömbelemben tárolt számot fogja kiírni, amely lehet példának okáért a lakás száma. Egy ilyen tömb jól használható például térben értelmezhető adatok tárolására, mint itt is.
Említettem, hogy a Blassic sajnos nem ismeri a tömbök tól-ig jellegű megadását, de más basicekben mínusz indexxel értelmesen kezelhető lenne a pinceszintek kérdése is: DIM HAZ(-2 to 9,3,3) A Blassicben, ha az egész ház adatait egy tömbben akarnánk kezelni, eszerint egy (12,3,3) kiterjedésű tömböt kellene definiálni a két pinceszint hozzáadására, és kénytelenek lennénk úgy tekinteni, hogy a kettes első indexű elemek vannak a földszinten, ami kissé idétlen megoldás. Az az igazság, hogy amikor a magasságnak nincs kitüntetett szerepe – mint az emeletek esetében –, akkor térbeli helyek indexeit nem ebben a sorrendben szokás megadni, hanem a mértanban használatos módon : DIM TOMB(x,y,z) Az x, y, z
A rajzról leolvasható, hogy a kis lila gömb középpontjával jelzett hely a térben a TOMB(7,4,5)elemmel célozható meg. Bár ilyen bonyolult dolgokkal egyelőre nem foglalkozunk, elképzelhető egy olyan program, amely éppen így, egy tömb segítségével tárol egy térrészletet, és aszerint, hogy a megcélzott kockában mondjuk 0 vagy 1 értéket talál, üresen hagyja az adott térkockát vagy valamit rajzol belé. A következő kép éppen ilyen módszerrel készült, igaz, hogy nem basic, hanem POV-Ray nyelven, amely kifejezetten térbeli rajzolásra való: Itt tehát egy tömböt töltöttem föl véletlenszerűen 0 és 1 értékekkel. A program ezután végigolvasott minden elemet, és ahol nullánál nagyobb értéket talált, ott a tömbindexeket egyben a golyók helyének meghatározására is felhasználva kirajzolt egy-egy gömböcskét. Ahol a golyók sűrűbben helyezkednek el, jól kivehetők a (10,10,10) méretű háromdimenziós tömb határai. Az adatok áttekinthetetlensége miatt a gyakorlatban ritkán használják ki, de alkalmazhatunk még több dimenziós tömböket is. Ezekről valahol olvastam azt a lemondó véleményt, hogy ezeket már úgysem tudjuk szemléletesen elképzelni, így nem is érdemes használni őket, legfeljebb valami tudományos számításnál, ahol egyáltalán értelme van mondjuk a negyedik dimenziónak. Azonban nem egészen ez a helyzet. A baj a szerző gondolkodásával van, aki az iménti rajzokhoz hasonlóan mindenképpen térbeli irányként akarja elképzelni a megadott dimenziókat. Vegyük viszont a következő példát: Egy nagy egyetem hármas számú épületében, a tizenötödik terem negyedik szekrényében, a második polcon lévő tizenhetedik könyv érdekel bennünket. Ezt egy rafinált nyilvántartó rendszerben egy ilyen tömbbejegyzés jelképezheti: KONYV$(3,15,4,2,17) Ez bizony, akárhogy nézzük, egy ötdimenziós tömb egyik eleme, és – ha a rendszert ismerjük – egyáltalán nem nehéz elképzelnünk, hogy mit jelent, mint ahogyan magában az adatban sincs semmi csodálatos, az egy egyszerű szövegváltozó, mondjuk A két Lotti vagy Mesék Mátyás királyról. Ez a példa elég szerencsésen rámutat arra, hogy érdemes néha a dolgokat a megszokottól eltérő szemszögből is megvizsgálni, hátha nem is a világ bonyolult, csak mi vagyunk tökkelütöttek...