A program mint adathalmaz



1, A program mint utasítások sorozata
2, A magas szintű programnyelvek
    2a, A C/C++ nyelvhez ajánlott IDE-k
3/1, Ugrások/elágazások a programban
    3a, A teljes feltételes utasítás
    3b, A rövidített feltételes utasítás
    3c, A switch-case utasítás
3/2, Ciklusok a programban
    3d, A while ciklus
    3e, A do-while ciklus
    3f, A for ciklus
4, Ciklusokkal kapcsolatos utasítások
    4a, break
    4b, continue


1, A program mint utasítások sorozata

Mint erről már szó esett, a processzor egy számológéptől eltérően tetszőleges logikai műveletek alapján, vagy azok nélkül utasításokat hajt végre, és ennek megfelelően számolhat is.

A program alapvetően nem más mint olyan utasítások sorozata, amiket a processzor közvetlenül megért. Természetesen ez nem mindig ilyen egyszerű.

Vegyünk példának egy közvetlen rendszert, ahol a gép erőforrásaihoz engedély nélkül hozzáférhet a program - ilyen az pl. DOS. Egy DOS alkalmazás elindítás után betöltődik a memóriába, azt teljesen el is foglalhatja - átírhatja, miközben az összes hardverrel közvetlenül operálhat.

Egy ilyen alkalmazás általában .COM kiterjesztéssel van ellátva, és valóban igaz rá hogy közvetlenül ad utasításokat a processzornak. Miután elindul, a processzor az utasításokat sorban végrehajtja [hogy hol jár azt megadja az utasításmutató], aminek következtében mi futás közben élvezzük az eredményt. Természetesen a programban lehet elágazás, feltétel, ilyenkor ugrásokat kell benne csinálni [az utasításmutató elmozdításával] feltételek alapján.

Egy hasonló alkalmazás elkészítésének két módja van:

  • A processzor számára érthető állományt állítunk elő - hozzátenném hogy az utasítások számként vannak tárolva (gépi kód)
  • A mi számunkra érthető állományt állítunk elő - és egy "fordító" segítségével átalakítjuk a gép számára érthető formátumba (programkód).
    Egy ilyen állományt .ASM kiterjesztéssel bármilyen szövegszerkesztővel előállíthatunk, és fordítóval .COM fájllá alakíthatjuk. A programnyelv amit itt használhatunk az Assembly, és közvetlenül a processzor utasításait adjuk meg, ám számok helyett ezúttal szövegesen:
    mov ax, 13h
    int 10h
    mov dx, 3c8h
    xor al, al
    out dx, al
    inc dx
    mov cx, 256
    xor al, al

Természetesen nem kell ilyen programnyelven is megtanulni a C/C++ használatához, de nem árt tudni hogy valamikor innen indultak a dolgok.

2, A magas szintű programnyelvek

Ha Assembly-t használunk hamar felakadhatunk azon hogy a megírt program nem fog minden gépen elfutni. Hát elég belegondolni hogyha egy kicsit más a másik gép processzora, akkor egy utasításra lehet hogy van alternatíva, vagy épp csak az van és emiatt újraírhatjuk az egészet.

A másik probléma a programmal hogy a részekre bontásának a lehetőségei korlátozottak, és elég nehézkésen is érthető maga a kód - ki tudja mit csinál a fenti részlet pl.

Ezen okok miatt megjelentek a "magas szintű programnyelvek" mint a C/C++, Pascal, stb. Ezeknek néhány fő jellemzője:

  • a program több fájlból is állhat, azokon belül számtalan részegységből
  • a programban nem kell mindent kivitelezni amit a processzor alap utasításkészlete nem tud - függvénytárak
  • a programot a fordító által bármilyen gépre/op. rendszerre előállíthatja módosítás nélkül
  • a program kódja szemléletes, ránézésre meg lehet mondani mit csinál

Természetesen itt mindjárt előjönnek problémák. Hiszen hogyha több fájlból áll, akkor azokat nem olyan egyszerű egybeolvasztani. Itt már a folyamatba beékelődik egy újabb program a "linker"/"szerkesztő". Ennek a feladata az hogy a külön/külön lefordított állományokat egy .EXE fájlba gyúrja össze.

Ha már ugye .EXE fájllal dolgozunk érdemes említést tenni a védett mód szépségeiről is, hiszen ezekkel megjelent az is (védett módú DOS, Windows, stb.), és mivel több program futhat egyszerre védett módban, azokat össze kell szinkronizálni hogy ne ütközzenek.

  • fizikai memória:
    • korlátozható memóriafogyasztás
    • egyedi címtartomány - a nulladik cím ezúttal nem a memória első blokkja, hanem az amit az op. rendszertől megkap
    • ebből kifolyólag egyik sem írhatja/olvashatja a másik memóriaterületét, hiszen ugyanaz a cím a másik programban is megvan és ott teljesen mást jelent
  • processzor:
    • az utasításokat a rendszer osztja el - úgy tűnik mintha minden egyszerre történne pl. 3 programban - ehelyett folyamatosan alternálva futnak az utasításaik (processzoridőt kapnak)
  • merevlemez, adathordozók:
    • a fájlok megosztásra kerülnek, a program eldöntheti hogy kizárólagos hozzáférést kér a fájlhoz, vagy megengedi hogy egy másik program beleolvasson/beleírjon amíg dolgozik a fájllal, vagy csak egy részét zárolja a fájlnak
  • nyomtató, egyéb nem megszakítható eszközök:
    • mivel elég érdekes lenne ha egy nyomtatás közepén másik dokumentum nyomtatása is megkezdődne, ezek az eszközök várólistát kapnak, és egymás után fogják végrehajtani a megkapott feladatokat

