El is érkeztünk a második részhez, remélem mindenki türelmetlenül várta már, hogy mi következhet még. Mint az az előző részből látható volt, semmi olyat nem valósítottunk meg, amit C-ben ne tudtunk volna megcsinálni, csak sokkal átláthatóbb lett az egész, szerves egységet alkot, nem arról van csak szó, hogy egy struct köré építünk pár kezelő függvényt. Most lehet azt gondolni, hogy az OOP megszállotja vagyok. Előfordulhat, de miért is tagadnék meg valami olyasmit, ami megkönnyíti az életemet, kevesebb és sokkal átláthatóbb kódot eredményez?

A mai terv első körben a Complex objektum szorzásának (operator*) és az összehasonlító operátornak (operator==) megvalósítása és a komplex számunk megjelenítése valamiféleképpen a képernyőn. Az első két dologgal nem lesz túl sok gondunk az előző rész alapján. Csak két új függvénnyel kell bővítenünk az objektumot:

Complex operator*(const Complex& num)
{
    return Complex(re*num.re - im*num.im, re*num.im + im*num.re);
}

bool operator==(const Complex& num) const
{
    return (re == num.re && im == num.im);
}

Tuladjonképpen semmi új, leszámítva a második függvény első sorának a végén azt a kis const szavacskát, ami azt hivatott jelezni, hogy a függvény az objektumot nem módosítja.
Most, hogy ezt is tisztáztuk, evezzünk izgalmasabb vizekre. Fontos megemlíteni, hogy nem csak az osztályon belül definiálhatunk operátorokat, hanem az osztályon kívül is. Ez már csak azért is egy hasznos dolog, mert elég gyakran adódhat olyan eset, hogy nem tudunk belenyúlni a kódba, amihez viszont egy operátort hozzá akarunk csapni (értelmes és mindent megmagyarázó példát lásd egy picit lejjebb).

Copy konstruktor

A copy konstruktor szépsége esetünkben abban testesül meg, hogy használata teljesen felesleges, viszont bevezetését már ezen a szinten fontosnak tartom, mivel a későbbiekben létfontosságúvá válik, szóval jobb, ha már most hozzászokunk. Ez a konstruktor se más mint a többi, a lényegbeli különbség csak annyi, hogy paraméterként egy, az objektum típusával megegyező objektumot kap, amivel kezdenie kell valamit.

Complex(const Complex& other) : re(other.re), im(other.im) {}

Mivel esetünkben a megírt konstruktor viselkedése nem különbözik az alapértelmezettől (mert hát minden objektumnak ugyanúgy van copy konstruktora, mint konstruktora is, akár megadjuk a kódban akár nem), ezért célszerűbb elhagyni, mivel - A C++ programozási nyelv c. könyv állítása szerint - rövidebb gépikódot eredményez. Azért megjegyezném, hogy nem biztos, hogy tökéletesen átgondolt lépés volt tőlem, hogy int-nek választottam az osztály re és im változóit, lehet célszerűbb lett volna float vagy esetleg double, de ezt inkább az elvégzendő feladat határozza meg.

Kimenetgenerálás

Esetenként szükségünk lehet olyan dolgokra, hogy a véres verejtékkel kiszámolt komplex számunkat a júzer képernyőjén teljes pompájában jelenítsük meg. Az első gondolat természetsen valami olyasmi lehet, hogy csinálunk például egy to_string() vagy print() tagfüggvényt. Nem egy rossz gondolat, de ha már adva van egy operátor erre a célra (operator<<), akkor miért is ne használnánk azt, ahelyett hogy önkényesen definiálunk valami függvényt a probléma megoldására?

friend std::ostream& operator<<(std::ostream& out, Complex& num)
{
    out << num.re << " + " << num.im << "i";
    return out;
}

Bár fentebb azt említettem, hogy az osztályon kívül is definiálhatunk operátorokat, ez esetben mégsem tanácsos ezt tenni (a friend kulcsszó elhagyásával minden további nélkül helyes lenne szintaktikailag), mivel a Complex osztály re és im változói a private blokkban vannak, vagyis nem tudnánk elérni őket abban az esetben. Ezzel tulajdonképpen már meg is magyaráztam a friend kulcsszó lényegét: nélküle nem tudnánk két paraméteres operator<< függvényt megadni az osztályon belül. Ha viszont az osztályon kívül adnánk meg, akkor pedig nem tudnánk elérni a private blokkban lévő változókat.
Egyébként az ostream objektum felelős a kimenetért. A sikeres fordítás előfeltétele egy #include <iostream> valahol a .cpp fájl elején. A megkapott ostream objektum visszaadása a beleírás után hasonló célt szolgál, mint az előző bejegyzésben az egyenlőség operátornál (tehát, hogy lehessen ilyesmiket is írni: std::cout << "Hello, World!" << Complex(2,1) << std::endl;). Ezzel a végére is értünk a második résznek. A következő rész tartalmából: megpróbálunk valami új adatstruktúrát összehozni, kivesézni a jó- és rossztulajdonságait, összehasonlítani a szimpla tömbbel. És ha minden igaz még egy sablont is fogunk ráhúzni!