Labor, 3. hét: struktúrák és függvények

Pohl László, Czirkos Zoltán, Kohári Zsolt · 2023.09.27.

Struktúrák és függvények kezelése. Néhány vezérlési szerkezet.

Felkészülés a laborra:

1. Próba ZH

Ha még nem vitted föl a gyakorlaton írt próba ZH pontszámát, tedd meg!

2. Tömbök – frissítő

Adott egy valós számokat tartalmazó tömb, benne vegyesen mindenféle előjelű számokkal.

A feladat végén látható egy olyan program, amelyik kilistázza a tömböt az elemek indexeivel. (Emlékezz vissza, ezt a feladatot egyszer már megoldottad laboron.) Másold be a kódot egy új projectbe, próbáld ki, majd végezd el az alábbi feladatokat!

Az első lépés kigyűjteni egy másik tömbbe a negatív tömbelemek indexeit. Listázd ki ezeket az indexeket is!

Ebből 5 szám negatív.
Indexeik: 1 3 4 7 8

Ha ez is megvan, egy olyan programrészt kell írnod, amelyik az indexek ismeretében kiírja, hogy mik voltak a negatív számok. Fontos, hogy ne keresd meg újra a negatív számokat! Elvégre is, ha az indexeik megvannak, abból már lehet tudni, melyek voltak azok. A végleges eredmény valami ilyesmi legyen:

Összesen 10 szám van.
[0]=2.5 [1]=-69 [2]=5.4 [3]=-8 [4]=-7.7 [5]=6 [6]=2.9 [7]=-10 [8]=-3 [9]=9.8 

Ebből 5 szám negatív.
[1]=-69 [3]=-8 [4]=-7.7 [7]=-10 [8]=-3 

Rajzolj ábrát, amely a tömböket, a tömbelemekben tárolt számokat, és az azok közötti összefüggéseket ábrázolja! Hány elemű kell legyen az a tömb, amelyik az indexeket tárolja? Honnan fogja tudni a negatív elemeket kiíró programrész, hogy hány negatív elem volt?

#include <stdio.h>

int main(void) {
    double szamok[10] = { 2.5, -69, 5.4, -8, -7.7, 6, 2.9, -10, -3, 9.8 };

    /* Az eredeti tömb kiírása */
    printf("Összesen %d szám van.\n", 10);
    for (int i = 0; i < 10; ++i)
        printf("[%d]=%g ", i, szamok[i]);
    printf("\n\n");

    /* Negatívak indexeinek kigyűjtése */

    /* Negatívak kiírása */

    return 0;
}
Megoldás

Az adatszerkezetünk az alábbi:

A szamok[] tömb tartalmazza a vizsgálandó valós számokat. Fölötte a tömb indexei láthatóak. A neg_idx[] tömb az, amelybe a negatív számok indexei kerültek. Hasonlítsuk össze a benne tárolt számokat a fent látható indexekkel! Látszik, hogy a kékkel jelölt elemek sorszámai kerültek az alsó tömbbe.

A neg_idx[] tömb önmagában nem túl hasznos, a benne lévő indexek nem mondanak semmit. A szamok[] tömbbel együtt azonban az indexeknek jelentése van: minden index hivatkozik egy negatív számra a fenti tömbből. Az alsó tömb mondanivalója tehát: „a felső tömb 1., 3., 4., 7. és 8. indexű eleme kisebb nullánál”. Valóban, szamok[1], szamok[3], szamok[4], szamok[7] és szamok[8] mind negatívak. Ezek pont a kék elemek.

Az indexeket tároló tömb méretét felülről tudjuk becsülni: legfeljebb annyi index lesz benne, ahány szám az eredeti tömbben volt. Ilyen akkor fordulhat elő, ha az eredeti csak negatív számokat tartalmazott. Miközben vizsgáljuk az eredeti tömböt, szükség van egy számlálóra. Ez a számláló fogja tárolni azt, hogy hány negatívat találtunk. Ugyanezt a kiválogatás közben indexelésre is használhatjuk, ahogyan azt eddig is láttuk hasonló algoritmusoknál.

#include <stdio.h>

int main(void) {
    double szamok[10] = { 2.5, -69, 5.4, -8, -7.7, 6, 2.9, -10, -3, 9.8 };

    /* Az eredeti tömb kiírása */
    printf("Összesen %d szám van.\n", 10);
    for (int i = 0; i < 10; ++i)
        printf("[%d]=%g ", i, szamok[i]);   // 1
    printf("\n\n");

    /* Negatívak indexeinek kigyűjtése */
    int neg_idx[10];
    int neg_db = 0;
    for (int i = 0; i < 10; ++i) {
        if (szamok[i] < 0) {
            neg_idx[neg_db] = i;
            ++neg_db;
        }
    }

    /* Negatívak kiírása */
    printf("Ebből %d szám negatív.\n", neg_db);
    for (int i = 0; i < neg_db; ++i)
        printf("[%d]=%g ", neg_idx[i], szamok[neg_idx[i]]);   // 2
    printf("\n");

    return 0;
}

