8. Implementáció

Mostanra az algoritmusunk kész van és megfelelően működik, azonban mi nem fejben vagy papíron szeretnénk végrehajtani a lépéseit, hanem egy számítógép segítségével. Az a baj, hogy a számítógép sem a folyamatábrát, sem a pszeudokódot nem érti meg közvetlenül. Az utasítássorozatunkat át kell alakítani olyan formára, amelyet a számítógép is tud értelmezni, azaz valós programozási nyelven kell megadnunk az algoritmust. Most következik tehát az implementáció, azaz a tényleges program megírása és tesztelése, a számítógépes megvalósítás.

8.1. Programozási nyelvek

Első lépésben meg kell ismerkednünk a valós programozási nyelvek néhány általános jellemzőjével és számos általános fogalommal.

8.1.1. Programozási nyelvek osztályzása

Az elmúlt évtizedekben nem csak egyféle számítógépes programozási nyelv fejlődött ki, hanem több száz. Ennek a jegyzetnek nem célja egyetlen konkrét nyelv részletes bemutatása, csak példaként szerepel benne néhány programsor demonstrációs céllal. Mint a jegyzet címe is mutatja a programozás alapjait próbáljuk meg általánosan áttekinteni.

A mai modern számítógépek és az őseik is a Neumann-elveknek megfelelően kettes számrendszer használnak, azaz mindent egyesek és nullák sorozatával írnak le. Ez alól nem kivételek a végrehajtható programok sem. Azonban binárisan programozni, nagyon nehézkes és manapság szinte senki sem ír közvetlenül gépi kódot.

Már viszonylag korán kialakult a programozásnak egy kicsit egyszerűbb módja az assembly. Ezen a számítógép architektúrához nagyon közeli szinten egyszerű utasításokkal adhatunk meg mindent, a számítógép számára elemi lépések formájában. Melyik adatot melyik regiszterbe tegyem? Hogyan állítsam elő a memóriacímet, ahová egy művelet eredményét el akarom menteni? A program megírásához szükség volt a számítógép konkrét felépítésének és működésének az ismeretére. Ha megírt valaki egy programkódot egy számítógépen azt valószínűleg nem tudta futtatni egy másikon, azaz ezen a második generációs programozási szinten a kódok nem voltak hordozhatóak. Kizárólag szemléltetésként álljon itt egyetlen Intel x86 architektúrára írt assembly utasítás.

sub eax, DWORD PTR [ebp-24]

Ez az egyetlen assembly utasítás azt jelenti, hogy van egy memóriacím az ebp regiszterben, ebből a címértékből vegyél el 24-et, az így kapott memóriacímről olvass be egy dupla szó hosszúságú egész számot, ezt vond ki abból az értékből, ami az eax regiszterben van, végül a különbséget tárold el az eax regiszterbe felülírva annak korábbi tartalmát. Ugye milyen egyszerű?

Az 1950-es évek végétől kezdtek elterjedni az úgynevezett harmadik generációs nyelvek (3GL) vagy más néven a magas szintű programozási nyelv-ek. Ezekre jellemző az absztrakció és hordozhatóság. A programozónak a fejlesztés során nem kell ismernie az architektúrát, nem kell azzal foglalkoznia, hogyan is kerül át a paraméter értéke az alprogramhoz (a rendszer majd megoldja). Egy program futtathatóvá tehető egy teljesen más felépítésű számítógépen is. A programkód az ember számára sokkal könnyebben értelmezhető és általában rövidebb is. Az alábbiakban egy Java nyelvű utasítás látható.

if (Salary<100000) { Salary = Salary*1.1; }

Ennek megértése nem hiszem, hogy gondot okozna a pszeudokódot már ismerő olvasónak. Ha a Salary változó értéke kisebb, mint százezer, akkor megnöveljük az értékét 10%-kal. Jóval absztraktabb, mint az assembly. Nem kell a programozónak azzal foglalkoznia, hogy pontosan hol is van a változó a memóriában, sem azzal, hogy hamis feltétel után hol is van a következő utasítás.

Habár a harmadik generációs nyelvek ma is nagyon népszerűek, régóta használhatunk negyedik generációs nyelveket (4GL) is. Az absztrakció itt még magasabb szintet ér el, próbál a nyelv még emberbarátabb lenni. Sok esetben már szinte természetes emberi nyelvnek tűnik az, amit a programban olvasunk. Habár nem általános programnyelv, hanem adatbáziskezeléshez tervezték az SQL is a ebbe a csoportba sorolható.

SELECT  name  FROM  people  WHERE  age=20

Aki tud angolul sejti a fenti SQL utasítás jelentését programozói előismeretek nélkül is: Válogasd ki a neveket az emberek táblából, ahol a kor az 20! Nem csak azt nem tudjuk, hogy milyen regiszterek vannak a processzorban, még azt sem kell tudnunk hány és milyen változót alkalmazzunk közben. Bár sejtjük, hogy bizonyos lépéseket közben ismételni kell, de magának a ciklusnak a szervezését is rendszer biztosítja.

A programozási nyelvek gondolatmenete, szemlélete alapján különböző programozási paradigmákról beszélhetünk. A két legfontosabb paradigmába az imperatív és a deklaratív nyelvek csoportjai tartoznak. Az imperatív programozás utasításokra és változókra épül. A programozó feladata, hogy megadja az utasítások megfelelő sorrendjét, aminek hatására a megfelelő változók a megfelelő időpontban a megfelelő értékkel rendelkeznek, azaz a program egy bizonyos állapotban lesz. Tehát a fejlesztés során a programozónak el kell készítenie egy algoritmust, aminek a végrehajtása megadja az elvárt eredményt. Mint ahogy a latin imperium szó is kifejezi, utasítgatjuk a gépet. A legtöbb programozó ezzel találkozik először, a pszeudokód logikája is erre hasonlít.

Ezzel szemben a deklaratív nyelvek esetén azt kell megadnunk mi a probléma, mi a feladat, a megoldás menetét majd a rendszer szolgáltatja a matematikai logika vagy a függvénykalkulus alapján. A hogyan helyett a mit kell leírnunk a programban, amit kijelentő módban írunk. A változó a matematikai ismeretlen fogalmának felel meg, és így nincs is értékadás. Ciklusok helyett a rekurzív logika az alapértelmezett. Filozófiája teljesen eltér az imperatív nyelvek gondolatvilágától.

Programozási paradigmák

Az imperatív nyelvek világát két jelentősebb csoport alkotja: az eljárásorientált nyelvek és az objektumorientált nyelvek. Az eljárásorientált nyelveket a moduláris felépítés jellemzi. Szorosan kapcsolódnak a Neumann-architektúrához. A változók segítségével tárolt adatokon végzett műveleteket eljárások és függvények írják le. Ebbe a családba tartoznak például a C, a Pascal, a Fortran, a PL/I és a BASIC programozási nyelvek.

Az objektumorientált nyelvek az adatokat és a műveleteiket egységekét kezelik. Ez lesz az osztály, amelynek egy példány az objektum. Az osztályok nem függetlenek, hanem örökölhetnek jellemzőket más osztályoktól. Ez egy absztraktabb megközelítés, mint az eljárásorientált nyelveké. A leggyakrabban használt objektumorientált nyelvek a Java, a C++, a C#, a Python és a PHP.

A deklaratív világ egyik legfontosabb alcsoportja a logikai programozás, ami a matematikai logikán alapul. A program tényekre és szabályokra épül. Egyértelműen a legelterjedtebb képviselője a csoportnak a Prolog nyelv.

