Programozás II. segédlet
Csordás Martin
V1.5
Programozás II. segédlet
A segédletben megtalálható függvényeket eléritek a macsodev.hu oldalon.
A segédlet BETA jellegű, folyamatos javítás és bővítés alatt áll. Changelog: V1 V1.2
V1.3
V1.4
V1.5 V1.6
initial release Fájlkezelés Dinamikus memóriafoglalás Struktúra megjegyzés Stílus javítása Többdimenziós tömbök Struktúra kiegészítés Fájlkezelés feladat javítás Egyirányú láncolt lista Kétirányú láncolt lista Parancssori paraméterek Typo Light képek
Programozás II. segédlet
1. Struktúra A struktúra egy összetett adatszerkezet, amiben egyszerre több adatot is tárolhatunk. Segítségével objektum-szerű elemeket hozhatunk létre, amikkel könnyedén tárolhatunk adatokat. Egymáshoz kapcsolásukkal láncolt lista készíthető. Nézzük meg, hogyan is készíthetünk egy struktúrát:
Közvetlenül a main függvényünk előtt tudjuk ezeket a szerkezeteket felvenni. Itt nincs közvetlenül értéktárolás, csupán mintaként szolgál, amit majd a mainben felveszünk és használunk.
Itt egy öt elemű struktúra tömböt vettünk fel, amely az ember nevű struktúra mintájára készült. Éppen ezért ennek is van nev[] és eletkor tagja. A tagokat a tömbelem után írt pont segítségével érhetjük el. Innentől kezdve ugyanúgy használhatóak, mint a primitív adattípusokból álló változók.
Megjegyzés: -
Az azonos típusú struktúraelemek között megengedett az értékadás művelet. Az egyes struktúraelemek önmagukban érték szerint, míg tömbösítve cím szerint kerülnek átadásra a külső függvényeknek. A struktúrákat sorrendben kell deklarálni, így amelyik struktúra valamilyen formában hivatkozik egy másikra, a hivatkozott struktúrát a jelenleginél előrébb kell helyeznünk a kódban
Feladatok:
Írjunk olyan programot, mely 5 ember adatait kéri be ellenőrzötten. Az életkor legalább 1 és legfeljebb 120 lehet, a név minimum 6 maximum 20 karakterből áll, nem lehet benne betűn kívül más írásjel, valamint nagybetűvel kell hogy kezdődjenek. A bekérés végeztével külön eljárás segítségével rendezzük őket évszám szerint növekvő sorrendbe, majd írassuk ki a nevüket!
Írjunk olyan programot, amely autók rendszámát tárolja. Legfeljebb 5 darab autó bekérésére kell felkészülni, a rendszám formátuma XXX-YYY, ahol X betűket, Y pedig számokat jelöl. Kérjük be az autó nevét, valamint a rendszámot. Ha a rendszám kisbetűvel lett írva, írjuk át nagyra. A bekérés érjen véget, ha bekértük mind az 5 autó adatait, vagy ha az autó nevének bekérésekor üres sort adtunk meg. Bekérés után külön függvényben rendezzük őket rendszám szerint csökkenő sorrendbe, majd írassuk ki az autók adatait.
Programozás II. segédlet
2. Mutató A mutatók a változók egy speciális csoportja. Alapvetően viselkedhetnek rendes változóként, viszont fel tudják venni más változók memóriacímét, ezáltal hivatkozni rá. A hivatkozott elemeket ezután a mutató segítségével (azon keresztül) is elérhetőek, megváltoztathatóak. Mutatók használatával lehetőségünk van függvényeknek és eljárásoknak cím szerinti átadásra. Ezáltal az ott megváltoztatott érték az eredetit fogja módosítani. A C nyelvben a tömbök paraméterként való átadása mindig cím szerint történik.
Mutatókat a * operátorral tudunk deklarálni. Egy változó memóriacíme az & operátorral érhető el. Azt a területet, amire a mutatónk mutat a * operátorral érjük el, annak segítségével módosíthatjuk.
Feladatok:
Készítsünk olyan függvényt, mely bemenő paraméterként egy karaktertömböt és annak hosszát várja. A paraméterek felhasználásával alakítsuk át a nagy- és kisbetűket kis- és nagybetűs párjaikra. A paramétereket mutatóként adjuk át! Írjunk programot, mely 5 darab név-telefonszám párost két be. Írjunk olyan függvényt, mely az egyes struktúrák mutatóit várja paraméterként. A függvény határozza meg, hogy a telefonszám érvényes-e, és ezt mentse is el a struktúra egy tagjába. A nem érvényes számokat írassuk ki a szabvány kimenetre.
Programozás II. segédlet
3. Idő- és dátumkezelés A time.h fejfájl használatával idő- és dátumkezeléssel kapcsolatos adatszerkezeteket és különböző függvényeket kapunk. Ezek használatával meg tudjuk határozni a jelenlegi időt, valamint miliszekundumos pontossággal a futási időt. Egyszerű futási idő meghatározására két clock_t struktúrát kell felvennünk, az egyiket a program futásának legelején, a másikat a legvégén fogjuk lefixálni. Így a kettő közti időkülönbség osztva egy előre beépített konstanssal (CLOCKS_PER_SEC) adja a program futási idejét. Az érték beállításához a clock() függvény visszatérési értékét használjuk, amit float vagy double értékké alakítunk át típuskényszerítéssel.
Az aktuális dátum és idő kiíratásához kell egy struct tm mutatós adatszerkezet, valamint ennek beállítására szükség van a jelenlegi időre is, amit a time_t adatszerkezet alkot a time eljárással. A time() eljárás a time_t szerkezet címét várja paraméterként. Ezt felhasználva a localtime() függvénynek átadjuk a time_t szerkezet címét, visszatérési értékként megkapjuk a dátumot, melynek különböző tagjai az egyes időértékeket jeletnik (év, hónap, nap, stb…). Az évszám az 1900 óta eltelt éveket jelentik. A hónapokat egy egyszerű sztringtömb készítésével szövegesen is megjeleníthetjük.
Programozás II. segédlet
4. Fájlkezelés A fájlkezeléshez szükséges függvényeket és eljárásokat az stdio.h fejfájl tartalmazza. Egy fájl kezeléséhez egy FILE pointert társítunk, amire műveleteknél hivatkozni tudunk. Az állományokat az fopen() függvénnyel tudjuk megnyitni írásra és olvasásra. A függvény visszatérési értéke az elérési út memóriacíme, ezért ezt a pointerünkben tárolnunk kell. A függvény paraméterként várja az útvonalat (string) és a megnyitás módját, ami a következők egyike lehet:
”r” ”w” ”a”
– olvasás – írás – hozzáfűzés
Olvasáskor ha a fájl nem létezik, a pointer NULL értéket vesz fel. Íráskor a fájl létrehozódik a megadott helyen, ha létezik a fájl, akkor pedig tartalma törlődni fog, és üres fájlként lesz megnyitva. Hozzáfűzéskor a létező fájl beolvasásra kerül, a kurzor pedig a fájl végén fog elhelyezkedni, nem létező fájl esetén pedig létrehozza azt. A megnyitott fájlt a program utolsó fázisában (vagy ahol szükség van erre) be kell zárnunk az fclose() eljárással, ezzel visszaíratjuk a memóriából a fájlba a módosításokat. Fontos kiemelni még az fgets() eljárást, ami egy karaktertömbbe képes soronként beolvasni a fájlból. Ezen felül az fseek() beépített funkció is hasznos lehet, segítségével a fájlon belül tudunk navigálni, bezárás nélkül tudjuk a kurzort a fájl elejére, végére, vagy egy tetszőleges helyre állítani. Fájlokat az fprintf() eljárással tudunk írni, ami gyakorlatilag egy módosított printf() eljárás. A fájl mutatójával kiegészítve ugyanazon módon írható a fájl, mintha csak szabvány kimenetre íratnánk ki valamit. fp = fopen(dest,”X”)
Fájl megnyitása. fp a felvett fájlmutató, dest az útvonal (string), X a megnyitás módja (r, w, a)
fclose(fp)
Fájl bezárása
fgets(mibe_str,mennyit,honnan_fp) Egy sor beolvasása a fájlból (sor vége: \n) fseek(fp,0,SEEK_SET)
A kurzor helyének beállítása
rewind(fp)
A kurzor visszaállítása a fájl elejére
fprintf(fp,”kiiratas fajlba\n”)
Kiíratás a fájlba
c = getc(fp)
A következő karakter beolvasása a fájlból
Pointerünk NULL értéket kap, ha a fájlmegnyitás nem sikerült, EOF jelzést, ha a kurzor a fájl legvégén (végjelen) áll. Az fseek() függvény konstans értékei: SEEK_SET (fájl eleje) és SEEK_END (fájl vége).
Feladatok:
Írjunk programot, mely 5 bolt adatait tartja nyilván. Minden boltban 5 féle árú lehet, mindegyik árúnak van egységára, és neve. Minden boltnak kérjük be a nevét és az egyes árúhoz tartozó rendelések számát, majd írassuk ki egy fájlba az egyes üzletekhez tartozó rendelések összértékét. Határozzuk meg a legnagyobb beruházást végző üzlet nevét. Az egyes árúk neveit és egységárát a program elején egy külön eljárás segítségével határozzuk meg. o Módosítsuk az előző programot úgy, hogy egy előre (általunk) meghatározott szerkezető fájlból képes legyen 5 darab árút beolvasni, és a program lefutásához ezeket az adatokat használni. Írjunk olyan programot, mely egy tetszőleges fájlról megmondja, hogy tartalmazza-e a ”program” karakterláncot. Amennyiben tartalmazza, úgy írja ki a kurzor aktuális pozícióját.
Programozás II. segédlet
5. Dinamikus memóriafoglalás Számtalanszor előfordul az az eset, mikor előre meghatározhatatlan, hogy mekkora méretű tömböt kell felvennünk. A fix elemszámmal felvett tömb nem mindig megfelelő az adott célra, mert lehet, hogy nagyobb méretű fog kelleni, vagy pont fordítva; túl nagy a tömb és éppen ezért pazarlóan bánik a memóriával. A dinamikus memóriafoglalással bármikor pontosan akkora memóriát tudunk lefoglalni, amekkora kell. A foglalásra írjunk egy külön függvényt, mely paraméterként a lefoglalandó tömbelemek számát várja, visszatérő értéktípusa pedig a lefoglalt memóriaterületen elhelyezett tömb kezdőcíme, vagyis egy mutató. Abban az esetben, ha a foglalás valamilyen oknál fogva sikertelen volt, a mutató NULL értéket vesz fel. A foglalás a calloc() beépített függvénnyel történik, amely paraméterként a lefoglalandó tömb elemszámát, valamint az egyes elemek méretét várja. Előbbit átadtuk a foglaló függvénynknek, utóbbira pedig a sizeof(adattipus) kifejezést használhatjuk. Visszatérési értéktípusa megegyezik a fenntebb leírtakkal. A kapott tömböt ezután ugyanúgy használhatjuk (akár indexekkel is), ahogyan azt megszokhattuk a fix mérettel felvett tömböknél. A memóriaterületet a program futása végén szabadítsuk fel a free() eljárással. Megjegyzés: a calloc() függvény a felvett memóriaterületet nullázza, ami plusz művelet. A malloc() függvény érintetlenül adja át a memóriaterületet, cserébe valamivel gyorsabb. A gyakorlatban ennek nincs nagy jelentősége.
Feladatok: -
-
Írjunk olyan programot, mely egy festékbolt készletét képes tárolni. Az adatok legyenek struktúrában tárolva. Egyes festékeknek van neve (színek) és mennyisége. Azt, hogy hány ilyen adategyüttest kérjünk be a program a futása elején kérje be tőlünk, és ennek megfelelően foglaljunk neki helyet a memóriában. A bekérés végeztével az adatokat írassuk ki egy fájlba keszlet.txt néven! Írjunk olyan programot, melyben két általunk megírt függvény kerül meghívásra. Ez egyik kérjen be tölünk egy pozitív egész számot, majd ezt a számot használjk fel egy egész típusú tömb felvételére. A tömb elemeinek adjunk értékeket. A másik eljárás írassa ki egy fájlba a tömbben lévő elemeket tabulátorral elválasztva!
Programozás II. segédlet
6. Többdimenziós tömbök A többdimenziós tömbök olyan összetett tömbök, melynek elemei is tömbökből állnak. Az egyes elemeket többszintű indexeléssel érhetjük el. Használtunk már sztringekből álló tömböt, ami nem más, mint egy 2D karaktertömb (a sztring egy karakterekből álló tömb). A kétdimenziós tömb közismertebb neve a mátrix. Bármilyen primitív vagy összetett adattípusból tudunk többdimenziós tömböket létrehozni. A többdimenziós tömbök felvételét az alábbi ábra szemlélteti egy egész számokat tartalmazó mátrixszal:
Ebben az esetben (int) az első index a sor, a második index az oszlop elemszáma. Fontos megjegyezni, hogy a sorrend felcserélhető, ekkor viszont ügyelnünk kell a sorrendre minden tömbműveletnél. Stringek esetében az első index a sztringek kívánt számát, míg a második index az egyes sztringek (karaktertömbök) elemszámát határozza meg.
A tömb egyes elemeinek az elérése a megszokott módon történik, az egyedüli változást a többes indexelés jelenti (mátrixoknál pl.: matrix[i][j] vagy matrix[0][MAX-1]). A helyes indexelés után az egyes tömbelemek az eddigi megszokott módon kezelhetőek. Sztringek esetében a getline() függvénynek át kell adnunk a tömbünk nevét, valamint azt, hogy a mátrix hányadik sorába szeretnénk bekérni. Így az eddigi megszokott paraméter kiegészül a tömb megfelelő indexével.
Feladatok: -
Kérjünk be futási időben meghatározott számú valós számot. A számokat tárolás után egy külön eljárás segítségével rendezzük sorba, majd írassuk ki egy fájlba sor.txt néven! Futási időben kérjünk be két egész pozitív számot 5 és 10 között. Az adatoktól függően dinamikusan vegyünk fel egy int mátrixot, amin soronként végighaladva töltsük fel 1-től kezdve számokkal. Az kitöltött mátrixot jelenítsük meg a szabvány kimeneten, és mentsük el egy fájlba is matrix.txt néven!
Programozás II. segédlet
7. Egyirányú láncolt lista A listák olyan struktúraelemek összessége, melyeknek száma dinamikusan méretezhető, és az egyes elemek egy (vagy két) mutató segítégével össze vannak kapcsolva, ezáltal egy bizonyos sorrendiséget kialakítva. Gyakorlatban ez egy struktúra, melynek adattagjai között szerepel egy (vagy két) ugyanolyan struktúra típusra felvett mutató, és mellette a struktúrára jellemző más primitív vagy összetett adattagok. Egyirányú lista esetén csupán egy mutató áll rendelkezésünkre, így a listában való kereséskor csak egy irányban tudjuk bejárni a listát. Az utolsó listaelem mutatója minden esetben NULL értéket vegyen fel. Az első elem memóriacímét el kell tárolni, mert a lista használatakor tudnunk kell, hogy a lista hol kezdődik. Minden esetben az egyes láncelemek dinamikusan (malloc vagy calloc) kerülnek felvételre, majd felfűzésre. Egy elem törléséhez szükségünk lesz az előző elem címére, így bejáráskor két mutatóra lesz szükségünk; az aktuális elemre mutató mutató, és egy, az előző elem memóriacímét tartalmazó mutató. A következő elem az aktuális elem tagjaként elérhető. Egy példa egyirányú láncolt listára:
Feladatok: -
Írjunk olyan programot, mely üres sorig képes színeket bekérni a felhasználótól. Az egyes színeket a program lefutása után írjuk ki egy fájlba. Írjunk olyan programot, mely az előző feladatban letárolt színek neveit beolvassa fájlból, és egy láncolt listát hoz létre belőlük. A lista bejárásával írassuk ki a színeket a képernyőre.
Programozás II. segédlet
8. Kétirányú láncolt lista Hamarosan…
Programozás II. segédlet
9. Parancssori paraméterek Ha parancssorból hívjuk meg a programunkat, akkor lehetőségünk van paraméterezni a programunkat. Valójában ez a paraméter a main függvényünk paramétere. A paraméterek számossága egy int argc nevű változóban, míg maguk a paraméterek egy char **argv 2D karaktertömbben foglalnak helyet. Az egyes paramétereket használhatjuk akár kapcsolókként is, melyekkel befolyásolhatjuk programunk működését, lefutásának módját.
Fontos, hogy a paraméterek a képen látható sorrendben legyenek: először a paraméterszámláló, majd a karaktertömb. Egy példaprogram a paraméterek kiíratására:
A program kimenete –kapcsolo1 és –kapcsolo2 paraméterekkel: Az első paraméter mindig a programunk elérési útvonala. Ebből következik, hogy a számláló értéke minden esetben nagyobb vagy egyenlő mint 1.