5. gyakorlat: függvények, top-down tervezés

A mai témák: felülről lefelé tervezés (top-down tervezés) gyakorlása, függvények írása és a karakter típus használata.

1 nagybetu(), kisbetu()

Írjunk függvényeket, amelyek karaktereket kapnak paraméterként, és a visszatérési értékük a nagybetűsített/kisbetűsített karakter: pl. a→A. Egyéb bemenetek esetén pedig adják változatlanul vissza az eredetit, pl. 8→8.

Megoldás

A betűk is csak számok a gép számára. Így aztán két karakter kivonása is értelmes művelet. Az általában használt ASCII kódtáblában egymás mellett vannak ábécé sorrendben a nagybetűk, és egy másik tartományban egymás mellett a kisbetűk. Emiatt a 'C'-'A' kifejezés 2-t ad (mivel a C és az A betű között 2 a távolság). Az 'A'-'a' kifejezés a két tartomány távolságát adja meg.

/* visszaadja a karakter nagybetus parjat, vagy sajat magat */
char nagybetu(char c)
{
    if (c>='a' && c<='z')
        return c-'a'+'A';
    return c;
}

/* visszaadja a karakter kisbetus parjat, vagy sajat magat */
char kisbetu(char c)
{
    if (c>='A' && c<='Z')
        return c-'A'+'a';
    else
        return c;
}

2 Madárnyelv

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

#include <stdio.h>

int main()
{
    char c;

    while (scanf("%c", &c)==1) {
        if (c=='a' || c=='e' || c=='i' || c=='o' || c=='u')
            printf("%cv%c", c, c);
        else
            printf("%c", c);
    }

    return 0;
}

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

Megoldás

#include <stdio.h>

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

