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:
- elágazás: feltételek alapján más és más történjen (feltételes utasítás)
- 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. */ |