OOP-előkészítő C feladatok

2. Egyszerű sztringek

  1. Definiálj Sztring struktúrát, amelyik egy dinamikus karaktertömböt tartalmaz (amely nullával lezárt sztringet fog tárolni), és a hozzá tartozó hosszt.
  2. Írj függvényt, amelyik paraméterként vesz át egy ilyet, illetve egy nullával lezárt char const * tömböt. Inicializálja a függvény az eddig memóriaszemetet tartalmazó struktúrát úgy, hogy ezt a karaktertömböt bemásolja!
  3. Írj függvényt, amelyik három sztringet vesz át paraméterként; az elsőt és a másodikat összefűzve másolja be a harmadikba. A harmadik sztring már inicializálva volt, és tárol egy sztringet, amelyet felül kell írni!

A dinamikus tömböt és a hosszat mindenhol együtt kezeld! C-s sztringkezelő függvényeket (strcpy, strlen) használni lehet.

Megoldás
struct Sztring {
    char *adat;
    int hossz;
};

void sztring_init(Sztring *s, char const *kezd) {
    s->hossz = strlen(kezd);
    s->adat = new char[s->hossz + 1]; /* +1 a lezaro 0-nak */
    strcpy(s->adat, kezd);
}

void sztring_osszefuz(Sztring const *egyik, Sztring const *masik, Sztring *ide) {
    delete[] ide->adat;     /* mert mar tartalmazott adatot! */

    ide->hossz = egyik->hossz + masik->hossz;
    ide->adat = new char[ide->hossz + 1];
    strcpy(ide->adat, egyik->adat);     /* masol */
    strcat(ide->adat, masik->adat);     /* hozzafuz */
}

3. Sztringből kiszedi a megadott karaktereket

Egészítsd ki az előző programot egy olyan függvénnyel, amelyik egy megadott sztringből kiszedi a paraméterként kapott karakter összes előfordulását! A dinamikus tömb a szűrés után is pont akkora legyen, amekkora kell!

Megoldás
struct Sztring {
    int hossz;
    char *szov;
};

/* 1. megszámoljuk, mekkora lesz az új sztring
   2. foglalunk neki helyet
   3. átmásoljuk a karaktereket, amiket kell
   4. felszabadítjuk a régit
   5. átállítjuk a pointert */

/* érték szerinti paraméterátadás -> Sztring * kell */
void kiszed(Sztring *sz, char mit) {
    /* 1. */
    int db = 0;
    for (int i = 0; i < sz->hossz; i++) /* összes char */
        if (sz->szov[i] != mit)         /* ha nem olyan */
            db++;                       /* növel */

    /* 2. */
    char *uj = new char[db + 1];        /* ekkora az új! */

    /* 3. */
    int j = 0;
    for (int i = 0; i < sz->hossz; i++) /* összes eredeti */
        if (sz->szov[i] != mit) {       /* ha nem olyan */
            uj[j] = sz->szov[i];        /* másol */
            j++;                        /* másik index! */
        }
    uj[j] = '\0';                       /* lezáró 0 */

    /* 4. */
    delete[] sz->szov;

    /* 5. */
    sz->szov = uj;
}

4. Memóriakezeléses elvi hibás verem

Alább egy C-ben megírt verem adatszerkezet. A verem_init() függvény inicializálja az addig memóriaszemetet tartalmazó struktúrát. A verem_berak() függvény betesz egy számot a verembe. Ha kell, átméretezi a dinamikus memóriaterületet. A verem_free() egy feleslegessé vált verem dinamikus területét szabadítja fel.

A program memóriakezelési hibát tartalmaz. Hol? Hogyan lehet javítani?

struct Verem {
    double *adat;   /* dinamikus */
    int db;         /* értékes adatok száma */
    int kapacitas;  /* foglalt terület mérete (db <= kapacitas) */
};

int main() {
    Verem v1, v2;
    verem_init(&v1);
    verem_init(&v2);
    verem_berak(&v2, 5.1);
    v1 = v2;
    verem_free(&v1);
    verem_free(&v2);
    return 0;
}
Megoldás

A fenti kódban az elvi hiba a v1=v2 értékadásnál van. Itt ugyanis minden egyes adattag átmásolódik v1-be v2-ből; ez a darabnál például nem lenne gond, a pointernél viszont nagyon is az. Ugyanis innentől kezdve v1 és v2 pointere ugyanoda mutat, és a vermek nem működnek majd helyesen. Például az egyik verembe berakott adat a másikban is lehet, hogy meg fog jelenni. A v1.adat pointer ilyen módon történő felülírása memóriaszivárgást is okoz.

A struktúrák közötti értékadás maga egyébként működik, lefordul, már a sima C is ismeri! Nem az a hiba, hogy ilyen nincs, hanem amit csinál, az nem jó nekünk itt. (Gyakorlatilag a struktúra értékadásával megsértjük azt a szabályt, hogy nem nyúlunk a struktúra belsejébe - ugyanis kívülről nem tudhatjuk, hogy le szabad-e egyesével másolni az adattagjait. Ezt csak az tudja, aki a struktúrát kezelő függvényeket megírja.) Írni egy másolós függvényt, amelyik egyesével átmásolja a struktúra adattagjait, az ugyanúgy rossz, mintha egy sima értékadással lerendezzük, mert akkor a memóriaszivárgás és a rossz pointerek ugyanúgy meglesznek. Ezen kívül, az egymásra mutató pointerek mindenképpen rosszak, nem csak akkor, ha a két verem darabszáma vagy kapacitása épp eltérő.

A memória felszabadítása lesz az, ahol a program lefagy; egészen pontosan v2 felszabadítása. v1.adat és v2.adat ugyanoda mutat, ezért a felszabadító függvény kétszer próbálja meg majd azt a memóriaterületet felszabadítani (free() vagy delete[], attól függ, hogyan vannak megírva).

Javítani úgy lehet, ha az értékadás helyett másoló függvényt írunk, és a v1=v2 értékadást egy verem_masol(&v1, &v2); sorra cseréljük.

void verem_masol(Verem *cel, const Verem *forras) {
    /* A cél veremből természetesen minden adat eltűnik, felülírjuk. */
    delete[] cel->adat;

    /* ez egyértelmű */
    cel->db = forras->db;
    /* igazából ez is; legegyszerűbb leutánozni a másik vermet, és
       akkor itt nem kell azzal foglalkozni, hogy mi a foglalási
       stratégia. a másik úgyis aszerint jött létre. */
    cel->kapacitas = forras->kapacitas;

    /* forrástól független memória foglalása */
    cel->adat = new double[cel->kapacitas];
    /* másolás */
    for (int i=0; i<cel->db; ++i)
        cel->adat[i]=forras->adat[i];
}

5. Melyik változó hova kerül?

Rajzold le az alábbi program változóinak elhelyezkedését a memóriában, amikor a végrehajtás a *-gal jelölt résznél tart! Melyik változó hova kerül (globális, verem, dinamikus memóriaterületre)? Az egyes pointereket nyíllal jelöld, hova mutatnak. Van egy hiba a programban. Hol? Hogyan lehet javítani?

#include <cstdlib>

/* n*n-es matrixot foglal */
double **nxn_matrix(int const n) {
    double **matr;
    matr = (double **) malloc(n * sizeof(double*));
    matr[0] = (double *) malloc(n * n * sizeof(double));
    for (int y = 1; y < n; y++)
        matr[y] = matr[0] + y * n;
    return matr;
}

int s = 3;

int main() {
    double **m;
    m = nxn_matrix(s);
    m[2][1] = 5.1;         // *
    free(m);
    return 0;
}
Megoldás

A program változói a lenti ábrán láthatóak. Az s globális területre kerül, mivel függvényen kívül van. Az m a verembe, mivel az a main() függvény globális változója. Ezeken kívül pedig a heapre kerül a két malloc()-olt tömb.

Az első foglalt tömb pointereket tartalmaz, mégpedig hármat. Ennek a tömbnek az első elemébe egy pointert teszünk, amelyet a malloc() függvény ad; ez kilenc darab double változónak hely. A többi pointert úgy inicializáljuk, hogy ennek a tömbnek a belsejébe mutat, 3 és 6 elemmel arrébb.

