1. gyakorlat

(a tárgy oldala)

Forrásfájlok az első gyakorlathoz - frissítve.

A fenti .zip fájlt az első gyakorlatok megtartása után frissíteni fogom az órán
továbbfejlesztett programokkal.

Összefoglaló

  • Hogyan fordítsak?
  • C ki- és bemenet
  • Primitív típusok
  • C szöveg / nyers adat tárolása
  • Parancssori argumentumok kezelése

Hogyan fordítsak?

g++ -o kimenet -Wall bemenet1.cpp bemenet2.cpp ...

C ki- és bemenet

Ugyan a programozás 2 kurzus alapvetően a C++ nyelvről szól, a C ki- és bemeneti függvények
ismerete nem csupán történeti okokból fontos, hanem azért is, mert számtalan esetben találkozhatunk
olyan kódokkal, amelyekben ezeket használják, valamint a vizsgán is előfordulhatnak.

Elvben ezeket már tanultátok a Programozás Alapjai kurzus keretében.

Formázott ki- és bemenet

A formázott I/O az adatok magasszintű ki- és bevitelét teszi lehetővé a program számára.
A legtöbb esetben munkák során erre van szükség. Amikor egy számot kiírunk a képernyőre,
nem szeretnénk azzal foglalkozni, hogy azt a számítógép hogyan tárolja. (Pl. big-endian,
little-endian-e, hány byte hosszúságon tárolódik, stb.) Formázott bemenet esetén a C standard
függvénykönyvtár megfelelő függvényei (pl. printf, scanf) ezeket elrejtik előlünk.

A formázott I/O fontos függvényei megismerhetőek a gyakorlat 05cinput.cpp fájljából, ahol
minden függvény első felhasználása előtt kommentben megtalálható egy URL az adott
függvényre vonatkozó referenciáról.

Formázatlan ki- és bemenet

A C standard függvénykönyvtára módot ad byte-ok "nyers" kiírására és olvasására. Ahhoz, hogy
ezt megvalósítsa, csupán két dologra van szükség: Kiírás esetén arra, hogy mely memóriacímről
szeretnénk kiírni az adatokat, valamint, hogy hány byte-ot szeretnénk kiírni. Olvasás esetén pedig
arra, hogy mely memóriacímre szeretnénk olvasni, és, hogy hány bytera van szükségünk.

Példa formázatlan kimenetre:

// A forrasfajl elejen:
#include   // ki- es bemenet
#include  // az abort() fuggveny miatt

// ...

    // Valamely fuggveny reszekent
    FILE* handle = fopen("binaris-kimenet.dat","w");
    if(!handle) {
        perror("fopen");
        abort(); // kilepunk, "elszall" a program
    }
    int a = 42;
    if(fwrite(&a, sizeof(a), 1, handle) != 1) { // 1 a kiirando, sizeof(a) meretu elemek szama
        perror("fwrite");
        abort();
    }
    fclose(handle);

// ...

Példa formázatlan bemenetre:

// A forrasfajl elejen:
#include   // ki- es bemenet
#include  // az abort() fuggveny miatt

// ...

    // Valamely fuggveny reszekent
    FILE* handle = fopen("binaris-bemenet.dat","r");
    if(!handle) {
        perror("fopen");
        abort(); // kilepunk, "elszall" a program
    }
    int a;
    if(fread(&a, sizeof(a), 1, handle) != 1) { // 1 a kiirando, sizeof(a) meretu elemek szama
        perror("fread");
        abort();
    }
    fclose(handle);

// ...

Látható, hogy a két példa lényegében csupán az fwrite és az fread függvényekben tér el.
Ezekhez referencia: fwrite, fread.

Primitív típusok a C++ nyelvben

A primitív típusok a nyelv alapelemeiként megjelenő, beépített, logikailag tovább nem osztható típusok.

