7. hét: template, függvények kirajzolása
Czirkos Zoltán, Dobra Gábor · 2019.02.27.
Heti kiegészítő feladatok
Adott a lentebb látható Page osztály. Ez egy dinamikusan megadható méretű rajztáblát ad, amelyen karakterek jeleníthetőek meg:
Page p1(75, 20);
p1.set(50, 10, 'X');
p1.set(52, 11, 'Y');
p1.print();
Kiindulás: page.cpp letöltése
A dinamikus 2D tömb használata fölösleges. Bár a rajztábla kívülről 2D tömbnek látszik, de belül nem kell annak lennie. Hogy sokkal egyszerűbb és gyorsabb legyen a memóriakezelés, érdemes belül 1D sorfolytonos tömböt használni. Pl. egy 3×4-es rajztábla eredetileg így épül fel:
1D sorfolytonos leképezéssel:
Ilyenkor a sorfolytonos tömb 3×4 = 12 elemű. Egy adott cella elérése az
y * szélesség + x
képlettel történik; a sor száma × sor szélesség
taggal ugrunk az adott sorhoz, aztán a másik taggal azon belül egy cellához.
Alakítsd át ilyenre az osztályt, és teszteld a meglévő kóddal! A másoló konstruktort és destruktort még mindig ne írd meg!
Vedd észre, hogy ezzel az átalakítással a Page
osztály belsejében
lényegében újraimplementáltál egy dinamikus tömb osztályt. A Page
osztálynak
jelenleg két feladata van:
- rajztáblát valósít meg (
set
,clear
,print
), - és belül dinamikus memóriával pepecsel.
Ez így nincs rendjén, a dinamikus tömböt egyszer már megírtuk. Refaktoráld ezért a kódot!
Írj egy nagyon-nagyon egyszerű CharTomb
osztályt, ezzel a funkcionalitással:
CharArray arr(100); // méret megadása konstruktorban
arr[12] = 'X'; // indexelő operátor
Egyelőre ennek se írd meg a másoló konstruktorát, értékadó operátorát. Helyette inkább
dolgozd át a Page
osztályodat, töröld ki belőle a memóriakezelést, használd fel
adattagként az új CharTomb
osztályod:
class Page {
private:
int w, h;
CharArray array;
};
Nagyon fontos: vedd észre, hogy ezzel a Page
destruktora és másoló konstruktora
fölöslegessé vált. Ha a CharArray
destruktora és másoló konstruktora jó, akkor
a Page
-hez a fordító által generált függvények automatikusan jók lesznek!
Végül töröld ki a dummy CharArray
osztályodat is. Van ilyen beépítve a C++-ba,
a neve std::vector
. Így kell használni:
#include <vector>
std::vector<int> v1(100); // 100 elem
v1.resize(200); // átméretezés 200-ra
v1[12] = 123;
std::vector<char> v2(10, 'Q'); // 10 elem, csupa Q betű
v2.resize(30, 'Y'); // 30-ra nyúlik, Y-okkal tölti fel
std::cout << v2.size();
Írd át a Page
osztályt úgy, hogy belül egy std::vector<char>
legyen! Innentől kezdve new
, delete
, semmi hasonló nem lesz sehol.
Módosítsd a Page
osztályt, hogy sablonparamétere legyen a tárolt "egység" (ami eddig a karakter volt)!
Page<char> p1(75, 20);
p1.set(50, 10, 'X');
p1.set(52, 11, 'Y');
p1.print();
Egyelőre elég, ha a plot()
függvény fixen Page<char>
-okon dolgozik.
Megoldás
template<typename TIP>
class Page {
public:
Page(int width, int height) : w(width), h(height), page(w*h, '.') {}
void set(int x, int y, TIP c) {
if (x >= 0 && x < w && y >= 0 && y < h)
page[y * w + x] = c;
}
void print() const {
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x)
std::cout << page[y * w + x];
std::cout << std::endl;
}
}
int width() const {
return w;
}
int height() const {
return h;
}
private:
int w, h;
std::vector<TIP> page;
};
A plot() függvény
Adott kód plot()
függvénye egy adott rajztáblára, adott karakterekkel
kirajzolja a megadott függvényt:
void plot(Page &page, char c, double (*f)(double));
Page p1(75, 20);
plot(p1, 's', sin);
Ez szép és jó, de így a függvények nem paraméterezhetőek. Jobb lenne függvényobjektumokat használni, pl. így:
Linear lin(0.5, 1.7); // 0.5x + 1.7
Parabolic para(-0.2, 0.5, 2); // -0.2x² + 0.5x + 2
plot(p1, 'l', lin);
plot(p1, 'p', para);
A probléma egyik megoldása lehet, ha az elképzelt függvény osztályainknak bevezetünk egy
közös ősosztályt, a Function
-t. Számunkra ez a megoldás most kevéssé érdekes.
Teljesen más megközelítése a problémának, ha a plot()
függvény mindent ki tud rajzolni,
ami függvényként viselkedik.
Tippek a megoldáshoz:
- Hogy vehető át paraméterként bármi (ami kirajzolható) a
plot()
függvényben, beleértve a Linear és Parabolic osztályok példányait? Írd úgy át! - Függvénypointerekkel hogyan fog továbbra is működni a kirajzolás?
- Objektumok (pl. a fenti
lin
és apara
) hogyan tudnak függvényként viselkedni?
Megoldás
- Az
f
paraméter típusa legyen sablonparaméter, nevezzük mondjuk FUNC-nak - Úgy, hogy a függvénypointer is meghívható függvényként, és (majdan) a kért objektumok is.
- Overload-olni kell a függvényhívás operátorukat,
operator()
.
template<typename FUNC>
void plot(Page<char> &page, char c, FUNC f) {
for (int x = 0; x < page.width(); ++x) {
double fx = (x - page.width()/2)/4.0;
double fy = f(fx);
int y = (fy * 4.0) * -1 + page.height()/2;
page.set(x, y, c);
}
}
Írd meg a Linear
és Parabolic
osztályokat is!
Módosítsd a plot()
függvényt, hogy ne csak char
-okat tartalmazó, hanem bármilyen Page
-re
tudjon rajzolni! Mi lehet ilyenkor a második paramétere?
Megoldás
Ugyanaz, mint a Page
által tárolt típus.
template<typename TIP, typename FUNC>
void plot(Page<TIP> &page, TIP c, FUNC f) {
for (int x = 0; x < page.width(); ++x) {
double fx = (x - page.width()/2)/4.0;
double fy = f(fx);
int y = (fy * 4.0) * -1 + page.height()/2;
page.set(x, y, c);
}
}
Egy függvény deriváltja maga is egy függvény. Ha ismerjük az eredeti függvényt,
a deriváltját közelíthetjük egy differenciahányadossal, ahol Δx
egy nagyon kicsi szám, pl. 0.001
:
f(x+Δx) - f(x)
f'(x) = ––––––––––––––
Δx
Írj derivált osztályt, amely egy bármilyen függvényt eltárol, és maga is függvényként használható! Milyen paraméterű ennek az osztálynak a konstruktora?
Parabolic para(-0.2, 0.5, 2);
Derived<Parabolic> der(para);
plot(p1, 'p', para);
plot(p1, 'd', der);
Megoldhatod azt is, hogy a differenciálhányadost is lehessen paraméterezni.
Ennek mintájára készíthetsz további osztályokat is, pl. olyat, amelyik két függvény szorzatát adja, egy függvény eltoltját, vagy szinuszát adja.
Megoldás
template<typename FUNC>
class Derived {
public:
explicit Derived(FUNC const& f, double dx = 0.001) : f(f), dx(dx) {}
double operator()(double x) const {
return (f(x+dx) - f(x)) / dx;
}
private:
FUNC f;
double dx;
};
Lett az előbb lineáris (elsőfokú) és parabolikus (másodfokú) függvényünk. Ha lenne
Polynomial
osztályunk, abból bárhanyadfokú függvényt példányosíthatnánk.
A terv ez:
Polynomial pol{2, 3, 4, 5}; // 2x³ + 3x² + 4x + 5
plot(p1, 'p', pol);
Ehhez a következőket kell tudni (C++11):
- Lehetséges olyan konstruktort írni, amelyik tetszőlegesen sok, egyforma típusú
adattal hívható. Ilyen az
std::vector
egyik konstruktora is, aminek segítségével úgy inicializálható, mintha tömb lenne:
std::vector<int> v = { 2, 3, 4, 5 };
- Az ilyen konstruktor nem kerek
()
, hanem kapcsos{}
zárójellel hívható. Ezért van a fenti kódocskában is{}
apol
objektum konstruktorparaméterei körül. - Ezt a nyelvi elemet inicializáló listának nevezzük. (Ez nem keverendő a
konstruktorokban a kettőspont után írt résszel – sajnos ugyanaz a neve mindkettőnek.)
Egy saját
IntArray
osztálynak így írhatnánk ilyen konstruktort:
#include <initializer_list>
class IntArray {
public:
IntArray(std::initializer_list<int> ints);
/* ... */
};
IntArray arr = { 2, 3, 4, 5 };
- Az eszköz használatához a fordítót C++11 módba kellhet állítani, a Code::Blocks verziójától függően. (Menüben Settings, Compiler, ...)
Ez a nyelvi elem itt jól fog jönni, mert a polinom fokszáma akármekkora lehet.
Legjobb ötlet átvenni egy inicializáló listát a konstruktorban, és azt rögtön tovább
is passzolni egy std::vector
adattag konstruktorának, mert az együtthatókat
úgyis el kell tárolni. A vektor mérete később bármikor lekérdezhető – ez lényegében a
polinom fokszáma, ami az adott x
helyen történő kiértékelésekor kelleni fog.
Írd meg a Polynomial
osztályt! Akár a Horner-elrendezést is használhatod.
Megoldás
Ezt a két osztályt igazából a Polynomial segítségével triviális implementálni:
- használj privát öröklést és delegálást,
- figyelj a konstruktorok paraméterezésére!
Megoldás
class Linear : private Polynomial {
public:
Linear(double a, double b) : Polynomial{a, b} {}
using Polynomial::operator();
};
class Parabolic : private Polynomial {
public:
Parabolic(double a, double b, double c) : Polynomial{a, b, c} {}
using Polynomial::operator();
};