Labor, 3. hét: osztályok, tagfüggvények, operátorok

Dobra Gábor · 2019.02.27.

Heti kiegészítő feladatok az OOP alapok gyakorlásához

Ezen a héten az alapvető OOP újdonságokat gyakoroljuk.

1. Felkészülés a laborra

Olvasd el a jegyzet 2. fejezetét, különös tekintettel az alábbiakra:

  • OOP elvek áttekintése
  • konstruktor fogalma és szintaktikája
  • tagfüggvény fogalma és szintaktikája
  • operator overloading szintaktikája

2. Áttekintés

Kiindulási alapnak vegyünk egy C struktúrát. A mai labor során ezt kell apránként C++ osztállyá alakítani.

struct Komplex {
    double re;
    double im;
};

Töltsd le a kiinduló Code::Blocks projektet innen, és nyisd meg a projektfájlt. Ezeket a fájlokat találod benne:

  • komplex.h: a Komplex típus deklarációit tartalmazza
  • komplex.cpp: egyelőre üres, itt kell majd megvalósítanod a függvényeket
  • teszt.cpp: általunk előkészített tesztek, ami automatikusan ellenőrzi az elkészült feladatokat

A projektet lefordítva és lefuttatva azt kell látnod, hogy minden teszteset FAILED.

Fontos, hogy a Komplex adattagjait ne változtasd vagy cseréld meg, mert az automatikus tesztek – sajnos – függnek ettől.

3. Konstruktor

Írj a Komplex struktúrának konstruktort! Így kell működnie:

Komplex k1;             // 0+0i
Komplex k2 = 3.0;       // 3+0i
Komplex k3(3.0, 4.0);   // 3+4i

Hány konstruktort kell ehhez ténylegesen megírni?

Válasz

Elég egyet, default paraméterekkel.

Megoldás
struct Komplex {

    // ...

    Komplex(double _re = 0, double _im = 0) {
        re = _re;
        im = _im;
    }
};

4. Adatrejtés

A Komplex belsejét még mindenki ismeri, hozzáférnek a re, im adattagokhoz. Ezért egyelőre nem lehet büntetlenül (a rá épülő kód eltörése nélkül) kicserélni az adattagokat például r, fi adattagokra.

Rejtsd el az adattagjait, és biztosíts elérést (get_re, get_im tagfüggvényekkel) a szám valós és képzetes részéhez!

Megoldás
class Komplex {
    double re;
    double im;

public:
    Komplex(double _re = 0, double _im = 0) {
        re = _re;
        im = _im;
    }

    double get_re() const {
        return re;
    }

    double get_im() const {
        return im;
    }
};

5. Inserter (kiíró) operátor

Írj olyan operátort a Komplex osztályhoz, hogy kiírhatók legyenek std::cout-ra!

std::cout << Komplex(3, 4)  << std::endl; // 3+4i
std::cout << Komplex(3, -4) << std::endl; // 3-4i
Tipp

Ha a képzetes rész pozitív, akkor a képzetes rész előtti + jelet nekünk kell kiírni.

Megoldás

A header-ben elő kell deklarálni, a definíció pedig a cpp fájlba kerül:

std::ostream& operator<<(std::ostream& os, Komplex const& k) {
    os << k.get_re();
    if (k.get_im() >= 0)
        os << "+";
    os << k.get_im() << "i";
    return os;
}

6. Összeadás és szorzás

Írj olyan operátorokat, amivel két komplex számot össze lehet adni és szorozni! Pl. ezt is le lehessen írni:

Komplex c1 = 2;
Komplex c2(3, 4);
std::cout << c1 + c2 << std::endl; // 5+4i
std::cout << c1 * c2 << std::endl; // 6+8i

Legyenek ezek az operátorok globális függvények, ne tagfüggvények! A paramétereik legyenek konstans referenciák!

Matek

Összeadás: (a1 + b1i) + (a2 + b2i) = (a1 + a2) + (b1 + b2)i

Szorzás: (a1 + b1i) * (a2 + b2i) = (a1a2 - b1b2) + (a1b2 + a2b1)i

Vigyázz, a és b a kódban re-ként és im-ként jelenik meg!

Megoldás
Komplex operator+(Komplex const& lhs, Komplex const &rhs) {
    return Komplex(lhs.get_re() + rhs.get_re(), lhs.get_im() + rhs.get_im());
}