A kódban a hiba az, hogy a main() függvényben csak az első, pointereket tartalmazó tömb kerül felszabadításra. A második, double elemeket tartalmazó nem. (Egy malloc → egy free!) Javítani legegyszerűbben úgy lehet, hogy a free(m) sor elé egy free(m[0]) hívást írunk.

Félreértések:

  • Egy adott memóriaterülethez tartozó malloc() és free() hívásnak NEM kell egy függvényben lennie! Ha így lenne, akkor hogy lenne az lehetséges, hogy a malloc() működik?! Az is lefoglal valamit, de nem szabadítja fel!
  • A matr[] tömb minden eleme inicializálva van. Az első, matr[0] külön, mert oda egy malloc()-tól kapott pointert teszünk be. Az összes többi, 1-től kezdődő indexűek pedig a for() ciklusban lesznek inicializálva.
  • Éppen ezért helyes az is, hogy a for() ciklus y=1-től indul.
  • A dinamikus memóriát felszabadítani itt pontosan két free() hívással lehet. Az egyik a double számok tömbjét szabadítja fel, a másik pedig a pointerekét. Ha a foglalásnál nem for() ciklus foglalta a sorokat külön, akkor felszabadítani sem úgy kell!

C++ feladatok

7. Törtek I.

Az órai törtes példát egészítsd ki <, - operátorokkal! (A negáláshoz, negatív számlálóhoz, a törtet létrehozó függvényt módosítanod kell, az Euklidész-féle algoritmus nem bírja a negatív számokat.)

Tort a, b, c;
std::cout << (a < b);
c = -a;
std::cout << c;

8. Törtek II.

Csinálj fullos tört (racionális szám) osztályt! Legyen benne elrejtve a számláló és a nevező! Legyen konstruktora (amely ellenőrzi a nevező nulla értékét), legyenek getterek, amelyek lekérdezik a számlálót, a nevezőt; és legyenek mindenféle operátorok az aritmetikai műveletekhez! A függvények dobjanak kivételt, ha egy művelet nem végezhető el!

9. Törtek – interfész

A Tort osztály racionális számokat tárol, vagyis egész számok hányadosát. Két adattagja van, a számláló és a nevező. Ezeket elrejtjük a külvilág elől, nehogy pl. valaki jókedvében nullára állítsa a nevezőt. A következő programrésznek kéne helyesen működnie, vagyis kell tudni létrehozni, összehasonlítani, szorozni és összeadni:

Tort a(3, 5), b(1, 2), c(3), d;
/* a=3/5, b=1/2, c=3, d=0 */
if (c != a) std::cout << "c nem ugyanannyi, mint a.";
if (c == a) std::cout << "c ugyanannyi, mint a.";
c = a+b*c;
c.beolvas(std::cin);

Definiáld az osztályt! A szorzás műveletet definiáld is (írd meg), a többit elég csak deklarálni. (Két fő kérdés: 1. az egyes függvények neve, és a paramétereik típusa, 2. hány konstruktor kell?)

10. Időpont osztály

Írj egy osztályt, amelyik egy időpontot tárol (IdoPont)! Legyen egy konstruktora, amelynek óra (0..2), perc (0..59) formátumban lehet megadni az időpontot. Legyen alapértelmezett konstruktora, amely éjfelet állít be. Legyen == operátora, amellyel össze lehet hasonlítani két ilyet (bool igazzal tér vissza, ha egyenlőek)! Legyen - operátor függvénye, amellyel ki lehet vonni egymásból két időpontot, és egy egész számot ad (pl. 10:40 és 09:55 között 45 perc telt el – érdemes belül mindent percre átszámolni). Ki lehessen írni egy ostreamre: pl. IdoPont i(5, 35); std::cout<<i; hatására 5:35 jelenjen meg. Legyen inline és nem inline megvalósított tagfüggvény is!

Megoldás
#include <iostream>

class IdoPont {
  private:
    int ora, perc;
  public:
    IdoPont() { ora=perc=0; }
    IdoPont(int ora_, int perc_) { ora=ora_; perc=perc_; }
    friend bool operator==(const IdoPont &a, const IdoPont &b);
    int operator-(const IdoPont &b) const;
    friend std::ostream& operator<<(std::ostream &os, const IdoPont &i);
};

int IdoPont::operator-(const IdoPont &b) const {
    return (ora*60+perc) - (b.ora*60+b.perc);
}

bool operator==(const IdoPont &a, const IdoPont &b) {
    return a.ora==b.ora && a.perc==b.perc;
}

std::ostream& operator<<(std::ostream &os, const IdoPont &i) {
    os << i.ora << ':' << i.perc;
}

int main() {
    IdoPont i;
    IdoPont i1(10, 40);
    IdoPont i2(9, 55);
    if (i1 == i2)
        std::cout << i1 << " és " << i2 << " egyenlőek." << std::endl;

    std::cout << i1 << " és " << i2 << " között "
              << i1-i2 << " perc telik el." << std::endl;
}

11. Háromértékű logika I.

Az InfoC oldal adminisztrációs része háromértékű logikát használ a tárgykövetelmények teljesülésének jellemzéséhez. A szokásos IGEN és NEM mellett egy harmadik érték is lehetséges: LEHET. Így a félév közben sem mutat fals eredményt az eredmények táblázata.

A három érték jelentését könnyű megfogni a laborjelenlétek példáján. 14 hét van, 4-et lehet hiányozni. Ha valakinek 4-nél több hiányzása van, már nem kaphat jegyet: NEM. Ha 10-nél több jelenléte összegyűlik, akkor a hátralévő időben akármennyit hiányzik, már biztosan teljesül a követelmény: IGEN. Amúgy még nem lehet tudni, ha a félév közepén vagyunk: LEHET. A félév végén, a csoport lezárásakor az összes LEHET átváltozik NEM értékűre.

A háromértékű logikához logikai függvényeket is kellett definiálni. A logikai ÉS kapcsolat működése: ha mindkét érték IGEN, akkor az eredmény is IGEN; ha bármelyik érték NEM, akkor az eredmény NEM, amúgy pedig LEHET. Az igazságtáblát felrajzolva látszik, hogy ha nincs LEHET érték, akkor ez egyenértékű a Boole-féle kétértékű logikai ÉS függvénnyel.

A feladatok a következők. 1) Definiálj felsorolt típust, amely IGEN, NEM, LEHET értékű lehet. 2) Definiáld a logikai VAGY és TAGADÁS műveleteket is a háromértékű logikára. 3) Valósítsd meg C++-ban az &&, ||, ! operátorokat a háromértékű logikára! 4) Működnek a && és || operátorok, ha valamelyik operandusuk bool? 5) Mi a helyzet a háromértékű logikai típushoz definiált && és || operátorok rövidzár tulajdonságával?

12. Háromértékű logika II.

Írd meg a fenti „háromértékű logika” feladatot C++-ban úgy, hogy egy egyszerű enum helyett osztályt használsz! Legyen ennek konstruktora (amelyik mindig inicializálja valamilyen értékre a változót), legyenek a fentihez hasonló módon operátorai! Oldd meg, hogy kompatibilis tudjon ez lenni a beépített bool-okkal! Pl. a HaromErteku x = true; definíciónak működnie kell. Vajon van értelme annak, hogy a bool → HaromErteku mellett HaromErteku → bool konverzió is legyen?

13. Térbeli vektor

Írj egy háromdimenziós vektor osztályt! Legyen 3 paraméterű konstruktora (x, y, z komponensek), lehessen összeadni két vektort + és += operátorral, lehessen lekérdezni a hosszát egy adott vektornak. Lehessen kiírni egy std::ostream-re a << operátorral. Legyen minden konstans és/vagy referencia, ami csak lehet! Magyarázd el, miért úgy kell megvalósítani a << operátort, ahogyan – miért az az egyetlen lehetséges módja. Melyik osztálynak miért/miért nem lehetne tagfüggvénye az az operátor?

Megoldás
#include <cmath>
#include <iostream>

class Vektor {
private:
    double x, y, z;
public:
    Vektor(double x, double y, double z)
        : x(x), y(y), z(z) {
    }