Egész típusok:

  • int - Előjeles egész típus. Mérete az architektúra alapértelmezett szóhossza. (pl. 32 bit)
  • unsigned - Előjel nélküli egész típus. Mérete az architektúra alapértelmezett szóhossza. (pl. 32 bit)
  • short (int) - Előjeles egész típus, mérete lehet kisebb az arch. alapértelmezett szóhosszánál. (pl. 16 bit) Nem muszáj kiírni, hogy int.
  • short unsigned - Előjel nélküli egész típus. Mérete lehet kisebb az arch. alapértelmezett szóhosszánál. (pl. 16 bit)
  • long (int) - Előjeles egész típus. Mérete lehet nagyobb az architektúra alapértelmezett szóhosszánál. (pl. 64 bit) Nem muszáj kiírni, hogy int.
  • long unsigned - Előjeles egész típus. Mérete lehet nagyobb az arch. alapértelmezett szóhosszánál. (pl. 64 bit)

Lebegőpontos számok:

  • float - Standard (single precision) lebegőpontos számtípus
  • double - Kétszeres pontosságú lebegőpontos számtípus
  • long double - Sok esetben a double-nél is nagyobb pontosságú lebegőpontos számtípus

Logikai típus:

  • bool - Értéke csupán true vagy false lehet.

Szöveg / nyers adat

  • char[] / char* - Karakterek sorozatából álló tömb. Részletezve lentebb.

Szöveg/nyers adat típusok a C++ nyelvben

A C nyelvben a szövegeket tradícionálisan char*-rel, char[]-vel, const char* típusokkal azonosítják. Ez némiképp megőrződik a C++-ban is.

Amikor a kódunkban leírunk egy idézőjeles szöveg konstanst, akkor a fordító ezt szintaktikailag const char* értékként helyezi a kódunkba.
Ez egy olyan karakterekből álló tömb, amely nem módosítható, és utolsó karaktere után a memóriában egy bináris 0 byte található. Ez azért
jó, mert a hagyományos szövegekben (és főleg mondjuk ASCII-ben, vagy ISO-9-izében) nem fordulhat elő binárisan 0 a szöveg közepén.
Tehát ezen záró 0 byte pozíciójából kitalálható a szöveg hossza.

Szövegek esetén a C++ biztosít egy, a const char* típussal rendkívül jól együttműködő, biztonságos, a java-ból megismert stringhez hasonló
string osztályt. (És még equals()-szel sem kell szenvedni, mert az egyenlőséget természetes módon fejezhetjük ki köztük!) Erről azonban
később fogunk tanulni.

Amennyiben nyers, formázatlan bináris adatokról van szó, már nem ilyen szerencsés a helyzet a záróbájt szempontjából. Nem is kell messzire
menni, tulajdonképpen már egy bonyolultabb szöveg kódolásnál (encoding) is előállhat az a helyzet, hogy a bytesorozat közepén van egy
0 byte, amely az encoding szerint része a szövegnek, azonban a hagyományos, C-ből ismert szövegfeldolgozó függvények (strcmp, strcpy, strcat)
nem működnek helyesen az adaton. És akkor még nem is beszéltünk egy mp3 fájlról. Ki mondta, hogy annak a közepén ne fordulhatna elő nyugodtan
egy 0 byte? És mégis, milyen típussal lehet hozzáférni a nyers adatokhoz? A char* pont jó erre is. (Persze csupán a program legalacsonyabb szintjein)

A megoldás: Nyers, bináris adatsor, vagy egy különlegesebb encodinggal kódolt szöveg esetén, nyugodtan használjunk char*-t,
de ne támaszkodjunk semmilyen string feldolgozó függvényre, bármilyen hívogatónak is tűnik. Helyette jegyezzük fel a méretét
mi magunk (egy változóban), mert amikor bekerül az adat a programba, akkor úgyis tudjuk még, hogy mennyi került be. (Ezt többféle módon
tudhatjuk. Pl. számoljuk a beolvasott byte-okat, vagy valamilyen más forrásból megtudjuk, hogy az adott adatterület milyen hosszú.)
(Még jobb megoldás lehet, ha egy struktúrát csinálunk, amelyben elmentünk egy méret változót a const char* típusú memóriacímünk mellé.)

Parancssori argumentumok kezelése

Alább látható, hogy a main milyen paraméterezésével érhető el, hogy programunkban hozzáférhessünk a parancssorból kapott paraméterekhez.


int main(int argc, char** argv) {
    return 0;
}

Az argc az argumentumok számát, az argv pedig az argumentumok értékeit tartalmazó változó. Az argv karaktertömbök egy tömbjét határozza
meg, az alábbi ábra szerint látható módon:

argc és argv viszonyát ábrázoló ábra