int main()
{
    char c;

    while (scanf("%c", &c)==1)
        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ű után viszont már kisbetűvel kell kiírnia.

Megoldás

Egyszerű! A fenti, magánhangzó vizsgálatát végző függvénynek ne a beolvasott karaktert adjuk, hanem annak kisbetűsített párját. Így az nem is fogja látni, hogy nagybetűről vagy kisbetűről van szó. Innen adódik lent a maganhangzo(kisbetu(c)) kifejezés. Másrészt, a kiírásnál a v betű utáni duplázott magánhangzót ne az eredeti formájában írjuk ki, hanem kisbetűsítve. Ebből jön a printf() paramétereiben hívott kisbetu() függvény:

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

3 Kiírás adott számrendszerben

Írjunk programot, amelyik adott számot ír ki adott számrendszerben!

Megoldás

Mivel a kiírás balról jobbra halad, ezért először a legnagyobb helyiértékű számjegyre van szükségünk. Például ha 10-es számrendszerben szeretnénk kiírni a 123-at, akkor először az 1-est, utána a 2-est, végül pedig a 3-ast kell.

Ezért kell egy ciklus, amely a legnagyobb helyiértéktől a legkisebbig halad. A 123 példájában n=2-től (százasok) n=0-ig (egyesek). A megoldásban ez szerepel a kiir() függvényben. Hogy ez működni tudjon, két másik alproblémára kell még megoldás:

  • Tudnunk kell, melyik a legnagyobb helyiérték. Ehhez majd megírjuk a szamjegyek_szama() függvényt.
  • Ki kell tudnunk vágni a számból egy adott helyiértékű számjegyet. Ehhez pedig az n_edik_szamjegy() függvény lesz használható.

Ehhez az egyszerű feladathoz természetesen nem lenne feltétlenül szükséges függvényeket használni. De jól mutatják azt, hogyan lehet kisebb részekre bontani a problémát.

#include <stdio.h>

/* megmondja, hogy egy adott szam egy adott szamrendszerben
 * hany szamjegybol all. */
int szamjegyek_szama(int szam, int szamrendszer)
{
   int szamjegyek=0;
   while (szam>0) {
      szam/=szamrendszer;
      szamjegyek+=1;
   }

   return szamjegyek;
}

/* adott szam adott szamrendszerbeli alakjanak n. szamjegyet
 * adja vissza. n a kitevonek megfelelo hatvanykitevo,
 * vagyis n=0 adja az egyeseket, n=1 a tizeseket stb.,
 * ha epp 10-es szamrendszerben vagyunk. */
int n_edik_szamjegy(int szam, int szamrendszer, int n)
{
   /* pelda: szam=1234, n=2 -> szam=12 lesz, mert 2x osztja */
   while (n>0) {
      szam/=szamrendszer;
      n-=1;
   }
   /* pelda: szam=12 itt, szam%10 -> 2-vel ter vissza */
   return szam%szamrendszer;
}

/* adott szám kiírása adott számrendszerben. */
/* először ki kell találni, az egész hány számjegyből áll;
   mivel a legnagyobb helyiértékűt kell kiírni először. */
void kiir(int mit, int miben)
{
   int n;

   /* a kiírandó számjegyek, visszafelé */
   for (n=szamjegyek_szama(mit, miben)-1; n>=0; n-=1)
      printf("%d", n_edik_szamjegy(mit, miben, n));
}

int main()
{
   kiir(123, 10); printf("\n");
   kiir(255, 2); printf("\n");

   return 0;
}

A programban a megfelelő helyen nem véletlenül szerepel while és for ciklus. Az elsőnél a while praktikusabb (az igazi „amíg” jellegű), a másodiknál inkább a for (számlálásos jellegű).

Módosítsuk úgy a programot, hogy 10-nél nagyobb alapú számrendszerben is (pl. 16-osban) működjön! A 10-et, és annál nagyobb számjegyeket ilyenkor betűkkel szokás jelölni. Pl. 16-osban a 0…15 számjegyek: 012…89ABCDEF.

Megoldás

Ehhez csak a kiírást kell módosítani, hiszen az n_edik_szamjegy() függvény már eddigi formájában is elő tudja állítani a 10-nél nagyobb számjegyeket is. A kiírandó karaktert így tudjuk előállítani:

/* Visszatér egy karakterrel, amely az adott számjegyet ábrázolja.
 * 0..9 számjegyek -> '0'..'9' karakterek.
 * 10-nél nagyobb számjegyek -> betűk 'A'-tól kezdődően. */
char szamjegy_karakter(int szj)
{
    if (szj<10)
        return szj+'0';
    else
        return szj-10+'A';
}

4 Minimum, maximum, határ

Írjunk olyan függvényt, amely:

Írjunk olyan függvényt is a fentiek használatával, amely:

Megoldás

#include <stdio.h>

/* Visszaadja a két egész szám közül a kisebbiket. */
int min(int a, int b)
{
    if (a<b)        /* ha "a" kisebb */
        return a;
    else            /* amugy "b" kisebb, vagy egyenloek */
        return b;
}

/* Visszaadja a két egész szám közül a nagyobbikat. */
int max(int a, int b)
{
    if (a>b)
        return a;
    else
        return b;
}

/* Visszaadja a számot, ha az nagyobb, mint min, amúgy pedig min-t. */
int alulrol(int szam, int min)
{
    return max(szam, min);
}

/* Visszaadja a számot, ha az kisebb, mint max; amúgy a
 * maxot. Ugyanaz a helyzet, mint az előbbinél. */
int felulrol(int szam, int max)
{
    return min(szam, max);
}

/* A [min;max] intervallumba szorítja a megadott számot, és
 * azzal tér vissza. Csak akkor működik helyesen, ha min<=max. */
int korlatoz(int szam, int min, int max)
{
    return felulrol(alulrol(szam, min), max);
}

int main()
{
    int i;

    printf("min(5, 7)=%d\n", min(5, 7));
    printf("max(5, 7)=%d\n", max(5, 7));
    printf("alulrol(5, 0)=%d\n", alulrol(5, 0));
    printf("alulrol(-1, 0)=%d\n", alulrol(-1, 0));
    printf("felulrol(5, 0)=%d\n", felulrol(5, 0));
    printf("felulrol(-1, 0)=%d\n", felulrol(-1, 0));
    printf("-5..10 számok [0;5] közé korlátozva:\n");
    for (i=-5; i<=10; i+=1)
        printf("%d ", korlatoz(i, 0, 5));
    printf("\n");

    return 0;
}

Beugrató: az alulrol() függvény a max() hívásával oldható meg. Ugyanis ha a limit alá csúszik a szám, akkor a limit lesz a nagyobb; Ha a limit felett van, akkor pedig a szám! Ugyanígy, a felulrol() függvényben a min()-t kell használni.

Természetesen megoldhatóak lennének az utóbbiak a min() és max() használata nélkül is. A korlatoz() pedig megoldható lenne a min() és max() használatával – ebben az esetben azonban figyelni kellene arra, hogy a formális paramétereit nem szabadna min-nek és max-nak elnevezni, hiszen akkor nem lehetne belőle meghívni a min() és max() függvényeket:

int korlatoz(int szam, int min, int max) // HIBÁS!
{
    return min(max(szam, min), max);     // HIBÁS!
}

A formális paraméter neve elfedi a függvényen kívül megadott másik függvény nevét. Érdemes ezt átgondolni a fent helyesen megírt alulrol() és felulrol() esetén is! Ott is megtörténik ez, csak nem probléma, mert mindkettőnél pont a másik függvényre van szükség.

5 Beolvasás adott számrendszerben

A szám kiírása feladat fordítottja: olvassunk be a billentyűzetről egy számot a megadott számrendszerben. (Előbb láttuk, mennyi dolga van ezzel a printf()-nek, most látjuk, mennyi dolga van a scanf()-nek.) Kövessük itt is a felülről lefelé tervezés elvét!

Megoldás

#include <stdio.h>
#include <ctype.h>

/* Visszaadja a számjegy értékét:
 * - 0-9 -> 0-9
 * - A-Z (a-z) -> 10-35
 * - egyéb karakter (érvénytelen) -> -1 */
int ertek(char c)
{
    if (isdigit(c))
        return c-'0';
    if (isalpha(c))
        return toupper(c)-'A'+10;
    /* nem szám, nem betű - ez bizony hiba */
    return -1;
}

/* beolvas egy egész számot a billentyűzetről,
 * az adott számrendszerben. visszatérési értéke
 * a beolvasott szám, vagy hiba esetén -1. */
int beolvas(int alap)
{
    int szam;
    char c;

    szam=0;
    while (scanf("%c", &c)==1 && !isspace(c)) {
        int ert = ertek(c);
        if (ert<0 || ert>=alap) /* ha hiba, vagy túl nagy az alaphoz */
            return -1;
        szam = szam*alap + ert;
    }
    return szam;
}

int main()
{
    printf("Irj 2-esben: ");
    printf("Erteke 10-esben: %d\n", beolvas(2));
    printf("Irj 16-osban: ");
    printf("Erteke 10-esben: %d\n", beolvas(16));

    return 0;
}

6 Tökéletes és barátságos számok

Tökéletes szám az a szám, amely megegyezik a nála kisebb osztóinak összegével. A legkisebb tökéletes szám a 6 (1+2+3), az utána következők a 28 (1+2+4+7+14) és 496.

Barátságos számoknak nevezzük azokat a számpárokat, amelyeknél az egyik szám nála kisebb osztóinak összege egyenlő a másik számmal, és fordítva. A legkisebb ilyen számpár a (220;284), mert 1+2+4+5+10+11+20+22+44+55+110=284 és 1+2+4+71+142=220. További párok (1184;1210) és (2620;2924).

Írjunk függvényt, amely 1) megmondja egy számról, hogy tökéletes-e, 2) megmondja egy számpárról, hogy barátságos-e! Kövessük a top-down tervezési elvet!

