KURZUS: Programozás alapjai
MODUL: I. modul
3. lecke: Alapismeretek 2/2
A harmadik leckében folytatjuk a C nyelv legalapvetőbb elemeinek megismerését. Ennek során megnézzük, hogyan lehet tömböket és karakterláncokat használni, saját függvényeket készítünk, de nem állunk meg a modularitásnak ezen a szintjén. Azt is megvizsgáljuk, hogyan tudjuk a programunkat több forrásfájlból elkészíteni. Megvizsgáljuk a lokális és globális változók közötti főbb különbségeket, és megismerkedünk a változók inicializálásának lehetőségeivel is. | |||||||||||||||||||||
A lecke végére: | |||||||||||||||||||||
| |||||||||||||||||||||
A lecke kulcsfogalmai: alul- és túlcsordulás, projektfájl, implicit függőség, folytatássor, lokális és globális változó, hatáskör, élettartam, tárolási osztály, inicializátor. | |||||||||||||||||||||
Tömbök | |||||||||||||||||||||
Készítsünk programot, mely a szabvány bemenetet olvassa EOF-ig! Megállapítandó és kijelzendő, hogy hány A, B, C stb. karakter érkezett! A kis- és nagybetűk között nem teszünk különbséget! A betűkön kívüli többi karaktert tekintsük egy kategóriának, s ezek darabszámát is jelezzük ki! | |||||||||||||||||||||
Megoldásunkban az elvalaszto karakteres változó, a k, a tobbi és a betu viszont egész típusú. A k tartalmazza a beolvasott karaktert, és ciklusváltozói funkciókat is ellát. A tobbi és a betu számlálók. A betu annyi elemű tömb, mint ahány betű az angol ábécében van. A tobbi a betűkön kívüli többi karakter számlálója. Az elvalaszto karakteres változóra azért van szükség, mert az eredmény csak két oszlop páros listaként közölhető egy képernyőn. Az algoritmus: | |||||||||||||||||||||
| |||||||||||||||||||||
/* PELDA8.C: Betuszamlalas a bemeneten */ | |||||||||||||||||||||
A char típusú változó egyetlen karakter tárolására alkalmas. A char ugyanakkor 8 bites, alapértelmezés szerint előjeles (signed), fixpontos belsőábrázolású egész típus is. Ennek is létezik előjel nélküli változata, sőt, a legtöbb programfejlesztő rendszerben az unsigned char alapértelmezésként is beállítható karakter típus. | |||||||||||||||||||||
A pelda8.c-ből kitűnően látszik, hogy a tömb definíciója | |||||||||||||||||||||
típus tömbazonosító[méret]; | |||||||||||||||||||||
alakú. Pontosabban a deklarációs utasítás azonosítólistája nem csak egyszerű változók azonosítóiból állhat, hanem tömbazonosító[méret] konstrukciók is lehetnek köztük. A tömbdefinícióban a méret pozitív, egész értékű állandó kifejezés, és a tömb elemszámát határozza meg. Állandó kifejezés az, aminek fordítási időben (a program fordítása során) kiszámítható az értéke. | |||||||||||||||||||||
A tömb egy elemének helyfoglalása típusától függ. Az egész tömb a memóriában összesen | |||||||||||||||||||||
sizeof(tömbazonosító) | |||||||||||||||||||||
bájtot igényel. Például 16 bites int-et feltételezve a sizeof(betu) | |||||||||||||||||||||
Vegyük észre, hogy a betu[0]-ban a program az A, a betu[1]-ben a B, ..., és a betu[25]-ben a Z karaktereket számlálja! Tételezzük fel, hogy k értéke 68! Ez ugyebár a D betű ASCII kódja. Ilyenkor a betu[k-'A'] számláló nő eggyel. Az A ASCII kódja 65. Tehát betu[68-65]-ről, azaz betu[3] növeléséről van szó! | |||||||||||||||||||||
Figyeljünk fel még arra, hogy az eredményeket közlő ciklusbeli printf-ben a k+'A' egész kifejezés értékét %c formátumspecifikációval jelentetjük meg, azaz rendre 65-öt, 66-ot, 67-et stb. íratunk ki karakteresen, tehát A-t, B-t, C-t stb. látunk majd. | |||||||||||||||||||||
Lássuk még be, hogy az elvalaszto változó értéke szóköz és soremelés karakter közt váltakozik, s így két betű-darab pár képes megjelenni egy sorban. Tehát az elvalaszto változó segítségével produkáljuk a két oszlop páros eredménylistát. | |||||||||||||||||||||
Listázni viszont csak azt érdemes, ami valamilyen információt hordoz! Tehát a zérus darabszámú betűk kijelzése teljesen felesleges! Magyarán a for ciklusbeli printf-et így kéne módosítani: | |||||||||||||||||||||
if(betu[k]>0) printf("%4c|%5d%c", k+'A', betu[k], elvalaszto); | |||||||||||||||||||||
Függvények | |||||||||||||||||||||
A függvényeket többféleképpen csoportosíthatnánk, de a legpraktikusabb úgy, hogy: | |||||||||||||||||||||
| |||||||||||||||||||||
A nyelv központi eleme a függvény. A más programozási nyelvekben szokásos eljárás (procedure) itt explicit módon nem létezik, mert a C szellemében az egy olyan függvény, aminek nincs visszaadott értéke: | |||||||||||||||||||||
void eljárás(); | |||||||||||||||||||||
Jelezzük ki egy táblázatban az 1001 és 1010 közötti egész számok köbét! | |||||||||||||||||||||
/* PELDA9.C: Kobtablazat */ | |||||||||||||||||||||
A függvénydefiníció és a függvényhívás fogalmával megismerkedtünk már a Kapcsoló-szerkesztés fejezetben. A függvénydefinícióban van meg a függvény teste, azaz az a kód, amit a függvény meghívásakor végrehajt a processzor. Egy függvényre a programban csak egyetlen definíció létezhet, és ennek nem mondhatnak ellent a prototípusok (deklarációk)! A függvénydefinícióban előírt visszaadott érték típusának egyeznie kell ebből következőleg a programban bárhol előforduló, e függvényre vonatkozó prototípusokban (deklarációkban) megadott visszatérési érték típussal. A meghívott függvény akkor ad vissza értéket a hívó függvénynek a hívás pontjára, ha a processzor kifejezéssel ellátott return utasítást hajt végre benne. A "valamit" szolgáltató függvényben tehát lennie kell legalább egy return kifejezés; utasításnak, és rá is kell, hogy kerüljön a vezérlés. A visszaadott érték meghatározatlan, ha a processzor nem hajt végre return utasítást, vagy a return utasításhoz nem tartozott kifejezés. A visszaadott érték típusa bármi lehet végül is eltekintve a tömbtől és a függvénytől. Lehet valamilyen alaptípus, de el is hagyható, amikor is az alapértelmezés lesz érvényben, ami viszont int. | |||||||||||||||||||||
Nézzük a return szintaktikáját! | |||||||||||||||||||||
return <kifejezés>; | |||||||||||||||||||||
A fordító kiértékeli a kifejezést. Ha a függvény visszatérési típusa típus, akkor a kifejezés típusának is ennek kell lennie, vagy implicit konverzióval ilyen típusúvá alakítja a kifejezés értékét a fordító, és csak azután adja vissza. | |||||||||||||||||||||
Lássuk be, hogy a pelda9.c-beli return-ben az explicit (long) típusmódosítás nem azért van, hogy megtakarítsuk a kifejezés értékének visszaadás előtti implicit konverzióját! Az igazi ok az, hogy egy 16 bites int köbe nem biztos, hogy elfér az int-ben! Gondoljunk 1000 köbére, ami 1000000000! Ez jóval meghaladja a 32767-es felsőábrázolási korlátot. | |||||||||||||||||||||
C-ben az egész típusok területén nincs sem túlcsordulás, sem alulcsordulás! Pontosabban, amelyik művelet eredménye túl magas vagy túl alacsony lenne az adott típusban történő reprezentáláshoz, az mindenféle üzenet nélkül elveszik. | |||||||||||||||||||||
A függvényhívás átruházza a vezérlést a hívó függvényből a hívottba úgy, hogy az aktuális paramétereket is átadja - ha vannak - érték szerint (azaz az aktuális paraméterek másolatait elhelyezi a verembe, és a hívott függvény ezekkel a másolatokkal dolgozik). A vezérlést a függvénytest első végrehajtható utasítása kapja meg. void visszatérésű függvény blokkjában aztán a végrehajtás addig folytatódik, míg kifejezés nélküli return utasítás nem következik, vagy a függvény blokkját záró }-re nem kerül a vezérlés. Ezután a hívási ponttól folytatódik a végrehajtás. | |||||||||||||||||||||
Vegyük észre, hogy a return utasítás szintaktikájában az elhagyható kifejezés a paraméter nélküli return-t kívánta jelölni! | |||||||||||||||||||||
Belátható, hogy a függvény prototípusnak mindig meg kell előznie a hívást a forrásszövegben. A fordító így tisztában van a hívás helyén a függvény paramétereinek számával, sorrendjével és típusával, ill. ismeri a függvény visszatérési értékének típusát is. | |||||||||||||||||||||
A fordító a prototípus ismeretében implicit típuskonverziót is végrehajt az aktuális paraméter értékén a függvénynek történő átadás előtt, ha az aktuális paraméter típusa eltérő. | |||||||||||||||||||||
Ha nincs prototípus, akkor nincs implicit konverzió, és csak a "csoda" tudja, hogy mi történik az átadott nem megfelelő típusú értékkel. Például a kob(3.0) hívás eredménye zérus, ami remélhetőleg kellően szemlélteti a prototípus megadásának szükségességét. Ha nincs prototípus, akkor a fordító azt feltételezi (tehát olyan hívási kódot generál), hogy a függvénynek az alapértelmezés miatt int visszaadott értéke van. Ez ugyebár eléggé érdekes eredményre vezet void, vagy lebegőpontos visszatérésű függvények esetében. A nem int visszaadott értékű függvényt legalább deklaráni kell a hívó függvényben! | |||||||||||||||||||||
A függvénydeklaráció bemutatásához átírjuk a pelda9.c-t: | |||||||||||||||||||||
/* PELDA9.C: Kobtablazat */ | |||||||||||||||||||||
Vegyük észre rögtön, hogy deklarációs utasításunk szintaktikája ismét módosult! Az azonosítólista nem csak egyszerű változók azonosítóiból és tömbazonosító[méret] konstrukciókból állhat, hanem tartalmazhat | |||||||||||||||||||||
függvénynév() | |||||||||||||||||||||
alakzatokat is. Természetesen a teljes függvény prototípus is beírható a deklárációs utasításba, | |||||||||||||||||||||
long kob(int); /* Fuggveny deklaracio. */ | |||||||||||||||||||||
de ilyenkor a függvény prototípus csak ebben a blokkban lesz érvényben. | |||||||||||||||||||||
Lássuk be, hogy az utóbbi módszer nem ajánlható olyan több függvénydefinícióból álló forrásfájlra, ahol a kérdéses függvényt több helyről is meghívják! Sokkal egyszerűbb a forrásszöveg elején megadni egyszer a prototípust, mint minden őt hívó függvényben külön deklarálni a függvényt. | |||||||||||||||||||||
A függvény definíciója prototípusnak is minősül, ha megelőzi a forrásszövegben a függvényhívást. | |||||||||||||||||||||
/* PELDA9.C: Kobtablazat */ | |||||||||||||||||||||
C-ben tilos függvénydefiníción belül egy másikat kezdeni, azaz a függvénydefiníciók nem ágyazhatók egymásba! | |||||||||||||||||||||
Projekt | |||||||||||||||||||||
Ha a végrehajtható program forrásszövegét témánként, vagy funkciónként külön-külön forrásfájlokban kívánjuk elhelyezni, akkor C-s programfejlesztő rendszerekben ennek semmiféle akadálya sincs. Be kell azonban tartani a következő szabályokat: | |||||||||||||||||||||
| |||||||||||||||||||||
Szedjük szét két forrásmodulra: foprog.c-re és fuggv.c-re, a pelda9.c programunkat! | |||||||||||||||||||||
/* FOPROG.C: Kobtablazat */ | |||||||||||||||||||||
A projektfájl neve, szerkezete fejlesztőkörnyezetenként eltérő lehet. A VS-ban eleve nem is lehet projekt nélkül dolgozni, ezért most csak annyi a teendő, hogy a korábban beletett fájlokat kivesszük belőle és betesszük az újakat. A "Solution explorer"-ben a "Source files" mappában láthatóak a projektben lévő forrásfájlok. Ezek valamelyikén a jobb kattintással előhívható "Remove" menüponttal lehet a fájlt eltávolítani. Tanulmányaink során, amikor is nem jellemzőek az összetett szoftverfejlesztési tevékenységek, hosszabb távon érdemes lehet csak egyszer készíteni egy projektet, majd abban cserélgetni a forrásfájlokat, hogy elkerüljük az új projektek létrehozásával járó nehézségeket. | |||||||||||||||||||||
| |||||||||||||||||||||
| |||||||||||||||||||||
Fedezzük fel, hogy a végrehajtható fájl a projekt nevét kapja meg! A projektet alkotó fájlok között implicit függőség van. Ez azt jelenti, hogy a projekt futtatásakor csak akkor történik meg a tárgymodul alakjában is rendelkezésre álló forrásfájl fordítása, ha a forrásfájl utolsó módosításnak ideje (dátuma és időpontja) későbbi, mint a vonatkozó tárgymodulé. A kapcsoló-szerkesztés végrehajtásához az szükséges, hogy a tárgymodulok, ill. a könyvtárak valamelyikének ideje későbbi legyen a végrehajtható fájlénál. Az implicit függőség fennáll a forrásfájl és a bele #include direktívával bekapcsolt fájlok között is. A tárgymodult akkor is újra kell fordítani, ha valamelyik forrásfájlba behozott fájl ideje későbbi a tárgymodulénál. | |||||||||||||||||||||
| |||||||||||||||||||||
A projektfájlban a forrásmodulokon kívül megadhatók tárgymodulok és könyvtárak fájlazonosítói is. A kapcsoló-szerkesztő a tárgymodulokat beszerkeszti a végrehajtható fájlba. A könyvtárakban pedig függvények tárgykódjait fogja keresni. | |||||||||||||||||||||
Karaktertömb és karakterlánc | |||||||||||||||||||||
A karaktertömbök definíciója a Tömbök fejezetben ismertetettek szerint: | |||||||||||||||||||||
char tömbazonosító[méret]; | |||||||||||||||||||||
Az egész tömb helyfoglalása: | |||||||||||||||||||||
méret*sizeof(char) | |||||||||||||||||||||
bájt. A tömbindexelés ebben az esetben is zérustól indul és méret-1-ig tart. | |||||||||||||||||||||
A C-ben nincs külön karakterlánc (sztring) adattípus. A karakterláncokat a fordítónak és a programozónak karaktertömbökben kell elhelyeznie. A karakterlánc végét az őt tartalmazó tömbben egy zérusértékű bájttal ('\0') kell jelezni. Például a "Gizike" karakterláncot így kell letárolni a nev karaktertömbben: | |||||||||||||||||||||
| |||||||||||||||||||||
Vegyük észre, hogy a karakterlánc első jele a nev[0]-ban, a második a nev[1]-ben, s a legutolsó a nev[5]-ben helyezkedik el, és az ezt követő nev[6] tartalmazza a lánczáró zérust! Figyeljünk fel arra is, hogy a karakterlánc hossza (6) megegyezik a lezáró '\0' karaktert magába foglaló tömbelem indexével! Fedezzük még rögtön fel, hogy a zérus egész konstans (0) és a lánczáró '\0' karakter értéke ugyanaz: zérus int típusban! Hiszen a karakter konstans belsőábrázolása int. | |||||||||||||||||||||
A C-ben nincs külön karakterlánc adattípus, s ebből következőleg nem léteznek olyan sztring műveletek sem, mint a karakterláncok | |||||||||||||||||||||
| |||||||||||||||||||||
Ezeket a műveleteket bájtról bájtra haladva kell kódolni, vagy függvényt kell írni rájuk, mint ahogyan azt a következő példában bemutatjuk. (A szabványos függvénykönyvtár igazából tartalmaz előre elkészített függvényeket ezekre a gyakran használt feladatokra, de többet tanulunk abból, ha kezdetben magunk készítjük el ezeket.) | |||||||||||||||||||||
Készítsen programot, mely neveket olvas a szabvány bemenetről EOF-ig vagy üres sorig! Megállapítandó egy fordítási időben megadott névről, hogy hányszor fordult elő a bemeneten! A feladat megoldásához készítendő | |||||||||||||||||||||
| |||||||||||||||||||||
/* PELDA10.C: Nevszamlalas */ | |||||||||||||||||||||
Ha a forráskód egy sorát lezáró soremelés karaktert közvetlenül \ jel előzi meg, akkor mindkét karaktert elveti a fordító (pontosabban az előfeldolgozó), s a két fizikai sort egyesíti, és egynek tekinti. Az ilyen értelemben egyesített sorokat már a másodiktól kezdve folytatássornak nevezik. A main első printf-jét folytatássorral készítettük el. | |||||||||||||||||||||
A két elkészített függvény prototípusából és definíciójából vegyük észre, hogy a formális paraméter karaktertömb (és persze más típusú tömb is) méret nélküli! | |||||||||||||||||||||
Az strcmp megírásakor abból indultunk ki, hogy két karakterlánc akkor egyenlő egymással, ha ugyanazon pozícióikon azonos karakterek állnak, és ráadásul a lánczáró zérus is ugyanott helyezkedik el. Az i ciklusváltozót zérusról indítva, s egyesével haladva, végigindexeljük az s1 karaktertömböt egészen a lánczáró nulláig (s1[i]!=0) akkor, ha közben minden i-re az s1[i]==s2[i] is teljesült. Ebből a ciklusból tehát csak két esetben van kilépés: | |||||||||||||||||||||
| |||||||||||||||||||||
A visszaadott s1[i]-s2[i] különbség csak akkor: | |||||||||||||||||||||
| |||||||||||||||||||||
Az strcmp-t már megírták. Tárgykódja benne van a szabvány könyvtárban. Nem fáradtunk azonban feleslegesen, mert a szabvány könyvtári változat ugyanúgy funkcionál, mint a pelda10.c-beli. Használatához azonban be kell kapcsolni a prototípusát tartalmazó string.h fejfájlt. | |||||||||||||||||||||
Írjuk be a pelda10.c #include direktívája után a | |||||||||||||||||||||
#include <string.h> | |||||||||||||||||||||
és töröljük ki a forrásszövegből az strcmp prototípusát és definícióját! Végül próbáljuk ki! | |||||||||||||||||||||
Feltéve, hogy s1 és s2 karaktertömbök, lássuk be, hogy C programban, ugyan szintaktikailag nem helytelen, semmi értelme sincs az ilyen kódolásnak, hogy: | |||||||||||||||||||||
s1==s2, s1!=s2, s1>=s2, s1>s2, s1<=s2, s1<s2 | |||||||||||||||||||||
E módon ugyanis s1 és s2 memóriabeli elhelyezkedését hasonlítjuk össze, azaz bizton állíthatjuk, hogy az s1==s2 mindig hamis, és az s1!=s2 pedig mindig igaz. | |||||||||||||||||||||
A getline ciklusváltozója ugyancsak zérusról indul, és a ciklus végéig egyesével halad. A ciklusmagból látszik, hogy a szabvány bemenetről érkező karakterekkel tölti fel az s karaktertömböt. A ciklus akkor áll le, | |||||||||||||||||||||
| |||||||||||||||||||||
Ezután kitesszük a tömb i-ik elemére a lánczáró zérust, s visszaadjuk i-t, azaz a behozott karakterlánc hosszát. | |||||||||||||||||||||
getline függvényünk jó próbálkozás az egy sor behozatalára a szabvány bemenetről, de van néhány problémája, ha a bemenet a billentyűzet: | |||||||||||||||||||||
| |||||||||||||||||||||
Írjuk csak át a pelda10.c main-jében a | |||||||||||||||||||||
while(getline(s,MAX)>0) | |||||||||||||||||||||
sort a következőre | |||||||||||||||||||||
while(getline(s,4)>0) | |||||||||||||||||||||
és a program indítása után gépeljük be a | |||||||||||||||||||||
GiziJaniKataJani<enter> | |||||||||||||||||||||
sorokat! | |||||||||||||||||||||
Az első két probléma az operációs rendszer viselkedése miatt van, s jelenleg sajnos nem tudunk rajta segíteni. A második gondhoz még azt is hozzá kell fűznünk, hogy fájlvéget a billentyűzet szabvány bemeneten a getline-nak csak üres sor megadásával tudunk előidézni. | |||||||||||||||||||||
A harmadik probléma viszont könnyedén orvosolható, csak ki kell olvasni a bemeneti puffert végig a getline-ból való visszatérés előtt. Tehát: | |||||||||||||||||||||
int getline(char s[],int n){ | |||||||||||||||||||||
Lokális, globális és belső, külső változók | |||||||||||||||||||||
Ha alaposan áttanulmányozzuk a pelda10.c-t, akkor észrevehetjük, hogy mind az strcmp-ben, mind a getline-ban van egy-egy, int típusú i változó. Feltehetnénk azt a kérdést, hogy mi közük van egymáshoz? A rövid válasz nagyon egyszerű: semmi. A hosszabb viszont kicsit bonyolultabb: | |||||||||||||||||||||
| |||||||||||||||||||||
Tehát a két i változónak a névegyezésen túl nincs semmi köze sem egymáshoz. A változó hatásköre (scope) az a programterület, ahol érvényesen hivatkozni lehet rá, el lehet érni. Nevezik ezt ezért érvényességi tartománynak is. | |||||||||||||||||||||
A lokális változó hatásköre az a blokk, és annak minden beágyazott blokkja, amiben definiálták. A blokkon belülisége miatt a lokális változót belső változónak is nevezik. | |||||||||||||||||||||
A változó élettartama (lifetime) az az időszak, amíg memóriát foglal. | |||||||||||||||||||||
A lokális változó akkor jön létre (rendszerint a veremben), amikor a vezérlés bejut az őt definiáló blokkba. Ha a vezérlés kikerül onnét, akkor a memóriafoglalása megszűnik, helye felszabadul (a veremmutató helyrejön). | |||||||||||||||||||||
A függvényekben, így a main-ben is, definiált változók mind lokálisak arra a függvényre, amelyben deklarálták őket. Csak az adott függvényen belül lehet hozzájuk férni. Ez a lokális hatáskör. Futási időben a függvénybe való belépéskor jönnek létre, és kilépéskor meg is semmisülnek. Ez a lokális élettartam. A lokális változó kezdőértéke ebből következőleg "szemét". Pontosabban az a bitkombináció, ami azokban a memóriabájtokban volt, amit most létrejövetelekor a rendszer rendelkezésére bocsátott. Tehát a belső változónak nincs alapértelmezett kezdőértéke. Az alapértelmezett kezdőértéket implicit kezdőértéknek is szokták nevezni. | |||||||||||||||||||||
Mi dönti el, hogy a belső változó melyik blokkra lesz lokális? Nyilvánvalóan az, hogy az őt definiáló deklarációs utasítást melyik blokk elején helyezik el. | |||||||||||||||||||||
Tisztázzuk valamennyire a változó deklarációja (declaration) és definíciója (definition) közti különbséget! Mindkét deklarációs utasításban megadják a változó néhány tulajdonságát, de memóriafoglalásra csak definíció esetén kerül sor. | |||||||||||||||||||||
A lokális változó az előzőekben ismertetetteken kívül auto tárolási osztályú is. | |||||||||||||||||||||
Egészítsük ki újból a deklarációs utasítás szintaktikáját! | |||||||||||||||||||||
<tárolási-osztály> <típusmódosítók> <alaptípus> azonosítólista; | |||||||||||||||||||||
A tárolási-osztály a következő kulcsszavak egyike lehet: auto, register, static, vagy extern. E lecke keretében nem foglalkozunk a register és a static tárolási osztállyal! | |||||||||||||||||||||
A szintaktikát betartva most felírható, hogy | |||||||||||||||||||||
auto i; | |||||||||||||||||||||
ami az auto signed int típusú, i azonosítójú változó definíciója. Hogyan lehet auto tárolási osztályú a lokális változó? Nyilván úgy, hogy a lokális helyzetű deklarációs utasításban az auto tárolási osztály alapértelmezés. Az auto kulcsszó kiírása ezekben az utasításokban teljesen felesleges. | |||||||||||||||||||||
Foglaljuk össze a lokális változóval, vagy más néven belső változóval, kapcsolatos ismereteinket! | |||||||||||||||||||||
| |||||||||||||||||||||
Meg kell említeni, hogy a függvények formális paraméterei is lokális változóknak minősülnek, de | |||||||||||||||||||||
| |||||||||||||||||||||
Az előfeldolgozáson átesett forrásmodult fordítási egységnek nevezik. A fordítási egység függvénydefiníciókból és külső deklarációkból áll. Külső deklaráció alatt a minden függvény "testén" kívüli deklarációt értjük. Az így definiált változót külső változónak, vagy globális változónak (global variable) nevezik. Az ilyen változó: | |||||||||||||||||||||
| |||||||||||||||||||||
Bánjunk csínján az extern kulcsszó explicit használatával, mert deklarációs utasításban való megadása éppen azt jelenti, hogy az utasítás nem definíció, hanem csak deklaráció! Más fordítási egységben definiált külső változót kell ebben a forrásmodulban így deklarálni a rá való hivatkozás előtt. | |||||||||||||||||||||
| |||||||||||||||||||||
Projektünk álljon e két forrásmodulból! Az int típusú i változót és a t karaktertömböt Forrásmodul 2-ben definiálták. Itt történt tehát meg a helyfoglalásuk. Ezek a változók Forrásmodul 1-ben csak akkor érhetők el, ha a rájuk történő hivatkozás előtt extern kulcsszóval deklarálják őket. Vegyük észre, hogy a Forrásmodul 1 elején elhelyezett deklarációs utasítások egyrészt globális szinten vannak, másrészt valóban csak deklarációk, hisz a tömb deklarációjában méret sincs! Persze azt, hogy a tömb mekkora, valahonnét a Forrásmodul 1-ben is tudni kell! | |||||||||||||||||||||
Írjuk át úgy a pelda10.c-t, hogy a beolvasott sor tárolására használatos tömböt globálissá tesszük, majd nevezzük át pelda11.c-re! | |||||||||||||||||||||
/* PELDA11.C: Nevszamlalas */ | |||||||||||||||||||||
Vegyük észre, hogy a pelda10.c- s verzióhoz képest a getline paraméter nélkülivé vált, és az strcmp-nek meg egyetlen paramétere maradt! Miután a globális s karaktertömb deklarációs pontja megelőzi a két függvény definícióját, a függvényblokkok s hatáskörében vannak. Belőlük tehát a tömb egyszerű hivatkozással elérhető, s nem kell paraméterként átadni. A getline második paramétere tulajdonképpen a MAX szimbolikus konstans volt, s ezt beépítettük a for ciklusba. | |||||||||||||||||||||
Elemezgessük egy kicsit a "külső változó, vagy paraméter" problémát! | |||||||||||||||||||||
| |||||||||||||||||||||
Világos, hogy nincs egyértelműen és általánosan ajánlható megoldás a problémára. A feladat konkrét sajátosságai döntik el, hogy mikor milyen függvényeket kell írni, kellenek-e külső változók stb. | |||||||||||||||||||||
Summázzuk a globális változóval, vagy más néven külső változóval, kapcsolatos ismereteinket! | |||||||||||||||||||||
| |||||||||||||||||||||
Inicializálás | |||||||||||||||||||||
Az inicializálás kezdőérték adást jelent a deklarációban, azaz az inicializátorok kezdőértékkel látják el az objektumokat (változókat, tömböket stb.). | |||||||||||||||||||||
A statikus élettartamú objektumok egyszer a program indulásakor inicializálhatók. Implicit (alapértelmezett) kezdőértékük tiszta zérus, ami: | |||||||||||||||||||||
| |||||||||||||||||||||
A lokális élettartamú objektumok inicializálása minden létrejövetelükkor megvalósul, de nincs implicit kezdőértékük, azaz "szemét" van bennük. | |||||||||||||||||||||
Módosítsuk újra a deklarációs utasítás szintaktikáját változókra és tömbökre! | |||||||||||||||||||||
típus azonosító<=inicializátor>; | |||||||||||||||||||||
ahol az inicializátorlista inicializátorok egymástól vesszővel elválasztott sorozata, és a típus a <tárolási-osztály> <típusmódosítók> <alaptípus>-t helyettesíti. | |||||||||||||||||||||
A változókat egyszerűen egy kifejezéssel inicializálhatjuk, mely kifejezés opcionálisan {}-be is tehető. Az objektum kezdeti értéke a kifejezés értéke lesz. Ugyanolyan korlátozások vannak a típusra, és ugyanazok a konverziók valósulnak meg, mint a hozzárendelés operátornál. Magyarán az inicializátor hozzárendelés-kifejezés. Például: | |||||||||||||||||||||
char y = 'z', k; /* y 'z' értékű, és a k-nak meg */ | |||||||||||||||||||||
C-ben a statikus élettartamú változók inicializátora csak konstans kifejezés lehet, ill. csak ilyen kifejezések lehetnek tömbök inicializátorlistájában. A lokális objektumok inicializátoraként viszont bármilyen legális kifejezés megengedett, mely hozzárendelés kompatibilis értékké értékelhető ki a változó típusára. | |||||||||||||||||||||
#define N 20 | |||||||||||||||||||||
Emlékezzünk vissza, hogy globális változók csak egyszer kapnak kezdőértéket: a program indulásakor. A lokális objektumok viszont mindannyiszor, valahányszor blokkjuk aktívvá válik. | |||||||||||||||||||||
A szövegben használatos objektum fogalom nem objektum-orientált értelmű, hanem egy azonosítható memória területet takar, mely konstans vagy változó érték(ek)et tartalmaz. Minden objektumnak van azonosítója (neve) és adattípusa. Az adattípus rögzíti az objektumnak | |||||||||||||||||||||
| |||||||||||||||||||||
Tömbök esetén az inicializátorlista elemeinek száma nem haladhatja meg az inicializálandó elemek számát! | |||||||||||||||||||||
float tomb[3] = {0., 1., 2., 3.}; /* HIBÁS: több | |||||||||||||||||||||
Ha az inicializátorlista kevesebb elemű, mint az inicializálandó objektumok száma, akkor a maradék objektumok a statikus élettartamú implicit kezdőérték adás szabályai szerint kapnak értéket, azaz nullázódnak: | |||||||||||||||||||||
float tmb[3] = {0., 1.}; /* tmb[0]==0.0, tmb[1]==1.0 és | |||||||||||||||||||||
Az inicializálandó objektum lehet ismeretlen méretű is, ha az inicializátorlistából megállapítható a nagyság. Például: | |||||||||||||||||||||
int itmb[] = { 1, 2, 3}; /* Az itmb három elemű lesz. */ |
Feladatok | |||||||||
1. Mit kell írni a pelda4.c megjegyzésbe tett részébe, hogy a Forint 1000-től 100-ig csökkenjen 100-asával? | |||||||||
#include <stdio.h> | |||||||||
/* ? */ helyére írandó kifejezés:
![]() | |||||||||
2. Megpróbáltuk átalakítani a pelda4.c-t úgy, hogy most az Euró növekedjen 1-től 10-ig egyesével, de sajnos nem sikerült. Melyik sort kellene lecserélni, hogy jól működjön? | |||||||||
/* 1 */#include <stdio.h> | |||||||||
Melyik sort kellene lecserélni, hogy jól működjön?
![]() | |||||||||
3. Megpróbáltuk átalakítani a pelda4.c-t úgy, hogy a Forint 100-tól 2000-ig növekedjen 100-asával, az eredmény pedig két oszloppárban, oszlopfolytonosan növekedjék. (Tehát az első oszlopban a Forint 100-tól 1000-ig nő, a másodikban pedig 1100-tól 2000-ig.) Sajnos nem jártunk teljes sikerrel. | |||||||||
/* 1 */#include <stdio.h> | |||||||||
Melyik sort kellene lecserélni, hogy jól működjön a program?
![]() | |||||||||
4. Megpróbáltuk átalakítani a pelda4.c-t úgy, hogy a Forint 100-tól 2000-ig növekedjen 100-asával, az eredmény pedig két oszloppárban, sorfolytonosan növekedjék. (Tehát az első oszlopban a Forint 100-tól 1900-ig nő 200-asával, a másodikban pedig 200-tól 2000-ig.) Sajnos nem jártunk teljes sikerrel. | |||||||||
/* 1 */#include <stdio.h> | |||||||||
Melyik sort kellene lecserélni, hogy jól működjön a program?
![]() | |||||||||
5. Alakítsa át úgy a pelda4.c programot, hogy a felhasználótól először bekéri, hány oszloppárban szeretné megjeleníteni az adatokat! Az oszloppárok száma legalább 1, legfeljebb 4 lehet. Ismételtesse meg a bevitelt, ha hibás adatot adtak meg! Végül jelenítse meg az eredményt sorfolytonosan, azaz egy soron belül a Forint 100-asával nőjön, a legkisebb Forint érték 100 legyen, a legmagasabb pedig az oszloppárok számától függően 1000, 2000, 3000 vagy 4000! | |||||||||
/* TOBBOSZL.C: Forint-euro atszamitas 1 - 4 oszlopban. */ | |||||||||
6. Alakítsa át a pelda4.c-t úgy, hogy a forint 100-tól 10000-ig növekedjen 100-asával! A lista nem futhat el a képernyőről, azaz fejléccél ellátva lapozhatóan kell megjelentetni! Ez azt jelenti, hogy először kiíratjuk a lista egy képernyő lapnyi darabját, majd várunk egy gombnyomásra. A gomb leütésekor produkáljuk a lista következő lapját, és újból várunk egy gombnyomásra, és így tovább. | |||||||||
/* LAPOZ.C: Forint-euró átszámítási táblázat lapozással. */ | |||||||||
7. Készítsen programot, mely a szabvány bemenetet EOF-ig olvassa, és közben megállapítja, hogy hány sor volt a bemeneten! A bemenet karakterei közt a '\n'-eket kell leszámlálni. Az utolsó sor persze lehet, hogy nem '\n'-nel végződik, hanem EOF-fal. | |||||||||
#include <stdio.h> | |||||||||
8. Készítsen programot, mely a szabvány bemenetet EOF-ig olvassa, és közben megállapítja, hogy hány szó volt a bemeneten! A szó nem fehér karakterekből áll. A szavakat viszont egymástól fehér karakterek választják el. Az utolsó szó lehet, hogy nem fehér karakterrel zárul, hanem EOF-fal. | |||||||||
#include <stdio.h> | |||||||||
9. Megpróbáltuk úgy módosítani a pelda8.c programot, hogy összeszámolja a különféle számjegy karaktereket. Az összes többi karaktert egy kategóriának tekintettük, és ezek számát is megjelenítettük, de sajnos az átalakítás nem sikerült tökéletesen. | |||||||||
/* 1*/#include <stdio.h> | |||||||||
Jelölje meg azokat a sorokat, amiket le kellene cserélni, hogy a program az elvárásnak megfelelően működjön!
![]() | |||||||||
10. Megpróbáltunk elkészíteni egy copy függvényt, ami egy karakterlánc tartalmát egy másik tömbbe másolja. Sajnos nem lett tökéletes. | |||||||||
/*1*/#include <stdio.h> | |||||||||
Kérem, jelölje meg, hogy melyik sorokat kellene megváltoztatni!
![]() | |||||||||
11. Megpróbáltunk elkészíteni egy length függvényt, ami egy karakterlánc hosszát adja eredményül. Sajnos nem lett tökéletes. | |||||||||
/*1*/#include <stdio.h> | |||||||||
Kérem, jelölje meg az összes olyan sort, amivel helyessé lehetne tenni a programot!
![]() |