    double hossz() const {
        return sqrt(x*x+y*y+z*z);
    }

    // ettől változik az, ami a bal oldalán van, a *this
    // ezt tagként csináltam, de lehetne globális is
    Vektor& operator+=(Vektor const& rhs) {
        this->x += rhs.x;
        this->y += rhs.y;
        this->z += rhs.z;
        return *this;   // emiatt lehet ref
    }

    friend std::ostream& operator<<(std::ostream& os, Vektor const& rhs);
};

// a két paraméter NEM változik, hanem egy újjal tér vissza!
// globálisként csináltam, de lehetne tag is
Vektor operator+(Vektor const& lhs, Vektor const& rhs) {
    Vektor uj(lhs);
    uj += rhs;            // visszavezettem erre
    return uj;            // emiatt NEM lehet ref
}

// ez csak globális lehet
std::ostream& operator<<(std::ostream& os, Vektor const& rhs) {
    os << '(' << rhs.x << ',' << rhs.y << ',' << rhs.z << ')';
    return os;
}

int main() {
    Vektor a(0,0,0), b(2,2,2), c(3,3,3);

    a = b+c;
    std::cout << b << c;

    b += c;
    std::cout << b << c;
}

A lényeg, hogy a += operátor megváltoztatja a bal oldalán álló vektort, a + operátor pedig nem. Jelen esetben a + operátort egyszerűen visszavezettem a már megírt +=-re. Így még barátnak sem kell lenni, mert csak a publikus interfészét használja: a másoló konstruktorát és a += operátorát.

A kiíró operátor azért lehet csak globális, mert a bal oldalán egy std::ostream típusú objektum van. Ha tagfüggvény lenne, akkor annak kellene a tagfüggvénye, ami viszont nem lehet, hiszen az az osztály már definiálva van, nem tehetünk hozzá új tagot. Nem feltétlenül kell barátnak is lennie; itt azért lett az, mert a Vektor publikus interfészén keresztül nem látja azokat a tagokat, amelyeket kiír (a vektor komponenseit.) A vektornak azért nem lehet tagja, mert akkor a vektor kellene az operátor bal oldalán álljon.

14. Komplex számok

A Komplex osztály belül re+im*j alakban tárol számokat. Készítsd el az osztályt, amelyet használva működőképessé válik a lenti program! A kiemelt részek utalnak a megvalósítandó függvényekre. Próbáld minél kevesebb függvénnyel megoldani a dolgot (bőven elfér a lapon), illetve az operátorokat lehetőség szerint visszavezetni egymásra. Átgondonandó: re és im publikusak vagy privátak? Miért? Hány konstruktor kell? Melyik operátor vezethető vissza melyikre? Referenciákkal kapcsolatban: a függvények a paraméterüket akkor vehetik át referenciaként, ha nem akarják lemásolni (mert az lassú lenne), vagy egyáltalán nem is másolható (pl. std::cout). Visszatérési érték is lehet referencia, ugyanezekben az esetekben. Ha a függvényen belül jön létre a visszaadott objektum, akkor viszont tuti nem használhatunk referenciát, mert visszatéréskor megszűnik a lokális változó. Használj a kódban mindenhol referenciát, ahol lehet, és objektumot, ahol muszáj!

Komplex a(3.4, 5), b, c(1);
if (c!=a) std::cout << "c nem ugyanannyi, mint a.";
if (c==a) std::cout << "c ugyanannyi, mint a.";
c = a+b*c;
c += a;
b *= a;
std::cout << c;

15. Komplex szám és explicit

Az előző feladat kiegészítéseként: Complex c1(2, 3); std::cout << c1+7; – miért működik? Mi történik?

16. Másodfokú egyenlet

Írj egy olyan komplex szám osztályt, amellyel megoldható egy másodfokú egyenlet! A komplex osztály olyan műveletekkel kell rendelkezzen, hogy a megoldóképlet egy az egyben beírható legyen a kódba.

17. Sztring * int

Pythonban a sztringek megszorozhatók egész számokkal, és ez sokszorozást jelent. Pl. print "fru"*2 → frufru. Írj a C++-os sztring osztályodhoz ilyen operátort! A szorzásnak fordítva is működnie kell: int*string ugyanazt az eredményt kell adja, mint a string*int.

18. Prímtényezős felbontás

  • Írj függvényt, amely paraméterként egy egész számot kap, visszatérési értékként pedig a szám prímtényezőit adja vissza növekvő sorrendben!
  • Írj függvényt, amely átvesz két prímtényezős felbontást, ahogyan azt az előző függvény adta, és visszaadja a legkisebb közös többszörös prímtényezős felbontását!
  • Írj függvényt, amely egy prímtényezős felbontást megkapva előállítja a számot!
  • Írj programot, amely kér két számot a felhasználótól, és a fenti függvények használatával kiírja a legkisebb közös többszörösüket!

Használd a beépített tárolókat, ne írj sajátot!

19. Split és join

  • Írj függvényt (split), amelyik egy sztringet felbont sztringek tömbjére egy adott karakter mentén. Például "alma körte barack" és ' '{"alma", "körte", "barack"}. Figyelj arra, hogy több elválasztó karakter is lehet a szavak között, például több szóköz.
  • Írj függvényt (join), amelyik sztringek tömbjét fűzi össze, az adott sztringet közéjük téve! pl. {"alma", "körte", "barack"} és ", ""alma, körte, barack".
  • Írj függvényt, amelyik szétbontja a beírt, szóközökkel elválasztott szavakat egy tömbre, majd egyesíti azokat vesszőkkel – pont, mint a fenti példákban!

Használd a beépített tárolókat, ne írj sajátot!

20. Const I.

Hova kell const-ot rakni?

class Verem {
  private:
    double *szamok;
    int meret;
  public:
    Verem() _____ { szamok = nullptr; meret = 0; }
    ~Verem() _____ { delete[] szamok; }
    Verem(_____ Verem& masik) _____;
    _____ Verem& operator=(_____ Verem& masik) _____;
    _____ double legutobbi() _____ { return szamok[meret-1]; }
    _____ double kivesz() _____;
    bool ures_e() _____;
    void berak(_____ double mit) _____;
};

21. Const II.

Hova kell const-ot rakni?

struct Komplex {
    double re, im;
};

bool operator==(_____ Komplex& lhs, _____ Komplex& rhs) {
    return lhs.re==rhs.re && lhs.im==rhs.im;
}

bool operator!=(_____ Komplex& lhs, _____ Komplex& rhs) {
    return !(lhs==rhs);
}

_____ Komplex operator*(_____ Komplex& lhs, _____ Komplex& rhs) {
    return Komplex(lhs.re*rhs.re-lhs.im*rhs.im, lhs.re*rhs.im+lhs.im*rhs.re);
}

_____ Komplex& operator*=(_____ Komplex& lhs, _____ Komplex& rhs) {
    lhs=lhs*rhs;
    return lhs;
}

_____ std::ostream& operator<<(_____ std::ostream& os, _____ Komplex& k) {
    os << k.re << '+' << k.im << 'j';
    return os;
}

22. Tömb

Egydimenziós tömb osztályt kell írni, amelyik sokféle konstruktorral rendelkezik. Adattagjai int méret és int *számok. Van egy paraméter nélküli konstruktora, amely üres tömböt kell létrehozzon (méret=0). A második konstruktora egy méretet kap paraméterként; létrehoz annyi számnak helyet, de nem ad nekik értéket. A harmadik konstruktor egy kezdőértéket is átvesz, amelyet minden létrehozott elembe bemásol. A negyedik egy tömböt vesz át kezdőcímével és méretével; úgy hozza létre a tömb objektumot, hogy a tömbből átmásolja a számokat. Definiáld az osztályt és írd meg a konstruktorokat! Esetleg vannak olyanok, amelyeket össze lehet vonni default paraméterrel?

int t[6]={ 1, 2, 5, 9, 11, 7 };
Tomb v1;          /* üres, mérete sincs */
Tomb v2(10);      /* 10 elemű, inicializálatlan */
Tomb v3(20, 1);   /* 20 elemű, létrejöttekor mind 1-es */
Tomb v4(t, 6);    /* 6 elemű, és t-ből átmásolja a számokat */
Tomb v5(v2);      /* másoló ctor. */