Komplex operator*(Komplex const& lhs, Komplex const &rhs) {
    return Komplex(
            get_re() * rhs.get_re() - get_im() * rhs.get_im(),
            get_re() * rhs.get_im() + get_im() * rhs.get_re());
}

Ha van + és *, akkor elvárjuk, hogy legyen += és *= is. Írd meg ezeket is globális függvényként, visszavezetve a + és a * operátorokra! Mivel kell visszatérnie ezeknek az operátoroknak?

Válasz

A bal oldali paraméterrel, referencia szerint.

Miért jobb ebben az esetben, hogy globálisak ezek az operátorok?

Válasz
  • Nem szükséges ismerni az osztály belsejét (adattagjait), így az operátorok az osztály átírása esetén is maradhatnak változatlanok.
  • A double → Komplex implicit konverzió miatt automatikusan működik a double+Komplex és a Komplex+double összeadás, és a szorzás is.
Megoldás
Komplex& operator+=(Komplex& lhs, Komplex const &rhs) {
    lhs = lhs + rhs;
    return lhs;
}

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

7. Osztás valós számmal

Írj olyan operátort, hogy egy Komplex-et el lehessen osztani egy double-lel!

Komplex c(4, 6);
std::cout << c / 2.0 << std::endl; // 2+3i

Írd meg hozzá a /= operátort is, a fenti += és *= operátorokhoz hasonlóan!

Megoldás
Komplex operator/(Komplex const &k, double d) {
    return Komplex(k.get_re() / d, k.get_im() / d);
}

Komplex& operator/=(Komplex& lhs, double d) {
    lhs = lhs / d;
    return lhs;
}

8. Gyökvonás negatív számból

Írd meg a Komplex_sqrt globális függvényt, ami bármely valós számból négyzetgyököt tud vonni! Az eredmény mindig Komplex szám.

std::cout << Komplex_sqrt(4.0)  << std::endl; // 2+0i
std::cout << Komplex_sqrt(-4.0) << std::endl; // 0+2i
Megoldás
Komplex Komplex_sqrt(double d) {
    if (d >= 0)
        return Komplex(sqrt(d));
    else
        return Komplex(0, sqrt(-d));
}

9. Váltás trigonometriai alakba

Előfordulhat, hogy egy komplex számot trigonometriai alakban érdemes használni (szorzásnál, osztásnál). Oldd meg, hogy a Komplex-től le lehessen kérdezni a hosszát és az irányszögét (get_r és get_fi tagfüggvények)! Ezeket a cpp fájlban definiáld, az osztályon belül csak a deklarációjuk szerepeljen!

Matek

r = sqrt(a2 + b2)

fi = atan2(b, a)

Megoldás
double Komplex::get_r() const {
    return sqrt(re * re + im * im);
}

double Komplex::get_fi() const {
    return atan2(im, re); // Imre, haha
}

10. Váltás trigonometriai alakból

Gyakran megesik, hogy egy komplex számot trigonometriai alakban kapunk meg, és abból szeretnénk Komplex objektumot előállítani. Írd meg a Komplex_from_r_fi globális függvényt, ami ezt az átváltást elvégzi!

std::cout << Komplex_from_r_fi(sqrt(2), M_PI/4)  << std::endl; // 1+1i
Matek

a = r * cos(fi)

b = r * sin(fi)

Megoldás
Komplex Komplex_from_r_fi(double r, double fi) {
    return Komplex(r * cos(fi), r * sin(fi));
}

11. Haladó (IMSC) feladatok

  • Írd át az operator*-ot, hogy trigonometriai alakban számoljon!
  • Legyen hatványozás operátor (operator^), amivel valós kitevőre haványozhatsz egy egész számot! Használd a trigonometriai alakot!
  • A teszt.cpp végére írj teszteseteket ezen feladat új operátoraihoz!

12. További feladatok

  • Ha van Komplex számok közötti + és * operátor, akkor legyen -, -=, / és /= is!
  • Ha van kétoperandusú + és -, akkor legyen egyoperandusú + és - is! (semmit-nem-csinál és ellentett operátorok)
  • Legyen == és != operátor két Komplex szám egyenlőségének a vizsgálatához! A valós számok számítási pontatlanságai miatt egymáshoz egy bizonyos tűréshatárnál (legyen ez 10-10) közelebb lévő számokat tekintsünk egyenlőnek: |x-y|<10-10
  • A teszt.cpp végére írj teszteseteket ezen feladat új operátoraihoz!
Megoldás