A deklaratív paradigmába tartozik a funkcionális programozás, ahol minden függvényként van értelmezve (még a konstansok is) és a megoldáshoz egy függvénykiértékeléssel jutunk. Nincs változó, így könnyen bizonyítható egy program helyessége. Alapjául a lambda-kalkulus szolgál. Ezt az elvet valló nyelvek többek között a Lisp, a Haskell, az F# vagy az Erlang.

A harmadik nagyobb deklaratív programcsalád az adatfolyam-vezérelt nyelvek csoportja. Itt a műveletek összeköttetésben állnak egymással (mint egy irányított gráf csomópontjai) és egy művelet azonnal végrehajtódik, ha minden bemenete elérhető lesz (ha az adatok megérkeznek, vagy ha bizonyos események bekövetkeznek). Távol állnak a Neumann-architektúrától, mivel alapvetően párhuzamos végrehajtást feltételeznek. Ide tartozik például a LabVIEW (G), a Verilog és a VHDL.

8.1.2. Implementációs hibák

Függetlenül attól milyen paradigmában is vagyunk, a program írás azt jelenti, hogy a programozó létrehozza a program forráskód-ját. Ez a legtöbb programozási nyelv esetén (de nem mindig) egy szöveges fájl, aminek a tartalma az adott programozási nyelv formai és tartalmi szabályrendszerének vagyis szintaxisá-nak megfelelő karaktersorozat. A szintaxis többek között meghatározza, hogy milyen karakterkészletet használhatnunk, melyek a nyelv által lefoglalt kulcsszavak, hogyan válasszuk el az egyes utasításokat, hol kezdődhet egy utasítás, milyen jelölést használhatunk a konstansokra, és így tovább. Ilyen szabályai a pszeudokódnak is voltak. Például nem lehetett else nevű változónk, a while után szerepelnie kell egy do majd később egy enddo kulcsszónak is, a "darab" (idézőjelek között) egy egyszerű karaktersorozat például egy kimenetben, míg a darab (idézőjelek nélkül) egy változó nevét jelölheti, stb. Ezek a szabályok minden nyelv esetén mások, más a nyelvek szintaxisa. Az alábbiakban egy természetes szám faktoriálisának kiszámítására szolgáló függvény definícióját láthatjuk négy különböző nyelven.

Fortran nyelvű kód:

      REAL FUNCTION FACT(N)
      FACT=1
      DO 20 i=2,N
20    FACT=FACT*i
      RETURN
      END

Pascal nyelvű kód:

FUNCTION FACT(N:INTEGER):REAL;
var i: integer;
begin
  FACT:=1;
  i:=2;
  while i<=N do
  begin
    FACT:=FACT*i;
    i:=i+1;
  end;
end;

C nyelvű kód:

int Fact(unsigned N){
  int i, f=1;
  for(i=2;i<=N;i++)
    f*=i;
  return f;
  }

Python nyelvű kód:

def Fact(N):
    f=1
    for i in range(2,N+1):
        f*=i
    return f

Első ránézésre a négy kód nagyon különböző (pedig mindegyik rokon nyelv, imperatív nyelvekről van szó). Ha alaposan megnézzük a logika mindegyik esetén ugyanaz, csak a külalak más. Ahhoz, hogy valaki meg tudjon írni egy programot, két dolog kell: képes legyen létrehozni a megfelelő algoritmust és ismerje egy valós nyelv szintaxisát. Az elsőként említett kompetencia elsajátítását segíti ez a jegyzet, a másodikhoz segítenek a „Programozási nyelvek 1-2” nevű tantárgyak.

Mint láttuk a szintaxis nagyon változatos lehet, de mind a négy programrészletben az alapvető lépések, az alkalmazott algoritmus azonosak. A programnak ezt a fajta jelentését, működésbeli viselkedését szemantiká-nak hívjuk. Az összes fenti esetben más lenne a kódok szemantikája, ha a szorzásjelek helyett összeadásjeleket használnánk, holott a szintaxis azt is megengedi. A szemantikát az algoritmus határozza meg.

Ezek alapján belátható, hogy például egy kisebb elgépelés különböző jellegű hibákhoz vezet, eltérő következményekkel járhat. Ha a formai szabályokat sértjük meg ún. szintaktikai hibá-t vétünk. Ha például véletlenül endo szót írunk enddo helyett, akkor a rendszer egyből érzékelni fogja, hogy nincs meg a do párja, nem lehet tudni, meddig tart a ciklusmag. Nem írhatunk if helyett when szót, vagy az A és B változók szorzatát nem írhatjuk sem AB alakban, sem AxB alakban csak A*B formában. A program biztosan nem fog lefutni ilyen hibák jelenléte esetén.

Más tévedések nem ennyire könnyen észrevehetőek. Ha az egyik programsorba az x=x+1 utasítás helyett az x=y+1 utasítást írjuk, akkor ez még formailag elfogadható, nem sérti a szintaktikai szabályokat. A program akár le is futhat, csak az eredmény nem lesz az, amit elvárunk. A tévedés az algoritmust változtatja meg. Ez a szemantikai hiba. A kifejlesztett tesztelési stratégia alkalmazásával könnyen rájöhetünk, hogy baj van valahol, de a hiba pontos helyének felderítése sokkal nehezebb.

Néha a program szintaktikailag helyes, és alapvetően az elvárt eredményt adja, tehát a szemantikája is jónak tűnik, mégis bizonyos körülmények azt eredményezhetik, hogy a program elbukjon. Előfordulhat például, hogy a programunk egy konkrét fájl tartalmát szeretné beolvasni, korábban jól is működött a program, de a következő futtatás előtt letöröljük a fájlt. Ez a menet közben kiderülő futtatási körülmény a végrehajtás sikertelenségét eredményezi. Hasonló a helyzet, ha egy mennyiség négyzetgyökét szeretnénk kiszámolni, de az aktuálisan adott bemenetek éppen olyanok, hogy a mennyiség negatív lesz. Ezeket futási idejű hibá-nak nevezzük.

Azt szeretnénk egy utasításban megadni, hogy a C változó értéke legyen az A és B változók értékének hányadosa. Milyen hibákat véthetünk közben? Ha C=A\B sort írunk, akkor az egy szintaktikai hiba, mivel a legtöbb nyelv esetén a visszafelé perjel (\) nem egy kétoperandusú operátor (nem az osztás jele), így a sor nem értelmezhető. Megsértettük a szintaktikai szabályokat, a program le sem fut. Abban az esetben ha C=A-B utasítást írunk, akkor ez formailag teljesen helyes, a program le is fut, de ha figyelmesek vagyunk, észrevehetjük, hogy valószínűleg nem az elvárt eredményt kaptuk. Ez egy szemantikai hiba, mivel megváltoztattuk az utasítás jelentését (hányados helyett különbség), nem a megfelelő algoritmust reprezentálja a program forráskódja. Amennyiben C=A/B utasítást írunk, akkor mind formailag, mind a jelentését tekintve helyes programot kapunk, a program lefut és helyes eredményt ad (általában). Viszont ha egyszer a B változó értéke nulla lesz, akkor a program „kiakad”, futási idejű hibával szembesülünk.

8.1.3. A forráskód elemei

Hogyan épül fel egy program? Mint már említésre került a forráskód legtöbb esetben egy szöveges állomány, karaktereket tartalmaz. Karakterekből épülnek fel a lexikális egységek, ezek alkotják a szintaktikai egységeket, amelyek az utasítások részei lehetnek. Az utasításokból programegységeket hozhatunk létre, ezekből áll a program. Ezek közül valamilyen formában már szinte mindegyik elemmel találkoztunk. Ebben az alfejezetben csak azokat emelnénk ki, amelyeket eddig nem említettük vagy a valós nyelvek esetén kiegészítésre szorulnak.