23. Várakozási sor (FIFO)

Írj egy Fifo osztályt, amelyik int számok várakozási sorát valósítja meg. A sor végére rakhatunk új számokat, illetve ha elveszünk egyet, azt mindig a sor elejéről kapjuk (vagyis amelyiket legkorábban raktuk be – first in, first out). Pl. belenyomunk 1, 2, 3-at (ilyen sorrendben), akkor utána először az 1-et tudjuk kivenni, és marad benne 2, 3.

  1. Az osztálynak legyen paraméter nélküli (default) konstruktora.
  2. Legyen destruktora.
  3. Legyen helyesen működő értékadó operátora.
  4. Helyesen működő másoló konstruktora.
  5. Legyen berak(int) tagfüggvénye.
  6. Legyen int kivesz() tagfüggvénye.
  7. Legyen ures_e() tagfüggvénye, amely igazzal tér vissza, ha a fifo üres.

Minden függvény legyen const tag, ha lehet. Belülről a fifo tartalmazhat tetszőlegesen tömböt vagy láncolt listát. Írj egy main() függvényt, amely:

  1. Létrehoz egy üres fifót (f1).
  2. Belerak három számot.
  3. Kivesz egyet, és kiírja.
  4. Létrehoz egy másik fifót (f2), amely az előbbinek (f1) a másolata.
  5. Ciklussal kivesz belőle mindent, amíg ki nem ürül, és kiírja a számokat.
  6. Létrehoz egy harmadik fifót (f3) üresen.
  7. Értékadással bemásol mindent f1-ből.
  8. Ebből is ciklussal kivesz belőle mindent, amíg ki nem ürül, és kiírja a számokat.

Mi a különbség az operator= és a másoló konstruktor között? Mire kell figyelni a megvalósításuknál?

24. Komplex osztály sablonnal

Adott a következő, komplex számokat kezelő C++ osztály, és hozzá tartozó függvény. Alakítsd sablonná! Hozz létre a sablonból int-es és double-s komplex osztályt létrehozni, és pár sorban mutasd meg a használatukat!

class Komplex {
  private:
    int re;
    int im;
  public:
    Komplex(int re_=0, int im_=0) {
      re=re_;
      im=im_;
    }
    bool operator==(Komplex m);
    friend Komplex operator+(Komplex k1, Komplex k2);
};

bool Komplex::operator==(Komplex m) {
  return re==m.re && im==m.im;
}

Komplex operator+(Komplex k1, Komplex k2) {
  return Komplex(k1.re+k2.re, k1.im+k2.im);
}
Megoldás

Sablonná alakítás: az osztályból sablon lesz, a tagfüggvényeiből sablon lesz. Az osztályon belül nem kell kiírni sehova, hogy , mivel az már eleve egy sablon osztályon belül van. A kívül implementált tagfüggvények elé viszont mindenhova oda kell írni a fejlécben.

A globális operator+ kicsit problémás; annak is sablonnak kell lennie. A friend-nél meg kell adni, hogy a T-vel példányosított operator+ a barát (mivel a friend osztályon kívüli dologra hivatkozik, rá nem érvényes, hogy template-lt kódon belül van). Ehhez viszont elődeklarálni kell az osztály előtt az operator+ sablonfüggvényt, ami miatt pedig elődeklarálni kell magát az osztályt.

template <typename T>
class Komplex;

template <typename T>
Komplex<T> operator+(Komplex<T> k1, Komplex<T> k2);

template <typename T>
class Komplex {
  private:
    T re;
    T im;
  public:
    Komplex(T re_=0, T im_=0) {
      re=re_;
      im=im_;
    }
    bool operator==(Komplex m);
    friend Komplex<T> operator+ <T>(Komplex<T> k1, Komplex<T> k2);
};

template <typename T>
bool Komplex<T>::operator==(Komplex<T> m) {
  return re==m.re && im==m.im;
}

template <typename T>
Komplex<T> operator+(Komplex<T> k1, Komplex<T> k2) {
  return Komplex<T>(k1.re+k2.re, k1.im+k2.im);
}

int main() {
    Komplex<int> a;
    Komplex<double> b, c;
    b = b+c;
}

25. Tömb osztálysablon

Írj egy tömb osztálysablont. Legyen megírva a konstruktora (méret integer paraméterrel), destruktora, másoló konstruktora (ez osztályon kívül definiálva), két indexelő operátora, darabszám lekérdező függvénye. Az értékadó operátorát csak deklaráld. Add meg, a tartalmazott objektumokról milyen műveleteket vársz el, és hol használod ezeket.

Megoldás
template <class T>
class Tomb {
    int meret;
    T *adat;
public:
    /* konstruktor, méret int paraméterrel; kell neki T::T() */
    explicit Tomb(int m): meret(m) { adat=new T[meret]; }
    /* destruktor, kell neki T::~T() */
    ~Tomb() { delete[] adat; }
    /* másoló konstruktor */
    Tomb(const Tomb&);
    /* értékadó operátor */
    Tomb& operator=(const Tomb&);
    /* méret lekérdező */
    int get_meret() const { return meret; }
    /* két indexelő: konstans és nem konstans */
    T& operator[](int i) { return adat[i]; }
    const T& operator[](int i) const { return adat[i]; }
};

/* másoló konstruktor, mivel Tomb sablon,
   a függvény is sablon.
     - T-ket tartalmazó Tomb (Tomb<T>)
     - másoló konstruktora (::Tomb)
     - ami paraméterben egy konstans
           T-ket tartalmazó Tomb ref-t vesz át
           (const Tomb<T>&)
   kell neki T::T() és T::operator=(T&) */
template <class T>
Tomb<T>::Tomb(const Tomb<T>& eredeti)
    : meret(eredeti.meret)
{
    adat=new T[meret];
    for (int i=0; i<meret; ++i)
        adat[i]=eredeti.adat[i];
}

Írj egy for_each_kiir nevű függvénysablont, amelyik paraméterként vesz át egy ilyen tömböt. Írja ki a tömb minden elemét a képernyőre. Barátja kell legyen ez a függvény az osztálynak? (Másik: írj egy for_each nevű függvénysablont, amelyik paraméterként vesz át egy ilyen tömböt, és egy műveletet. Hívja meg a tömb összes elemére a megadott műveletet.)

Megoldás
/* Nem kell barát legyen, mert csak a publikus tagjait
   használja (get_meret és operator[]). T szerint sablon,
   T-ket tartalmazó Tomb-öt veszünk át. */
template <class T>
void for_each_kiir(const Tomb<T>& t) {
    for (int i=0; i<t.get_meret(); ++i)
        std::cout << t[i] << ' ';
    std::cout << std::endl;
}

/* Olyan for_each, ami végigmegy az egész tömbön, és
   mindegyikre meghívja m-et. A MIT típus lehet egy függvény,
   aminek T a paramétere, de lehet egy objektum is, aminek
   van T paraméterű fv. hívó operátora. */
template <class T, class MIT>
void for_each(const Tomb<T>& t, MIT& m) {
    for (int i=0; i<t.get_meret(); ++i)
        m(t[i]);
}```

```c++
/* A kipróbáláshoz, nem része a feladatnak */
class IntOsszegzo {
    int osszeg;
public:
    IntOsszegzo(): osszeg(0) {}
    void operator()(int i) { osszeg+=i; }
    int get_osszeg() const { return osszeg; }
};

int main() {
    Tomb<int> t(3);
    t[0]=2; t[1]=9; t[2]=5;
    for_each_kiir(t);

    IntOsszegzo o;
    for_each(t, o);
    std::cout<<o.get_osszeg();
    return 0;
}

26. Verem osztálysablon (LIFO)

Írj egy Verem osztálysablont! A veremnek a következő műveletei legyenek:

  • betesz(x) – beteszi x-et a verembe.
  • kivesz() – visszatér a legutóbb betett értékkel. Ha üres a verem, akkor dobjon valamilyen kivételt!
  • ures() – igazzal tér vissza, ha a verem üres, amúgy hamissal.

