KURZUS: Programozás alapjai
MODUL: II. modul
6. lecke: Utasítások
Ebben a leckében elmélyedünk a korábban már megismert utasítások (pl. feltételes szerkezet, elöltesztelő ciklus) részleteiben, és további, eddig kiaknázatlan lehetőségeket is megismerünk (pl. hátultesztelő ciklus, többirányú elágazás switch-csel). A példaprogramok nem csak az új ismeretek elmélyítését segítik, de néhány egyszerű algoritmust is bemutatnak, melyeknek a gyakorlatban később hasznát vehetjük (pl. rendezés, keresés). | |||||||||||||||||||||||||||||||||
A lecke végére a hallgatónak tisztában kell lennie: | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
A lecke kulcsfogalmai: blokk, láthatóság, címkézett utasítás, üres utasítás. | |||||||||||||||||||||||||||||||||
Az utasítások fajtái | |||||||||||||||||||||||||||||||||
Az utasításokat - ha mást nem mondanak - megadásuk sorrendjében hajtja végre a processzor. Az utasításoknak nincs értéke. Végrehajtásuk hatással van bizonyos adatokra, vagy programelágazást valósítanak meg stb. Definíciójuk a következő: | |||||||||||||||||||||||||||||||||
utasítás: | |||||||||||||||||||||||||||||||||
Összetett utasítás | |||||||||||||||||||||||||||||||||
összetett-utasítás: | |||||||||||||||||||||||||||||||||
deklarációlista: | |||||||||||||||||||||||||||||||||
utasításlista: | |||||||||||||||||||||||||||||||||
Az összetett utasítás utasítások (lehet, hogy üres) listája { }-be téve. Az összetett utasítást blokknak is nevezik, mely szintaktikailag egy utasításnak minősül, és szerepet játszik az azonosítók hatáskörében és láthatóságában. | |||||||||||||||||||||||||||||||||
Ha a deklarációlistában előforduló azonosítót már korábban az összetett utasításon kívül is deklarálták, akkor a blokkra lokális azonosító elfedi a blokkon kívülit a blokk teljes hatáskörében. Tehát az ilyen blokkon kívüli azonosító a blokkban nem látható. | |||||||||||||||||||||||||||||||||
C blokkban előbb a deklarációs, s csak aztán a végrehajtható utasítások következnek. | |||||||||||||||||||||||||||||||||
Az összetett utasítások akármilyen mély szinten egymásba ágyazhatók, s az előbbi szerkezet a beágyazott blokkra is vonatkozik. | |||||||||||||||||||||||||||||||||
Tudjuk, hogy az auto tárolási osztályú objektumok inicializálása mindannyiszor megtörténik, valahányszor a vezérlés a "fejen át" kerül be az összetett utasításba. Ez az inicializálás azonban elmarad, ha a vezérlés ugró utasítással érkezik a blokk "közepére". Már említettük, de újra elmondjuk, hogy tilos ;-t írni az összetett utasítás záró }-e után! | |||||||||||||||||||||||||||||||||
Címkézett utasítás | |||||||||||||||||||||||||||||||||
címkézett-utasítás: | |||||||||||||||||||||||||||||||||
A case és a default formák csak switch utasításban használatosak. Ezek ismertetésével majd ott foglalkozunk! Az | |||||||||||||||||||||||||||||||||
azonosító : utasítás | |||||||||||||||||||||||||||||||||
alakban az azonosító (címke) célja lehet például egy feltétlen elágaztató | |||||||||||||||||||||||||||||||||
goto azonosító; | |||||||||||||||||||||||||||||||||
utasításnak. A címkék hatásköre mindig az őket tartalmazó függvény. Nem deklarálhatók újra, de külön névterületük van, és önmagukban nem módosítják az utasítások végrehajtási sorrendjét. | |||||||||||||||||||||||||||||||||
C-ben csak végrehajtható utasítás címkézhető meg, azaz | |||||||||||||||||||||||||||||||||
{ | |||||||||||||||||||||||||||||||||
A megoldás helyessé válik, ha legalább egy üres utasítást teszünk a cimke után: | |||||||||||||||||||||||||||||||||
{ | |||||||||||||||||||||||||||||||||
Kifejezés utasítás | |||||||||||||||||||||||||||||||||
kifejezés-utasítás: | |||||||||||||||||||||||||||||||||
Ha a kifejezést pontosvessző követi, kifejezés utasításnak minősül. A fordító kiértékeli a kifejezést, s eközben minden mellékhatás érvényre jut, mielőtt a következő utasítás végrehajtása elkezdődne. Például az | |||||||||||||||||||||||||||||||||
x = 0, i++, printf("Hahó!\n") | |||||||||||||||||||||||||||||||||
kifejezések, s így válnak kifejezés-utasításokká: | |||||||||||||||||||||||||||||||||
x = 0; i++; printf("Hahó!\n"); | |||||||||||||||||||||||||||||||||
A legtöbb kifejezés utasítás hozzárendelés vagy függvényhívás. | |||||||||||||||||||||||||||||||||
Üres utasítást (null statement) úgy kapunk, hogy kifejezés nélkül pontosvesszőt teszünk. Hatására természetesen nem történik semmi, de ez is utasításnak minősül, azaz szintaktikailag állhat ott, ahol egyébként utasítás állhat. | |||||||||||||||||||||||||||||||||
Szelekciós utasítások | |||||||||||||||||||||||||||||||||
szelekciós-utasítás: | |||||||||||||||||||||||||||||||||
Látszik, hogy két szelekciós utasítás van: az if és a switch. A feltételesen elágaztató | |||||||||||||||||||||||||||||||||
if(kifejezés) utasítás1 else utasítás2 | |||||||||||||||||||||||||||||||||
utasításban a kifejezésnek aritmetikai, vagy mutató típusúnak kell lennie. Ha értéke zérus, akkor a logikai kifejezés hamis, máskülönben viszont igaz. Ha a kifejezés igaz, utasítás1 következik. Ha hamis, és az else ág létezik, akkor utasítás2 jön. Mind utasítás1, mind utasítás2 lehet összetett utasítás is! | |||||||||||||||||||||||||||||||||
A nyelvben nincs logikai adattípus, de cserében minden egész és mutató típus annak minősül. Például: | |||||||||||||||||||||||||||||||||
if(ptr == 0) /* Rövidíthető "if(!ptr)"-nek. */ | |||||||||||||||||||||||||||||||||
Az else ág elhagyhatósága néha problémákhoz vezethet. Például a | |||||||||||||||||||||||||||||||||
if(x == 1) | |||||||||||||||||||||||||||||||||
forrásszövegből a programozó "elképzelése" teljesen világos. Sajnos azonban egyet elfelejtett. Az else mindig az ugyanazon blokk szinten levő, forrásszövegben megelőző, else ág nélküli if-hez tartozik. A megoldás helyesen: | |||||||||||||||||||||||||||||||||
if(x == 1){ | |||||||||||||||||||||||||||||||||
Az if utasítások tetszőleges mélységben egymásba ágyazhatók, azaz mind az if, mind az else utasítása lehet újabb if utasítás is. Például: | |||||||||||||||||||||||||||||||||
if(x == 1) | |||||||||||||||||||||||||||||||||
Többirányú elágazást (szelekciót) valósít meg a következő konstrukció: | |||||||||||||||||||||||||||||||||
if( kifejezés1 ) utasítás1 | |||||||||||||||||||||||||||||||||
Ha valamelyik if kifejezése igaz, akkor az azonos sorszámú utasítást hajtja végre a processzor, majd a konstrukciót követő utasítás jön. Ha egyik if kifejezése sem igaz, akkor viszont utasításN következik. | |||||||||||||||||||||||||||||||||
Ha egy n elemű, növekvőleg rendezett t tömbben keresünk egy x értéket, akkor ezt bináris keresési algoritmust alkalmazva így interpretálhatjuk: | |||||||||||||||||||||||||||||||||
int binker(int x, int t[], int n){ | |||||||||||||||||||||||||||||||||
A binker háromirányú elágazást realizál. x t tömbbeli előfordulásának indexét szolgáltatja, ill. -1-et, ha nincs is x értékű elem a tömbben. | |||||||||||||||||||||||||||||||||
A bináris keresés növekvőleg rendezett tömbben úgy történik, hogy először megállapítjuk, hogy a keresett érték a tömb alsó, vagy felső felébe esik. A módszert aztán újraalkalmazzuk az aktuális félre, s így tovább. A dolognak akkor van vége, ha a kereső tartomány semmivé szűkül (ilyenkor nincs meg a keresett érték), vagy a felező tömbelem egyezik a keresett értékkel. | |||||||||||||||||||||||||||||||||
Rendezett sorozatban egy érték keresésének ez a megfelelő módja, és nem a minden elemhez történő hasonlítgatás (soros vagy szekvenciális keresés)! | |||||||||||||||||||||||||||||||||
A többirányú programelágaztatás másik eszköze a | |||||||||||||||||||||||||||||||||
switch(kifejezés) utasítás | |||||||||||||||||||||||||||||||||
melyben a kifejezésnek egész típusúnak kell lennie. Az utasítás egy olyan speciális összetett utasítás, mely több case címkét | |||||||||||||||||||||||||||||||||
case konstans-kifejezés : utasítás | |||||||||||||||||||||||||||||||||
és egy elhagyható default címkét | |||||||||||||||||||||||||||||||||
default : utasítás | |||||||||||||||||||||||||||||||||
tartalmazhat. A vezérlést azon case címkét követő utasítás kapja meg, mely konstans-kifejezésének értéke egyezik a switch-beli kifejezés értékével. A végrehajtás aztán itt addig folytatódik, míg break utasítás nem következik, vagy vége nincs a switch blokkjának. | |||||||||||||||||||||||||||||||||
A case címkebeli konstans-kifejezésnek is egész típusúnak kell lennie. Ez a Típusok és konstansok lecke Deklaráció fejezetében írottakon túl további korlátozásokat ró a konstans kifejezésre. Operandusai csak egész, felsorolás, karakteres és lebegőpontos állandók lehetnek, de a lebegőpontos konstanst explicit típuskonverzióval egésszé kell alakítani. Operandus lehet még a sizeof operátor is, aminek operandusára természetesen nincsenek ilyen korlátozások. | |||||||||||||||||||||||||||||||||
A végrehajtás során a kifejezés és a case konstans-kifejezések értékén is végbemegy az egész-előléptetés. A kifejezés az összes esetleges mellékhatásával egyetemben valósul meg, mielőtt az értékhasonlítás megkezdődne. | |||||||||||||||||||||||||||||||||
Ha nincs a switch kifejezés értékével egyező case címke, akkor a vezérlést a default címke utasítása kapja meg. Ha nincs default címke sem, akkor vége van a switch-nek. | |||||||||||||||||||||||||||||||||
Az utasítás használatához még két megjegyzést kell fűzni: | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
A switch utasítások egymásba is ágyazhatók, s ilyenkor a case és a default címkék mindig az őket közvetlenül tartalmazó switch-hez tartoznak. | |||||||||||||||||||||||||||||||||
A switch utasítást szemléltetendő írjuk át a szabvány bemenet karaktereit kategóriánként leszámláló pelda7.c-t! | |||||||||||||||||||||||||||||||||
/* PELDA17.C: A bemenet karaktereinek | |||||||||||||||||||||||||||||||||
Belátható, hogy a switch utasítás használhatóságát szegényíti, hogy a kifejezés csak egész típusú lehet és, hogy a case címkék csak egészértékű konstans-kifejezések lehetnek. Az if-nél említett többirányú elágaztatási konstrukció így jóval általánosabban használható. | |||||||||||||||||||||||||||||||||
Iterációs utasítások | |||||||||||||||||||||||||||||||||
iterációs-utasítás: | |||||||||||||||||||||||||||||||||
Szemmel láthatóan háromféle iterációs utasítás van, amiből kettő elöltesztelő ciklusutasítás. A | |||||||||||||||||||||||||||||||||
while(kifejezés) utasítás | |||||||||||||||||||||||||||||||||
kifejezéséről ugyanaz mondható el, mint az if utasítás kifejezéséről. Lépésenként a következő történik: | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
Világos, hogy a kifejezés értékének valahogyan változnia kell az utasításban, különben a ciklusnak soha sincs vége. Az utasítás állhat több utasításból is, azaz összetett utasítás is lehet. Ha az utasítások közt ugró utasítás is van, akkor ezen mód is kiléphetünk a ciklusból. | |||||||||||||||||||||||||||||||||
A | |||||||||||||||||||||||||||||||||
for(<init-kifejezés>; <kifejezés>; <léptető-kifejezés>) utasítás | |||||||||||||||||||||||||||||||||
elöltesztelő, iteratív ciklusutasítás, melynek megvalósulása a következő lépésekből áll: | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
Ha a for utasítást while-lal szeretnénk felírni, akkor azt így tehetjük meg, ha a ciklusmagban nincs continue: | |||||||||||||||||||||||||||||||||
<init-kifejezés>; | |||||||||||||||||||||||||||||||||
A szintaktikai szabályt összefoglalva: a for-ból akár mindegyik kifejezés is elhagyható, de az első kettőt záró pontosvesszők nem! A vessző operátor alkalmazásával mind az init-, mind a léptető-kifejezés több kifejezés is lehet. Ezt szemlélteti az előző szakaszban a pelda16.c-ban megírt strrv függvény. | |||||||||||||||||||||||||||||||||
Írjunk void rendez(int a[], int n) függvényt, mely növekvőleg rendezi saját helyén az n elemű a tömböt! | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
void rendez(int a[],int n){ | |||||||||||||||||||||||||||||||||
Ez ugyebár példa az egymásba ágyazott ciklusokra! | |||||||||||||||||||||||||||||||||
Készítsük el az int egesze(char s[]) rutint, mely ellenőrzi a decimális egész konstans írásszabályát a paraméter karaktertömbön. 1-et szolgáltat, ha a dolog rendben van, s zérust, ha nem! | |||||||||||||||||||||||||||||||||
/* A decimális számjegyek száma maximálisan: */ | |||||||||||||||||||||||||||||||||
Látszik, hogy HSZ 16 bites int esetén 5 (32767), és 32 bitesnél 10 (2147483647). Arra való, hogy ennél több decimális számjegyet ne fogadjon el a rutin. | |||||||||||||||||||||||||||||||||
Vegyük észre, hogy a függvény visszautasítja, ha egyetlen számjegy karaktert sem tartalmaz a numerikus karakterlánc! Elveti azt is, ha a numerikus lánc rész nem fehér karakterrel, vagy lánczáró zérussal végződik. Az ellenőrzés persze nem tökéletes, hisz a numerikus karakterlánc ábrázolási határok közé férését nem biztosítja. Például 16 bites int-nél minden ötjegyű számot érvényesnek tekint, holott a -99999 és -32769, valamint a 32768 és 99999 közötti számok ábrázolhatatlanok. Ez az ellenőrzés azonban csak akkor lenne könnyen megvalósítható, ha tesztelés közben konvertálnánk is a számot. Ekkor azonban már "többe kerülne a leves, mint a hús". | |||||||||||||||||||||||||||||||||
Készítsük programot, mely egész számokat kér be. Meghatározza az átlagukat, a minimumukat, maximumukat, és növekvőleg rendezi is őket! Az egészek darabszáma csak futás közben dől el. | |||||||||||||||||||||||||||||||||
/* PELDA18.C: Egesz szamok atlaga, minimuma, maximuma és | |||||||||||||||||||||||||||||||||
Nem rendezett sorozatban a minimum, vagy maximum megkeresésének az a módja, hogy vesszük a sorozat egy létező elemét (többnyire az elsőt), és aztán hasonlítgatjuk a többiekhez, hogy vajon van-e nálánál kisebb, ill. nagyobb. Ha van, akkor attól kezdve azzal folytatjuk a hasonlítást. | |||||||||||||||||||||||||||||||||
El kell ismerni természetesen, hogy a minimum és maximum megkeresése teljesen felesleges volt, mert a rendezés után ezeket az értékeket a[0] és a[n-1] amúgy is tartalmazza. | |||||||||||||||||||||||||||||||||
Vegyük még észre, hogy az egészek összegét a for ciklus léptető-kifejezésében számítjuk ki! | |||||||||||||||||||||||||||||||||
A | |||||||||||||||||||||||||||||||||
do utasítás while(kifejezés); | |||||||||||||||||||||||||||||||||
hátultesztelő ciklusutasítás. A kifejezésre ugyanazon megkötések érvényesek, mint a while-nál. A dolog lényege az, hogy a fordító a kifejezés értékétől függetlenül egyszer biztosan | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
Összesítve: Az utasítást egyszer mindenképp végrehajtja a processzor, s ezt követően mindaddig ismétli, míg a kifejezés hamis nem lesz. | |||||||||||||||||||||||||||||||||
A szemléltető példában az itoa függvény az int n paraméterét karakterlánccá konvertálja, és elhelyezi a paraméter s karaktertömbben: | |||||||||||||||||||||||||||||||||
#include <stdio.h> | |||||||||||||||||||||||||||||||||
A belsőábrázolási formából karakterlánccá alakító itoa rutinban az i ciklusváltozó. A j logikai változó, s induló értéke 0. Egyetlen egy esetben egyértékű, ha n éppen INT_MIN. A probléma az ugye, hogy mindenképp pozitív értéket kívánunk konvertálni az esetleges negatív előjelet megjegyezve, de az INT_MIN -1-szerese nem ábrázolható int-ként, hisz éppen eggyel nagyobb INT_MAX-nál. Ha ilyenkor megnöveljük eggyel n értékét, akkor -1-szerese épp a felső ábrázolási korlát lesz, amivel már nincs probléma. A j logikai változó tehát akkor 1, ha volt növelés. Az elojel változóra n eredeti előjelének megtartása végett van szükség, és azért, hogy negatív n esetén a keletkező karakterlánc végére ki lehessen tenni a mínuszjelet. | |||||||||||||||||||||||||||||||||
Az algoritmus éppen megfordítva állítja elő a karakterláncot. Nézzük csak asztali teszttel a 16 bites int esetét! Tegyük fel, hogy n értéke az ominózus -32768! | |||||||||||||||||||||||||||||||||
Amig a do-while utasításig nem érünk, j 1 lesz, n 32767 és i zérus marad. Lejátszva a ciklust a következő történik: | |||||||||||||||||||||||||||||||||
| |||||||||||||||||||||||||||||||||
A táblázatban az az állapot látszik, amikor a do-while-t követő két utasítás is lezajlott. Az s[0]-ból, vagyis '7'-ből, '8' lesz, és aztán a szabvány könyvtári strrev megfordítja a saját helyén az eredmény karakterláncot. | |||||||||||||||||||||||||||||||||
Ugró utasítások | |||||||||||||||||||||||||||||||||
ugró-utasítás: | |||||||||||||||||||||||||||||||||
Csak iterációs (while, do-while és for) vagy switch utasításon belül használható a | |||||||||||||||||||||||||||||||||
break; | |||||||||||||||||||||||||||||||||
mely befejezi ezeket az utasításokat, azaz hatására a vezérlés feltétel nélkül kilép belőlük. Több egymásba ágyazott iterációs utasítás esetén a break csak abból a ciklusból léptet ki, amelyben elhelyezték, azaz a break csak egyszintű kiléptetésre képes. | |||||||||||||||||||||||||||||||||
Készítendő egy int trim(char s[]) függvény, mely a paraméter karaktertömb elejéről és végéről eltávolítja a fehér karaktereket a saját helyén, s visszatér az így letisztított karakterlánc hosszával! | |||||||||||||||||||||||||||||||||
#include <string.h> | |||||||||||||||||||||||||||||||||
Csak iterációs utasításokban (while, do-while és for) alkalmazható a | |||||||||||||||||||||||||||||||||
continue; | |||||||||||||||||||||||||||||||||
amely a vezérlést a kifejezés kiértékelésére viszi while és do-while estén, ill. hatására a léptető-kifejezés kiértékelése következik for utasításnál. Egymásba ágyazott iterációs utasítások esetén ez is mindig csak az őt magába foglaló iterációs utasításra vonatkozik. (switch-ben csak iterációs utasításon belül használható a continue!) | |||||||||||||||||||||||||||||||||
Feltétlen vezérlésátadást hajt végre az azonosítóval címkézett utasításra a | |||||||||||||||||||||||||||||||||
goto azonosító; | |||||||||||||||||||||||||||||||||
Az utasítást bármilyen mély blokk szintről is végrehajtja a processzor, de a cél címkézett utasításnak ugyanabban a függvényben kell lennie, ahol a goto is van. | |||||||||||||||||||||||||||||||||
Megjegyezzük, hogy a goto utasítás használata szintaktikai értelemben nem hiba, viszont ettől függetlenül kerülendő új programok létrehozásakor! Viszonylag könnyű belátni, hogy egy megfelelően felépített blokkszerkezettel rendelkező programban a goto utasításra valójában semmi szükség nincs, és a címkék is csak switch utasításban indokolhatók. A goto használata sokkal nehezebben karbantartható, több potenciális hibát tartalmazó, nehezebben ellenőrizhető működésű programokhoz vezet. | |||||||||||||||||||||||||||||||||
A void visszatérésűektől eltekintve a függvény testében lennie kell legalább egy | |||||||||||||||||||||||||||||||||
return <kifejezés>; | |||||||||||||||||||||||||||||||||
utasításnak. Ha a rutin visszatérési típusa típus, akkor a kifejezés típusának is ennek kell lennie, vagy hozzárendelési konverzióval ilyen típusúvá alakítja a kifejezés értékét a fordító. A return hatására visszakapja a vezérlést a hívó függvény, s átveszi a visszatérési értéket is. | |||||||||||||||||||||||||||||||||
A típus visszatérésű függvényt hívó kifejezés | |||||||||||||||||||||||||||||||||
fv(aktuális-paraméterlista) | |||||||||||||||||||||||||||||||||
típus típusú jobbérték, s nem balérték: | |||||||||||||||||||||||||||||||||
típus t; | |||||||||||||||||||||||||||||||||
A függvényhívás hatására beindult végrehajtás a return utasítás bekövetkeztekor befejeződik. Ha nincs return, akkor a függvény testét záró }-ig megy a végrehajtás. | |||||||||||||||||||||||||||||||||
Ha a függvény által visszaadott érték típusa void, és a függvényt nem a testét záró }-nél szeretnénk befejezni, akkor a függvény belsejébe a kívánt helyre return utasítást kell írni. |
Feladatok | |||||||||||||||
1. Olyan int int binkerf(int x, int t[], int n) függvényt szerettünk volna írni, ami a csökkenőleg rendezett, n elemű t tömbben megkeresi x előfordulását, és ennek indexével tér vissza, vagy -1-gyel, ha a keresett érték nem található. Sajnos nem sikerült. | |||||||||||||||
/*1*/int binkerf(int x, int t[], int n){ | |||||||||||||||
Jelölje meg azokat a sorokat, amikkel a függvény helyes működésre bírható!
![]() | |||||||||||||||
2. Alakítsa át a pelda18.c-ben adott programot úgy, hogy az ne kérje be előre a rendezendő számok darabszámát, hanem az érték bekérésekor megadott üres sor jelentse az adatbevitel végét! | |||||||||||||||
/* PELDA18X.C: Egesz szamok atlaga, minimuma, maximuma és | |||||||||||||||
3. Mely sorokat kellene átalakítani a void rendez(int a[],int n)függvényben és mire, hogy az valós számok rendezésére alkalmassá váljon? | |||||||||||||||
/*1*/void rendez(int a[],int n) { | |||||||||||||||
Jelölje meg azokat a sorokat, amikkel a függvény a kívánt működésre bírható!
![]() |