A karakterkészlet mindig tartalmaz betűket, számjegyeket és egyéb karaktereket. A nyelvek viszont eltérően kezelhetik ezeket a fogalmakat. A legtöbb nyelv az angol ABC karakterein kívül betűnek tekinti például az aláhúzás jelet (_). Egyes nyelvek a kis és nagybetűket megkülönböztetik, mások azt mondják, hogy a ProGraM és a pROgraM szavak azonosak. Néhány nyelvben használhatunk nemzeti nyelvi betűket is.

A lexikai egységek közé tartoznak többek között a szimbolikus nevek (kulcsszavak, azonosítók), a konstansok és a megjegyzés. Az első két csoportról már korábban esett szó. A megjegyzés egy olyan karaktersorozat a forráskódban, ami a programozónak szól, a rendszer nem veszi figyelembe őket. Tehát nincs hatásuk a program végrehajtására, csak megkönnyítik a kód értelmezését az olvasó számára. Nagyon hasznos tud lenni, nem csak akkor, ha egy programon több programozó dolgozik, hanem magunk számára is érdemes lehet megjegyzéseket írni. Ha később vissza kell térnünk egy már megírt kódrészletre, a megjegyzések által könnyen megérthetjük mit, miért írtunk a kódba.

A szintaktikai egységek közé tartoznak a kifejezések. Megtanultuk, hogy ezek operátorokból és operandusokból és kerek zárójelekből állnak. Tudjuk, hogy a műveleteknek van egy erősségi sorrendje (precedenciája), például a szorzás erősebb, mint az összeadás. A 2+3*4 kifejezés és a 3*4+2 kifejezés ugyanazt az értéket jelenti, mert először a szorzást kell elvégeznünk. Az operátorok felírási sorrendje önmagában nem dönti azt el, hogy melyikkel kezdődik a kiértékelés. Amennyiben a alapértelmezett kiértékelés sorrendjén változtatni szeretnénk, akkor zárójeleket kell alkalmaznunk. Holott a (2+3)*4 kifejezésben az operátorok felírási sorrendje ugyanaz, mint a 2+3*4 esetén az eredmény más lesz. A kifejezések ezen alakja nem egyértelmű.

Eddig kétoperandusú operátorok mindig az operandusaik között voltak. A 7 * 5 jelentése: a hetet szorozzuk meg öttel. Ezt infix kifejezés-nek hívjuk. Korábban mindig csak ezt használtuk és a imperatív nyelvek is többnyire azt alkalmazzák. Azonban a kifejezéseket más formában is fel lehet írni. Az egyik lehetőség az, hogy a kétoperandusú operátort követik sorban az operandusai. Például a * 7 5 kifejezés azt jelenti, hogy szorozzuk meg a hetet öttel. Ezt az alakot prefix kifejezésnek hívjuk. Egy harmadik lehetőség az, ha a kétoperandusú operátor az operandusai után van. Tehát a 7 5 * kifejezés jelentése az, hogy a hét és az öt értékeket szorozzuk össze. Ez az ún. posztfix kifejezés. Az előbbi kifejezések értéke mindhárom esetben 35, csak a külalak változik.

Mi a helyzet akkor, ha több operátor is van egy kifejezésben? Hogyan kell kiértékelni az ilyen összetett kifejezéseket? Vegyük először a prefix esetet, a / * - 123 71 3 + 11 2 példa kifejezéssel! Itt az egyszerűség kedvéért minden érték konstans. A kiértékelés menetét az alábbi természetes nyelven megadott algoritmus írja le.

  1. Ha a kifejezés nem tartalmaz operátort, akkor kész vagy, egyébként menj tovább!

  2. Balról jobbra haladva keresd meg az első olyan részkifejezést, ami operátor konstans konstans alakú!

  3. Ezt az egyszerű prefix részkifejezést értékeld ki, vagyis hajtsd végre az adott műveletet az operandusokon!

  4. A eredménnyel írd felül a most kiértékelt részkifejezést!

  5. Folytasd az 1. lépéssel!

Ezek alapján a fenti kifejezés kiértékelésének a lépései a következők lesznek:

/ * - 123 71 3 + 11 2
/ * 52 3 + 11 2
/ 156 + 11 2
/ 156 13
12

A posztfix kifejezések kiértékelésének módja nagyon hasonló, csak a keresett részkifejezés más.

  1. Ha a kifejezés nem tartalmaz operátort, akkor kész vagy, egyébként menj tovább!

  2. Balról jobbra haladva keresd meg az első olyan részkifejezést, ami konstans konstans operátor alakú!

  3. Ezt az egyszerű posztfix részkifejezést értékeld ki, vagyis hajtsd végre az adott műveletet az operandusokon!

  4. A eredménnyel írd felül a most kiértékelt részkifejezést!

  5. Folytasd az 1. lépéssel!

A 123 71 - 3 * 11 2 + / kifejezés kiértékelésének lépései tehát a következők.

123 71 - 3 * 11 2 + /
52 3 * 11 2 + /
156 11 2 + /
156 13 /
12

Mint látható mind a prefix, mind a posztfix példánk értéke ugyanaz. Ez nem véletlen ugyanis mindkettő ugyanaz a kifejezés csak eltérő alakban. Ez a kifejezés infix formában így néz ki: ( 123 - 71 ) * 3 / ( 11 + 2 ). Vegyük észre, hogy ezt az infix kifejezést zárójelek nélkül nem tudjuk felírni, mert akkor mást jelentene. A másik két formában minden kifejezést fel tudunk írni zárójelek nélkül is. Ki kell hangsúlyoznunk azt is, hogy prefix és posztfix alakban nincs az operátoroknak precedenciájuk, a kiértékeléshez nem kell tudnunk, hogy melyik az erősebb operátor, a felírás mindig egyértelműen eldönti a kiértékelés sorrendjét. Ez különösen akkor lehet hasznos, ha figyelembe vesszük, hogy például a C nyelvben 46 különböző operátor van és egy jó programozónak fejből tudnia kell ezek pontos erősorrendjét, mivel ott csak infix kifejezést használhatunk. A deklaratív nyelvek előszeretettel alkalmaznak nem infix alakot. A prefix alak egy másik tipikus előfordulása megfigyelhető például az MS Excel táblázatkezelőben is. Ott egy 4 ^ ( 2 + 1 ) * 3 kifejezés (ahol a ^ operátor a hatványozást jelenti) így néz ki:

1
   =SZORZAT(HATVÁNY(4;SZUM(2;1));3)

A szintaktikai egységekre (például a kifejezésekre) épülnek következő szintként az utasítások. Ezekre is láttunk már példát pszeudokódban, gondoljunk csak az input/output utasításokra, az értékadásra, az eljáráshívásra, az elágaztatásra (if-then-else-endif szerkezet) vagy a ciklusokra (while-do-enddo szerkezet). A valós programnyeleken ezek közül néhánynak több formája is lehet, illetve vannak további utasítások. Ezeket a következő bekezdések foglalják össze röviden.

A pszeudokódban amikor csak eszünkbe jutott kitaláltunk és használtunk egy új változót gond nélkül. Számos valós nyelv megköveteli, hogy első használat előtt tisztázzunk pár dolgot az új változóval kapcsolatban. Ezt nevezzük deklaráció-nak. Azokban a nyelvekben, ahol a deklarációs utasítás szükséges, ott azt kell megadnunk ebben az utasításban, hogy az adott nevű változóban milyen jellegű adatokat és milyen módon szeretnénk tárolni. Később még foglakozni fogunk azzal, hogy például máshogy tárolódnak az egész számok és a valós számok, a szövegek pedig természetesen mindkettőtől eltérő módon. Ezen felül más műveleteket is végezhetünk rajtuk (valós számokat nem lehet maradékosan osztani, szövegeket nem lehet hatványozni, stb.). Ezek alapján az adatokat típus-okba sorolhatjuk. A típusos nyelvek esetén a deklarációs utasításban kell megadnunk egy új változó típusát és később ezen nem is változtathatunk.

