Felsorolt típus

2. Felsorolt típus: meghatározott értékek

Felsorolt típus

Olyan típus, amelynek értékkészlete egy nevekkel megadott, véges értékhalmaz.


Kártya színe

♠ ♣

Napok

Hétfő, kedd, szerda… vasárnap.

Tic-tac-toe

üres, kör, iksz

Közlekedési lámpa

piros, piros+sárga, zöld, sárga
Tic-tac-toe játék
Közlekedési lámpa

Definíciója

enum Lampa {
   piros,
   piros_sarga,
   zold,
   sarga
};

Használata

enum Lampa l;

l = piros;

if (l == zold)
   printf("Mehet!\n");

3. Praktikus párja: a switch() szerkezet

enum Lampa {
   piros,
   piros_sarga,
   sarga,
   zold
};
/* a lámpa állapota */
enum Lampa lampa1;


/* az egyes lencsék */
int p, s, z;

Mivel a switch()-ben is értékeket szoktunk felsorolni…

Közlekedési lámpa
switch (lampa1) {
   case piros:       p=1; s=0; z=0; break;
   case piros_sarga: p=1; s=1; z=0; break;
   case zold:        p=0; s=0; z=1; break;
   case sarga:       p=0; s=1; z=0; break;
}

4. Felsorolt típus: különféle szintaktikák

Ugyanazok a szintaktikák használhatóak, mint a struct esetén. Névtelen típust azonban nem ajánlott létrehozni – ha más miatt nem, azért, mert a fordító a hibaüzeneteiben nem tud rá hivatkozni.

enum Cella {
   ures,
   kor,
   iksz
};
typedef enum Cella Cella;

Typedefelni szokás, mint a struktúrákat.

/* egyben a typedeffel */
typedef enum Cella {
   ures,
   kor,
   iksz
} Cella;

Ez a leggyakrabban használt forma.


/* rögtön két változó is */
enum Cella {
   ures,
   kor,
   iksz
} c1 = ures, c2 = iksz;

Ezzel definiáljuk az enum Cella típust, és egyből létre is hozunk ilyen típussal két változót: c1-et és c2-t. Még inicializáljuk is őket.

/* névtelenül - ritkán */
enum {
   ures,
   kor,
   iksz
} palya[3][3];

Egy névtelen felsorolt típus, amelyből 3×3-as tömböt építünk.

5. Felsorolt típus: a hozzárendelt értékek

A színfalak mögött: minden névhez egy egész számot rendel a fordító. A lefordított programban azok a számok szerepelnek. Így a program gyors, nekünk pedig érthető a kód!

Számozás: 0-tól 1-esével felfelé (vagy amit mondunk)

A fordító a felsorolt típus értékeihez 0-tól fölfelé 1-esével egész számokat rendel. (Minden új érték az előző érték +1.) Ezt akár felül is bírálhatjuk. Például megtehetjük azt, hogy egyforma értékek megadásával egymással egyenértékűneveket hozunk létre, pl. észak=fel. De ha lehet, ilyenkor sem érdemes a számozást kézzel elvégezni: hiszen a felsorolt típusnál ezt a fordító megoldja helyettünk! Ezért a lenti példánál is jobb ötlet a jobb oldalt látható formát alkalmazni.

enum Irany {
    fel = 0, eszak = 0,
    balra = 1, nyugat = 1,
    le = 2, del = 2,
    jobbra = 3, kelet = 3
};
enum Irany {
    fel, eszak = fel,
    balra, nyugat = balra,
    le, del = le,
    jobbra, kelet = jobbra
};

Trükkös konstans

Az érték megadásának lehetősége miatt azonban gyakran az enum-ot konstans létrehozására is szokás használni:

enum { MERET = 100 };

int i, tomb[MERET];     /* szabad: fordítási idejű konstans! */
for (i = 0; i < MERET; ++i)
   scanf("%d", &tomb[i]);

Ennek az az előnye, hogy nem kell mindenhova beírni ugyanazt a számot a forráskódba. Sőt ha változtatni szeretnénk, azt csak egyetlen egy helyen kell megtenni! Persze így csak egész szám konstanst lehet létrehozni.

Komplex példa: tic-tac-toe játék

7. Feladatspecifikáció

Tic-tac-toe játék

Tic-tac-toe játék
  • Tároljuk a pályát
  • Tároljuk, melyik játékos lép legközelebb
  • Új játékot kezdünk
  • Kirajzoljuk a pályát
  • Lépünk egy játékossal
  • Ellenőrizzük, nyerésre áll-e valamelyik játékos