Érdemes összehasonlítani az 1-es és 2-es jelű sorokat. Mindkettő arra hivatott, hogy kiírjon egy tömbindexet és a tömbbeli elemet a program kimenetére. De míg az első esetben a teljes tömböt kiírjuk, a másodikban már csak a negatív számokat. Ilyenkor a kiírandó indexet (sorszámot) is a neg_idx[] tömbből vesszük, és a szamok[] tömböt is olyan sorszámmal indexeljük meg, amelyet a neg_idx[] tömbből vettünk ki.

Fontos, hogy ezeken a pontokon nincs mit keresgélni a tömbökben. A neg_idx[i] kifejezéssel megkapjuk a szükséges indexet; ezek után a szamok[] tömbön nem futtatunk semmiféle ciklust, hanem csak megindexeljük és kész.

3. Függvények – alapok

Írj függvényeket, amelyek valós számot vesznek át, és visszatérnek az:

  • kob() – harmadik hatványával,
  • abszolut() – abszolút értékével (van fabs() függvény, de most ne használd)!

Írj programot, amelyik a = −1-től +1-ig, tizedenként lépve, kiírja egymás mellé a, a3, |a| és sin(a) értékét, mindig négy tizedesjegy pontossággal!

Megoldás
#include <stdio.h>
#include <math.h>

double kob(double x) {
    return x*x*x;
    /* vagy: return pow(x, 3); */
}

double abszolut(double x) {
    if (x < 0)
        return -x;
    else
        return x;
    /* vagy: return x < 0 ? -x : x; */
}

int main(void) {
    for (double a = -1; a <= +1; a += 0.1) // lásd a kód mögötti megjegyzést!
        printf("%10.4f %10.4f %10.4f %10.4f\n", a, kob(a), abszolut(a), sin(a));

    return 0;
}
Megjegyzés: a megjelölt sor nem tökéletes, (a számábrázolás előadáson tanuljuk majd a valós értékek helyes összehasonlítását), de a célnak most megfelel.

Hasonló feladatok

Ha ez a feladat nehezen ment, megoldhatsz pár hasonló feladatot a példatárból, mielőtt a következő feladatra rátérsz.

Vigyázz, a laborfeladatokat erősen ajánlott az utolsó feladatig megoldani, hogy a jövő hétre felkészült legyél. Ha nem sikerül, fejezd be őket otthon!

4. Madárnyelv

Adott az alábbi program, amely madárnyelven (mavadávárnyevelveven) írja ki a beírt szöveget.

#include <stdio.h>

int main(void) {
    char c;
    while (scanf("%c", &c) != EOF) {
        if (c=='a' || c=='e' || c=='i' || c=='o' || c=='u')
            printf("%cv%c", c, c);
        else
            printf("%c", c);
    }

    return 0;
}

Írj függvényt, amelyik megmondja egy betűről, hogy magánhangzó-e! Alakítsd át úgy a programot, hogy a megírt függvényt használod a main()-ben!

Megoldás
#include <stdio.h>
#include <stdbool.h>

/* Igaz ertekkel ter vissza, ha a parametere egy maganhangzo. */
bool maganhangzo(char c) {
    return c=='a' || c=='e' || c=='i' || c=='o' || c=='u';
}

int main(void) {
    char c;
    while (scanf("%c", &c) != EOF)
        if (maganhangzo(c))
            printf("%cv%c", c, c);
        else
            printf("%c", c);

    return 0;
}

Hogyan lehetne megoldani azt, hogy a nagybetűvel kezdődő szavakat is helyesen kezelje a program? Pl. az „Alma” szóra azt kell kiírnia, hogy „Avalmava”. Ehhez fel kell tudnia ismerni a nagybetűvel írt magánhangzókat is. Kiíráskor a v betű előtt az eredeti karaktert kell kiírni, utána pedig a kisbetűsítettet. Használhatod a meglévő maganhangzo() függvényt is, csak a beolvasott karaktert kisbetűsítve kell odaadnod neki.

Megoldás