Pszeudokódban egyetlen módszert alkalmaztunk az iteráció megvalósítására, azonban valós nyelvek esetén számos további ciklusszervező utasítás is elérhető. A korábban alkalmazott while-do-enddo szerkezet a feltételes ciklus-ok közé tartozik, mivel egy logikai feltétel dönti azt el, hogy kell-e ismételnünk a magot. A mag végrehajtása előtt a feltétel mindig kiértékelődik, tehát ez egy kezdőfeltételes ciklus volt. Ilyet a legtöbb valós nyelv alkalmaz. Van viszont végfeltételes ciklus is sok nyelvben. Ennél a ciklusmag egyszer mindenképpen végrehajtódik, majd kiértékelődik a ciklus végén megadott feltétel. Ha ez igaznak bizonyul a mag újra lefut, különben mehetünk a következő utasításra. Így a ciklus sohasem lehet üres ciklus.

A ciklusok egy teljesen más logikát követő csoportja az ún. előírt lépésszámú ciklus. Itt mindig van egy ciklusváltozó, aminek van egy kezdőértéke, egy végértéke és egy (előjeles) lépésköze. Ez az értékehármas meghatároz egy intervallumot és abban értékek egy véges rendezett sorozatát. A ciklusváltozó minden egyes értéket felvesz ezek közül és a mag ezekkel fut le minden alkalommal. Már a ciklus megkezdése előtt meg lehet mondani, hogy hányszor fog lefutni a ciklus, nem úgy, mint a feltételes esetben. Nem minden nyelv használja. A valós programnyelvek némelyike az eddigiektől eltérő ciklusokat is ismer (pl. felsorolásos ciklus, formailag végtelen ciklus), de ezek nem ismertek széles körben.

Mint említettük az utasítások programegységeket alkothatnak. Az alprogramok, tehát a függvény és az eljárás is programegységek. Ezeken kívül gyakran találkozhatunk még a blokk fogalmával, ami egy másik programegységben előforduló utasítások név nélküli csoportja. A blokknak fontos szerepe van a hatáskörkezelésben. További programegységek még a csomag, a taszk vagy az osztály is.

A fenti kiegészítésekkel át is néztük a forráskód főbb elemeit. Elkészülhetett a program forráskódja.

8.1.4. Programírástól a futtatásig

Amikor a programozó megír egy programot, az még nem futtatható közvetlenül. Ez még csak a forráskód, ami legtöbbször egy vagy több szöveges fájl. A processzorok csak gépi kódot tudnak futtatni, így valamilyen módszerrel át kell alakítanunk a forráskódot gépi kóddá. Erre többféle lehetőség is van. Vannak fordítóprogramos, interpreteres és hibrid technikát alkalmazó programozási nyelvek.

A fordítóprogramos nyelvek esetén létezik egy speciális program (fordítóprogram, complier), ami a forráskódot feldolgozza és egy ún. tárgykódot állít elő. A fordítóprogram előfeldolgozást, szintaktikai ellenőrzést, szemantikai elemzést, lexikai elemzést, optimalizálást és kódgenerálást hajt végre. Ha a forráskódban akár csak egyetlen szintaktikai hiba is van, akkor a folyamat megszakad, nem keletkezik tárgykód, a hibákról visszajelzést kapunk. Szintaktikai hibáktól mentes kód esetén ugyan előáll a tárgykód, de ez még nem futtatható.

A legtöbb esetben több forráskódból kell összeraknunk egy programot. Lehet, hogy egyes részeket nem is mi írtunk meg. Emiatt több tárgykódra is szükségünk lehet. Ahhoz hogy ezek a független tárgykódok együtt tudjanak működni, szükség van egy speciális programra, a kapcsolatszerkesztő-re (más néven linkerre). Ez a tárgykódú fájlokból előállít egyetlen futtatható fájl-t. Ezeknek például Windows operációs rendszerben .exe kiterjesztésük lehet.

Amikor elindítjuk a futtatható programunkat, akkor egy újabb rendszerkomponens, a betöltő (vagy loader) a fájl alapján betölti a gépi kódot a memóriába, létrehoz egy folyamatot, azaz egy végrehatás alatt álló programot, és átadja a vezérlést az új folyamatnak. A program ennek hatására elindul végre és lefut (ha nem ütközünk futási idejű hibába).

A fenti elven működő fordítóprogramos nyelvek egyik előnye, hogy csak egyszer kell előállítani a gépi kódot (egyszer kell a programot lefordítani) és utána az akárhányszor gyorsan futtatható. Olyan ez, mint amikor egy elvégzi valaki egy szöveg angol-magyar fordítását. Lehet, hogy sokáig tart, de csak egyszer kell megtenni, utána bármikor azonnal el tudjuk olvasni a lefordított magyar szöveget. Ha a fordítás sikeres biztosak lehetünk benne, hogy a program mentes mindenféle szintaktikai hibától. Ezt az elvet követik a C, a C++, a Pascal, az Fortran, a PL/I vagy a LabVIEW.

A programozási nyelvek egy másik nagy csoportja egy másik módszert, az interpreteres technikát használják. Ilyenkor a kiindulási forráskódot egy parancsértelmező (vagyis interpreter) kapja meg, ami sorról-sorra elkezdi értelmezni és azonnal végre is hajtani a programot (soronként végez lexikai/szintaktikai ellenőrzést, kódgenerálást, stb.) Amikor az első sor végrehajtódott, csak akkor kezdi el értelmezni a második sort. Így az is előfordulhat, hogy néha rejtve marad egy szintaktikai hiba (később gondot fog okozni, de egyelőre nem vesszük észre). Ez például akkor fordulhat elő, ha a hiba mondjuk egy elágazás else ágában van, de tesztelés során mi csak az igaz ágat hajtottuk vége. Egy másik hátránya a hogy nincs közvetlenül futtatható kód, minden egyes program végrehajtáskor az interpreter látja el a processzort gépi kódokkal (az értelmezés után), emiatt az interpreteres futtatás mindig lassabb lesz (minden futtatásnál újra szükséges a forráskód értelmezése). Ez ahhoz hasonlít, mint amikor egy csak magyarul értő személyt folyamatosan angolul instruálunk, így annak először meg kell kérnie a folyton vele lévő tolmácsot, hogy fordítsa le az instrukciókat (még akkor is, ha tizedik alkalommal hallja ugyanazt). Az adott nyelv interpretere nélkül nem tudjuk futtatni az adott gépen a programot. Ezt az elvet vallják a Python, a Perl, a PHP, a C# vagy a MATLAB.

Néhány nyelv a fenti két módszer egyfajta keverékét használja. A forráskódot először le kell fordítani egy speciális köztes kódra, az a bájtkód. Később ezt a leegyszerűsített kódot fogja interpreterként lépésről lépésre végrehajtani egy úgynevezett virtuális gép. Ezt a technikát alkalmazza a manapság nagyon elterjedt Java nyelv.