Teendők a program megírásához

  • Definiáljunk típusokat
  • Írjuk meg a függvényeket

8. Tic-tac-toe adatok: a játék elemei

A pálya egy cellája

Üres, kör, iksz. A pálya 3×3-as.

typedef enum Cella {
    c_ures, c_kor, c_iksz
} Cella;

Cella palya[3][3];

Melyik játékos következik?

A körrel vagy az iksszel játszó.

typedef enum Jatekos {
    j_kor, j_iksz
} Jatekos;

9. Tic-tac-toe adatok: a játék

A játék adatszerkezet

A játék állásához, állapotához rögzíteni kell a pálya állapotát, és a következő körben lépő játékost. Az összetartozó adatokhoz létrehozunk egy struktúrát:

typedef struct Jatek {
    Jatekos kovetkezo;
    Cella palya[3][3];
} Jatek;

A használat

A főprogramot így képzeljük el:

nem
működik
Jatek j1;
jatek_uj(j1);
jatek_kirajzol(j1);
jatek_lep(j1, 1, 1);
működik!
Jatek j1;
jatek_uj(&j1);
jatek_kirajzol(&j1);
jatek_lep(&j1, 1, 1);

Figyelem: C-ben a függvényeknél csak érték szerinti paraméterátadás létezik. A függvénynek átadott struktúra is érték szerint adódik át! A legtöbb függvényünk módosítani fogja a játék adatait tároló struktúrát, ezért indirekten, pointerrel kell nekik átadunk. Ha már a legtöbb ilyen, akkor érdemes az összeset ilyenre csinálni, hogy ne kelljen fejben tartani, melyiknek milyen a paraméterezése. Így nem csak a jatek_uj() és a jatek_lep(), hanem a jatek_kirajzol() függvényünk is cím szerint kapja a játék struktúrát.

10. Pointerek struktúrákra: a nyíl operátor

Jatek j1;

jatek_uj(&j1);              // címet adunk át
void jatek_uj(Jatek *pj) {  // struktúrára mutató pointer
    pj->kovetkezo = j_kor;
    /* ... */
}

Nyíl operátor – a pointer által mutatott struktúra adattagja:

(*pj).kovetkezo = j_kor; // ezt szeretnénk

pj->kovetkezo = j_kor;   // így rövidítjük

Dereferál és mezőt kiválaszt. Nem kell zárójelezni, kényelmesebb!

A lényeg röviden: ha struktúrából egy mező kell, akkor ponttal választjuk ki. Ha pointer van a struktúrára, akkor pedig nyíllal.

A nyíl operátort azért találták ki, mert a . mezőkiválasztó operátornak magasabb a precedenciája, mint a * dereferáló operátornak. Ha ennyit írnánk: *j.kovetkezo, akkor azt a . magasabb precedenciája miatt *(j.kovetkezo)-ként próbálná meg értelmezni a fordító, ami viszont nyilván nem lehetséges, hiszen pr nem struktúra, hanem pointer, amelynek nincsenek mezői. Ha nem lenne a nyíl operátor, akkor minden ilyen hozzáférést zárójelezni kellene, mint ahogyan az fent a példában is látszik.

Struktúrákra mutató pointerek esetén mindig a nyilat használjuk, mert egyszerűbb, olvashatóbb kódot kapunk, mintha zárójelezni kellene. Ugyanúgy, ahogyan az indexelő operátor esetén: tomb[i] helyett sem írunk *(tomb+i)-t, mert nehézkesebb, bonyolultabb, és semmivel nem jobb, mint az indexelő operátoros forma.

11. Tic-tac-toe: a függvények implementációja

Az adatszerkezet és a függvények tehát így néznek ki:

typedef struct Jatek {
    Jatekos kovetkezo;
    Cella palya[3][3];
} Jatek;

void jatek_uj(Jatek *pj);
void jatek_kirajzol(Jatek *pj);
void jatek_lep(Jatek *pj, int x, int y);

