Gyakorlat, 7. hét: mutatók használata

Czirkos Zoltán · 2017.08.14.

Mutatók használata: cím szerinti paraméterátadás, tömbök és pointerek kapcsolata. Tömbök átadása függvényeknek. Sztringek mint nullával lezárt tömbök, alacsony szintű sztringkezelés.

A lenti programokban gyakran tömböt adunk át egy függvénynek. Az alapvető különbség a sima tömbök és a nullával lezárt sztringek között az, hogy az utóbbiaknál a tömb tartalma (karakterek és lezáró nulla) alapján tudjuk azt, hogy hol van vége annak. Ebben a speciális esetben nem kell átadni a tömb méretét a függvényeknek, máskor viszont mindig igen! Érdemes ilyen szemmel összehasonlítani a programokat.

Másrészről: a paraméterként kapott tömböt a függvény tudja módosítani, hiszen cím szerint kapja meg azt. Az eredeti memóriát látja, nem a tömb egy másolatát! Ezt is kihasználjuk a függvényekben.

Felkészülés a gyakorlatra:

1. Cím szerinti paraméterátadás

Írjunk egy függvényt, amelyik kiszámolja és visszaadja két szám összegét és szorzatát!

Megoldás

Mivel két adatot is vissza kell adnia, cím szerinti átadást kell használnunk. A visszatérési érték helyett átvesszük cím szerint a két változót, amelybe írni kell.

void szamol(int a, int b, int *osszeg, int *szorzat) {
    *osszeg = a + b;
    *szorzat = a * b;
}

int o, s;
szamol(9, 11, &o, &s);

Természetesen ilyenkor is érték szerinti átadás történik, csak az az érték a memóriacím. A függvény belsejében az osszeg típusa int-re mutató pointer, a szorzat változóé ugyanaz.

2. Tömb átadása függvénynek

Írjunk függvényt, amely paraméterként átvesz egy egész számokból álló tömböt, és szétválogatja azokat egy második és egy harmadik tömbbe, aszerint hogy párosak vagy páratlanok!

Írjunk függvényt, amely kiírja a paraméterként kapott, egész számokból álló tömb elemeit!

Írjunk ezekhez főprogramot, amelyikkel szétválogatunk egy számsort, és kiírjuk külön a párosakat, külön a páratlanokat!

Megoldás

A szétválogatás algoritmusa viszonylag egyszerű, és szerepelt előadáson is. Három tömbbel dolgozunk, és mivel akárhány páros és páratlan szám lehet össze-vissza, három indexre van szükségünk:

  • A bemeneti tömböt indexeljük. Ez az index mindig nő.
  • Az előálló páros számok tömbjét indexeljük. Akkor nő, ha páros számot találtunk.
  • Ugyanez a páratlanokra.

Észre kell vennünk, hogy a függvény a tömb átadásakor csak egy pointert fog kapni a tömb elejére, ezért további paraméterként át kell adnunk neki a tömb elemszámát is. És aztán észre kell vennünk azt is, hogy vissza is kell adnunk a hívó számára egy darabszámot. Legalább az egyik új tömb elemszámát (párosak + páratlanok = összes), különben a kiírásnál gondban lesz.

Figyeljük meg: a kiírásnál a kiir() függvény nem a paros[] és ptln[] tömbök méretét kapja, hanem az értékes elemek számát! Mind a két tömböt ugyanakkorára kellett méretezni, mint az eredeti tömb (lehet, az összes szám páros vagy az összes páratlan volt), de valószínűleg nincsenek tele a tömbök a válogatás után. A végükön memóriaszemét van.

#include <stdio.h>

void paritas_valogat(int *be, int be_db, int *ki_paros, int *ki_ptln, int *ki_paros_db) {
    int i, i_paros, i_ptln;

    i_paros = i_ptln = 0;
    for (i = 0; i != be_db; ++i) {
        if (be[i] % 2 == 0) {
            ki_paros[i_paros++] = be[i];
        } else {
            ki_ptln[i_ptln++] = be[i];
        }
    }
    *ki_paros_db = i_paros;
}

void egesz_tomb_kiir(int *be, int be_db) {
    int i;
    for (i = 0; i != be_db; ++i)
        printf("%d, ", be[i]);
    printf("\n");
}

int main(void) {
    int szamok[10] = { 1, 7, 2, 6, 9, 3, 4, 5, 9, 1 };
    int paros[10], ptln[10];
    int paros_db, ptln_db;

    paritas_valogat(szamok, 10, paros, ptln, &paros_db);
    ptln_db = 10 - paros_db;

    printf("Parosak:\n");
    egesz_tomb_kiir(paros, paros_db);
    printf("Paratlanok:\n");
    egesz_tomb_kiir(ptln, ptln_db);

    return 0;
}

3. Fordítva

A feladatunk megfordítani egy sztringet. Írjuk meg ezt két külön változatban, azaz két külön függvényben! Az egyik függvény két tömböt kapjon, a bemenetet és a kimenetet. A másik függvény pedig végezze el a sztring megfordítását helyben: módosítsa a paraméterként kapott sztringet!

Megoldás

A két változat, amellett hogy nagyon eltérő módon kell használni őket, belül is másképp működik. Az új tömbbe másolós verzió érdemi része szinte csak egy másolásból áll, figyelve arra, hogy az egyik sztringben hátrafelé, a másikban előrefelé kell haladni, mert így alakul ki a fordított sorrend. A helyben megfordítós változat a sztring két végén lévő karakterek cserélgetésén alapul – persze csak a közepéig kell eljutni a cserékkel, különben visszafordul a sztring.

Mindkét esetben előbb meg kell határozni a sztring végét, a lezáró nulla megkeresésével. A függvények nem kapják meg a tömbméretet paraméterként, semmi értelme nem lenne.