Mint már említettem több programnyelven is lehet ilyen védett módú .EXE állományt előállítani, csak éppen a fordító fog eltérni. A C/C++ nyelvben a GCC fordítót, és az LD linkert érdemes használni futtatható állomány előállításához - mivel ezek több rendszer alá is elérhetőek (pl. Windows, Linux, ...).

2a, A C/C++ nyelvhez ajánlott IDE-k

Az IDE lényege hogy a már említett eszközöket egy programcsomagba foglalja, illetve lehetőséget biztosít a hibakeresésre is grafikus felületből - ezeken felül pedig tartalmaz egy szövegszerkesztőt is, ami legtöbb esetben a példákhoz hasonló módon színezi a kulcsszavakat/konstansokat/stb...

Mivel az IDE kiválasztását már megtetted az első cikkben (remélhetőleg), itt csak egy áttekintés lenne arról - hogy mégis melyik mire képes stb. - illetve olyan csomagok hogy a legmodernebb gépeken is helyesen menjenek.

Tovább az IDE gyűjtemény oldalára: link

3, Ugrások/elágazások/ciklusok a programban

A programban lévő ugrásoknak/elágazásoknak két célja lehet:

  1. elágazás: feltételek alapján más és más történjen (feltételes utasítás)
  2. ugrás: amíg egy adott feltétel nem áll fenn, a program bizonyos részét ismételgetjük

A programozási gyakorlatban mind kettőnek fontos szerepe van.

Megjegyzés: érdemes megtartani/megtanulni a példákban látható tagolást - a későbbiekben átlátható programot eredményez.

Fontos! ha egy ciklusnál rossz feltételt adunk meg a program körbe-körbe fog állni egy helyben, miközben 80-90%-os processzorterhelést csinál - mindig győződjünk meg arról hogy van-e kiút a ciklusból, és ha igen akkor józan korlátokon belül van-e. - ezek feltételes, elágaztató utasításokra nem vonatkoznak

3a, A teljes feltételes utasítás

Egy feltételtől függően megadható hogy mi történjen ha teljesül, esetleg mi hogyha nem:

if (állítás) utasítás;

 Hogyha az állítás teljesül, akkor az utasítás lefut egyébként nem. Ha több utasításunk is van használhatunk listát, vagy kapcsos zárójelet:

if (állítás)
{
  utasítás1;
  utasítás2;
}

Hogyha szeretnénk megadni azt is hogy mi van ha a feltétel nem teljesül:

if (állítás)
{
  utasítás1;
  utasítás2;
}
else
{
  utasítás3;
  utasítás4;
}

Természetesen a kapcsos zárójelek nélkül is működhet, ha csak egy utasításunk van:

if (állítás)
  utasítás1;
else
  utasítás2;

És a sokszorosítható az ágak száma is, pl.:

if (állítás1)
{
  utasítás1;
  utasítás2;
}
else if (állítás2)
{
  utasítás3;
  utasítás4;
}
else
  utasítás5;

Konkrét példa (abszolút érték számítása):

int szam = 5, abszolut_ertek;

if
(szam >= 0)
  abszolut_ertek = szam;
else

  abszolut_ertek = -szam;

3b, A rövidített feltételes utasítás

Ennél a variációnál mindig két ág van - ha teljesül, ha nem. Az előnye az előzőhöz képest hogy beékelhető utasítások közé is (lásd itt):

állítás ? utasítás_ha_igaz : utasítás_ha_hamis;

Konkrét példa (abszolút érték számítása):

int szam = 5, abszolut_ertek = (szam >= 0 ? szam : -szam);

Egy hangyányit rövidebb lett ezen a módon. Természetesen több ág is elágaztatható, ha egybeágyazunk ilyeneket - bár ilyenkor sokkal jobban átlátható a teljes változat.

3c, A switch-case utasítás

Előfordul hogy nagyon sok lehetséges esetet kéne megadni, és ilyenkor az if-else if-...-else ág nem a legmegfelelőbb, ezért vezették be a switch-case utasítást.

A switch-case csak sorszámozott típusra alkalmazható. A valós szám pl. nem az, sem a szöveg - ilyenkor marad az if-es megoldás illetve trükkösen egy for-if+tömb kiválthatja ilyenkor ezt.

switch (változó)
{
  
case eset1:
   ...
  
case esetN:
     utasítás1;
     ...
     utasításN;
    
break;

   case eset2:
   ...
   case esetM:
     utasítás1;
     ...
     utasításM;
    
break;
 
   ...

  
default:
     utasítás1;
     ...
     utasításI;
    
break;
}