Példányosítsd a vermet double és char típusokkal! Írj rövid programot, amely a billentyűről számokat olvas (a 0 érték beolvasásáig), utána pedig kiírja fordított sorrendben a beírt számokat – mindezt a verem segítségével. Írj egy olyan programrészt is, amely szöveget olvas be sor végéig (\n karakterig), és kiírja azt fordítva!

Megoldás

Ezt nagyon egyszerűen le lehet kódolni láncolt listával. Ugyanis ha mindig a lista elejére fűzünk be elemet (az a legegyszerűbb művelet), és mindig a lista elejéről veszünk ki (az is a legegyszerűbb), akkor pont egy vermet kapunk – hiszen azt vesszük ki először, amit legutoljára beraktunk! Emiatt egyébként a lista egyszeresen láncolt is lehet, és strázsa nélkül is könnyen megvalósítható.

A lent látható megvalósítás egyébként csak egy másoló konstruktort vár el T-től (meg persze destruktort).

#include <iostream>

template <class T>
class Verem {
    class Elem {
    public:
        T adat;
        Elem *kov;
        Elem(T const& adat): adat(adat) {}
    };

    Elem *elso;
public:
    /* üres verem */
    Verem(): elso(NULL) {}
    ~Verem() { /* kéne neki */ }
    Verem(Verem const&) { /* kéne neki */ }
    Verem& operator=(Verem const&) { /* kéne neki */ }

    /* lista elejére fűz. T const& célszerű. */
    void berak(T const& mit) {
        Elem *uj = new Elem(mit);
        uj->kov = elso;
        elso = uj;
    }
    /* lista elejéről vesz el. muszáj T, T& nem lehet. */
    T kivesz() {
        if (elso == NULL)
            throw "kifogytam";
        T kivett = elso->adat;
        Elem *masodik = elso->kov;
        delete elso;
        elso = masodik;
        return kivett;
    }
};

27. Kivételek

  1. Származtass az std::exception osztályból egy IndexHiba osztályt! A konstruktora vegyen át egy egész számot, amelyik a hibás indexet fogja tartalmazni. Dobjon ilyen típusú hibát a fentebb megírt tömböd túlindexelés esetén! (Mindkét indexelő operátorból - const és nem const!)
  2. Írj programrészt, amelyben kipróbálod ezt a hiba dobást. Kapd el a hibát, és írasd ki a kivételben tárolt üzenetet (amely a what() függvénnyel kérdezhető legyen lekérdezhető).
  3. Származtass NincsTobbElem hibát az std::exception osztályból. Dobjon ilyet a Verem osztályod, ha a kivesz() függvényt üres veremre hívod. Természetesen ennek is legyen működő, és a hibaüzenetet visszaadó what() függvénye.

28. Halmaz I.

Írj egy valós számokat tartalmazó halmaz osztályt, amellyel a következők lehetségesek:

  • Üres halmaz létrehozása (default konstruktorral).
  • Valós szám betétele a halmazba (ha már benne van, ne történjen semmi).
  • Szám kivétele a halmazból (ha nincs benne, ne történjen semmi).
  • Adott elem vizsgálata, szerepel-e a halmazban (igaz/hamis → bool).
  • Elemszám lekérdezése.
  • Üres voltának (igaz/hamis → bool) lekérdezése.
  • Elemek kiírása a << operátorral.
  • Halmaz másolása (másoló konstruktorral).
  • Halmazok közötti értékadás.
  • (És persze destruktor.)

29. Halmaz II.

Az előző feladatot folytatva:

  • Legyen uniót képző globális függvénye: h1 = unio(h2, h3);. (Ehhez az unió függvénynek egy halmazzal kell visszatérnie; a h1-be történő értékadás már az operator= dolga.)
  • Legyen ugyanígy működő metszet függvénye: h1 = metszet(h2, h3);.
  • Legyen kivonás függvény: h1 = h2\h3 a kódban h1 = kivon(h2, h3); formában jelenjen meg.
  • Írj főprogramot, amely teszteli ezeket a függvényeket. Figyelj a tesztadatokra, hogy ténylegesen a megfelelő programrészeket mozgasd meg: unió képzésénél legyen olyan elem a két forrás halmazban, amely közös (hogy a cél halmazban az ne szerepeljen kétszer.) Metszet képzésénél legyenek olyanok a forrásokban, amelyek csak az egyikben, illetve csak a másikban szerepelnek stb.
  • Írj egy operator==-t a halmaz számára. Vigyázz, ez is trükkös: nem lehet páronként összehasonlítani a két halmaz elemeit, mert lehet, hogy nem ugyanabban a sorrendben vannak. Ami belül {5,6} és {6,5}, azok egyforma halmazok! (Egyáltalán mi van belül?)

Az első három függvény legyen globális. Ha kell, friend-dé is teheted őket. Az operator== lehet tagfüggvény vagy globális is, ez rád van bízva. Figyelj arra, hogy a programban sehol ne legyen memóriaszivárgás! Ahol new[] van, ott (vagy máshol) kell legyen delete[] is.

30. Statikus adattag

Van egy Auto osztályunk, amely az autó márkáját (elég a fix méretű sztring, nem kell dinamikus most) és a gyártási évét (int ev) tárolja (pl. Honda, 2004). Szeretnénk, ha bármikor meg lehetne tudni, hogy éppen hány autó objektumot tárolunk összesen a memóriában. Aki használja az autó osztályt, annak természetesen ne kelljen foglalkoznia ezzel. (Ötlet: amikor létrejön egy autó objektum, növeljük a darabot. Ha megszűnik, csökkentjük.) Definiáld az osztályt, és írd is meg csak az ehhez szükséges részeit. Hozz létre néhány autót, és ezután írd ki a darabszámot! Adott a lenti három függvény is; írj példát, melyiket hogyan kell meghívni egy bizonyos autóra, és hogy amikor az egyes függvények belsejében van a végrehajtás, mennyi épp az autók darabszáma! Ha ilyen programot írunk, fogjuk-e szeretni az első függvényt?

void auto_fv_o(Auto a);
void auto_fv_r(Auto& a);
void auto_fv_p(Auto* a);

31. Fájlrendszer

A fájlrendszereinkben mappákat és fájlokat tárolunk. Minden mappának és fájlnak van neve. A fájloknak mérete is van, a mappák pedig fájlokat és további mappákat tartalmaznak. Készíts objektumorientált modellt a fájlrendszerhez! Írj rekurzív függvényt, amely kiszámítja egy mappában (és annak almappáiban) tárolt fájlok összesített méretét! Hozz létre szimbolikus link típust, amely segítségével egy fájlnak több név adható! A szimbolikus linknek saját neve kell legyen, és egy másik, meglévő fájlra vagy mappára kell mutasson, amelyik akár másik mappában is lehet, mint a link maga. Figyelj az öröklés miatt szükséges indirekcióra, és a helyes memóriakezelésre!

32. Étterem

Egy étteremnek írunk programot. Azt szeretnénk, ha a programba a pincérek be tudnák vinni az egyes rendeléseket, amelyeket az asztaloknál (20db) adnak fel a vendégek. Összesen húsz asztal van a teremben. A rendeléseknél megkülönböztetünk étel- és italrendeléseket. Az ételek egységáron számolódnak, de lehet fél adagot rendelni, amely esetben az ár 70%-át kérik el. Az italokat mennyiség (dl) és Ft/dl árral visszük be. Természetesen minden rendelésnél a megnevezést is felírjuk, hogy részletes számlát tudjunk adni.

  • Hogyan viszonyulnak egymáshoz az említett objektumok? Rajzold le az objektummodellt!
  • Definiáld az összes osztályt! A tagfüggvényeket részletesen megírni nem kell. STL bátran használható.
  • Írd meg azokat a metódusokat, amelyek egy számla kiállításához szükségesek. (Egyáltalán melyik objektum állítja ki a számlát?)
Megoldás
#include <iostream>
#include <vector>
#include <string>

class Rendeles {
    std::string megnevezes;
  public:
    Rendeles(std::string nev) : megnevezes(nev) { }
    virtual int ar() const = 0;
    void kiir() { std::cout << megnevezes << ' ' << ar() << std::endl; }
};

