Programmeerimine keeles C++
Praktikum 3: mallid, C++ standardteegi konteinerid, iteraatorid
Ülesanded
Üldised tingimused
Tähtis! Loe läbi ülesannete vormistamise tingimused aine veebilehelt! Ülesandeid esitatakse läbi aine veebilehel asuva vormi. E-posti teel lahendusi vastu ei võeta. Küsimustega pöörduda aine listi või praktikumijuhendaja poole.
Lahendamiseks on aega 14 päeva.
Tähtaeg: 21.03.2021 23:59:59
NB: Käesolevast praktikumist alates pole dokumenteerimine Doxygeniga enam kohustuslik. Tudengitelt eeldatakse edaspidi mõistlikult kommenteeritud koodi.
Ülesanne 1 – Geomeetria suvalise mõõtmete arvuga ruumis
Selles praktikumis kirjutame seni kõige täiuslikumad punkti, sirglõigu ja kera klassid. Nimelt kasutame klassimalle, et luua geomeetrilisi objekte suvalise mõõtmega ruumis (märkus: ma palun, et teie kood annaks triviaalseid vastuseid ka siis, kui parameetritega määratakse 0-mõõtmeline ruum). Järgmiste klasside realiseerimiseks kasutage malle.
Kui teil ei teki cpp-faile, siis ärge tehke ka geometry-teegile .a faili. Esimeses ülesandes võib nii juhtuda kergesti.
1.1. Universaalne punkt (point.h
) (3 punkti)
Looge klass Point
, millele saab ette anda koordinaatide arvu (template <unsigned short n>
). Etteantav mittenegatiivne täisarv n määrab, mitu murdarvulist mõõdet vektoril on. Punkti koordinaate salvestage STL konteinerklassi std::list
abil klassi muutujas nimega coords
.
Mõned näited:
Point<2> kahem66tmelinePunkt; // punkt tasandil Point<10> kymnem66tmelinePunkt; // punkt kümnemõõtmelises ruumis
Lisage klassile meetodid:
Meetod | Eesmärk |
---|---|
Point<n> () | algväärtustab koordinaadid nullidega |
Point<n> (list<float> crds) | väärtustab vektori koordinaadid etteantud väärtustega |
float distanceFrom (Point<n> v) | tagastab kauguse teisest sama mõõtmehulgaga punktist |
string toString () | tagastab vektori esituse sõnena (x1, x2, ..., xn) |
operator << | väljastab vektori andmed (kasuta ära toString meetodit) |
Väljundvoogu nihutamise operaatori ülelaadimisel on abiks friend
võtmesõna.
NB! Võtmesõna friend
kasutamine mallide sees võib põhjustada veidraid vigu või hoiatusi kompileerimisel. Vaata lisaks https://isocpp.org/wiki/faq/templates#template-friends
NB! Operaatori ülelaadimist on lihtsam realiseerida, kui parameetriks antud Point
objekt pole konstant. Ilusama lahenduse kirjutamiseks vaata vihjeid viimasel leheküljel.
Juhul, kui tekivad veaolukorrad, näiteks kui malliga määratud koordinaatide arv n erineb etteantud koordinaatide vektori suurusest, visake string-tüüpi erind (vt materjali). Erindi teksti kirjutage inimkeelne selgitus selle kohta, mis toimus. Arvestage, et 0-koordinaadiga vektor on põhimõtteliselt täiesti lubatud ning seda veajuhuks lugema ei peaks.
1.2. Universaalne sirglõik (line.h
) (1 punkt)
Looge klassimall Line
, mille parameetriks on punkti klass T
(template <class T>
). Klass esindab sirglõike üle suvalise mõõtme punktide. Klassil on kaks muutujat – p1
ja p2
, mõlemad on tüüpi T
– need esindavad sirglõigu tippe. Tulemusena peab saama teha kahe tipuga sirglõike nii:
Line< Point<2> > kahem66tmelineSirgl6ik; Line< Point<7> > seitsmem66tmelineSirgl6ik;
Lisage klassile meetodid:
Meetod | Eesmärk |
---|---|
Line<T> () | vaikekonstruktor – loob tipud (T vaikekonstruktoriga) |
Line<T> (T np1, T np2) | parameetritega konstruktor – väärtustab klassi elemendid |
float length () | tagastab sirglõigu pikkuse kasutades klassi T meetodit distanceFrom |
string toString () | tagastab lõigu esituse sõnena ((tipp1)-(tipp2)) |
operator << | väljastab lõigu andmed (kasuta ära toString meetodit) |
1.3. Universaalne kera (sphere.h
) (3 punkti)
Looge klassimall Sphere
, mille parameetriks on punkti klass T
(template <class T>
). Klass esindab kerasid (ja kahemõõtmelisel erijuhul ringe). Klassil olgu T
tüüpi muutuja o
, mis esitab keskpunkti ja float
-tüüpi murdarv r
, mis esitab raadiust. Raadius ei tohiks olla kunagi negatiivne.
Tulemusena peab saama teha ringe ja kerasid nii:
Sphere< Point<2> > ring; Sphere< Point<3> > kera;
Meetod | Eesmärk |
---|---|
Sphere<T> () | vaikekonstruktor – loob T tüüpi tipu ja paneb raadiuseks nulli |
Sphere<T> (T no, float nr) | parameetritega konstruktor – kasutab antud tippu ja raadiust |
bool contains (T v) | tagastab true , kui tipp on kera pinnal või sees, muidu false |
bool contains (Line<T> l) | tagastab true , kui antud lõik on kera sees, muidu false |
void scale (float factor) | korrutab kera raadiuse antud väärtusega |
string toString () | tagastab kera esituse sõnena ((tipp), raadius) |
operator << | väljastab kera andmed (kasuta ära toString meetodit) |
See ei ole ülesande osa, aga jätke meelde, et keeles on olemas ka võimalus konkreetsete parameetritega implementatsioonide kirjutamiseks. Näiteks võite realiseerida erijuhtudena kahemõõtmelise kera (ringi) jaoks ümbermõõdu ja pindala ning kolmemõõtmelise kera jaoks ruumala arvutamise. Vastav märksõna, mida uurida, on template specialization.
NB! Ärge muutke soovitatud muutujanimesid ning hoidke muutujad avalikena. Nii on testimine lihtsam. Samuti tehke päisefail geometry.h
, mille lisamisel lisatakse nii tipu, sirglõigu kui kera klasside päised.
Ülesanne 2 – universaalse hulknurga klass (3 punkti)
Tulemusena on vaja luua klassimall Polygon
, mille parameetriteks on punkti klass T
ja täisarv n
(template <class T, unsigned short n>
). Klass esindab n-tipulisi hulknurki üle etteantud punktide. Lisage klassimallile meetodid:
Meetod | Eesmärk |
---|---|
Polygon<T, n> () | algväärtustab tipud nullidega |
Polygon<T, n> (vector<T> pts) | väärtustab vektori koordinaadid etteantud väärtustega |
float perimeter () | annab hulknurga perimeetri (külgede pikkuste summa) |
string toString () | tagastab hulknurka esitava sõne ((tipp1),...,(tipp2)) |
operator << | väljastab hulknurga andmed (kasuta toString meetodit) |
Vihjeid
Abiks väljundvoogu nihutamise operaatori ülelaadimisel (vaata esmalt teise praktikumi materjalist „Lisalugemist väljundvoogu suunamise operaatori ülelaadimise kohta“).
Kui funktsiooni parameeter on konstant, siis andes selle parameetri edasi mõnele teisele funktsioonile peab ta jääma konstantseks. Näiteks, kui klass on kirjeldatud järgmiselt
class Vector2 { ... string toString(); friend ostream& operator<<(ostream& out, const Vector2& vec); }
Siis ei saa operaatori ülelaadimist realiseerida nii
ostream& operator<<(ostream& out, const Vector2& vec) { out << vec.toString(); // veateade return out; }
Probleem seisneb selles, et kompilaator ei tea, mis toString meetodis tehakse ja kas see meetod
muudab Vector2
klassi objekti. Lahenduseks tuleks toString
meetodi lõppu kirjutada const
(string toString() const;
), mis tagab, et objekti seal meetodis ei muudeta ning võimaldab toString
ile teha väljakutseid konstantsete muutujate pealt.
Lisaülesanne 3 – funktsiooniobjektid või lambdafunktsioonid (1 lisapunkt)
Üldised nõuded
Kolmandaks lisaülesandeks on teil valida kahe harjutuse vahel. Võite lahendada 3.1 või 3.2 või mõlemad, kuid punkte on võimalik saada ainult ühe eest. Mõlema lisaülesande esitamisel hinnatakse esimest ehk funktsiooniobjektide harjutust.
Ülesande lahendamise käigus saate harjutada algoritmide ja funktsiooniobjektidega töötamist.
Tulemusena tekkiv kood peaks sisalduma teegifailis nimega libmyfunctors.a
ja vastav päis olgu myfunctors.h
. Lahenduse testimiseks kirjutan ma programmi, kus rakendan teie kirjutatud koodi
ning kontrollin tulemuste vastavust ülesande nõuetele. Soovitan teil teha testprogramm, kus oma
lahendust ise järele proovite. Testprogrammi ei hinnata.
Lahendus paigutage samasse kataloogipuusse esimese ülesande geomeetriakoodiga. Makefile peab vaikimisi ehitama valmis teegi. Testifailis on kaasas ka vaikimisi mõningad testid.
Osa 3.1 - Funktsiooniobjektid ehk funktorid
Ühe parameetriga olekuga funktsiooniobjekt konteineri elementide summeerimiseks
Kiire sissejuhatus olekuga funktorite teemasse. Funktorid võivad olla defineeritud ka enne nende rakendamist. Sellega kaasneb meeldiv lisavõimalus kasutada neid näiteks väärtuste kogumisel. Funktor luuakse enne algoritmi käivitamist ning pärast töö lõppu loetakse objekti seest tulemus. Selle toetamiseks peavad funktori klassis olema lisaks tehtele ()
defineeritud veel vajalikud muutujad ning meetodid. Teie ülesandeks on kirjutada klassiparameetriga T
klassimall SumElements<T>
, mida saaks kasutada T
tüüpi elemente sisaldavate konteinerite sisu summeerimiseks. Eeldame, et tüübil T
on defineeritud tehe +
(sobivad näiteks int
, float
, string
). Kui koodis proovitakse objekti luua mõne tüübiga, millel tehet pole, tekib viga.
Funktsiooniobjekti võiks realiseerida päises. Näide sellest, kuidas objekti peaks saama kasutada.
vector<unsigned long> values; // vektor täisarvudest SumElements<unsigned long> addThisValue; // sama tüüpi funktor addThisValue = for_each(values.begin(), values.end(), addThisValue); // summeeri unsigned long sum = addThisValue.result (); // loeme kogunenud summa
Lahendus peab töötama nii arvuliste tüüpide kui ka sõnede korral. Vihje: Loogeliste sulgude kasutamine objekti loomisel algväärtustab arvulised tüübid nulliks ja sõned tühisõnedeks. Näiteks
int x; // x'i väärtuseks võib olla ükskõik mis int x{}; // x algväärtustatakse nulliks int x(); // Kompilaator arvab, et tegemist on funktsiooni deklaratsiooniga
Osa 3.2 - Konteineri elementide summeerimine lambdafunktsiooni abil
Lambda või lambdafunktsioon on nimeta
funktsioon mida saab kasutada ühekordse funktsiooniobjekti loomiseks, näiteks algoritmide jaoks. Seetõttu ei pea algoritmi jaoks eraldi funktsiooniobjekti defineerima, mis omakorda säästab aega ja vähendab koodi mahtu. Lambdafunktsiooni kere koosneb tavaliselt kolmest osast:
[püütud_muutujad](argumendid) { avaldis }
// püütud_muutujad - Lokaalse skoobi muutujad, mida võib avaldises vaja minna, näiteks summa hoiustamiseks. // argumendid- Lambdaavaldisele ette antavad argumendid (sarnaselt nagu funktsioonilegi). // avaldis - Lambdafunktsiooni avaldis, mis defineerib käitumise. // Muutujaid saab püüda üksikult ka viite või väärtuse järgi vastavalt vajadusele. // Et püüda kõik skoobi muutujad viite järgi kasutage [&] // Näited lambdafunktsioonidest [&a](int x, int y=1) { return a -= x*y; } // Püüame ainult 1 skoobi muutuja (a) viite põhjal. [&](int x) { return a -= x + b; } // Püüame kõik skoobi muutujad (a,b) viite põhjal. [a](int x, int y=1) { return a -= x*y; } // Püüame ühe skoobi muutuja (a) väärtuse põhjal. [=](int x, int y=1) { return a -= x*y + b; } // Püüame kõik skoobi muutujad (a,b) väärtuse põhjal.
Lambdafunktsioon tagastab tegelikult funktsiooniobjekti ehk funktori.
Näide lambdafunktsioon kasutamisest:
vector<int> arvuvektor {32,71,12,45,26,80,53}; // defineerime vektori arvudega // Kolmas argument: "[](int x) {return (x % 2 == 0); }" on lambdafunktsioon // Lokaalse skoopi muutujaid ei kasutata, seega ühtegi muutujat kinni ei püüta. partition(arvuvektor.begin(), arvuvektor.end(), [](int x) {return (x % 2 == 0); } ); // tulemus: 12,26,32,80,45,53,71
Ülesandeks on kirjutada mallifunktsioon, mis oskab T
tüüpi vektorite elemente summeerida ning tagastab nende summa. Eeldame, et T
tüüpi elementidel on defineeritud operaator +
ning neid saab ka väljundvoogu nihutada. Funktsioon tuleks realiseerida kasutades for_each
i ja sobivat lambdafunktsiooni. Mallifunktsiooni kere on järgnev:
template <typename T> T summeeri_malli_vektor( vector<T> vec ) { // Realiseeri mind }
Mallifunktsiooni võiks realiseerida päises. Näide sellest, kuidas funktsiooni peaks saama kasutada.
vector<string> values {"a","b","c","d","e"}; // vektor tähtedest/sõnedest vector<int> values1 {5,4,3,2,1}; // vektor täisarvudest vector<float> values2 {5.0f,4.0f,3.0f,2.0f,1.0f}; // vektor ujukomaarvudest cout << "Sum is: " << summeeri_malli_vektor(values) << endl; // prindib "Sum is abcde" cout << "Sum is: " << summeeri_malli_vektor(values1) << endl; // prindib "Sum is 15" cout << "Sum is: " << summeeri_malli_vektor(values2) << endl; // prindib "Sum is 15.000000"
Lisainfo:
https://en.cppreference.com/w/cpp/language/lambda
http://en.cppreference.com/w/cpp/language/value_initialization
http://en.cppreference.com/w/cpp/language/default_initialization