Az utasítás lényege hogy megadunk értékeket, amikre ez-és-ez történik. Az utasításokból több is lehet a break; kulcsszóig. Ha hiányzik a break, az összes többi variáció is lefut majd ami alatta van, így ez problémákat okozhat a program futása során.

A default kulcsszó elhagyható - az és szakasza akkor fut le ha egyik lehetőség sem áll fenn (feltétel nélküli else ág).

Íme egy konkrét példa (pontszám alapján osztályozás):

int pontszam = 7, erdemjegy;

switch (pontszam)
{
  
case 5:
  
case 6:
      erdemjegy = 2;
     
break;
  
case 7:
  
case 8:
      erdemjegy = 3;
     
break;
  
case 9:
      erdemjegy = 4;
     
break;
  
case 10:
      erdemjegy = 5;
     
break;
  
default:
      erdemjegy = 1;
     
break;
}

3d, A while ciklus

Amíg a feltétel teljesül, addig ismételgeti az adott utasításokat (itt is több alak van):

while (állítás) utasítás;


while
(állítás)
{
  utasítás1;
  utasítás2;
}

Konkrét példa (számok összeadása 1..10-ig):

int szam = 1, szumma = 0;

while
(szam <= 10)
{
  szumma = szumma + szam;
  szam++;
}

Természetesen a C/C++ híresen jó a rövidítésekben - ez is ugyanezt jelenti [segítség: +=, ++]:

int szam = 1, szumma = 0;

while
(szam <= 10) szumma += szam++;

Megjegyzés: ha a feltétel alapból hamis (pl. szam = 12 alapból), a while egyszer sem fut le. Ezért ez az utasítás "elöltesztelős ciklus".

3e, A do-while ciklus

Addig ismételgeti az adott utasításokat, amíg a feltétel teljesül. A while ciklussal szemben, itt az utasítás mindig lefut legalább egyszer, és csak ez után fogja megvizsgálni a feltételt. Ezért ez az utasítás "hátultesztelős ciklus".

do
{
  utasítás1;
  utasítás2;
}
while
(állítás);

Az előzőből kiindulva ezt nem is ragoznám tovább íme egy példa:

int szam = 1, szumma = 0;

do

{
   szumma += szam++;
}
while (szam < 10);

/* mivel most a növelés után ellenőrzünk nem kell egyenlőség */

3f, A for ciklus

Az adott utasításokat egy megadott darabszámban megismétli, miközben tárolja hol jár. A C/C++ for ciklus elképesztően rugalmas, jobb esetben egy do-while, while ciklust is ki tud helyettesíteni.

for (kezdeti_utasítások; állítás; növelés_módja) utasítás;


for (kezdeti_utasítások; állítás; növelés_módja)
{
  utasítás1;
  utasítás2;
}

Konkrét példa (számok összeadása 1..10-ig) [először normálisan]:

int szam, szumma = 0;

for
(szam = 1; szam <= 10; szam++)
{
   szumma += szam;
};

/* a növelést most a ciklus végzi a fentebb leírt módon, önmaga végén, nem kell menet közben, és nem is szabad beleavatkozni */

De ezért szeretjük a C/C++ nyelvet:  [segítség: lista, +=, ++]

int szam, szumma;

for
(szam = 1, szumma = 0; szam <= 10; szumma += szam++);

A for ciklus nagyon rugalmas akkor is ha a határ nem egyértelmű - most csak páros számokat adunk össze 1..100-ig:

int szam, szumma;

for
(szam = 2, szumma = 0; szam <= 100; szumma += szam, szam += 2);

Habár a feltételben a százas szám ott van, mégis csak 50x fog lefutni, hiszen ezt kértük.

4, Ciklusokkal kapcsolatos utasítások

A C/C++ nyelvben van néhány utasítás amivel a ciklusok működése befolyásolható - habár mindegyiken működnek az egyszerűség kedvéért mindegyik for cikluson lesz bemutatva:

4a, break

A break utasítás befejezi az aktuális ciklust az adott pontban. A kód futása ettől még folytatódik, mintha lejárt volna a ciklus.

int szam, szumma = 0;

for
(szam = 1; szam <= 1000; szam++)
{
   szumma += szam;

   if (szam = 10) break;
};

/* Csak 1..10-ig adja össze a számokat, hiába az 1000-es határ, a break kiléptet 10-nél. */

4b, continue

Ha a ciklus egyszeri végigfutását szakasznak nevezzük, akkor a continue működése úgy írható le, hogy az utasítás után lévő műveletek az adott szakaszban kimaradnak, és a ciklus a következő szakaszba lép.

int szam, szumma = 0;

for
(szam = 1; szam <= 10; szam++)
{
   if (szam = 7) continue;
 
   szumma += szam;
};

/* Csak 1..6 + 8..10-ig adja össze a számokat, mivel azt mondtuk hogyha a 7-es szám jönne, akkor azt a szakaszt hagyja ki. */