class Ital: public Rendeles {
    int forint_per_deci;
    int deci;
  public:
    Ital(std::string nev, int egyseg, int dl)
        : Rendeles(nev), forint_per_deci(egyseg), deci(dl) { }
    int ar() const { return forint_per_deci*deci; }
};

class Etel: public Rendeles {
    int etlap_ar;
    bool fel_adag;
  public:
    Etel(std::string nev, int ar, bool fel=false)
        : Rendeles(nev), etlap_ar(ar), fel_adag(fel) { }
    int ar() const { return fel_adag?etlap_ar*0.7:etlap_ar; }
};

class Asztal {
    std::vector<Rendeles*> rendelesek;
  public:
    void szamla() {
        int vegosszeg=0;
        for (int i=0; i < rendelesek.size(); i++) {
            rendelesek[i]->kiir();
            vegosszeg+=rendelesek[i]->ar();
        }
        std::cout << "fizetendo: " << vegosszeg << std::endl;
    }
    void rendel(Rendeles *r) { rendelesek.push_back(r); }
};

class Etterem {
  public:
    Asztal asztalok[20];
};

int main() {
    Etterem e;

    /* A NEW MIATT AZ ASZTALOKNAK KELLENE DESTRUKTOR */
    e.asztalok[0].rendel(new Ital("mango lassi", 150, 3));
    e.asztalok[0].rendel(new Etel("penne all'arrabbiata", 1200));

    e.asztalok[0].szamla();
}

Az objektumok közötti kapcsolathoz... Egy egyszerűbb feladatnál a szövegben lévő főnevek utalnak az osztályokra, és az igék utalnak az osztályok tagfüggvényeire. Ezek alapján:

  1. Az étel- és az italrendeléseket tároljuk.
  2. Kétféle rendelés létezik, étel- és italrendelés, ebből adódik egy öröklődés.
  3. Az asztalhoz tartoznak a rendelések. Ez nem öröklés, hanem tartalmazás, hiszen az asztal nem rendelés.
  4. Az étterem tartalmaz asztalokat.
  5. A számlát egy adott asztalnál ülő ember kéri, és a számla az annál az asztalnál ülő emberek fogyasztásának összege. Vagyis azoké a rendeléseké, amelyeket annál az asztalnál adtak fel. A számla ezért nem a rendelés osztály feladata, hanem az asztal osztályé!

33. Strand

Strandcikket kölcsönző kisvállalkozás részére szeretnénk nyilvántartó programot készíteni. Minden cikkről szeretnénk egy alapinformációt tárolni: vonalkód (31 karakter); és tudni szeretnénk, hogy ki van-e kölcsönözve. A kölcsönző jelenleg csak két fajta cikk kölcsönzésével foglalkozik, de remélhetőleg az üzlet fellendül, és lesz forrás a bővítésre. Az biztos, hogy 50 cikk kölcsönzésénél többel nem akar foglalkozni a cégtulajdonos, mert a raktárhelyiség kicsi. Jelenleg a következőket lehet kölcsönözni: vízibicikli (személyek száma, vonalkód), kenu (tömeg, fajta(verseny/túra), vonalkód).

  • Hogyan és miből öröklődnek ezek az osztályok? Rajzold le az öröklés ábráját!
  • Definiáld az egyes osztályokat! A tagfüggvényeket részletesen megírni nem kell, csak deklarálni.
  • Hozd létre az 50 elemű tömböt, és rakj bele néhány tetszőleges vízibicikli és kenu példányt.
  • Listázd ki az eszközöket, amelyek ki vannak kölcsönözve. Az ehhez szükséges tagfüggvényeket írd meg!
Megoldás

Ez itt a megoldás. Nagyon rövid így, hogy csak azok a függvények vannak definiálva, amiket kért a feladat! Ez persze önmagában még nem fog futni.

  • A vonalkód típusa char[]. A feladat szövege: 31 karakter.
  • A kikölcsönözve adattag típusa bool, nem int. Ki van kölcsönözve? Igen, nem.
  • A kenu fajtája enum! Nem int (milyen fajta kenu? kettő?!), nem bool (milyen fajta kenu? igen?!).
  • Get/set tagfüggvények feleslegesek.
  • Az adatok kiírása nem működhet az operator<<-vel, mert az nem tagja a strandcikk osztálynak. Így azon nem működhet a virtuális fv. mechanizmus sem.
class StrandCikk {
protected:
    char vonalkod[32];
public:
    bool kint;
    StrandCikk(char const *vk);
    virtual void adatok() = 0;
};

class ViziBicikli: public StrandCikk {
    int szemelyek;
public:
    ViziBicikli(char const *vk, int szem);
    void adatok() {
        cout << "ViziBicikli, "<< vonalkod << ", " << szemelyek << " szemelyes.\n";
    }
};

class Kenu: public StrandCikk {
public:
    enum Fajta { Verseny, Tura };
private:
    int kilo;
    Fajta tip;
public:
    Kenu(char const *vk, int kg, Fajta f);
    void adatok() {
        cout << "Kenu, " << vonalkod << ", " << kilo << " kilos, ";
        switch(tip) {
            case Verseny: cout << "verseny"; break;
            case Tura:    cout << "tura"; break;
        }
        cout << ".\n";
    }
};

StrandCikk *cuccok[50];
cuccok[0] = new ViziBicikli("129943", 4);
cuccok[1] = new Kenu("996532", 10, Kenu::Verseny);
for (int i = 0; i < 2; ++i)
    if (cuccok[i]->kint)
        cuccok[i]->adatok();

Ez itt pedig a futtatható megoldás. Copypastelhető fordítóba.

// Ez jóval több kód, mint amennyit a feladat kért! A rajz is több.
#include <cstring>
#include <iostream>

class StrandCikk {
protected:
    char vonalkod[32];
    bool kint;
public:
    StrandCikk(char const *vk): kint(false) { strcpy(vonalkod, vk); }
    virtual ~StrandCikk() {}      // virt bármi → virt destr ökölszabály
    virtual void adatok()=0;
    bool kint_van_e() const { return kint; }
    void kivisz() { kint=true; }
    void visszahoz() { kint=false; }
};

class ViziBicikli: public StrandCikk {
    int szemelyek;
public:
    ViziBicikli(char const *vk, int szem)
        : StrandCikk(vk), szemelyek(szem) {}
    void adatok() {
        std::cout<< "ViziBicikli, " << vonalkod << ", " << szemelyek << " szemelyes.\n";
    }
};

class Kenu: public StrandCikk {
public:
    enum Fajta { Verseny, Tura };
private:
    int kilo;
    Fajta tip;
public:
    Kenu(char const *vk, int kg, Fajta f)
        : StrandCikk(vk), kilo(kg), tip(f) {}
    void adatok() {
        std::cout << "Kenu, " << vonalkod << ", " << kilo << " kilos, ";
        switch(tip) {
            case Verseny: std::cout << "verseny"; break;
            case Tura:    std::cout << "tura"; break;
        }
        std::cout<<".\n";
    }
};

int main()
{
    StrandCikk *cuccok[50];
    for (int i=0; i<50; ++i)        /* üres raktár */
        cuccok[i]=0;
    cuccok[0] = new ViziBicikli("129943", 4);
    cuccok[0]->kivisz();            /* kikölcsönzik */
    cuccok[1] = new Kenu("996532", 10, Kenu::Verseny);
    for (int i=0; i<2; ++i)         /* csak 2-t raktunk bele */
        if (cuccok[i]->kint_van_e())
            cuccok[i]->adatok();

    for (int i=0; i<50; ++i)
        delete cuccok[i];
}

34. Húsvéti nyúl

