12. gyakorlat Enum; Tárolási osztályok Preprocesszor utasítások; Moduláris programozás
Házi (f0174) Egy sor kiíratási formátuma: "nev: %s; pont: %d;". Olvasd be a kiírt számot úgy, ha tudod, hogy a kiírt sztring nem tartalmazhat pontosvesszőt, de bármi mást igen. Ellenőrizd le, hogy az input sor valóban helyes-e. Módosítsd úgy a programot, hogy az stdin és stdout fájlokat, valamint a fscanf és fprintf függvényeket használja. Módosítsd úgy a programot, hogy valódi fájlokat használjon. Hibaüzenettel és hibakóddal lépjen ki a program, ha valamelyik fájl megnyitása nem sikerült.
Felsorolás - Enum C-ben létrehozhatunk egy olyan adattípust, aminek mi határozzuk meg az értékkészletét (tehát azt, hogy milyen értékeket vehet fel) Ezt az elemek felsorolásával tehetjük meg Később ilyen típusú változóknak a felsorolt értékeket adhatjuk értékül A rendezés relációt a felsorolás sorrendjével definiáltuk Alakja enum <név> { elem1, elem2,, elemn } [változó];
Felsorolás - Enum #include <stdio.h> Hasznos, ha nem számként szeretnénk bizonyos értékeket tárolni, de szeretnénk, ha lenne köztük rendezés reláció Pl. hét napjai, érdemjegyek int main() { enum het { Hetfo, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap } nap; for(nap=hetfo; nap <= Vasarnap; nap+ +) { } printf("%d\n", nap); return 0; }
Felsorolás - Enum Az enum típus értékei valójában egész számokra képződnek le (így biztosított a rendezés) Alapesetben 0-tól indulnak az értékek és egyesével nőnek Ezt felülírhatjuk úgy, ha valamelyik érték mellé értékadást írunk Pl. enum het { hetfo = 5,... }; #include <stdio.h> int main() { enum het { Hetfo = 1, Kedd, Szerda, Csutortok, Pentek, Szombat, Vasarnap } nap; for(nap=hetfo; nap <= Vasarnap; nap++) { } printf("%d\n", nap); Ekkor ez az érték kapja ezt a számot értékül, a többiek pedig innen nőnek egyesével } return 0;
Feladat (f0150) Vizsgáld meg az enum.c programot. Javítsd ki a kovetkezo() függvény módosításával úgy, hogy az első kiírás (az év első tíz napja) helyes legyen! És most javítsd ki a második ciklust (a hét napjai), hogy ne legyen végtelen ciklus! Ha kell használhatsz másfajta ismétlést is! Mi történik, ha Hetfo = 1 -ként adod meg az enum het típus első elemét? Mi történik, ha Szombat = 10 -ként adod meg a hatodik elemet? Tudod-e az enum több elemének is ugyanazt az értéket adni? Mi történik, ha mindegyiknek ugyanazt adod?
Feladat (f0154) Módosítsd úgy, hogy az enum kulcsszó csak egyszer szerepeljen benne, de a program lényegében ne változzon meg! A megvalósításhoz nem használhatsz #define-t! Tipp: volt valami ilyesmi a struktúráknál is...
Preprocesszor makrók Létrehozhatunk makrókat, amit a preprocesszor (előfeldolgozó) dolgoz majd fel Ez a fordítás előtt történik, tehát még fordítás előtt módosíthatjuk vele a kódot Ezt a már ismert #define utasítással lehet Egy makrónak van neve és értéke, lehetnek paraméterei is Az értékre nincs semmi megkötés, mivel egyszerűen csak bemásolódik
Preprocesszor makró példák #define DEBUG Itt nincs értéke, csak létrehoztunk egyet #define N 3 Ezt már ismerjük, van neve és értéke is #define NL printf("\n") A makró értéke egy parancs, ezt fogja behelyettesíteni
Feladat (f0151) Vizsgáld meg a preproc.c programot Hol jelez hibát a fordító? Mi lesz, ha elhagyjuk a main függvény elől az int-et? Vizsgáld meg a preprocesszált állományokat. Mit tapasztalsz? Miért működik az egyik, és miért nem a másik verzió? (A preprocesszált fájlokat a gcc -E kapcsolójával tudod előállítani, ami a standard kimenetre ír, tehát érdemes lehet a kimenetet a shell-ben egy.i kiterjesztésű fájlba irányítani.)
Paraméteres PP makrók Van neve, paramétere és értéke Ahova a nevet írtuk, oda behelyettesíti az értéket (mint eddig), de a paramétereket is #define MIN(X,Y) ( (X)<(Y)? (X) : (Y)) Tehát ha valahova ezt írjuk, hogy printf("%d", MIN(a+b, a+c)); A fordítóhoz ez jut már el: printf("%d", ( (a+b) < (a+c)? (a+b) : (a+c) ));
Paraméteres PPmakrók A zárójeleket ne hagyjuk le, mert a PP nem gondolkodik, csak másol és így más lehet a jelentése. Pl. #define negyzet(x) X*X negyzet(a) -> a*a negyzet(a-1) -> a-1*a-1 Csak rövid, egyszerű dolgokra használjuk Nézzük meg a maximum, minimum megállapító makrókat, valamint a minimum megállapítót három paraméter esetén! 02_ppmakro.c
Feladat (f0260) Készíts egy-egy paraméteres makrót egy szám négyzetének, két szám minimumának, maximumának, valamint négy szám minimumának és maximumának kiszámolására. Nézzük meg preprocesszálás után, fordítás előtt a kódunkat! gcc -E prep.c > prep.i
Makró fordításkor Fordításkor is definiálhatunk makrókat, nem csak a programkód elején gcc -Dnev=ertek
Feltételes fordítás Egy makrót a #undef nev utasítással tudunk törölni, innentől kezdve nem él ez a makró Vannak olyan preprocesszor utasítások, amik kihagynak a fordításból bizonyos kódrészleteket, ha egy adott feltétel teljesül.
Feltételes PP utasítások #if ha egy feltétel teljesül, akkor lefordítjuk az utána lévő részt #elsif ugyan olyan, mint az else if, csak itt így kell írni #else else ág, ha a hozzá tartozó if nem teljesült #endif feltételvizsgálat vége, az if és endif közötti részre vonatkozik a feltétel #ifdef ha a makró definiált, akkor igaz #ifndef ha a makró nem definiált, akkor igaz
Feladat (f0269) Írj egy programot, ami egy láncolt listába olvas be egész számokat egy konstansként megadott végjelig, majd fordított sorrendben kiírja a beolvasott értékeket. Ha a program fordításkor a -DDEBUG kapcsolót kapja, akkor a beolvasás és kiírás során is írja ki az aktuális listaelem és a következő listaelem címét.
Tárolási osztályok C-ben lehetőségünk van módosítani a változók tárolási osztályát, ezzel befolyásolva a tárolási tulajdonságot (pl. élettartam, érvényességi kör) Ezt a változó típusa elé írt módosítóval tehetjük meg, mint pl. a méretét vagy az előjelét, ezeknek a sorrendje mindegy
Tárolási osztályok auto alapértelmezett, ha nem írunk semmit, akkor ezt a tárolási osztályt használja. Akkor célszerű kiírni, ha sok más változónál módosítjuk az alapértelmezést, akkor ezzel nyomatékosíthatjuk, hogy ez viszont változatlan. register a program megpróbálja a regiszterben tartani a változó értékét, ezáltal gyorsabb lesz a program, viszont nem használhatunk pointereket rá (a regiszterben tartásra nincs garancia) volatile gyakran változó értékű változók esetén hasznos, mindig a memóriából olvassa ki az értéket, még akkor is, ha cachelve van vagy a regiszterben van. Így kicsit lassabb lesz, de mindig az aktuáis értéket kapjuk extern valahol máshol deklaráltuk a változót, de itt is érvényes. Több modulból álló program esetén hasznos
Tárolási osztályok const konstans, az értéke nem változtatható. Pointerekkel megkerülhető, de nem állhat értékadás bal oldalán static a program indulásakor lesz lefoglalva, és a program futásának végén szabadul fel, így megőrzi az értékét. Pl. függvényeknél elérhetők a korábbi futások eredményei
Feladat (f0219) Vizsgáld meg a tarolas.c programot! Minimális változtatással tedd fordíthatóvá! Mi történik, ha a counter() függvényben kihagyod a static módosítót? Nézd meg a kiírt címeket! Változtasd meg a nemvaltozo értékét úgy, hogy a deklarációját nem módosíthatod!
Konstans pointerek Pointerek is lehetnek konstansok két módon const int *p = NULL; //amire mutat, az nem változik, de mutathat máshova int * const c = NULL; //nem mutathat máshova, de ahova mutat, ott változhat az érték
Feladat (f0220) Fordítsd le a deklaraciok.c programot! Milyen hibákat tapasztalsz fordítás közben? Javítsd ki! Ezek után futás közben miért száll el a program? (Ha nem teszi, növeld meg N értékét, és próbáld megválaszolni azt is, hogy kisebb értékkel miért nem szállt el!)
Parancssori paraméterek A linux alapoknál láthattuk, hogy az egyes programoknak paraméterben adhatjuk át az értéket, amin dolgozni szeretnénk. Ugyanez C-ben is megvalósítható. Az így adott paramétereket a main függvény paramétereként láthatjuk. Ekkor a main függvényt a következőre kell módosítani int main(int argc, char *argv[]) vagy int main(int argc, char **argv) vagy int main(int argc, char **argv, char **envp)
Parancssori paraméterek int main(int argc, char **argv) argc: bemenetként kapott paraméterek száma (0. a program neve) argv: egy stringeket tároló tömb (azaz stringeket tartalmazó memóriaterületek címeit tartalmazó tömb), itt vannak a paraméterek, pontosan argc darab int main(int argc, char **argv, char **envp) argc és argv ugyan az, mint az előbb envp: környezeti változók tömbjei. Ennek nem kapjuk meg a méretét, az utolsó elemet végejellel, azaz NULL pointerrel jelöljük
Feladat (f0240) Írj egy programot, ami összeadja a parancssorban kapott valós számokat, és kiírja az összegüket. Figyelem: a számokat most stringként (char*) kapjuk!
Feladat (f0237) Egészítsük ki úgy az előző órai kép invertálós programot, hogy a képeket fájlból olvassuk és fájlba írjuk, a fájlok nevét pedig a program paramétereként kell megadni.
Moduláris programozás Egy bizonyos programméret felett már érdemes logikai egységekre bontanunk a programunkat Könnyebb munkamegosztás Könnyebb bővítés, fejlesztés Forráskód áttekinthetőbb és újrafelhasználható lesz C-ben ezt modulokkal valósíthatjuk meg Minden modul egy header (.h) és egy source (.c) fájlból áll Kell lennie pontosan egy modulnak, amiben a main függvény lesz, ennek nem lesz headerje
Moduláris programozás Ha egy forrásban használni szeretnénk egy másik modult, akkor a header fájlt be kell includeolni hozzá Ha nem a rendszer include könyvtárában lévő headert szeretném használni, akkor <sajat.h> helyett sajat.h alakban kell írni a header nevét Forrásfájlokat sosem szabad includeolni, csakis header fájlokat!
Moduláris programozás Header fájlba kerül a modulunk interfésze Deklaráljuk a függvényeket (de nem szabad itt definiálni, tehát a függvény törzs a forrásba megy majd), azaz leírjuk a függvény prototípusát Definiáljuk a konstansokat, típusokat, globális változókat (ezeknél használni kell az extern tárolási osztályt) Nem szabad egy headert többször includeolni ugyan oda, ezt feltételes PP makrókkal biztosíthatjuk A forrás fájlba kerülnek a függvény definíciók és a globális változók, nem externként
Moduláris programozás Fordításkor meg kell adnunk az összes forrásfájlt Header fájlokat nem Ebből egy futtatható állomány lesz Létrehozhatunk a modulokból függvénykönyvtárakat. Ekkor nem kell main függvényt tartalmazó modul, hanem csak a modulokat lefordítjuk egyesével, és object fájlokban tároljuk. Ekkor az implementációnk rejtve marad, de mégis máshol felhasználható Header fájlban csak akkor includeoljunk, ha muszáj, sok kavarodás történhet.
Moduláris programozás Nézzünk meg egy moduláris programot! lib.h a modul header fájlja lib.c a modul source fájlja libmain.c a main modul Fordítás egyenként, majd linkelés gcc -Wall -pedantic -c lib.c gcc -Wall -pedantic -c libmain.c gcc -Wall -pedantic -o lm lib.o libmain.o Fordítás és linkelés egyben gcc -Wall -pedantic -o lm lib.c libmain.c
Feladat (f0262) Hozz létre egy típust háromdimenziós pontok tárolására. Készíts egy függvényt, ami két ilyen térbeli pont távolságát adja vissza, valamint egy függvényt, ami három oldalhossz alapján kiszámolja egy háromszög területét. A fenti programelemekből készíts egy modulként használható lib.h és lib.c párost. Ezek után írj egy tetra.c nevű programot ami négy háromdimenziós koordináta-hármasból kiszámítja egy pontok által határolt térrész (egyfajta szabálytalan "tetraéder", oldalaikkal és csúcsaikkal érintkező négy háromszög által határolt test, melynek csúcsai a megadott pontok) felszínét. A szükséges adatokat a program parancssori paraméterként kapja meg.
Továbbiak Jövő héten nagy ZH Utána héten nagy ZH újraírási lehetőség (bárki megírhatja, de minden esetben az új pontszám fog élni)