#include <stdio.h>

void megfordit_uj_tombbe(char *be, char *ki) {
    /* elmegyünk a legutolsó karakterig olyan módon,
     * hogy megkeressük a lezáró nullát, és visszalépünk 1-et */
    int i = 0;
    while (be[i] != '\0')
        ++i;
    --i;

    /* aztán visszajövünk, és közben a másik tömbbe írjuk a betűket */
    int j = 0;
    while (i >= 0) {
        ki[j] = be[i];
        ++j;
        --i;
    }

    /* végére kell lezáró nulla! */
    ki[j] = '\0';
}

void megfordit_helyben(char *str) {
    /* meghatározzuk a méretét */
    int i;
    for (i = 0; str[i] != '\0'; ++i)
        ;
    int meret = i;

    /* utána a közepéig menve, csere */
    for (i = 0; i != meret/2; ++i) {
        /* str[i] <-> str[meret-1-i] */
        char temp = str[i];
        str[i] = str[meret-1-i];
        str[meret-1-i] = temp;
    }

    /* a lezáró nulla maradt a helyén. */
}

int main(void) {
    char elso[] = "hello vilag!";

    char masodik[20];
    megfordit_uj_tombbe(elso, masodik);
    printf("[%s] [%s]\n", elso, masodik);

    printf("[%s]\n", masodik);
    megfordit_helyben(masodik);
    printf("[%s]\n", masodik);

    return 0;
}

4. Mindentegybevéve

Írjunk függvényt, amelyik paraméterként kap egy sztringet. Változtassuk meg olyan módon, hogy a szóközöket kiszedjük belőle! Dolgozzunk a megadott sztringen! (A keletkező sztring ne egy másik memóriaterületen legyen, hanem az eredetit változtassuk meg!)

Megoldás

Gondoljuk végig:

  • Bele fog férni? Igen, mert ettől csak rövidülhet.
  • Belül kell segédtömb? Nem, mert menet közben is csak rövidül.
  • Le kell írni egymás alá egy példa bemenetet és kimenetet, abból jól látszik a megoldás: két változónk van, honnan másolunk (hányadik karaktert) és hova másolunk (hányadik helyre a tömbben).
#include <stdio.h>

void spacetelenit(char *t) {
    /* végigmegyünk az összes karakteren */
    /* "honnan" mindig nő, "hova" csak néha */
    int honnan, hova;
    hova = 0;
    for (honnan = 0; t[honnan] != '\0'; ++honnan) {
        /* és ami nem space, másoljuk */
        if (t[honnan] != ' ') {   /* NEM 32, hanem ' '! */
            t[hova] = t[honnan];
            hova++;
        }
    }
    t[hova] = '\0';  /* lezáró nulla! */
}

int main(void) {
    char hello[] = "H e l l o, vilag!";

    spacetelenit(hello);
    printf("Mindentegybeirok: [%s]\n", hello);

    return 0;
}

5. Palindrom

Írjunk programot, amelyik egy sztringről eldönti, hogy palindrom-e: azaz oda-vissza olvasva ugyanaz-e, mint önmaga. Közismert magyar nyelvű palindrom mondat az „Indul a görög aludni.” Hogyan egyszerű megírni, ha módosíthatjuk a sztringet, vagy akkor, ha nem? Mi köze ennek az előző két feladathoz?

Megoldás

Az egyszerűség kedvéért tekintsünk csak kisbetűs, írásjel nélküli mondatokat! „indul a görög aludni” megfordítva „indula görög a ludni”. A szóközök máshol vannak. De ha a szóközöket előbb kiszedjük, akkor már a megfordított sztring, palindrom esetén, teljesen megegyezik az eredetivel. A palindrom sztringeket vizsgáló program két részfeladatának megoldását láttuk az előbb.

6. További feladatok

Palindrom

Írjunk függvényt, amelyik megmondja egy szövegről, hogy palindrom-e! Ez azt jelenti, hogy visszafelé olvasva ugyanazt adja. Szóközök nem számítanak, kisbetű-nagybetű különbség sem számít.

Megoldás

A megoldásban a sztring két végéről indulunk. Mindig megkeressük mindkét végéről a következő nem szóköz karaktert, és összehasonlítjuk őket (nagybetűsítés után).

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

bool palindrom_e(const char * str)
{
    int eleje = 0, vege = strlen(str) - 1;
    while (eleje < vege)
    {
        while (str[eleje] == ' ')
            eleje++;
        while (vege >= 0 && str[vege] == ' ')
            vege--;
        if (toupper(str[eleje]) != toupper(str[vege]))
            return false;
        eleje++;
        vege--;
    }
    return true;
}


int main()
{
    printf("%s\n", palindrom_e("kukutyin") ? "igen" : "nem");
    printf("%s\n", palindrom_e("Keresik a tavat a kis erek") ? "igen" : "nem");
    return 0;
}

Pointerek

Írjuk meg a gyakorlat összes függvényét úgy, hogy sehol nem használunk tömb indexelést, sem *(ptr + int) alakú kifejezéseket! Helyette mindenhol pointerekkel lépkedünk a tömbökben.

Megoldás

Példaként a szóköztelenítő:

#include <stdio.h>

void spacetelenit(char *t) {
    char* honnan;
    char* hova;

    hova = t;
    for (honnan = t; *honnan != '\0'; ++honnan) {
        if (*honnan != ' ')
            *hova++ = *honnan;
    }
    *hova = '\0';
}

int main(void) {
    char hello[] = "H e l l o, vilag!";

    spacetelenit(hello);
    printf("Mindentegybeirok: [%s]\n", hello);

    return 0;
}