(Régebbi NZH feladat copy-paste.) A Húsvéti Nyúl háromfajta ajándékot (Present) oszt: tojást (Egg), csokit (Chocolate) és cukrot (Candy). Mindegyik különböző értékű, az alapegység egy statikus változója az alaposztálynak (basePrice=50), a tojás darabja ennek konstansszorosa (eggFactor=0.4), míg a csoki esetében ez a tényező chocolateFactor=2.6, a cukornál candyFactor=1.5. A Nyúlnak írt programunk egy tömbben tartja nyilván a kiosztott ajándékokat. Az egyes ajándékok darabszámot is tartalmaznak. A célunk kiszámolni és kiírni az egyes ajándékok értékét (getPrice()).

  1. Tervezzük meg és vázoljuk fel az osztályok öröklési hierarchiáját! Használjuk fel a fenti osztály-, függvény- és változóneveket! Az osztályok téglalapjaiban tüntessük fel az összeget lekérdező függvény (getPrice) deklarációját és láthatóságát! Ügyeljünk az elegáns OO megoldásokra!
  2. Implementáljuk az osztályokat és konstansokat figyelve arra, hogy esetlegesen egyes konstansokat is tagként vagy statikus tagként érdemes implementálni. Ne legyen egy függvénytörzsben sem felesleges, nem használt kód! Egy új ajándéktípus esetleges felvételéhez ne kelljen a már meglévő osztályokat módosítani!
  3. Írjunk egy egyszerű programrészletet nem dinamikus tömbbel, ami megmutatja három különböző típusú ajándék felvételét, valamint kiírja a nevüket és árukat.

35. Alakzatok

Az SVG (scalable vector graphics) egy szöveges fájlformátum, amellyel vektorgrafikus rajzok adhatók meg. Elterjedten használják weboldalakon. Egy SVG fájl a következőképpen néz ki:

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <circle cx="100" cy="50" r="20" stroke="black" fill="yellow" />
  <line x1="100" y1="70" x2="100" y2="90" stroke="black" />
  <circle cx="90" cy="45" r="3" stroke="black" fill="blue" />
  <circle cx="110" cy="45" r="3" stroke="black" fill="blue" />
  <rect x="80" y="80" width="40" height="70" stroke="black" fill="blue" />
  <line x1="90" y1="150" x2="60" y2="190" stroke="black" />
  <line x1="110" y1="150" x2="140" y2="190" stroke="black" />
  <line x1="80" y1="90" x2="40" y2="70" stroke="black" />
  <line x1="120" y1="90" x2="160" y2="70" stroke="black" />
</svg> 

Vagyis a kezdő svg és lezáró /svg között különféle formákat lehet megadni:

  • circle = kör, amelynek cx,cy a középpontja, r a sugara (valós számok), fill pedig a kitöltési színe (sztring).
  • rect = téglalap, amelynek x,y a bal felső sarka, és width,height a méretei, fill a kitöltési színe.
  • line = szakasz, x1,y1 ponttól x2,y2 pontig.
  • és még egy csomó egyéb dolgot.

Láthatóan minden alakzat rendelkezik egy, a körvonal színét meghatározó tulajdonsággal (ez sztring), továbbá egy koordinátával (középpont, bal felső sarok, kezdőpont.) Az előbbit a fájlban mindig stroke-nak (ecsetvonás) hívják, az utóbbi viszont más-más neveken szerepel (cx,cy; x,y; x1,y1).

A feladat: írj heterogén kollekciót használó programot, amely eltárolja egy ilyen kép adatait! Valósítsd meg a kör, téglalap és szakasz osztályokat! A heterogén kollekció legyen kiírható egy SVG fájlba a következő kódrészlettel:

#include <fstream>                     /* std::ofstream-hez kell */

std::ofstream os("misi.svg"); /* fájl nyitása írásra */

os << "\n";
for (i = 0; i < 9; ++i)
  alakzat[i]->kiir(os);                /* os típusa is std::ostream */
os << "\n";

A keletkező SVG fájlt bármelyik modern böngészővel (Firefox, Chrome) meg tudod nyitni. (Ha szintaktikai hibás, és nem jeleníti meg, akkor pedig érdemes Notepad-del megvizsgálni.) Figyelj a kiíráskor az idézőjelekre; az idézőjel karaktert a sztring belsejében escapelni kell: \", ahogy a fenti kódban is látható.

Egy másik rajz:

<svg width="200" height="200" xmlns="http://www.w3.org/2000/svg" version="1.1">
    <ellipse cx="100" cy="120" rx="30" ry="40" stroke="black" fill="white" />
    <ellipse cx="70" cy="90" rx="10" ry="10" stroke="black" fill="white" />
    <ellipse cx="130" cy="90" rx="10" ry="10" stroke="black" fill="white" />
    <ellipse cx="130" cy="150" rx="10" ry="12" stroke="black" fill="white" />
    <ellipse cx="70" cy="150" rx="10" ry="12" stroke="black" fill="white" />

    <ellipse cx="88" cy="30" rx="8" ry="25" stroke="black" fill="white" />
    <ellipse cx="112" cy="30" rx="8" ry="25" stroke="black" fill="white" />
    <ellipse cx="88" cy="30" rx="2" ry="14" fill="pink" stroke="red" />
    <ellipse cx="112" cy="30" rx="2" ry="14" fill="pink" stroke="red" />
    <circle cx="100" cy="60" r="23" stroke="black" fill="white" />

    <circle cx="92" cy="50" r="3" fill="red" stroke="red" />
    <circle cx="108" cy="50" r="3" fill="red" stroke="red" />
    <ellipse cx="100" cy="60" rx="5" ry="3" fill="pink" stroke="red" />

    <line x1="105" y1="58" x2="130" y2="50" stroke="gray" />
    <line x1="105" y1="60" x2="130" y2="60" stroke="gray" />
    <line x1="105" y1="62" x2="130" y2="70" stroke="gray" />
    <line x1="95" y1="58" x2="70" y2="50" stroke="gray" />
    <line x1="95" y1="60" x2="70" y2="60" stroke="gray" />
    <line x1="95" y1="62" x2="70" y2="70" stroke="gray" />
</svg>

36. Titkosítás

A programunk titkosító objektumokkal dolgozik. Egy ilyen objektum arra képes, hogy egy neki adott karaktert titkosít. Például így lehet használni:

void kiir(char const *szoveg, Titkosito& t) {
    for (int i = 0; szoveg[i] != '\0'; ++i)
        std::cout << t.titkosit(szoveg[i]);
}
  1. Definiáld a Titkosito absztrakt alaposztályt a fenti használat alapján!

  2. Származtass belőle egy ABCaesar osztályt, amelyik a Caesar-féle titkosítást használja a=b kulccsal, vagyis a→b, b→c, … z→a a titkosítás menete. Írj egy egy-két soros programot, amelyik a fenti függvénnyel, és egy ABCaesar objektummal bekódol egy általad megadott sztringet.

  3. Származtass egy Caesar osztályt is, amelynek a kulcsa egy egész számmal adható meg. Pl. ha a kulcs 3, akkor a→d, mert abcd, b→e, mert bcde. (Hol tárolódik a titkosítás kulcsa? Az objektumban!) Titkosíts egy szöveget ezzel is!

  4. Szorgalmi feladat: írj egy SzoCaesar osztályt, amelynek egy szót lehet megadni, ami a titkosítás kulcsa. Ennek a szónak a betűi mutassák azt, hogy az egymás utáni karaktereket milyen kulccsal kell titkosítani. Pl. ha a kulcs "bcd", akkor az első karakternél a→b a kulcs, a másodiknál a→c, a harmadiknál a→d, a negyediknél újra a→b stb. (A működését könnyű tesztelni az "aaaaaaaa" sztringen.)

Megoldás
#include <iostream>

class Titkosito {       /* az absztrakt alaposztály */
public:
    virtual char titkosit(char) const = 0;
};

void kiir(char const *szoveg, Titkosito const& t) {
  for (int i = 0; szoveg[i] != 0; ++i)
    std::cout << t.titkosit(szoveg[i]);
}

class ABCaesar: public Titkosito {
public:
    virtual char titkosit(char c) const {
        if (c < 'a' || c > 'z') /* többit nem */
            return c;       
        /* csak a kisbetűket */
        if (c == 'z')
            return 'a';
        return c+1;
    }
};

class Caesar: public Titkosito {
    int kulcs;
public:
    Caesar(int kulcs_): kulcs(kulcs_) {}
    virtual char titkosit(char c) const {
        if (c < 'a' || c > 'z') /* többit nem */
            return c;       
        /* csak a kisbetűket */
        return (c- 'a' + kulcs) % ('z' - 'a' + 1) + 'a';
    }
};