int main(void) {
    Jatek j1;
    jatek_uj(&j1);
    jatek_kirajzol(&j1);
    jatek_lep(&j1, 1, 1);
    jatek_kirajzol(&j1);
    /* ... */

Új játék: a struktúra inicializálása

void jatek_uj(Jatek *pj) {
    /* ő kezd */
    pj->kovetkezo = j_kor;
    /* üres pályán */
    int x, y;
    for (y = 0; y < 3; ++y)
        for (x = 0; x < 3; ++x)
            pj->palya[y][x] = c_ures;
}

Érdemes itt megfigyelni a struktúra tagjait kiválasztó kifejezéseket. pj->kovetkezo: a struktúra „következő” nevű tagja (egészen pontosan, a pointer által mutatott struktúra tagja). A pj->palya[y][x] kifejezés balról jobbra olvasandó: a pointer által mutatott struktúrából a pálya, azon belül is az y-adik sor, azon belül az x-edik oszlop. Ez már egy konkrét Cella, amelynek c_ures (Cella típusú) érték adható.

A pálya kirajzolása

void jatek_kirajzol(Jatek *pj) {
    printf("+---+\n");
    int x, y;
    for (y = 0; y < 3; ++y) {
        printf("|");
        for (x = 0; x < 3; ++x) {
            switch (pj->palya[y][x]) {
                case c_ures: printf(" "); break;
                case c_kor:  printf("o"); break;
                case c_iksz: printf("x"); break;
            }
        }
        printf("|\n");
    }
    printf("+---+\n");
}

Kirajzolás közben az egyes lehetséges cella értékekhez hozzá kell rendelni a megfelelő karaktert. Itt ez a switch() dolga. (Esetleg úgy is meg lehetett volna ezt oldani, ha az enum értékeit eleve úgy definiáljuk, hogy azok megegyezzenek a karakterkódokkal.)

A következő játékos lépése

void jatek_lep(Jatek *pj, int x, int y) {
    switch (pj->kovetkezo) {
        case j_kor:
            pj->palya[y][x] = c_kor;
            pj->kovetkezo = j_iksz;
            break;
        case j_iksz:
            pj->palya[y][x] = c_iksz;
            pj->kovetkezo = j_kor;
            break;
    }
}

Ha a körrel játszó játékos jön, kört teszünk az adott pozícióra. Ha az iksszel játszó, akkor ikszet.

A teljes program letölthető innen: tictac.c. Ez a „melyik játékos nyert” függvényt még nem tartalmazza.

NZH gyakorlás

13. Első feladat

Melyik az a szám?

Az 1245 szám számjegyeit magyar nyelven, betűkkel kiírva összesen 3+5+4+2 = 14 betűt használunk fel: „egy kettő négy öt”.

C programodban keresd meg a legnagyobb négyjegyű számot, amely osztóinak száma 14 (beleértve saját magát és az 1-et is), és a számjegyeit leírva 14 betűt kell felhasználni! Írd ki, melyik ez a szám, vagy ha nincs ilyen, akkor azt!

Ne írj felesleges kódot! Használj fentről lefelé tervezést!

14. Első feladat – megoldás

Fontos, hogy a legnagyobbat nem úgy keressük meg, hogy eltároljuk a számokat egy tömbben, és azt rendezzük. (Azt se tudjuk, hány ilyen szám van...) Ha föntről lefelé futtatjuk a keresést, akkor pont a legnagyobbat találjuk meg elsőnek.

#include <stdio.h>

int osztokszama(int n) {
    int db = 0;
    int i;
    for (i = 1; i <= n; ++i)
        if (n % i == 0)
            db += 1;
    return db;
}

int szamhossz(int n) {
    /* nulla egy ketto harom negy ot hat het nyolc kilenc */
    int hosszak[10] = { 5, 3, 5, 5, 4, 2, 3, 3, 5, 6 };
    int osszeg = 0;
    while (n > 0) {
        osszeg += hosszak[n%10];
        n /= 10;
    }
    return osszeg;
}

int main(void) {
    int i;
    for (i = 9999; i >= 1000; --i)
        if (szamhossz(i) == 14
            && osztokszama(i) == 14)
            break;
    if (i >= 1000)
        printf("A keresett szam: %d\n", i);
    else
        printf("Nincs ilyen.\n");

    return 0;
}

15. Második feladat

Gólyabál

Definiálj típust egy nemével (fiú/lány) és magasságával (cm) jellemzett hallgató tárolására.

  • Írj függvényt, amely két hallgatóról meghatározza, hogy klasszikus táncpárba állíthatóak-e, vagyis: ellentétes neműek és magasságkülönbségük kevesebb, mint 10 cm.
  • Írj függvényt, amely egy hallgatót és egy hallgatótömböt kap paraméterként, és megadja, hogy a hallgató a tömb hány elemével állítható párba.
  • Írj függvényt, amely megadja, hogy egy tankör legmagasabb hallgatójának hány párja lehetne.
  • Egy kódrészletben hozz létre egy háromfős tankört, és írasd ki a legutóbbi függvény eredményét!

16. Második feladat – megoldás

Igazából ezt kommentár nélkül – alap algoritmusokat (megszámlálás, maximumkeresés) kell implementálni.

#include <stdio.h>
#include <stdbool.h>
#include <stdlib.h>

typedef enum Nem { fiu, lany } Nem;
typedef struct Hallgato {
    int magas;
    Nem nem;    /* de igen */
} Hallgato;

bool tancpar_e(Hallgato t1, Hallgato t2) {
    return t1.nem != t2.nem && abs(t1.magas-t2.magas) < 10;
}

int hany_par(Hallgato t1, Hallgato tomb[], int meret) {
    int db = 0;
    int i;
    for (i = 0; i != meret; ++i)
        if (tancpar_e(t1, tomb[i]))
            ++db;
    return db;
}

int legmagasabb_es_parja(Hallgato tomb[], int meret) {
    int maxi = 0;
    int i;
    for (i = 1; i != meret; ++i)
        if (tomb[i].magas > tomb[maxi].magas)
            maxi = i;
    return hany_par(tomb[maxi], tomb, meret);    /* magának úgyse párja */
}

int main(void) {
    Hallgato tankor[] = { { 165, lany }, { 175, fiu }, { 185, fiu }, };
    printf("%d", legmagasabb_es_parja(tankor, 3));
}

17. Harmadik feladat

Madárnyelv

Írj C függvényt, mely két sztringet vesz át paraméterként, amelyekből a második az angol abc kisbetűiből álló szöveget tartalmaz!

A függvény az elsőbe írja be a második "madárnyelvű" változatát, azaz a magánhangzókat megkétszerezi, és közéjük egy 'v' betűt rak. Felteheted, hogy az első sztring elég nagy, hogy elférjen benne az új érték!

Írj main() függvényt, melyből meghívod a madárnyelv függvényt! Pl. „madarnyelven” => „mavadavarnyevelveven”.

18. Harmadik feladat – megoldás

Figyelni kell, hogy a cél tömb mekkora. Hosszabb lesz, mint a forrás tömb!

#include <stdio.h>
#include <stdbool.h>

bool maganhangzo(char c) {
    return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u';
}

void madar(char *cel, char *forras) {
    int icel, iforr;

    icel = iforr = 0;
    while (forras[iforr]!='\0') {
        cel[icel++] = forras[iforr];
        if (maganhangzo(forras[iforr])) {
            cel[icel++] = 'v';
            cel[icel++] = forras[iforr];
        }
        ++iforr;
    }

    cel[icel] = '\0';
}

int main(void) {
    char ezt[] = "madarnyelven";
    char madarul[50];

    madar(madarul, ezt);

    printf("%s\n", madarul);

    return 0;
}

19. Negyedik feladat

Hibás sorozat

A programod a szabványos bemenetén megkapja egy számsorozat darabszámát, majd magát a sorozatot, amely legalább 3, legfeljebb 100 elemű.

A számok egy növekvő számtani sorozatot alkotnak. A sor egy helyen hibás, valahol hiányzik egy eleme. Ezért valamelyik szomszédos elempár között kétszer akkora a különbség, mint a többi helyen.

Írja ki a programod a beolvasás után a sorozat növekményét és a kijavított sorozatot, megjelölve a hiányzó elemet! Pl.

Írd be, hány szám lesz: 5
Írd be a számokat: 2 4 6 10 12
A sorozat növekménye: 2
A sorozat javítva: 2 4 6 (8) 10 12 

20. Negyedik feladat – megoldás

Valahol nagyobb távot látunk. Ha a másodiknál, akkor az elsőből kitalálható a d, amúgy a másodikból. A többit nem érdemes nézni, mert ha messzebb van, akkor itt mindkettőnél jó a táv.

Az elsőt kiírjuk, de a többinél megnézzük, hogy az új számnak az előző képest mi a távolsága. Ha rossz, akkor ott a hiányzó, kiírjuk azt is.

#include <stdio.h>

int main(void) {
    int tomb[100], i;

    printf("Írd be, hány szám lesz: ");
    int meret;
    scanf("%d", &meret);
    printf("Írd be a számokat: ");
    for (i = 0; i != meret; ++i)
        scanf("%d", &tomb[i]);

    int d;
    if (tomb[1]-tomb[0] < tomb[2]-tomb[1])
        d = tomb[1]-tomb[0];
    else
        d = tomb[2]-tomb[1];
    printf("A sorozat növekménye: %d\n", d);

    printf("A sorozat javítva: ");
    printf("%d ", tomb[0]); 
    for (i = 1; i != meret; ++i) {
        if (tomb[i-1] + d != tomb[i])
            printf("(%d) ", tomb[i-1] + d);
        printf("%d ", tomb[i]);
    }

    return 0;
}