Az implementáció folyamata során egy ún. integrált fejlesztői környezet-et (IDE) szokás használni. Ez egy szoftvercsomag, amely lehetővé teszi a gyors és hatékony szoftverfejlesztést. Ez mindenképpen tartalmaz egy szövegszerkesztőt, ahol megírhatjuk a forráskód szövegét. Ezek a szövegszerkesztők általában nyelv érzékenyek. Ez azt jelenti, hogy ismerik az adott programozási nyelv szintaktikáját, így már gépelés közben képesek jelezni az esetleges szintaktikai hibákat. Általában különböző színnel jelölik a kód különböző funkciót betöltő karaktersorozatait (kulcsszavakat, azonosítókat, konstansokat, stb.). Számos esetben gépelés közben javaslatot tesznek a már begépelt szöveg értelmes kóddá történő automatikus kiegészítésére. Fordítóprogramos nyelvek esetén tartalmazzák magát a fordítóprogramot, és kapcsolatszerkesztőt, interpreteres nyelv esetén pedig a parancsértelmezőt. Van bennük a nyomkövetéshez, hibakereséshez különböző eszközrendszer. Tartalmazhatnak verziókövetési és projektkezelései lehetőségeket is. Természetesen futtatási környezetet is biztosítanak. Némelyik IDE több, mint egy nyelvet támogat. A legnépszerűbb integrált fejlesztői környezetek a teljesség igénye nélkül, a NetBeans, a CodeBlocks, az Eclipse, a Dev-C++, a Microsoft Visual Studio, és a PyCharm.

A programozási nyelvekkel való általános ismerkedés után át kell tekintenünk hogyan is tárolja a számítógép az adatokat. Erről szól a következő alfejezet.

8.2. Adatábrázolás számítógépen

Ahhoz, hogy egy nyelven jól tudjunk programozni és a programunk megfelelőképpen működjön, tudnunk kell, hogy az adott rendszer hogyan is tárolja pontosan az adatainkat. Adatábrázolás és adattárolás részletei jelentősen befolyásolják mind a tervezés, mind az implementáció folyamatát.

A mai számítógépek a Neumann-elveknek megfelelően mindent binárisan, vagyis kettes számrendszerben tárolnak. Ezért egy informatikusnak ismernie kell, hogyan működik ez a számrendszer. Emiatt kicsit részletesebben kell most foglalkoznunk a témával, mint korábban. A hétköznapi életben tízes (decimális) számrendszert használunk, ami azt jelenti, hogy minden számot 10 különböző számjegy segítségével adunk meg. Egy szám minden helyiértékéhez hozzárendeljük a számrendszer alapszámának egyik hatványát. Ezek alapján például az 5738 jelentését megadhatjuk 5*1000 + 7*100 + 3*10 + 8*1 alakban. A számokat sorba tudjuk rendezni: 0, 1, 2, …, 9. Ezután, mivel nincs több eltérő számjegyünk egy trükköt alkalmazunk. Az adott helyiértéken a 9 helyett a 0 számjegyet írjuk le és az előtte lévő helyiértéken eggyel nagyobb számjegyet alkalmazzunk megkapva a 10-et.

Ugyanezt a logikát alkalmazva, tehát a kettes számrendszerben összesen két féle szimbólumunk van (a 0 és az 1, ebben a sorrendben). Egy kettes számrendszerben megadott, vagyis bináris szám értéke itt is a helyiértékeken és az alapszám (2) hatványain alapul. A bináris 100110 értéke tehát 1*32 + 0*16 + 0*8 + 1*4 + 1*2 + 0*1 vagyis ez a decimális 38. A számolás itt is ugyanazon elvekre épül, mint tízes számrendszerben csak összesen két számjegyünk van: 0, 1. Mivel elfogytak a számjegyek az 1 helyére 0-t írunk és az előző helyiértéken vesszük a következő számjegyet (ha kell rekurzívan), azaz a sorrendben következő bináris érték az 10, majd az 11. A legkisebb helyiértéken ezután az 1 helyére 0 és vennünk kell a sorban következő számjegyet a megelőző helyiértéken. Viszont ott is 1 szerepel, azaz ezt is 0-ra cseréljük és vesszük az azt megelőző helyiértéken a következő számjegyet, vagyis a következő bináris szám az 100. És így tovább. Az első 16 bináris pozitív egész szám tehát így néz ki:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
       1
      10
      11
     100
     101
     110
     111
    1000
    1001
    1010
    1011
    1100
    1101
    1110
    1111
   10000

Látható, hogy a legkisebb helyiértéken a két érték folyton váltakozik, előtte minden második lépésben, az azelőtti helyiértéken minden negyedik lépésben, majd 8, 16, 32, stb. lépésenként.

Néha egy tízes számrendszerbeli számot át kell váltanunk bináris alakra, néha pedig fordítva. Hogyan történik ez a konverzió? Egy általános valós szám decimálisból binárisba váltását leíró algoritmus így hangzik:

  1. Az átváltott szám előjele ugyanaz legyen, mint a váltandó szám előjele.

  2. Vedd először a váltandó számnak csak az egész részét!

  3. Ha ez az érték 0, akkor folytasd a 7. lépéssel, különben menj tovább!

  4. Az értéket maradékosan oszd el kettővel és jegyezd meg a maradék értékét (0 vagy 1)!

  5. Az értéket oszd el (hagyományosan is) kettővel és a hányados egész része legyen az új érték!

  6. Folytasd a 3. lépéssel!

  7. Írd ki a korábban megjegyzett maradék értékeket fordított sorrendben, vagy ha egy ilyen sem volt, akkor egy 0-t, majd írj le egy kettedes vesszőt!

  8. Vedd az eredeti átváltandó decimális számnak kizárólag a tört részét!

  9. Ha ez 0 (netán nem érdekel több tört helyiérték), akkor kész vagy, különben folytasd a következő lépéssel!

  10. Szorozd meg az értéket kettővel!

  11. A szorzat egészrészét írd le a következő (kisebb) helyiértékre!

  12. Az új vizsgált érték legyen az előbbi szorzat tört része!

  13. Folytasd a 9. lépéssel!

Ezek alapján beláthatjuk, hogy a decimális -213.5625 érték a bináris -11010101.1001 alakban írható fel. Megjegyzendő, a két szám értéke megegyezik, csak a felírásuk módja más. Számításunkat ellenőrizhetjük, ha elvégezzük a binárisból decimálisba váltást, aminek az algoritmusa így is felírható:

  1. Írd le az eredeti bináris szám előjelét!

  2. Vedd a bináris szám legnagyobb helyiértékű számjegyét és jegyezd ezt meg!

  3. Ha már nincs további egész helyiértékű számjegye a bináris számnak, akkor menj a 5. lépésre, különben a megjegyzett értéknek vedd a dupláját és add hozzá ezt következő bináris számjegyet! Most már ezt a számot tartsd fejben!

  4. Folytasd a 3. lépéssel!

  5. Írd le a megjegyzett számot majd egy tizedesvesszőt!

  6. Jegyezd meg a 0 számot és vedd a bináris szám legnagyobb tört helyiértékű számjegyét!

  7. Ha nincs további tört helyiértékű számjegye a bináris számnak, akkor menj a 9. lépésre, különben a megjegyzett értéknek vedd a dupláját és add hozzá a következő bináris számjegyet! Most már ezt a számot tartsd fejben!

  8. Folytasd a 7. lépéssel!

  9. Oszd el a megjegyzett számot kettőnek annyiadik hatványával, ahány tört tizedes jegy van az eredeti bináris számban!

  10. Írd le a hányados tört részét a tizedesvessző után! Kész vagy.

Más számrendszerek is hasznosak lehetnek az informatikában. Ilyen például a gyakran alkalmazott tizenhatos (hexadecimális) számrendszer. Itt 16 különböző számjegyünk van: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E és F. A számolás és átváltás logikája nagyon hasonló, mint a kettes számrendszer esetén, csak a 2 helyett 16-tal kell az műveleteket (maradékos osztás, szorzás, stb.) elvégezni. Észrevehetjük, hogy a bináris és a hexadecimális számrendszer között egy elég szoros kapcsolat van, ami megkönnyíti az egyikből a másikba történő közvetlen átváltást. Ugyanis a 16 az nem más, mint a 2 negyedik hatványa. Emiatt egy négy bináris számjegyet tartalmazó csoport felfogható egyetlen hexadecimális számjegyként. Például a bináris 11 0010 1110 1100 1101 1001 értéke megfelel a hexadecimális 32ECD9 értéknek (ami a decimális 3337433 értékkel egyenlő).

