KURZUS: Programozás alapjai
MODUL: I. modul
2. lecke: Alapismeretek 1/2
Az első lecke során a feladatok megoldását csak adatszerkezeti táblázatokkal és folyamatábrákkal tudtuk megfogalmazni. Ennek a leckének a során viszont elkészítjük első C programunkat, és elkezdjük megvizsgálni, milyen szintaktikai szabályok betartásával írhatjuk le gondolatainkat programnyelvi eszközök segítségével. Megvizsgáljuk, hogy a begépelt programszövegből milyen lépések eredményeképpen áll elő egy futtatható program. | |||||||||||||||||||
A lecke végére: | |||||||||||||||||||
| |||||||||||||||||||
A lecke kulcsfogalmai: programnyelv, forráskód, interpreter, compiler, megjegyzés, előfeldolgozó, fordító, kapcsoló-szerkesztő, direktíva, fehér karakter, fejfájl, escape szekvencia, futásidejű könyvtár, deklaráció, definíció, prototípus, összetett utasítás, bal- és jobbérték, formátumspecifikáció, szimbolikus állandó, verem. | |||||||||||||||||||
A C programnyelvről röviden | |||||||||||||||||||
Minden számítógépnek van egy saját utasításkészlete, és csak az ezzel megadott utasítás sorozatok végrehajtására képes. Ezt hívják gépi kódnak, amivel teljes körűen ki lehet ugyan aknázni egy számítógép képességeit, ám cserébe annak legmélyebb ismeretét és óriási szakértelmet igényel, rendkívül lassú lesz a program fejlesztése, az elkészült programokat nem lehet más hardveren használni, és számos más hátránya is van a használatának. Ezeket a gondokat csak kevéssé enyhíti az Assembly, pl. azzal, hogy a gépi utasításkódok helyett azok könnyebben megjegyezhető, rövid szöveges elnevezéseit (mnemonik) lehet használni. Azért alkalmazunk különféle programnyelveket, hogy az emberi gondolkodásmódra jobban emlékeztető módon, gyorsabban, tömörebben, kevesebb hibával, korábban már létrehozott megoldásokat újrahasználva stb. fogalmazhassuk meg szándékunkat, majd a programnyelv szabályai szerint elkészített ún. forráskódot (forrásszöveget, forrásprogramot) alakíttatjuk át a számítógép által közvetlenül is végrehajtható programmá. | |||||||||||||||||||
Ez az átalakítás alapvetően kétféle módon tud történni: az ún. értelmezők (interpreter) sorról-sorra, kis egységekben előrehaladva értelmezik és hajtatják végre a számítógéppel az utasításainkat, aminek egyik előnye, hogy a program a forrásszöveg elkészítését követően azonnal használatba vehető. A hátránya azonban a lassú végrehajtás, mert minden utasítást minden alkalommal, amikor szükség van rá (pl. egy ciklusmagban) újra és újra értelmezni kell. A másik fő megközelítési irány a fordítóprogramok (compiler), melyek a forráskódot egyszer lefordítják gépi kódra, majd ezt a kódot az operációs rendszerrel később bármikor újra le lehet futtatni. A fordítás egy összetett program esetében időigényes folyamat, ám csak ritkán van rá szükség, és a futtatás sebessége már megközelíti a gépi kódú programok sebességét. Természetesen különféle köztes megoldásokra is akad példa szép számmal, de ezek tárgyalása nem feladatunk. | |||||||||||||||||||
A C nyelvet tervezője, Dennis Ritchie, a Bell Laboratóriumban fejlesztette ki az 1970-es évek végén [5], és a UNIX operációs rendszer programnyelvének szánta. Ezt a változatot jelöli az ábrán a K&R. A C nyelv ezt követően praktikussága miatt széles körben elterjedt. Sokan készítettek sokféle C fordítót saját, vagy környezetük igényeinek megfelelően. A sokszínűségben amerikai nemzeti szabvánnyal (ANSI) teremtettek rendet az 1980-as évek végén [5]. Az ANSI C szabványt aztán Európában (ISO) is elfogadták néhány évvel később. Az ábrából látszik, hogy az ANSI C bővítette a K&R C halmazt. | |||||||||||||||||||
| |||||||||||||||||||
A C általános célú programnyelv, mely tömörségéről, hatékonyságáról, gazdaságosságáról és portabilitásáról (hordozhatóságáról) ismert. (Ugyanazt a forrásszöveget különféle operációs rendszerekre és hardverekre is lefordíthatjuk, majd azt használatba vehetjük.) Nem tartalmaz túl sok vezérlési szerkezetet. Bőséges viszont az operátorkészlete, és több adattípus megléte jellemzi. Jól használható tehát műszaki-tudományos, vagy akár adatfeldolgozási problémák megoldására. | |||||||||||||||||||
A C elég alacsony szintű - hardver közeli - programnyelv is egyben, hisz tervezője a UNIX operációs rendszert e nyelv segítségével készítette el néhány száz gépi kódú utasítás felhasználásával. A C programok gyakran ugyanolyan gyorsak, mint az Assembly "nyelven" készültek, de jóval könnyebben olvashatók és tarthatók karban. | |||||||||||||||||||
Elektronikus jegyzetünkben nem kívánunk elmerülni konkrét integrált programfejlesztő rendszerek, operációs rendszerek és processzorok részleteinek taglalásában. Ehelyett rögzítsük azt, hogy fogalmainkkal az IBM PC kompatibilis személyi számítógépek területén maradunk! Erre a gépcsaládra is rengeteg cég készített C fordítót. Ma már azonban gyakorlatilag nem találni olyan fordítóprogramot, amelyet kizárólag C programok fordítására lehetne használni. Jellemzően olyan C++ fordítókat fejlesztenek, melyek egyúttal C fordításra is alkalmasak. | |||||||||||||||||||
Annak érdekében, hogy a hallgatók a programozást önállóan is gyakorolhassák, és a jegyzetben szereplő példákat kipróbálhassák, módosíthassák, egy programfejlesztő környezet használatát nagyon röviden ismertetjük. A választott szoftver a Microsoft Visual Studio Community Edition 2017 (a továbbiakban: VS), mely ingyenesen letölthető és Windows operációs rendszereken használható tanulás céljára. A terméket a https://www.visualstudio.com/downloads/ weboldalról indulva lehet beszerezni. A telepítő "Workloads" ablakában lehet kiválasztani a szoftver telepítendő főbb komponenseit. Itt válasszuk a "Desktop development with C++" lehetőséget, majd a jobb oldali, "Summary" oszlopban a kategória elemei közül elegendő csak a "VC++ 2017 v141 toolset (x86, x64)" és a "Windows 10 SDK (10.0.15063.0) for Desktop C++ x86 and x64" bejegyzések előtt meghagyni a pipákat. (A bejegyzések pontos nevei az új fordító vagy operációs rendszer változatok bevezetésével változhatnak.) Ezután a telepítés az "Install" gombra kattintással indítható. | |||||||||||||||||||
| |||||||||||||||||||
Az operációs rendszer számunkra jobbára csak olyan szempontból érdekes, hogy legyen karakteres szabványos bemenete (STanDard INput), és létezzen karakteres szabvány kimenete (STanDard OUTput), valamint szabványos hibakimenete (STtanDard ERRor output). A szabvány bemenet alapértelmezés szerint a billentyűzet, a kimenetek viszont a karakteres üzemmódú képernyőre, vagy a karakteres konzol ablakba dolgoznak. (A szabványos ki- és bemenetek célja az, hogy az adatok forrását és célját a felhasználó megváltoztathassa, így ugyanaz a program képessé válik billentyűzet helyett pl. egy fájlból vagy a hálózatról beolvasni a működéséhez szükséges adatokat.) A karakteres képernyő, vagy konzol ablak felbontása természetesen változtatható, de mi minden példánál feltételezzük a 25 sorszor 80 oszlopot! A szabvány kimeneteken a mindenkori aktuális pozíciót kurzor jelzi. A kurzor pozícióját sajnos nem tudjuk szabadon változtatni: minden kiírt karakter után eggyel jobbra mozdul, illetve a sor végéről az új sor kezdetére ugrik. Ezen kívül néhány speciális kóddal lehet pl. új sort kezdeni, vagy rögzített tabulátorhelyre ugrani, de a lehetőségek ezzel ki is merültek. | |||||||||||||||||||
A forrásszöveg | |||||||||||||||||||
Első közelítésben induljunk ki abból, hogy a C program (a forrásprogram) fájlazonosítójában c vagy C kiterjesztéssel rendelkező, ASCII kódú szövegfájl, mely előállítható, ill. módosítható | |||||||||||||||||||
| |||||||||||||||||||
Az ASCII kódú szövegfájl sorokból áll. Egy sorban a szöveg sorbeli karaktereinek ASCII kódjai következnek rendre az egymás utáni bájtokban. A sorhoz Windows operációs rendszereken végül még két, a soremelést leíró bájt tartozik, melyekben egy soremelés (Line Feed - 10) és egy kocsi vissza (Carriage Return - 13) vezérlő karakter van. Minden más operációs rendszeren (pl. GNU/Linux, MacOS, UNIX) azonban a sorok végét csak egyetlen soremelés karakter zárja. Vigyázni kell ASCII kódú szövegfájlban a decimálisan 31 értékű bájt használatával, mert ez fájlvég jelzés a legtöbb operációs rendszerben! | |||||||||||||||||||
Készítsük el első C programunkat, és mentsük el pelda1.c néven! | |||||||||||||||||||
/* PELDA1.C */ | |||||||||||||||||||
Fordítsuk le a programot és futtassuk le! A mindenki által gyanított végeredmény az a képernyőn, hogy megjelenik a szöveg, és a következő sor elején villog a kurzor: | |||||||||||||||||||
Ez egy C program! | |||||||||||||||||||
A kívánt eredmény eléréséhez VS-ban a következő lépéseket kell elvégezni. A fejlesztőkörnyezetet elindítva válasszuk a "File / New / Project..." menüt. Erre azért van szükség, mert a VS-nak mindig teljes projektre van szüksége, még akkor is, ha abban csak egyetlen forrásfájl szerepel. A felbukkanó "New Project" dialógusablakban válasszuk ki a "Win32 Console Application" projekt típust, a "Name" mezőben adjunk nevet a projektnek, pl. "PrgAlapjai", majd kattintsunk az "OK" gombra! | |||||||||||||||||||
| |||||||||||||||||||
Ezután egy varázsló vezet végig a projekt létrehozásának lépésein. A "Next" gombbal a második oldalra lépve szüntessük meg a "Precompiled header" kijelölését, majd válasszuk ki az "Empty project" lehetőséget! A "Finish" gombra kattintva létrejön egy üres projekt, amiben már el lehet helyezni a lefordítani kívánt forrásfájlt. | |||||||||||||||||||
| |||||||||||||||||||
A "Solution explorer" ablakban a "Source files" elemen történő jobb kattintással hívjuk elő a helyi menüt, majd válasszuk az "Add / New item..." lehetőséget! Ennek hatására felbukkan az "Add New Item" dialógusablak, melyben meg kell adni az új fájl adatait. Mivel a C fájl típust nem ajánlja fel, ezért válasszuk a "C++ File"-t, majd a "Name" mezőbe írjuk be, hogy "pelda1.c"! A kiterjesztést felismerve a fejlőkörnyezet C fordítási üzemmódra fog átkapcsolni. Az ablak bezárásához kattintsunk az "Add" gombra! | |||||||||||||||||||
| |||||||||||||||||||
Ezután megnyílik a szerkesztőablakban az üres forrásfájl, amibe már be lehet gépelni a fenti forrásszöveget. Érdemes ezt lementeni a "File / Save pelda1.c" menüponttal. A futtatáshoz szükséges összes résztevékenységet automatikusan elvégzi a keretrendszer, ha a "Debug / Start without debugging" menüpontra kattintunk. Ha a programot korábban még nem fordítottuk le, megjelenik egy felbukkanó ablak a "Would you like to build it?" kérdéssel, melyre feleljünk "Yes"-szel! | |||||||||||||||||||
A fordító sikeres esetben a forrásprogramból egy vele azonos nevű (jellemzően o vagy obj kiterjesztésű) tárgymodult (object file) állít elő, és üzeneteket jelentet meg többek közt a hibákról. A VS-ban erre az "Output" ablak szolgál. | |||||||||||||||||||
| |||||||||||||||||||
Az üzenetek legalább kétszintűek: | |||||||||||||||||||
| |||||||||||||||||||
A (fatális) hibákat, melyek jobbára szintaktikai jellegűek, mindig kijavítja a programozó, mert korrekciójuk nélkül nem készíti el a tárgymodult a fordító. A figyelmeztető üzenetekkel azonban, melyek sok esetben a legsúlyosabb problémákat jelzik, nem szokott törődni, mert a fordító létrehozza a tárgymodult, ha csak figyelmeztető üzenetekkel zárul a fordítás. | |||||||||||||||||||
A pelda1.c programunk első sora megjegyzés (comment). A megjegyzés írásszabálya látszik a sorból, azaz: | |||||||||||||||||||
| |||||||||||||||||||
A megjegyzés több forrássoron át is tarthat, minden sorba is írható egy, sőt akár egy soron belül több is megadható a szintaktikai egységek között. Egyetlen tilos dolog van: a megjegyzések nem ágyazhatók egymásba! | |||||||||||||||||||
/* Ez a befoglaló megjegyzés eleje. | |||||||||||||||||||
Vegyük észre, hogy bármely hibás program rögtön "hibátlanná" válik, ha /*-ot teszünk az elejére, és a záró */-t elfelejtjük megadni a további forrásszövegben! | |||||||||||||||||||
A fordítót egybeépítették egy speciális előfeldolgozóval (preprocessor), mely az "igazi" fordítás előtt | |||||||||||||||||||
| |||||||||||||||||||
| |||||||||||||||||||
Az előfeldolgozó direktívák egy sorban helyezkednek el, és #-tel kezdődnek. Pontosabban # kell, legyen a sor első nem fehér karaktere (whitespace). | |||||||||||||||||||
Fehér karakterek a szóköz, a soremelés, a lapdobás karakter, a vízszintes és a függőleges tabulátor karakter. Meg kell említeni, hogy a megjegyzés is fehér karakternek minősül. A fehér karakterek szolgálhatnak szintaktikai egységek elválasztására, de a felesleges fehér karaktereket elveti a fordító. | |||||||||||||||||||
A pelda1.c programunk második sora egy #include direktíva, melynek hatására az előfeldolgozó megkeresi és betölti a paraméter szövegfájlt a forrásszövegbe, és elhagyja belőle ezt a direktívát. | |||||||||||||||||||
A pillanatnyilag betöltendő szövegfájl, az stdio.h, h kiterjesztésű. Az ilyen kiterjesztésű szövegfájlokat C környezetben fejfájloknak (header) nevezik. A fejfájl egy témával kapcsolatos adattípusok, konstansok definícióit és a vonatkozó függvények jellemzőit tartalmazza. | |||||||||||||||||||
Fedezzük fel, hogy az stdio a standard input output rövidítéséből származik, s így lényegében a szabvány kimenetet és bemenetet "kapcsoltuk" programunkhoz. | |||||||||||||||||||
Nem volt még szó arról, hogy a paraméter fájlazonosító <> jelek között áll! A <> jel pár tájékoztatja az előfeldolgozót, hogy milyen könyvtárakban keresse a szövegfájlt. A programfejlesztő rendszer beállításai közt van egy, melynek segítségével megadhatók a fejfájlok (include fájlok) keresési útjai. <fájlazonosító> alakú paraméter hatására az #include direktíva csak a programfejlesztő rendszerben előírt utakon keresi a fájlt, és sehol másutt. | |||||||||||||||||||
pelda1.c programunk harmadik és negyedik sora a main (fő) függvény definíciója. A függvénydefiníció szintaktikai alakja: | |||||||||||||||||||
típus függvénynév(formális-paraméterlista) { függvény-test } | |||||||||||||||||||
| |||||||||||||||||||
A { nyitja, a } zárja a blokkot. A { } párok összetartoznak. A } mindig a forrásszövegben őt megelőző {-t zárja. | |||||||||||||||||||
A forrásprogram több forrásmodulban (forrásfájlban) is elhelyezhető. Végrehajtható program létrehozásához azonban valamelyik forrásmodulnak tartalmaznia kell a main-t. Az indító program a végrehajtható program belépési pontja is egyben. Ez az a hely, ahova a memóriába történt betöltés után átadja a vezérlést az operációs rendszer. | |||||||||||||||||||
Az indító programnak természetesen a végrehajtható program "igazi" indítása előtt el kell látnia néhány más feladatot is, s a programozó által írt main-beli függvénytest csak ezután következik. A gyártók az indító programot rendszerint tárgymodul alakjában szokták rendelkezésre bocsátani. | |||||||||||||||||||
A pelda1.c programban a main függvény teste egyetlen függvényhívás. A függvényhívás szintaktikája: | |||||||||||||||||||
függvénynév(aktuális-paraméterlista) | |||||||||||||||||||
| |||||||||||||||||||
A példabeli karakterlánc konstans végén azonban van egy kis furcsaság: a \n! Emlékezzünk vissza rá, hogy az első 32 kód vezérlőjeleket kódol, és valahogyan azt is biztosítania kell a programnyelvnek, hogy ezek a karakterek, ill. a karakterkép nélküli kódpozíciók is megadhatók legyenek. A C programnyelvben erre a célra az úgynevezett escape szekvencia (escape jelsorozat, escape sequence) szolgál. | |||||||||||||||||||
Remélhetőleg világossá vált az előző okfejtésből, hogy az escape szekvencia helyfoglalása a memóriában egyetlen bájt! | |||||||||||||||||||
Az escape szekvencia \ jellel kezdődik, s ezután egy karakter következik. A teljesség igénye nélkül felsorolunk itt néhányat! | |||||||||||||||||||
| |||||||||||||||||||
Vegyük észre, hogy ha idézőjelet kívánunk a karakterlánc konstansba írni, akkor azt csak \" módon tehetjük meg! Ugyanez a helyzet az escape szekvencia kezdőkarakterével, mert az meg csak megkettőzve képez egy karaktert! Lássuk még be, hogy a \ooo alakkal az ASCII kódtábla bármely karaktere leírható! Például a \012 azonos a \n-nel, vagy a \060 a 0 számjegy karakter. | |||||||||||||||||||
A printf függvényhívás után álló ; utasításvég jelzés. | |||||||||||||||||||
pelda1.c programunk "utolsó fehér foltja" a printf, mely egyike a szabványos bemenet és kimenet függvényeinek. | |||||||||||||||||||
Kapcsoló-szerkesztés (link) | |||||||||||||||||||
A gyártók a szabvány bemenet, kimenet és más témacsoportok függvényeinek tárgykódját statikus könyvtárakban (a vagy lib kiterjesztésű fájlokban) helyezik el. Nevezik ezeket a könyvtárakat futásidejű könyvtáraknak (runtime libraries), vagy szabvány könyvtáraknak (standard libraries) is. A könyvtárfájlokból a szükséges tárgykódot a kapcsoló-szerkesztő (linker) másolja hozzá a végrehajtható fájlhoz. Lássuk ábrán is a szerkesztést! | |||||||||||||||||||
| |||||||||||||||||||
A programfejlesztő rendszerben be lehet állítani a statikus könyvtárfájlok (library) keresési útjait. Azt is meg lehet adni természetesen, hogy hova kerüljenek a fordítás és a kapcsoló-szerkesztés során keletkező kimeneti fájlok, de az alapértelmezett értékek többnyire megfelelőek. | |||||||||||||||||||
Futtatás | |||||||||||||||||||
A végrehajtható fájl a parancssorból azonosítójának begépelésével indítható. Amennyiben egy integrált szoftverfejlesztő környezetet (Integrated Development Environment, IDE) használunk (mint pl. a VS), valószínűleg annak menüjében is van valahol egy pont, mellyel az aktuális végrehajtható fájl futtatható. Többnyire létezik a három lépést (fordítás, kapcsoló-szerkesztés és futtatás) egymás után megvalósító egyetlen menüpont is (VS-ban: "Debug / Start without debugging" menüpont). | |||||||||||||||||||
Táblázat készítése | |||||||||||||||||||
Készítsük el a következő forint-euró átszámítási táblázatot! Egy euró pillanatnyilag legyen 310 forint! | |||||||||||||||||||
| |||||||||||||||||||
Adatstruktúra: | |||||||||||||||||||
| |||||||||||||||||||
Algoritmus: | |||||||||||||||||||
| |||||||||||||||||||
Készítsük el a programot! | |||||||||||||||||||
/* PELDA2.C: Forint-euro atszamitasi tablazat */ | |||||||||||||||||||
A pelda2.c-ből kitűnően látszik a C függvény szerkezete, azaz az úgy nevezett blokkszerkezet: | |||||||||||||||||||
| |||||||||||||||||||
A blokkszerkezet deklarációs és végrehajtható részre bontása a C-ben szigorú szintaktikai szabály, azaz egyetlen végrehajtható utasítás sem keveredhet a deklarációs utasítások közé, ill. a végrehajtható utasítások között nem helyezkedhet el deklarációs utasítás. (A C99 vagy újabb szabványt teljesítő fordítók megengedik a deklarációs és a végrehajtható utasítások keverését feltéve, hogy a deklaráció megelőzi a deklarált változó első felhasználását.) Természetesen a végrehajtható utasítások közé beágyazott (belső) blokkban a szabály újra kezdődik. | |||||||||||||||||||
Vegyük észre a példaprogramunk végén elhelyezkedő beágyazott vagy belső blokkot! Figyeljük meg a main két első sorában, hogy a deklarációs utasítás szintaktikája: | |||||||||||||||||||
típus azonosítólista; | |||||||||||||||||||
Az azonosítólista azonosítók sorozata egymástól vesszővel elválasztva. Megfigyelhető még, hogy az azonosítók képzéséhez ékezetes betűk nem használhatók! | |||||||||||||||||||
Foglalkozzunk kicsit a típusokkal! | |||||||||||||||||||
Az int (integer) 16, vagy 32 bites, alapértelmezés szerint előjeles (signed), fixpontos belsőábrázolású (kettes komplemens alakban felírt) egész típus. Előjeltelen (unsigned) esetben nincsenek negatív értékek. | |||||||||||||||||||
A float (floating point) 4 bájtos, lebegőpontos belsőábrázolású valós típus, ahol a mantissza és előjele 3 bájtot, s a karakterisztika előjelével egy bájtot foglal el. Az ábrázolási határok: 3.4*10-38 - 3.4*10+38. Ez a mantissza méret 6 - 7 decimális jegy pontosságot tesz lehetővé. | |||||||||||||||||||
Néhány programfejlesztő rendszer a lebegőpontos könyvtárakat (lib) csak akkor kapcsolja be a kapcsoló-szerkesztő által keresésnek alávethető könyvtárak közé, ha a programban egyáltalán igény jelentkezik valamilyen lebegőpontos ábrázolás, vagy művelet elvégeztetésére, vagy ha azt kifejezetten kéri a programozó. | |||||||||||||||||||
A pelda2.c végrehajtható részének első négy utasítása értékadás. Ki kell azonban hangsúlyozni, hogy a C-ben nincs értékadó utasítás, csak hozzárendelés operátor, s a hozzárendelésekből azért lesz utasítás, mert ;-t írtunk utánuk. | |||||||||||||||||||
A hozzárendelésre rögtön visszatérünk! Vegyük észre előbb az egész konstans írásszabályát! Elhagyható előjellel kezdődik, s ilyenkor pozitív, és ezután az egész szám jegyei következnek. | |||||||||||||||||||
A fejléc sort és az aláhúzást egyetlen printf függvényhívással valósítottuk meg. Látszik, hogy a táblázat oszlopait 9 karakter szélességűre választottuk. | |||||||||||||||||||
Figyeljük meg, hogy a pontos pozícionálást segítendő a fejléc sort és az aláhúzást a printf-ben két egymás alá írt karakterlánc konstansként adtuk meg! A C fordító a csak fehér karakterekkel elválasztott karakterlánc konstansokat egyesíti egyetlen karakterlánc konstanssá, s így a példabeli printf-nek végül is egyetlen paramétere van. | |||||||||||||||||||
A pelda2.c-ben az elöltesztelő ciklusutasítás következik, melynek szintaktikája: | |||||||||||||||||||
while(kifejezés) utasítás | |||||||||||||||||||
A kifejezés aritmetikai, azaz számértékű. Az elöltesztelő ciklusutasítás hatására lépésenként a következő történik: | |||||||||||||||||||
| |||||||||||||||||||
Az utasítás állhat több utasításból is, csak {}-be kell tenni őket. A {}-ben álló több utasítást összetett utasításnak (compound statement) nevezik. Az összetett utasítás szintaktikailag egyetlen utasításnak minősül. | |||||||||||||||||||
A példabeli belső blokk első és utolsó utasítása hozzárendelés, melynek szintaktikai alakja: | |||||||||||||||||||
objektum = kifejezés | |||||||||||||||||||
A hozzárendelés operátor (műveleti jel) bal oldalán valami olyan objektumnak kell állnia, ami értéket képes felvenni. A szaknyelv ezt módosítható balértéknek nevezi. Példánkban az összes hozzárendelés bal oldalán egy változó azonosítója áll. Az = jobb oldalán meghatározható értékű kifejezésnek (jobbértéknek) kell helyet foglalnia. A hozzárendelés hatására a kifejezés értéke - esetlegesen az objektum típusára történt konverzió után - felülírja az objektum értékét. Az egész "konstrukció" értéke az objektum új értéke, és típusa az objektum típusa. | |||||||||||||||||||
A legutóbbi mondat azt célozza, hogy ha a hozzárendelés kifejezés része, akkor ez az érték és típus vesz részt a kifejezés további kiértékelésében. | |||||||||||||||||||
Vegyük észre, hogy az eddigi printf függvényhívásainknak egyetlen karakterlánc konstans paramétere volt, mely változatlan tartalommal jelent meg a karakteres képernyőn! A belső blokkbeli printf-ben viszont három aktuális paraméter van: egy karakterlánc konstans, egy int és egy float. A gond ugye az, hogy az int és a float paraméter értékét megjelentetés előtt karakterlánccá kéne konvertálni, hisz bináris bájtok képernyőre vitelének semmiféle értelme nincs! | |||||||||||||||||||
A printf első karakterlánc paramétere | |||||||||||||||||||
| |||||||||||||||||||
áll. A karakterek változatlanul jelennek meg a képernyőn, a formátumspecifikációk viszont meghatározzák, hogy a printf további paramétereinek értékeit milyen módon kell karakterlánccá alakítani, s aztán ezt hogyan kell megjelentetni. | |||||||||||||||||||
A formátumspecifikáció % jellel indul és típuskarakterrel zárul. A formátumspecifikációk és a printf további paraméterei balról jobbra haladva rendre összetartoznak. Sőt ugyanannyi formátumspecifikáció lehet csak, mint ahány további paraméter van. | |||||||||||||||||||
Felsorolunk néhány típuskaraktert a következő táblázatban: | |||||||||||||||||||
| |||||||||||||||||||
A formátumspecifikáció pontosabb alakja: | |||||||||||||||||||
%<szélesség><.pontosság>típuskarakter | |||||||||||||||||||
A <>-be tétel az elhagyhatóságot hivatott jelezni. A szélesség annak a mezőnek a karakteres szélességét rögzíti, amiben a karakterlánccá konvertált értéket - alapértelmezés szerint jobbra igazítva, és balról szóközfeltöltéssel - kell megjelentetni. Ha a szélességet elhagyjuk a formátumspecifikációból, akkor az adat a szükséges szélességben jelenik meg. Maradjunk annyiban pillanatnyilag, hogy a pontosság lebegőpontos esetben a tizedes jegyek számát határozza meg! | |||||||||||||||||||
Lássuk be, hogy a "%9d|%9.2f\n" karakterlánckonstansból a %9d és a %9.2f formátumspecifikációk, míg a | és a \n sima karakterek! Vegyük észre, hogy a "nagy" pozícionálgatás helyett táblázatunk fejléc sorának és aláhúzásának megjelentetését így is írhattuk volna: | |||||||||||||||||||
printf("%9s|%9s\n---------+---------\n", "Forint", "Euró"); | |||||||||||||||||||
Foglalkoznunk kell még egy kicsit a műveletekkel! Vannak | |||||||||||||||||||
| |||||||||||||||||||
műveletek. Az egyoperandusos operátorokkal kevés probléma van. Az eredmény típusa többnyire egyezik az operandus típusával, és az értéke az operandus értékén végrehajtva az operátort. Például: -változó. Az eredmény típusa a változó típusa, és az eredmény értéke a változó értékének | |||||||||||||||||||
Problémák a kétoperandusos műveleteknél jelentkezhetnek, és hanyagoljuk el a továbbiakban az eredmény értékét! Ha kétoperandusos műveletnél a két operandus típusa azonos, akkor az eredmény típusa is a közös típus lesz. Ha a két operandus típusa eltér, akkor a fordító a rövidebb, pontatlanabb operandus értékét a hosszabb, pontosabb operandus típusára konvertálja, és csak ezután végzi el a műveletet. Az eredmény típusa természetesen a hosszabb, pontosabb típus. A ft/310. osztásban a ft egész típusú és a 310. konstans lebegőpontos. A művelet elvégzése előtt a ft értékét lebegőpontossá alakítja a fordító, és csak ezután hajtja végre az osztást. Az eredmény tehát ugyancsak lebegőpontos lesz. Ezt implicit típuskonverziónak nevezik. | |||||||||||||||||||
Vegyük észre közben a valós konstans írásszabályát is! Elhagyható előjellel kezdődik, amikor is pozitív, és aztán az egész rész jegyei jönnek. Aztán egy tizedespont után a tört rész számjegyei következnek. | |||||||||||||||||||
Ismét felhívjuk a figyelmet arra, hogy egészek osztásának eredménye is egész, és nincs semmiféle maradékmegőrzés, lebegőpontos átalakítás! Alakítsuk csak át az euro=ft/310. hozzárendelést euro=ft/310-re, s rögtön láthatjuk, hogy az eredményekben sehol sincs tört rész! | |||||||||||||||||||
Felvetődik a kérdés, hogyan lehetne ilyenkor a helyes értéket meghatározni? A válasz: explicit típusmódosítás segítségével, melynek szintaktikai alakja: | |||||||||||||||||||
(típus)kifejezés | |||||||||||||||||||
Hatására a kifejezés értékét típus típusúvá alakítja a fordító. A konkrét esetben az osztás legalább egyik operandusát float-tá kéne módosítani, és ugye akkor a kétoperandusos műveletekre megismert szabály szerint a másik operandus értékét is azzá alakítaná a fordító a művelet tényleges elvégzése előtt, azaz: | |||||||||||||||||||
euro = (float)ft / 310; | |||||||||||||||||||
A forint-euró átszámítási táblázatot elkészítő pelda2.c megoldásunkkal az a "baj", hogy túl sok változót használunk. Könnyen beláthatjuk, hogy az ft-től eltekintve a többi változó nem is az, hisz a program futása alatt nem változtatja meg egyik sem az értékét! Készítsünk pelda3.c néven egy jobb megoldást! | |||||||||||||||||||
/* PELDA3.C: Forint-euro atszamitasi tablazat */ | |||||||||||||||||||
pelda3.c programunkban két új dolog látható. Az egyik a | |||||||||||||||||||
for(<init-kifejezés>; <kifejezés>; <léptető-kifejezés>) utasítás | |||||||||||||||||||
elöltesztelő, iteratív ciklusutasítás, melynek végrehajtása a következő lépések szerint történik meg: | |||||||||||||||||||
| |||||||||||||||||||
Ha a for utasítást while-lal szeretnénk felírni, akkor azt így tehetjük meg: | |||||||||||||||||||
<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 pelda3.c programbeli másik új dolog az, hogy a printf aktuális paramétereként kifejezés is megadható. | |||||||||||||||||||
A pelda3.c ugyan sokat rövidült, de ezzel a megoldással meg az a probléma, hogy tele van varázs-konstansokkal. Ha megváltoztatnánk átszámítási táblázatunkban a tartomány alsó, ill. felső határát, módosítanánk a lépésközt, vagy az euró árfolyamot, akkor ennek megfelelően át kellene írnunk varázs-konstansainkat is. Az ilyen átírogatás 8 soros programnál könnyen, és remélhetőleg hibamentesen megvalósítható. Belátható azonban, hogy nagyméretű, esetleg több forrásfájlból álló szoftver esetében, amikor is a varázs-konstansok rengeteg helyen előfordulhatnak, ez a módszer megbízhatatlan, vagy legalább is nagyon hibagyanús. | |||||||||||||||||||
A C a probléma megoldására a szimbolikus konstansok, vagy más megnevezéssel: egyszerű makrók, használatát javasolja. A metódus a következő: | |||||||||||||||||||
| |||||||||||||||||||
A változtatás is nagyon egyszerűvé válik így: | |||||||||||||||||||
| |||||||||||||||||||
A szimbolikus állandó a | |||||||||||||||||||
#define azonosító helyettesítő-szöveg | |||||||||||||||||||
előfeldolgozó direktívával definiálható. A szimbolikus állandó - tulajdonképpen az azonosító - a direktíva helyétől a forrásszöveg végéig van érvényben. Az előfeldolgozó kihagyja a direktívát a forrásszövegből, majd végigmegy rajta, és az azonosító minden előfordulását helyettesítő-szövegre cseréli. | |||||||||||||||||||
Lássuk a "medvét"! | |||||||||||||||||||
/* PELDA4.C: Forint-euro atszamitasi tablazat */ | |||||||||||||||||||
Szokás még - különösen több forrásmodulos esetben - a #define direktívákat (és még más dolgokat) külön fejfájlban elhelyezni, s aztán ezt minden forrásfájl elején #include direktívával bekapcsolni. Készítsük csak el ezt a variációt is! | |||||||||||||||||||
/* PELDA5.H: Fejfajl az atszamitasi tablahoz */ | |||||||||||||||||||
Vegyük észre, hogy az #include direktíva fájlazonosítója nem <>-k, hanem ""-k között áll! Ennek hatására az előfeldolgozó a megadott azonosítójú fájlt először az aktuális mappában - abban a könyvtárban, ahol az a .c fájl is elhelyezkedik, melyben a #include direktíva volt - keresi. Ha itt nem találja, akkor továbbkeresi a programfejlesztő rendszerben beállított utakon. | |||||||||||||||||||
Bemenet, kimenet | |||||||||||||||||||
A kissé "lerágott csont" forint-euró átszámítási táblázatos példánkban nem volt bemenet. Megtanultuk már, hogy a szabvány bemenet és kimenet használatához be kell kapcsolni az stdio.h fejfájlt: | |||||||||||||||||||
#include <stdio.h> | |||||||||||||||||||
Alapértelmezés szerint szabvány bemenet (stdin) a billentyűzet, és szabvány kimenet (stdout) a képernyő. A legtöbb operációs rendszerben azonban mindkettő átirányítható szövegfájlba is. | |||||||||||||||||||
Egy karaktert olvas be a szabvány bemenetről az | |||||||||||||||||||
int getchar(void); | |||||||||||||||||||
függvény. Ezt aztán balról zérus feltöltéssel int-té típusmódosítja, és visszaadja a hívónak. | |||||||||||||||||||
Azt, ahogyan az előbb a getchar-t leírtuk, függvény prototípusnak nevezik. A függvény prototípus teljes formai információt szolgáltat a szubrutinról, azaz rögzíti: | |||||||||||||||||||
| |||||||||||||||||||
A getchar fájl végén, vagy hiba esetén EOF-ot (End Of File) szolgáltat. Az EOF az stdio.h fejfájlban definiált szimbolikus állandó: | |||||||||||||||||||
#define EOF (-1) | |||||||||||||||||||
Tekintsük csak meg az stdio.h fejfájlban! Nézegetés közben vegyük azt is észre, hogy a fejfájl tele van függvény prototípusokkal. | |||||||||||||||||||
Már csak az a kérdés maradt, hogy mi a fájlvég a szabvány bemeneten, ha az a billentyűzet? Egy operációs rendszertől függő billentyűkombináció: Ctrl+Z vagy Ctrl+D. | |||||||||||||||||||
A paraméter karaktert kiviszi a szabvány kimenet aktuális pozíciójára az | |||||||||||||||||||
int putchar(int k); | |||||||||||||||||||
és sikeres esetben vissza is adja ezt az értéket. A hibát épp az jelzi, ha a putchar szolgáltatta érték eltér k-tól. | |||||||||||||||||||
Készítsünk programot, ami a szabvány bemenetet átmásolja a szabvány kimenetre! | |||||||||||||||||||
/* PELDA6.C: Bemenet masolasa a kimenetre */ | |||||||||||||||||||
Fogalmazzuk meg minimális elvárásainkat egy programmal szemben! | |||||||||||||||||||
| |||||||||||||||||||
A kétirányú szelekció szintaktikai alakja: | |||||||||||||||||||
if(kifejezés) utasítás1 | |||||||||||||||||||
Az elhagyhatóságot most is a <> jelzi. Ha a kifejezés igaz (nem zérus), akkor utasítás1 végrehajtása következik. Ha a kifejezés hamis (zérus) és van else rész, akkor az utasítás2 következik. Mindkét utasítás összetett utasítás is lehet. | |||||||||||||||||||
while((k=getchar())!=EOF) | |||||||||||||||||||
A whilekifejezése egy nem egyenlő reláció, melynek bal oldali operandusa egy külön zárójelben álló hozzárendelés. Előbb a hozzárendelés jobb oldalát kell kiértékelni. Lássuk csak sorban a kiértékelés lépéseit! | |||||||||||||||||||
| |||||||||||||||||||
A kifejezésből a hozzárendelés körüli külön zárójel nem hagyható el, mert a hozzárendelés alacsonyabb prioritású művelet a relációnál. Ha mégis elhagynánk, akkor a kiértékelés során a fordító: | |||||||||||||||||||
| |||||||||||||||||||
Figyeljük meg a pelda6.c futtatásakor, hogy a getchar a bemenetről olvasott karaktereket az operációs rendszer billentyűzet pufferéből kapja! Emlékezzünk csak vissza! A parancssorban a begépelt szöveget szerkeszthetjük mindaddig, míg Enter-t nem nyomunk. A billentyűzet pufferben levő karakterek tehát csak akkor állnak a getchar rendelkezésére, ha a felhasználó leütötte az Enter billentyűt. | |||||||||||||||||||
Készítsünk programot, mely fájlvégig leszámlálja, hogy hány | |||||||||||||||||||
| |||||||||||||||||||
érkezett a szabvány bemenetről! | |||||||||||||||||||
Megoldásunkban az összes változó egész típusú. k tartalmazza a beolvasott karaktert. A num, a feher és az egyeb számlálók. Az algoritmus: | |||||||||||||||||||
| |||||||||||||||||||
/* PELDA7.C: A bemenet karaktereinek | |||||||||||||||||||
Pontosítani kell a deklarációs utasítás eddig megismert szintaktikáját! | |||||||||||||||||||
<típusmódosítók> <alaptípus> azonosítólista; | |||||||||||||||||||
Az elhagyható alaptípus alapértelmezés szerint int. Az ugyancsak elhagyható típusmódosítók az alaptípus valamilyen jellemzőjét változtatják meg. int típus esetén: | |||||||||||||||||||
| |||||||||||||||||||
Végül is a különféle int típusok méretei így összegezhetők: | |||||||||||||||||||
short <= int <= long | |||||||||||||||||||
Vegyük észre, hogy az ismertetett szabályok szerint a short, a short int és a signed short int azonos típusok. A short és a short int írásmódnál figyelembe vettük, hogy signed az alapértelmezés. A short felírásakor még arra is tekintettel voltunk, hogy a meg nem adott alaptípus alapértelmezése int. Ugyanezek mondhatók el a long, a long int és a signed long int vonatkozásában is. | |||||||||||||||||||
Ugyan a szintaktika azt mutatja, de a deklarációs utasításban a típusmódosítók és az alaptípus egyszerre nem hagyhatók el! | |||||||||||||||||||
Feltéve, hogy a, b és c balértékek, az | |||||||||||||||||||
a=b=c=kifejezés | |||||||||||||||||||
értelmezése megint abból fakad, hogy a hozzárendelés a C-ben operátor, | |||||||||||||||||||
azaz: | |||||||||||||||||||
a=(b=(c=kifejezés)) | |||||||||||||||||||
A fordító jobbról balra halad, azaz kiértékeli a kifejezést, és visszafelé jövet beírja az eredményt a balértékekbe. A konstrukció hatására a fordító gyorsabb kódot is hoz létre. Ugyanis a | |||||||||||||||||||
c=kifejezés; | |||||||||||||||||||
írásmódnál háromszor kell kiértékelni ugyanazt a kifejezést. | |||||||||||||||||||
C-ben a többágú (N) szelekcióra az egyik kódolási lehetőség: | |||||||||||||||||||
if(kifejezés1)utasítás1 | |||||||||||||||||||
Ha valamelyik if kifejezése igaz (nem zérus) a konstrukcióban, akkor a vele azonos sorszámú utasítás végrehajtása következik, majd a konstrukciót követő utasítás jön. Ha minden kifejezés hamis (zérus), akkor viszont utasításN hajtandó végre. | |||||||||||||||||||
Fedezzük fel a karakter konstans írásszabályát: aposztrófok között karakter, vagy escape szekvencia. A karakter konstans belsőábrázolása int, így az ASCII kód egész értéknek is minősül kifejezésekben. | |||||||||||||||||||
A kétoperandusos logikai operátorok prioritása alacsonyabb a relációkénál, és az && magasabb prioritású, mint a ||. Ha a kétoperandusos logikai művelet eredménye eldől a bal oldali operandus kiértékelésével, akkor a C bele sem kezd a másik operandus értékelésébe. Az és művelet eredménye eldőlt, ha a bal oldali operandus hamis. A vagy pedig akkor kész, ha az első operandus igaz. | |||||||||||||||||||
A C-ben van inkrementálás (növelés, ++) és dekrementálás (csökkentés, --) egész értékekre. Mindkét művelet egyoperandusos, tehát nagyon magas prioritású. A ++ operandusa értékét eggyel növeli meg, s a -- pedig eggyel csökkenti, azaz: | |||||||||||||||||||
++változó | |||||||||||||||||||
A problémák ott kezdődnek azonban, hogy mindkét művelet létezik előtag (prefix) és utótag (postfix) operátorként is! Foglalkozzunk csak a ++ operátorral! A ++változó és a változó++ hatására a változó értéke eggyel mindenképp megnövekedik. Kifejezés részeként előtag operátor esetén azonban a változó új értéke vesz részt a további kiértékelésben, míg utótag műveletnél a változó eredeti értéke számít be. Feltéve, hogy a és b egész típusú változók, és b értéke 6: | |||||||||||||||||||
a = ++b; /* a=7 és b=7 */ | |||||||||||||||||||
Figyeljünk fel rá, hogy a pelda7.c utolsó printf utasításában hosszmódosítók állnak a d típuskarakterek előtt a formátumspecifikációkban! Látszik, hogy a h jelzi a printf-nek, hogy a formátumspecifikációhoz tartozó aktuális paraméter short típusú (2 bájtos), ill. l tudatja vele, hogy a hozzátartozó aktuális paraméter long (4 bájtos). | |||||||||||||||||||
A megfelelő hosszmódosítók megadása a formátumspecifikációkban elengedhetetlen, hisz nem mindegy, hogy a függvény a verem következő hány bájtját tekinti a formátumspecifikációhoz tartozónak! | |||||||||||||||||||
A verem (stack) alatt itt azt a memóriaterületet értjük, amelyet a fordító a függvény paramétereinek átadásához illetve a hívás helyének tárolásához használ, ahol a hívó függvény futtatása a hívott függvény futásának befejeztével folytatódhat, de a fogalom jelentése ennél általánosabb. Verem alatt többnyire egy LIFO (Last In First Out) adatszerkezetet értenek, azaz olyan tárolót, amelyből mindig az utoljára belé helyezett adatot lehet kinyerni. Ennek érdekében legalább két műveletet is szokás biztosítani: a push (kb. rárak) segítségével tárolható el egy adat, és a pop (kb. levesz) művelettel olvasható ki, ami azt el is távolítja egyben a tárból. Ezt általában egy véges méretű összefüggő memóriaterülettel (mint pl. egy tömb) és egy veremmutató segítségével lehet kivitelezni, ahol a veremmutató megvalósítástól függően az utolsó verembe helyezett, vagy a következő alkalommal a verembe helyezendő adat helyét jelöli ki a vermen belül. Értékét mind a push, mind a pop művelet végrehajtását követően módosítani kell. Teli verembe értelemszerűen nem lehet további adatokat elhelyezni, üresből pedig eltávolítani, ilyenkor túl- vagy alulcsordul a verem. A vermet sokszor hardver szinten valósítják meg. | |||||||||||||||||||
Vegyük azt is észre, hogy a típusokhoz a mezőszélességgel is felkészültünk: a maximális pozitív short érték bizonyosan elfér 5 pozíción, s long pedig 10-en! Látható még, hogy arra is vigyáztunk, hogy a három maximális short érték összege részeredményként se csonkuljon! Ezért az explicit long-gá módosítás a printf utolsó paraméterében: | |||||||||||||||||||
(long)num+feher+egyeb |
Feladatok | |||||||||||||
1. Mit fog megjeleníteni a program a szabványos kimeneten, ha a /* ? */ helyére a következőket írjuk? #include<stdio.h> void main(void) { int v; v = 1; /* ? */ printf("%d", v); }
![]() | |||||||||||||
2. Mit fog megjeleníteni a program a szabványos kimeneten, ha a /* ? */ helyére a következőket írjuk? #include<stdio.h> void main(void) { int v1, v2, w; v1 = 2; v2 = 3; /* ? */ printf("%d", w); }
![]() | |||||||||||||
3. Mit fog megjeleníteni a program a szabványos kimeneten, ha a /* ? */ helyére a következőket írjuk? #include<stdio.h> void main(void) { int v; v = 12; if(/* ? */) printf("igen"); else printf("nem"); }
![]() | |||||||||||||
4. Mit fog megjeleníteni a program a szabványos kimeneten, ha a /* ? */ helyére a következőket írjuk? #include<stdio.h> void main(void) { int i, n; n = 5; for(i=0; i<n; i++) printf("%d ", /* ? */); }
![]() | |||||||||||||
5. Olyan programot szeretnénk készíteni, amelyik a karakteres üzemmódú képernyő egy 21x21-es ablakában csillag karakterekkel megjelentet egy keresztet a 10. sor és 10. oszlop feltöltésével. Jelölje meg azokat a sorokat, amelyeket a /* ? */ helyére helyettesítve helyes működést kapunk! #include <stdio.h> void main(void) { int i; /* ? */ if (i==10) printf("*********************\n"); else printf(" *\n"); }
![]() | |||||||||||||
6. Olyan programot szeretnénk készíteni, amelyik a karakteres üzemmódú képernyő egy 21x21-es ablakában csillag karakterekkel megjelenteti a főátlót (bal felső sarokból a jobb alsóba menőt). Jelölje meg azokat a sorokat, amelyeket a /* ? */ helyére helyettesítve helyes működést kapunk! #include <stdio.h> void main(void) { int i,j; for (i=0;i<21;i++) { for (/* ? */) printf(" "); printf("*\n"); } }
![]() | |||||||||||||
7. Olyan programot szeretnénk készíteni, amelyik a karakteres üzemmódú képernyő egy 21x21-es ablakában csillag karakterekkel megjelenteti a mellékátlót (jobb felső sarokból a bal alsóba menőt). Jelölje meg azokat a sorokat, amelyeket a /* ? */ helyére helyettesítve helyes működést kapunk! #include <stdio.h> void main(void) { int i,j; for (i=0;i<21;i++) { for (/* ? */) printf(" "); printf("*\n"); } }
![]() | |||||||||||||
8. Írjon olyan programot, amely a karakteres üzemmódú képernyő egy 21x21-es ablakában csillag karakterekkel megjelenteti a fő- és mellékátlót egyszerre, vagyis egy X-et! | |||||||||||||
#include <stdio.h> |