Jól látszik a megoldásban a fenti magyarázat: a maganhangzo függvénynek a kisbetűsített karaktert adjuk: maganhangzo(tolower(c)). A kiírásnál az első karakter az eredeti: c, a második a kisbetűsített: tolower(c). Használható a beépített tolower() függvény is (#include <ctype.h>), vagy egy saját változat.

while (scanf("%c", &c)==1)
    if (maganhangzo(tolower(c)))
        printf("%cv%c", c, tolower(c));
    else
        printf("%c", c);

Hasonló feladatok

Ha ez a feladat nehezen ment, megoldhatsz pár hasonló feladatot a példatárból, mielőtt a következő feladatra rátérsz.

Vigyázz, a laborfeladatokat erősen ajánlott az utolsó feladatig megoldani, hogy a jövő hétre felkészült legyél. Ha nem sikerül, fejezd be őket otthon!

5. Struktúra - bemelegítő

Definiálj egy típust síkbeli pontok reprezentálására. Az x és y koordináták valós számok.

Írj egy olyan programot, melyben létrehozol két pontot: az egyik a (2.2 ; 1.6) pont legyen, a másiknak a koordinátáit olvassuk be. Ezután hozzunk létre egy harmadikat, a fenti két pont által meghatározott szakasz felezőpontját. Írjuk ki a felezőpont koordinátáit.

Megoldás

A typedef nem kötelező, de érdemes minden típust rövid névvel ellátni.

#include <stdio.h>

typedef struct pont{
    double x,y;
}pont;

int main(void){
    pont a = {2.2,1.6}, b;
    //lehetne itt a.x = 2.2; a.y = 1.6;
    printf("Add meg a pont koordinatait: ");
    scanf("%lf%lf", &b.x, &b.y);
    pont f;
    f.x = (a.x + b.x) / 2;
    f.y = (a.y + b.y) / 2;
    //lehetne pont f = {(a.x + b.x) / 2, (a.y + b.y) / 2};
    printf("A felezopont: ( %f ; %f )\n", f.x, f.y);
    return 0;
}

6. Összetett adatszerkezet

Az itt látható táblázat egy futóverseny eredményeit tartalmazza.

IndexNévSzületésHelyezés
0Am Erika1984. 05. 06.1
1Break Elek1982. 09. 30.3
2Dil Emma1988. 08. 25.2
3Kasza Blanka1979. 06. 10.5
4Reset Elek1992. 04. 05.4

Alább egy elkezdett programot látsz, amelyben a megfelelő típusok már definiálva vannak, és az adatokat egy tömb tartalmazza. Egészítsd ki a programot, hogy kiírja a képernyőre a kommentekben megadott adatokat!

#include <stdio.h>

typedef struct Datum {
    int ev, ho, nap;
} Datum;

typedef struct Versenyzo {
    char nev[31];
    Datum szuletes;
    int helyezes;
} Versenyzo;

void datum_kiir(Datum d);

void versenyzo_kiir(Versenyzo v);

int main() {
    Versenyzo versenyzok[5] = {
        { "Am Erika", {1984, 5, 6}, 1 },
        { "Break Elek", {1982, 9, 30}, 3 },
        { "Dil Emma", {1988, 8, 25}, 2 },
        { "Kasza Blanka", {1979, 6, 10}, 5 },
        { "Reset Elek", {1992, 4, 5}, 4 },
    };

    /* 0-s versenyző neve - printf %s */
    /* 2-es versenyző helyezése */
    /* 4-es versenyző születési dátumát (írd meg a datum_kiir függvényt!) */
    /* 1-es versenyző nevének kezdőbetűjét (ne feledd, a sztring karaktertömb) */
    /* az 1-es versenyző dobogós-e? igen/nem, akár ?: operátorral, de egy printf-fel */
    /* az 4-es versenyző gyorsabb-e, mint a 3-as versenyző? */
    /* az 1-es versenyző ugyanabban az évben született-e, mint a 2-es? */
    /* egészítsd ki a versenyzo_kiir() függvényt,
     * aztán írd ki az 1-es versenyző összes adatát */
    /* végül listázd ki az összes versenyzőt sorszámozva, összes adatukkal. */

    return 0;
}

void datum_kiir(Datum d) {
    /* dátum kiírása */
}

void versenyzo_kiir(Versenyzo v) {
    /* a versenyző összes adatának kiírása */
}
Megoldás
#include <stdio.h>

typedef struct Datum {
    int ev, ho, nap;
} Datum;

typedef struct Versenyzo {
    char nev[31];
    Datum szuletes;
    int helyezes;
} Versenyzo;

void datum_kiir(Datum d);

void versenyzo_kiir(Versenyzo v);

int main() {
    Versenyzo versenyzok[5] = {
        { "Am Erika", {1984, 5, 6}, 1 },
        { "Break Elek", {1982, 9, 30}, 3 },
        { "Dil Emma", {1988, 8, 25}, 2 },
        { "Kasza Blanka", {1979, 6, 10}, 5 },
        { "Reset Elek", {1992, 4, 5}, 4 },
    };

    /* 0-s versenyző neve - printf %s */
    printf("%s\n", versenyzok[0].nev);
    /* 2-es versenyző helyezése */
    printf("%d\n", versenyzok[2].helyezes);
    /* 4-es versenyző születési dátumát a megadott függvénnyel */
    datum_kiir(versenyzok[4].szuletes);
    /* 1-es versenyző nevének kezdőbetűjét (ne feledd, a sztring karaktertömb) */
    printf("%c\n", versenyzok[1].nev[0]);
    /* az 1-es versenyző dobogós-e? igen/nem, akár ?: operátorral, de egy printf-fel */
    printf("%s\n", versenyzok[1].helyezes <= 3 ? "igen" : "nem");
    /* az 4-es versenyző gyorsabb-e, mint a 3-as versenyző? */
    printf("%s\n", versenyzok[4].helyezes < versenyzok[3].helyezes ? "igen" : "nem");
    /* az 1-es versenyző ugyanabban az évben született-e, mint a 2-es? */
    printf("%s\n", versenyzok[1].szuletes.ev == versenyzok[2].szuletes.ev ? "igen" : "nem");
    /* egészítsd ki a versenyzo_kiir() függvényt,
     * aztán írd ki az 1-es versenyző összes adatát */
    versenyzo_kiir(versenyzok[1]);
    /* végül listázd ki az összes versenyzőt sorszámozva, összes adatukkal. */
    for (int i = 0; i < 5; ++i) {
        printf("%d. ", i);
        versenyzo_kiir(versenyzok[i]);
    }

    return 0;
}

void datum_kiir(Datum d) {
    printf("%d.%d.%d.\n", d.ev, d.ho, d.nap);
}

void versenyzo_kiir(Versenyzo v) {
    printf("%s, %d.%d.%d., %d\n", v.nev, v.szuletes.ev, v.szuletes.ho, v.szuletes.nap, v.helyezes);
}

7. Menüvezérelt program

Készíts egyszerű menüvezérelt programot! A program tároljon el egy számot, melynek kezdőértéke a = 1. Ezt követően a program jelenítse meg a képernyőn a értékét, és az alább látható menüt. A megfelelő menüpont számának megadása (scanf()-fel) után hajtsa végre a-n a kiválasztott műveletet, írja ki újból a új értékét és a menüt! A menüből mindaddig lehessen újból választani, míg a kilépést nem választja a felhasználó! Használj switch-et és do ... while(), azaz hátultesztelő ciklust!

printf("0. Alapertek visszaallitasa (a = 1)\n"
       "1. Hozzaad 1-et\n"
       "2. Megforditja az elojelet\n"
       "3. Szorozza 2-vel\n"
       "9. Kilepes\n");

Minden egyes tevékenységet (műveletet) egy pici függvény valósítson meg, amelynek bemenő paramétere az a változó tartalma, visszatérési értéke pedig a megváltozott szám! A main() ezen függvények hívásával végezze el a feladatát!

Miért olyan lényeges ez a feladat?

Figyeld meg a kapott főprogramot! Ez irányítja a többi függvény működését: meghívja az egyes részfeladatokhoz tartozó alprogramokat, amelyek dolgukat végezve visszatérnek, újra a főprogram kezébe adva az irányítást. A főprogram és az alprogramok a paramétereken és a visszatérési értékeken keresztül kommunikálnak.

Megoldás

A tevékenység kiválasztásához a switch szerkezetet érdemes használni; úgy egymás alatt, a választott szám szerint felsorolhatóak az értékadások. Figyelni kell, ne maradjon ki a break! A menürendszer keretét pedig egy ciklus adja. Ez azért lehet hátultesztelő, mert egyszer biztosan végre kell hajtani a törzsét: a menü kiírását, a szám beolvasását.

#include <stdio.h>

int alap() {
    return 1;
}

int novel(int a) {
    return a+1;
}

int megfordit(int a) {
    return -a;
}

int duplaz(int a) {
    return 2*a;
}

int main(void) {
    int menupont;
    int a = alap();
    do {
        printf("a = %d\n\n", a);
        printf(
            "0. Alapérték visszaállítása (a = 1)\n"
            "1. Hozzáad 1-et\n"
            "2. Megfordítja az előjelét\n"
            "3. Szorozza 2-vel\n"
            "9. Kilépés\n"
            "? ");
        scanf("%d", &menupont);
        switch (menupont) {
            case 0: a = alap(); break;
            case 1: a = novel(a); break;
            case 2: a = megfordit(a); break;
            case 3: a = duplaz(a); break;
            case 9: /* semmi, majd kilepunk */ break;
            default: printf("NA!\n"); break;
        }
        printf("\n");          /* hogy ne folyjon ossze */
    } while (menupont != 9);

    return 0;
}

8. További feladatok

Ha elkészültél, folytasd a feladatgyűjtemény ehhez a témakörhöz kapcsolódó struktúrákkal kapcsolatos feladataival!