Amellett, hogy tudunk kettes számrendszerben számolni, hasznos lehet egy informatikus számára, ha matematikai műveleteket is tud végezni bináris számokkal. Most csak a bináris összeadás kerül bemutatásra, ezen belül is először a különálló számjegyek összege.

A

B

A+B

0

0

0

0

1

1

1

0

1

1

1

10

Az első három eset viszonylag egyértelmű. A negyediknél az eredmény bináris alakban már két számjegyű (ez a decimális 2). Több helyiértékű számok összeadása esetén majd ilyenkor azt mondjuk, hogy az adott helyiértéken az eredmény 0, de keletkezett egy átviteli érték, az 1, amit majd a következő helyiértéken való összeadásnál kell figyelembe vennünk. Ezek alapján láthatjuk, hogy általános esetben három bináris helyiérték összeadását kell megvalósítanunk. Ezt foglalja össze a következő táblázat, ahol tehát A az összeadás első tagjának adott helyiértékű számjegye, a B a másik tagé, C az előző helyiértéken keletkezett átvitel, D az eredmény adott helyiértékű számjegye, míg E a következő helyiértékre átvinni szükséges érték.

A

B

C

D

E

0

0

0

0

0

0

0

1

1

0

0

1

0

1

0

0

1

1

0

1

1

0

0

1

0

1

0

1

0

1

1

1

0

0

1

1

1

1

1

1

Ezek alapján az 101101010 és a 1101100 összege 111010110 (vagyis decimálisan 362+108=470).

Jó tudni, hogy minden matematikai műveletnek az algoritmusa használható bináris számokra is csak szem előtt kell tartanunk, hogy mindössze két számjegyünk van. Ha például végrehajtunk egy szorzást bináris számok között, könnyen rájöhetünk, hogy kettes számrendszerben a szorzótábla mindössze 4 elemű (a megszokott 100 helyett) és a négy érték közül ráadásul három 0.

Ahogy már említettük az informatikában mindent kettes számrendszerben írunk le. Egy bináris helyérték neve bit. Természetesen egy bit két értéket vehet fel: 0 vagy 1.

A fentiek alapján bármit is szeretnénk a számítógépen tárolni, azt először számokká kell alakítanunk és ezeket a számokat bitek sorozataként felírnunk. Azt a folyamatot, amely során egy tetszőleges adatot bitsorozattá alakítunk kódolás-nak hívjuk. Ezt ne keverjük össze a titkosítással! A kódolás az adat egy eddigitől eltérő megjelenítési formája, amelynek értelmezése mindenki számára elérhető. Az ezzel ellentétes folyamat tehát, amikor egy bitsorozatot valamilyen adatként próbálunk meg értelmezni, dekódolás-nak hívjuk.

A kódolás menete sokféle lehet. Vegyünk egy példát! Minden nap az ebédhez gyümölcsöt szeretnénk fogyasztani. Összesen három gyümölcs közül választhatunk: alma, banán, őszibarack. Ha az egyes választásokat el szeretnénk tárolni, azt számítógépen csak bitek formájában tudjuk megtenni. Kódolnunk kell tehát a gyümölcsöket. Rendeljünk hozzájuk egy-egy (bináris) számot! Például legyen az „alma” az 1, a „banán” a 2, és az „őszibarack” a 3. Mind a három értéket le tudjuk írni 2 bit segítségével, sőt mivel két biten 4 különbözőféle kombináció is lehet az egyik bitpáros kihasználatlan lesz. Így tehát a gyümölcseink kétbites kódolása így néz ki:

gyümölcs

kód

alma

01

banán

10

őszibarack

11

kihasználatlan

00

Persze, ha akarunk a kihasználatlan bitpárnak is tulajdoníthatnunk értelmet, jelentse például a 00 kód azt, hogy aznap egyáltalán nem fogyasztunk gyümölcsöt az ebédhez. Ha idővel bővülne a választék és választhatnánk körtét vagy szőlőt is a korábbiak mellett, akkor a kétbites kódolás már nem lenne megfelelő. Hat (5 gyümölcs + a „semmi”) különböző dolog kódolásához már legalább 3 bit szükséges. Ennek megfelelően az ENSZ által jelenleg elismert 193 ország kódolásához már 8 bit szükséges. Nyolc biten ugyanis 256 különböző bitkombináció szerepelhet, míg hét biten csak 128 (ami nem elegendő).

Tehát minden adathoz egy számot kell rendelnünk és ezt a számot bitek sorozataként felírni. Ennek a második lépésnek, tehát a számábrázolás-nak számos szabványosított megoldása terjedt el az informatikában. Ezek sohasem tetszőleges számú bitet használnak, mert az technikailag rettentően megnehezítené a számítógép működését. Minden adat 8 bites egységekben kerül felhasználásra. A 8 bit együttes neve bájt. Egy adatot tárolhatunk, mondjuk 2 vagy 4 bájton, de sohasem 21 biten.

Nézzünk egy való életbeli számábrázolási szituációt! Az egyik legelterjedtebb (és legegyszerűbb) módja természetes számok tárolásának az ún. előjel nélküli fixpontos számábrázolás. Ez sokszor 4 bájton történik, azaz a pozitív egész számokat és a nullát 32 biten tároljuk el, egyszerűen úgy, hogy a szám kettes számrendszerbeli alakját vezető nullákkal kiegészítjük 32 bitre. Például a 2452132 értéket az alábbi bitsorozat reprezentálja:

00000000 00100101 01101010 10100100

Könnyen belátható, hogy ez a technika korlátos, nem tudunk akármekkora számokat eltárolni. A tízmilliárd például bináris alakban 34 helyiértéket tartalmaz, így nem tárolható el 4 bájton.

Mi a helyzet a negatív számokkal? A fenti módszer definíció szerint nem foglalkozik negatív értékekkel, minimuma a nulla volt. Hogyan tároljunk ennél kisebb egész számokat? A hétköznapokban egy extra szimbólumot (-) használunk a 10 számjegy szimbólum mellett. Azonban a számítógépen csak 1-esek és 0-ák szerepelhetnek, nincs harmadik szimbólum. Egy trükkös megoldást kell alkalmaznunk, ami az előjeles fixpontos számábrázolás névre hallgat. Ezt (negatív és pozitív) egész számok megjelenítésére is használhatjuk. A nulla és a pozitív egészek ugyanúgy tárolódnak, mint az előjel nélküli esetben (bináris alakban szükség esetén vezető nullákkal kiegészítve). A negatív értékek reprezentációjának kitalálásakor egy fontos elvet követtek elődeink: ugyanaz az összeadó algoritmus használható legyen mind pozitív, mind negatív egész számok esetén is. Ennek a feltételnek a kielégítéséhez szükségünk van az egyes és kettes komplemens fogalmára. Egy bitsorozat egyes komplemensén azt a bitsorozatot értjük, amelyben minden bit az eredeti bit ellentéte. Egy szám kettes komplemense pedig nem más, mint a számot ábrázoló bitsorozat egyes komplemensénél egyel nagyobb bináris érték (adott számú biten). Ezek alapján írjuk fel az előjeles fixpontos számábrázolás algoritmusát!

  1. Jegyezd meg a reprezentálandó egész érték előjelét!

  2. Írd le a szám abszolút értékét binárisan!

  3. Egészítsd ki az eredményt vezető nullákkal!

  4. Ha az eredeti szám negatív volt, akkor folytasd az 5. lépéssel, különben menj a 7. lépésre!

  5. A teljes bitsorozatot váltsd át az ellentétére, azaz állítsd elő az egyes komplemensét!

  6. A kapott bináris értékhez adjál hozzá egyet, azaz vedd a kettes komplemensét!

  7. Ha az eredmény több hasznos bitet tartalmaz, mint megengedett (pl. 16, 32, 64 bit), akkor az érték nem ábrázolható ilyen feltételek mellett. Ellenkező esetben a bitsorozat végén lévő megfelelő számú bit (16, 32, 64) reprezentálja a kívánt előjeles egész számot.