int main() {
    ABCaesar abc;
    kiir("hello z!\n", abc);
    /* vagy: kiir("hello z!", ABCaesar()); */

    Caesar harommal_tol(3);
    kiir("hello z!\n", harommal_tol);
    /* vagy: kiir("hello z!", Caesar(3)); */
}

37. Iterátor vektorhoz I.

Dinamikus tömb osztályt kell írnod, iterátorral. Az adattárolás:

template <typename T>
class MyArray {
    size_t size;
    T*     data;
};

Írd meg ennek néhány alap függvényét, és készíts hozzá iterátort!

38. Iterátor vektorhoz II.

Ha az előző feladatban pointert használtál, most oldd meg úgy, hogy az interátorod indexet tárol. Ha indexet tároltál, oldd meg pointerrel!

Készítsd el az iterator + int, int + iterator és iterator - iterator operátorok függvényeit is!

39. Iterátor listához I.

Adott az alábbi láncolt lista kezdemény:

template <typename T>
class MyList {
    struct ListElement {
        T data;
        ListElement* next;
    };
    ListElement* head;
};

A lista egyik végén sem strázsás. Írj meg néhány függvényt, hogy listát tudj építeni, majd írj iterátort a lista osztályhoz! Mutass kódrészleteket, amelyekkel teszteled az iterátort!

40. Iterátor listához II.

Alakítsd át az előző osztályt duplán láncolt, mindkét végén strázsás listává! Módosítsd az iterátort (lehet -- operátora!), és az ahhoz tartozó függvényeket is!

Írj tagfüggvényt a listának, amely paraméterként egy iterátort kap, és kitörli a listából azt az elemet, amelyre az iterátor mutatott!

41. Iterátor listához III.

Gyakran szoktak csinálni tárolókhoz ún. reverse iterator-t is. Ez mindent fordítva csinál: a tároló .rbegin() függvénye nem az elejét, hanem a végét adja, az .rend() viszont pont az elejét. Az iterátor ++ operátora pedig a végétől az eleje felé lépked.

Csinálj ilyen iterátort az előző láncolt listához! Ha mindent jól csinálsz, az alábbi kódrészlet kiírja a numbers listát hátrafelé:

MyList<int>::ReverseIterator it;
for (it = numbers.rbegin(); it != numbers.rend(); ++it)
    std::cout << *it;

42. Iterátor fához

Adott az alábbi kód, amelyet egy bináris fa építésére szánunk:

template <typename T>
class MyTree {
    struct Node {
        T data;
        Node *left, *right, *parent;
    };
    Node *root;
};

Írj beszúró függvényt a fához! (Milyen operátorral kell ehhez rendelkezzen a T típus?) A parent pointer minden csomópontban a szülőre kell mutasson, vagy NULL értékű kell legyen (a legfölső csomópontban, azaz a gyökérben).

Írj iterátort a fához! A szülő csomópontok ismeretében ez nem olyan nehéz, csak ki kell találni azt, hogy egy adott csomópont ismeretében melyik a következő csomópont, amelyet az inorder bejárás adná.

(Ötlet: Egy adott elemet megelőző és az azt követő elem könnyen megtalálható iteratív algoritmussal is. Az őt megelőző elem a nála kisebbek közül a legnagyobb, tehát a bal oldali részfájának jobb szélső eleme. Az őt követő elem pedig szimmetrikusan a jobb oldali részfájának bal szélső eleme.)

43. Range (számtartomány) iterátor

Az alábbi kódrészlet kiírja a számokat 1-től 10-ig:

std::vector<int> v = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
for (std::vector<int>::iterator it = v.begin(); it != v.end(); ++it)
    std::cout << *it;

Egy olyan osztályt kell csinálnod, amellyel tetszőleges tartományban írathatók ki a számok:

Range r(1, 10);
for (Range::Iterator it = r.begin(); it != r.end(); ++it)
    std::cout << *it;

Ahhoz, hogy kiírjuk őket, igazából eltárolni nem kell a számokat. Így oldd meg a feladatot!

44. Speciális iterátor a plágiumkeresőhöz

(Az órai plágiumkereső programmal kapcsolatban.) A szövegek közös szópárjainak keresésekor igazából nem vagyunk kíváncsiak arra, hogy pontosan mik azok a szópárok, amik közösek, csak a számukra. Ezért a metszet halmazt tulajdonképpen felesleges előállítani. Jobb lenne írni egy olyan speciális iterátort, amely úgy viselkedik, mintha rendes iterátor lenne, de nem ír sehova, hanem csak számolja, hányszor hívták az operator++-át. (Ehhez érdemes jól megnézni a set_intersection() dokumentációját, hogy pontosan mit csinál a neki adott iterátorral!)

45. Plágiumkereső – szópárok?

(Az órai plágiumkereső programmal kapcsolatban.) És még egyszer: igazából nincsen szükség a konkrét szópárokra, elég a számukat tudni. Ezért szópárok helyett bármi más típust is használhatnánk. Például ha minden különböző szópárhoz hozzárendelünk egy egész számot, akkor a halmazokba ezeket az egész számokat lehet tenni – a metszet halmazok mérete pedig ugyanakkora lesz. Ezzel a program gyorsul (mert a metszéskor csak egész számokat kell összehasonlítania, O(1)), és a memóriaigénye is csökken (sztringek helyett intek vannak csak). A sztring→egész hozzárendelést beolvasás közben kell elvégezni, és erre egy std::map<std::string,int> kiválóan alkalmas.

46. Tömbfeldolgozó sablon

Írj egy függvénysablont, amelyik átvesz paraméterként egy C tömböt. Az elemei tetszőleges típusúak lehetnek. A függvény csináljon valamit sorban az összes elemmel. Ez a „valami” is legyen sablonparaméter.

Hozz létre egy öt elemből álló int tömböt, és a megírt sablon használatával írd ki minden elemét az std::cout-ra. (Ha a fenti függvénysablonban szerepel bármi a kiírással kapcsolatban, akkor természetesen az hibás.)

Megoldás
#include <iostream>

template <typename T, typename FUNC>
void mindegyikkel_csinal(T *tomb, int meret, FUNC csinal) {
    for (int i = 0; i < meret; ++i)
        csinal(tomb[i]);       /* kell legyen fv.hívó operátora */
}

/* ez a fv. még nem is létezett, amikor a
 * mindegyikkel_csinal sablont "írták" */
void kiir(int i) {
    std::cout << i << '\n';
}

int main() {
    int t[5] = {1, 2, 3, 4, 5};

    mindegyikkel_csinal(t, 5, kiir);
}

47. Függvénysablonok

Definiálj egy függvény objektumot sablonként, amelynek overloadolt függvényhívó operátora kap egy objektumot, és kiírja azt a képernyőre annak << operátorával. Utána definiálj egy függvényt sablonként, amely az első paraméterében egy std::vector-t vesz át, második paramétereként egy ilyen függvény objektumot. Mutass rá példát, hogyan kell ezek segítségével kiírni a vektor összes elemét!

48. Javítandó programrészek

Sta::Sta (const Sta& a) {... *this = a;}
Sta& Sta::operator=(Sta a) {...}
class A {
  public:
    A() { f(); }
    virtual int f() = 0;
};

class B: public A {
  public:
    int a;
    int f() { a++; }
};
class A {
  private:
    int a;
  public:
    void f() const { a++; }
};
void operator<<(ostream& os, B& e) { ... }

B b12;
cout << b12 << endl;
class Madar {
    double suly;
    virtual void jar() { cout<<"lepked"; }
};

class Vereb : public Madar {
    double suly;
    void jar() { cout<<"ugral"; }
};
double& fuggveny() {
    double d;
    ...;
    return d;
}
class Asztal {
    int labak_szama;
    bool fiokos;
};

class Butor : public Asztal {
    enum Anyag { fem, fa, muanyag } anyag;
    int szinkod;
};
class Krumpli {
    double kilo;
    int egysegar;
  public:
    static double ar();
};

double Krumpli::ar()
{
    return kilo*egysegar;
}
void fv(int &i);
int a, b;
fv(a+b);
class Alakzat {
    virtual void rajzol()=0;
};

class Teglalap: public Alakzat {
    void rajzol() { ... }
};

void fv(Alakzat k);

Teglalap t;
fv(t);