Megoldás

Induljunk ki a definícióból: tökéletes szám az, amelyik megegyezik a nála kisebb osztóinak összegével. A kisebb osztókat összeadő függvényt majd megcsináljuk később.

/* igazzal tér vissza, ha tökéletes szám */
int tokeletes(int szam)
{
    return szam==kisebboszto_osszeg(szam);
}

Barátságos számpár pedig az, amelyeknél egymásra vetítve működik ez ugyanígy.

/* igazzal tér vissza, ha barátságosak */
int baratsagos(int szam1, int szam2)
{
    return szam1==kisebboszto_osszeg(szam2) && szam2==kisebboszto_osszeg(szam1);
}

A tökéletes szám és a barátságos számok definíciója nagyon hasonlít egymáshoz. Tulajdonképpen a tökéletes szám önmaga barátja. Vegyük észre: az osztós függvény létezését feltételezve egy-egy sorban meg tudtuk oldani a problémákat! Sőt, mivel mindkét függvény ugyanannak a segédfüggvénynek a létezését feltételezi, elég azt már egyszer megírnunk! A teljes program:

#include <stdio.h>

/* visszatér a paraméterként kapott szám
   osztóinak összegével (magát a számot kivéve) */
int kisebboszto_osszeg(int szam)
{
    int osszeg, oszto;

    osszeg=0;
    for (oszto=1; oszto<=szam/2; oszto+=1)
        if (szam%oszto == 0)
            osszeg += oszto;

    return osszeg;
}

/* igazzal tér vissza, ha tökéletes szám */
int tokeletes(int szam)
{
    return szam==kisebboszto_osszeg(szam);
}

/* igazzal tér vissza, ha barátságosak */
int baratsagos(int szam1, int szam2)
{
    return szam1==kisebboszto_osszeg(szam2)
           && szam2==kisebboszto_osszeg(szam1);
}

int main()
{
    printf("6 tökéletes: %d\n", tokeletes(6));
    printf("7 tökéletes: %d\n", tokeletes(7));
    printf("220,284 barátságos: %d\n", baratsagos(220, 284));
    printf("220,285 barátságos: %d\n", baratsagos(220, 285));

    return 0;
}