Öröklés
Dobra Gábor · 2019.03.14.
A mai óra célja az öröklés használatának és nyelvi elemeinek megismerése.
Felkészülés a gyakorlatra
- Jegyzet 7. fejezete, csak az 1-5. és 7. pontok szükségesek.
- Ajánlott az öröklés félreértéseiről szóló extra írás elolvasása.
A gyakorlatvezető segítségével tekintsétek át a fontosabb örökléssel kapcsolatos fogalmakat!
Összefoglaló
- öröklés
- ősosztály
- leszármazott
- virtuális függvény
- tisztán virtuális függvény
- absztrakt osztály
:public
. (A:private
és a:protected
nem öröklés!)protected:
- polimorfizmus
- viselkedés
- bővítés, felüldefiniálás
- kompatibilitás
- szeletelődés
Tisztázzátok a polimorfizmus fogalmát!
Megoldás
A polimorfizmus jelentése: többalakúság, az öröklés nyújtotta polimorfizmusnál általánosabb fogalom. Az öröklésnél az ősosztály viselkedése az, ami polimorf, méghozzá futásidőben derül ki, hogy hogyan (bárki írhat leszármazottakat, tehát nyitott végű). Szintén polimorf még:
- C-ben az osztás működése:
1/2 == 0
, de1.0/2 == 0.5
, mert azoperator/
más típusú operandusokra máshogy viselkedik. Fordítási időben dől el, hogy mikor melyik. - C++-ban általánosabban: az operator overload, szintén fordítási idejű.
- Még általánosabban: függvényoverload, szintén fordítási idejű.
- template, ami akár a fenti fordítási idejű cuccok kombinációjából is eredhet, lásd a példát.
- [union, tagged union, variant, futásidejű, zárt végű.]
Fordítási idejű polimorfizmusra két példa egyben (az operator+
és a duplaz
függvény is polimorf):
template<typename T>
T duplaz(T const& x)
{
return x + x;
}
std::cout << duplaz(5) << std::endl;
std::cout << duplaz(std::string("hello")) << std::endl;
Ennek ellenére, ha valaki csak általánosságban beszél polimorfizmusról, valószínűleg az öröklésre gondol, szakcikkekben is szeretik összekeverni.
Írjunk tic-tac-toe játékot! Legyen paraméterezhető a játékosok stratégiája!
Milyen osztályok fognak kelleni a modellezéshez?
Megoldás
- mező: legyen most
char
, X, O, szóköz karakterekkel, szorgalmi átírni enumra. - tábla: legyen most egy
typedef char tabla[3][3];
ami nem szerencsés, szorgalmi osztályt írni a helyére. - játék: egyelőre nincs rá szükség, elég lesz egy
jatszma_lebonyolit
függvény - játékos: az ősosztály. Viselkedése: "eléd rakok egy táblát, megmondom, melyikkel játszol te, és mondd meg, mi lesz a következő lépésed!"
- játékos leszármazottai
Írjuk meg a Jatekos
osztályt! Milyen interfésze van egy játékosnak?
Megoldás
Fontos, hogy a játékos nem módosíthatja a táblát, hogy ne tudjon "csalni", ezért const&
-et kap rá. A visszatérési értékében jelzi, hogy hova akar lépni, és majd a lebonyolító lép helyette! Az egyes leszármazottaknak kell megadnia a stratégiát, ezért virtuális a függvény. Bárki meghívhatja rajta, ezért public. Itt nem tudjuk implementálni, ezért tisztán virtuális, aminek a nyelvi eleme az = 0
.
class Jatekos
{
public:
virtual Pont lep(tabla const& t, char sajat) = 0;
};
Ehhez kelleni fog egy Pont
osztály is. Publikus adattagok jók lesznek, és két konstruktor, hogy egyszerűbb legyen használni:
struct Pont
{
int x;
int y;
Pont() : x(0), y(0) {}
Pont(int x, int y) : x(x), y(y) {}
};
Írj két játékos osztályt:
- egy nagyon egyszerű gép, ami mindig random helyre lép (már ahova szabad)
- egy játékos, ami a felhasználótól kérdezi meg, hogy hova szeretne lépni (de előtte kirajzolja neki a táblát),
Megoldás
class RandomJatekos : public Jatekos
{
public:
Pont lep(tabla const& t, char sajat);
};
class HumanJatekos : public Jatekos
{
public:
Pont lep(tabla const& t, char sajat);
};
bool lepes_valid(tabla const& t, Pont p)
{
return p.x >= 0 && p.y >= 0 && p.x < 3 && p.y < 3 && t[p.x][p.y] == ' ');
}
Pont RandomJatekos::lep(tabla const &t, char sajat)
{
Pont lepes(rand() % 3, rand() % 3);
while(!lepes_valid(t, lepes))
lepes = Pont(rand() % 3, rand() % 3);
return lepes;
}
Pont HumanJatekos::lep(tabla const &t, char sajat)
{
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
std::cout << t[i][j];
Pont p;
do {
std::cin >> p.x >> p.y;
} while(!lepes_valid(t, p));
return p;
}
Írjunk olyan játékost, ami egy fájlból olvasva egyetlen játékos lépéseit visszajátssza! Ha a játszma lépéseit a két játékosra két külön fájlba tesszük, két ilyet egymás ellen játszatva látni fogjuk a meccset, mint ahogy a sakkban szokás.
Megoldás
A fájl olvasását végezzük std::ifstream
-mel, aminek egy példánya ugyanúgy használható, mint az std::cin
, mert leszármazottja az std::istream
-nek.
Vegyük észre, hogy a lépés függvény tartalma pont ugyanaz lesz, mint a humán játékosnál! Csak az std::cin
-t kell lecserélni a fájlra. Jó lenne egy közös ősosztályba tenni, és csak azt paraméterezhetővé tenni, hogy honnan olvassuk a bemenetet. Csináljunk hát egy StreamJatekos
osztályt, ami a közös viselkedést tartalmazza, azaz a lep
függvény definícióját. A forrásstreamet viszont a leszármazottak adhatják meg, tehát az implementációban cseréljük ki az std::cin
-t forras()
-ra!
class StreamJatekos : public Jatekos
{
public:
Pont lep(tabla const& t, char sajat);
virtual std::istream& forras() = 0;
};
Ezután a HumanJatekos
egyszerűsödik:
class HumanJatekos : public StreamJatekos
{
public:
std::istream& forras()
{
return std::cin;
}
};
A ReplayJatekos
-nak adattagja a fájl, amiből olvas, konstruktorában megnyitja fájlnévből, a többiről pedig maga gondoskodik.
class ReplayJatekos : public StreamJatekos
{
std::ifstream fajl;
public:
ReplayJatekos(std::string const& fajlnev)
: fajl(fajlnev.c_str())
{}
std::istream& forras()
{
return fajl;
}
};
Írjunk függvényt, ami két játékost egymás ellen játszat! Egészítsük ki főprogrammal, amit akár ki is próbálhatunk!
Megoldás
bool jatek_vege(tabla const&)
{
return false;
}
char ki_nyert(tabla const&)
{
return ' ';
}
void jatszmat_lebonyolit(Jatekos& iksz, Jatekos& kor)
{
tabla t;
for (int i = 0; i < 3; ++i)
for (int j = 0; j < 3; ++j)
t[i][j] = ' ';
char ki_jon = 'X';
while (!jatek_vege(t))
{
Jatekos& aktualis = ki_jon == 'X' ? iksz : kor;
Pont p = aktualis.lep(t, ki_jon);
if (!lepes_valid(t, p))
throw std::logic_error("Rossz lépés");
t[p.x][p.y] = ki_jon;
ki_jon = ki_jon == 'X' ? 'O' : 'X';
}
char nyertes = ki_nyert(t);
if (nyertes != ' ')
std::cout << "A nyertes: " << nyertes << std::endl;
else
std::cout << "Döntetlen" << std::endl;
}
int main()
{
srand(time(NULL));
RandomJatekos r_j;
HumanJatekos h;
jatszmat_lebonyolit(r_j, h);
}
A játékosoknak maguknak kell arra figyelniük, hogy ne lépjenek rosszat, mert ha mégis, akkor baj van. A játék menni fog, csak a leállás nem.