Ismét látható, hogy ez a módszer is korlátos. Természetesen adott számú biten nem tárolhatunk akárhány értéket. Lesz egy minimum és egy maximum, és ezek között minden egész szám ábrázolható lesz. Ennek a technikának van egy hasznos következménye. Ha az eredmény bitsorozat első, legnagyobb helyiértékű bitje 1, akkor tudhatjuk, hogy az érték negatív ellenkező esetben pedig vagy nulla, vagy pozitív. Emiatt ezt a bitet szoktuk előjelbitnek is nevezni.

A fentiek alapján tehát 2 bájtos előjeles fixpontos számábrázolás esetén a +100 értéket a 00000000 01100100 bitsorozat reprezentálja, míg a -100 értéket az 11111111 10011100 bitsorozat formájában kódolhatjuk a számítógépes szabvány szerint. Ha összeadjuk ezt a két értéket a korábban tanult bináris összeadás algoritmusát használva, akkor az eredmény számunkra fontos utolsó 16 bitjén csak 0-k szerepelnek, ami a nulla egész szám kódja. Ez nem meglepő, hiszen a +100 és a -100 összege 0, ami viszont fontos, az az, hogy így összeadással meg tudjuk valósítani a kivonást is. (Mivel az A-B felírható (+A)+(-B) alakban.

Egy előre meghatározott tartomány összes egész számát már le tudjuk kódolni bitekre, de mi a helyzet a valós (tört) számokkal? Itt már nem csak a szám helyiértékeit kell tárolnunk, hanem a nagyságrendjét, azaz a tizedesvessző helyét is. Erre egy jól bevált és széleskörűen alkalmazott szabvány a lebegőpontos számábrázolás. Egy valós szám kódolása ilyen módszerrel elég összetett. A részletekkel a „Számítógép architektúrák” tantárgy keretében ismerkedhet meg az olvasó. Magyarázat nélkül a 4 bájtos (ún. egyszeres pontosságú) lebegőpontos számábrázolás menete általános esetben a következő:

  1. Ha a reprezentálandó szám negatív, akkor a bitsorozat első értéke (előjelbit) 1 legyen, különben 0!

  2. Váltsd át a szám abszolút értékét kettes számrendszerbe!

  3. A kettedes vesszőt told el a legelső 1-es bit mögé! (Vagyis szorozd meg az értéket 2 annyiadik hatványával, hogy az eredmény nagyobb vagy egyenlő legyen eggyel, de kisebb, mint a bináris kettő!) Ahány pozícióval balra került a kettedes vessző azt a számot nevezzük karakterisztikának! (Jobbra tolódás esetén ez negatív érték.) A kettedes vessző eltolása révén kapott bináris értéket nevezzük mantisszának!

  4. A karakterisztika értékéhez adj hozzá 127-et és az így kapott érték 1 bájtos bináris alakja legyen az eredmény következő 8 bitje!

  5. A mantiszában a kettedes vessző után szereplő bitek legyenek az eredmény további bitjei! Ha ez több, mint 23 bitet jelent, akkor csak a kerekítés utáni első 23 bitre van szükség. Ha kevesebb, mint 23 (tört) bit szerepel a mantisszában, akkor 0 bitekkel kell kiegészíteni 23 bitre!

Például ha a -193,1875 binárisan -11000001,0011 alakú, az előjel miatt a végeredmény első bitje 1. Az abszolút értékben szereplő tizedesvessző eltolása után a mantissza 1,10000010011 lesz, a karakterisztika pedig 7 (mert ennyi pozícióval került balra a tizedesvessző). A karakterisztika plusz 127, most 134-et jelent, ami 8 biten binárisan 10000110. A mantissza tört része 23 biten pedig 10000010011000000000000. Vagyis a -193,1875 értéket egyszeres pontosságú lebegőpontos számábrázolással kódoló 4 bájt így néz ki:

11000011 01000001 00110000 00000000

Látható tehát, hogy különböző céllal sokféle számábrázolási, kódolási technika van használatban. Emiatt egy adott bitsorozat mindig többféle értéket is képviselhet. Az előbbi 32 bit tehát jelentheti a -193,1875 valós számot, de akár a -1019138048 egész számot, a -15551 és +12288 együttes értékpárt, de akár a "ĂA0" karaktersorozatot is. A programozónak kell tudnia, hogy milyen memóriaterületen, milyen reprezentációs technikával, milyen értéket tárol.

Ezt explicit módon segíti a számos programozási nyelv esetén használandó típus alkalmazása. Ezen nyelvek esetén a deklarációs utasításban minden változó esetén meg kell adni használat előtt annak típusát. Az egyes adattípusok eltérnek abban, hogy az ilyen típusú változók milyen értékeket vehetnek fel, azokat milyen bitsorozat reprezentálja a számítógépen, hogy rajtuk milyen műveleteket hajthatunk vége.

8.3. Szójegyzék

assembly

A programozásnak egy olyan formája, amely filozófiáját tekintve nagyon közel áll a gépi kódhoz és hardveres ismereteket igényel a konkrét célrendszerre vonatkozóan.

bájt

A számítógépes adattárolás alapegysége. 8 bitből álló adat neve, mely 256 különböző értéket képviselhet.

bit

Kettes számrendszerbeli számjegy. Két lehetséges állapot képes reprezentálni 1 és 0 értékekkel.

deklaráció

Az utasítások egy csoportja, amellyel egy változó nevét és típusát összerendelhetjük, és közben létrehozhatjuk magát a változót is a memóriában.

dekódolás

A kódolással ellentétes folyamat, mely során egy bitsorozatot egy megfelelő adatként tudunk értelmezni.

egyes komplemens

Egy bitsorozat össze bitjének az ellentétes bitre történő átváltása. Például a 01101100 egyes komplemense az 10010011.

előírt lépésszámú ciklus

Az iteráció egyik fajtája, ahol a ciklusváltozó egy megadott egyenközű értékkészlet elemeit feszi fel és mindegyik érték esetén egyszer lefut a ciklus magja.

feltételes ciklus

A ciklusok azon csoportja, ahol egy feltétel határozza meg, hogy kell-e még futtatni a ciklus magját. A valós nyelvekben általában igaz feltétel esetén fut le a ciklusmag. Beszélhetünk kezdőfeltételes és végfeltételes altípusokról.

fordítóprogram

Egy olyan szoftver, amely egy bizonyos programozási nyelven megírt forráskódot értelmezve előállít egy tárgykódot. Ilyen használ a C nyelv is.

forráskód

A programozó által az adott nyelv szintaxisának betartásával létrehozott szöveges fájl. Közvetlenül nem futtatható, fordítóprogramos vagy interpreteres technika révén állítható elő belőle a processzor által érthető gépi kód.

futási idejű hiba

Olyan programozási hiba, amelyet valamilyen a program futtatásának idején fellépő körülmény idéz elő. Például nullával való osztás, nem létező erőforráshoz való hozzáférési kísérlet, stb.

futtatható program

A fordítás és linkelés során előállt állomány, melyet elindítva a benne található gépi kód a számítógép számára végrehajthatóvá válik. Tetszőleges számban futtatható (a fordítóprogram nélkül is).

gépi kód

Bináris kód, amelyet a processzor közvetlenül is végre tud hajtani. 0 és 1 bitek sorozataként tárolja az algoritmusban meghatározott elemi tevékenységeket.

implementáció

Az a folyamat, amely során a korábban megírt algoritmust egy valós programozási nyelven adunk meg számítógépen létrehozva a forráskódot. Az implementáció folyamatának része az elkészült program tesztelése is.

infix kifejezés

A kifejezések olyan írásmódja, ahol a kétoperandusú operátorok az operandusok között vannak. Néha kötelező zárójeleket is használni. A kiértékelés sorrendjének meghatározásához ismernünk kell az egyes operátorok erősségét, azaz precedenciáját.

integrált fejlesztői környezetet

Egy szoftvercsomag, amely a programozó számára lehetővé teszi a gyors és hatékony szoftverfejlesztést. Különböző szolgáltatásokkal segíti a fejlesztés folyamatát.

interpreter

Egyes nyelvek által használt parancsértelmező, amely a program forráskódját utasításonként értelmezi és rögtön végre is hajtja azt. Nem állít elő tárgykódot és futtatható állományt.

kapcsolatszerkesztő

A különböző forrásokból származó tárgykódok összeszerkesztésére szolgál. Létrehozza a futtatható állományt.

kettes komplemens

Egy bitsorozaton végzett művelet, mely során annak egyes komplemenséhez binárisan hozzáadunk 1-et. Például az 10011000 kettes komplemense a 01101000.

kettes számrendszer

A numerikus értékek olyan megjelenítési formája, ahol kizárólag kétféle számjegyet használhatunk. Ezek a 0 és az 1.

kódolás

Adatok és bitkombinációk egyértelmű egymáshoz rendelése. Segítségével minden számítógépen tárolandó dolgot 0-ák és 1-esek sorozatává tudunk alakítani. A visszaalakítás, vagyis a dekódolás is egyértelmű kell legyen.

magas szintű programozási nyelv

A programozási nyelvek harmadik generációjába tartozó nyelvek csoportja. Jóval absztraktabb, mint a korábbi assembly programozás. Ide tartozik például a C, a C++, a C#, a Java és a Python nyelv is.

megjegyzés

A program forráskódjának egy kiegészítése, mely nem befolyásolja a végrehajtás mentét, de hasznosak lehetnek a programozó számára, segíthetik a kód könnyebb megértését.

posztfix kifejezés

A kifejezések egyik egyértelmű megjelenési formája, amelyben a kétoperandusú operátorok mindig az operandusaik után helyezkednek el.

prefix kifejezés

Kifejezések megadásának egyik egyértelmű módja, ahol az operátorok mindig megelőzik az operandusaikat.

számábrázolás

Numerikus értékek felírási módja adott darabszámú bit segítségével. Például egész számok esetén alkalmazhatjuk a fixpontos számábrázolást, törtek esetén pedig a lebegőpontos számábrázolást.

szemantika

Egy program értelmezése, jelentése. Azt határozza meg, mire szolgál, és hogyan működik egy program. A program által használt algoritmus határozza meg.

szemantikai hiba

A program (algoritmus) írása során vétett hiba, amely miatt az máshogy fog működni, mint ahogy terveztük.

szintaktikai hiba

Programozói hiba, amely akkor keletkezik, ha nem tartjuk be az adott nyelv szintaxisát/szintaktikáját (például elgépelünk valamit).

szintaxis

Egy adott programozási nyelv formai és tartalmi szabályrendszere, ami meghatározza, milyen eszközt hogyan használhatunk az adott nyelven írt forráskódban. Egy nyelv szintaxisának ismerete egyik (de nem az egyetlen) előfeltétele annak, hogy azon a nyelven képesek legyünk működőképes program megírására.

tárgykód

Fordítóprogramos nyelvek esetén a forráskódból generált állomány, mely bár közel áll a gépi kódhoz, önmagában nem futtatható.

típus

Adatok általános jellemzője, amely meghatározza az adat tárolásának módját és korlátait, valamint a rajtuk elvégezhető műveletek halmazát.

tizenhatos számrendszer

A numerikus értékek olyan megjelenítési formája, ahol összesen 16 féle különböző számjegyet használhatunk. Ezek rendre a következők: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E és F.

tizes számrendszer

A számok szokásos megjelenítési formája, ahol kizárólag 10 féle számjegyet használhatunk.

8.4. Kérdések, feladatok

  • Ha már ismersz valós programozási nyelveket, akkor tudd meg, hogy azok a programozási nyelvek melyik osztályába tartoznak!

  • Tegyük fel, hogy az alábbi pszeudokód a B számot az természetes számként megadott E kitevőre emeli és kiírja a hatvány értékét. Keress a kódban szintaktikai és szemantikai hibákat! [S802]

    1
    2
    3
    4
    5
    6
    7
       input B
       P=0
       wihle E<=0
         P=P*B
         E-1=E
       endo
       output P
    
  • Mennyi az értéke az alábbi prefix kifejezéseknek? [S803]

    + 103 - / * 6 35 14 7
    - - - 900 1000 3 20
    
  • Mennyi az értéke az alábbi posztfix kifejezéseknek?

    24 6 / 2 3 * + 10 -
    42 35 10 12 6 / / / /
    
  • Alakítsd át az alábbi prefix kifejezést infixé!

    * + 20 10 - 20 10
    
  • Alakítsd át az alábbi posztfix kifejezést infixé! [S806]

    1 3 4 * 1 2 + / 103 97 - * +
    
  • Alakítsd át az alábbi infix kifejezéseket posztfixé és prefixé is!

    1 + 2 * 3 + 4
    (1 + 2) * (3 + 4)
    1 + 2 * (3 + 4)
    (1 + 2) * 3 + 4
    
  • Egy interpreteres nyelven írt tetszőleges program forráskódja vagy tárgykódja hosszabb?

  • Az integrált fejlesztői környezetek automatikusan fel tudják hívni a programozó figyelmét a szemantikai hibákra? [S809]

  • Melyik a következő egész szám kettes számrendszerben a 110100101111 után?

  • Váltsd át kettes számrendszerbe a következő decimális számokat: 64; 63; 0,1; -25,25!

  • Váltsd át tízes számrendszerbe a következő bináris számokat: 10000; 1111; 0,1; -1011010,0101!

  • Váltsd át kettes számrendszerbe a következő hexadecimális számokat: 123; 7F; BAD!

  • Váltsd át tizenhatos számrendszerbe a következő bináris számokat: 011001100110; 1010111010; 1111111011101101!

  • Mennyi az összege a 10010011 és 111010 bináris számoknak?

  • Végezd el a 10001 és a 110 szorzását kettes számrendszerben! [S816]

  • Hány különböző bitkombinációt tudsz felírni 4 biten és 10 biten?

  • Minimálisan hány bitet használnál 30 különböző dolog kódolására?

  • Két bájton egyértelműen kódolhatjuk-e a Föld népességét? [S819]

  • Add egy a 01110110 bitsorozat egyes majd kettes komplemensét!

  • Mi az a 32 bit, amely előjeles fixpontos számábrázolás esetén a -1 értéket képviseli? [S821]

  • Ábrázold a +3,5 értéket egyszeres pontosságú (4 bájtos) lebegőpontos számábrázolással! [S822]

  • Mit gondolsz, típusokat használó programozási nyelv esetén egy változó bármilyen értéket felvehet? [S823]