Institute of Computer Science
  1. Courses
  2. 2022/23 spring
  3. Programming in C++ (LTAT.03.025)
ET
Log in

Programming in C++ 2022/23 spring

  • Pealeht
  • 1. Muutujad ja andmetüübid
  • 2. Keele põhikonstruktsioonid I
  • 3. Keele põhikonstruktsioonid II
  • 4. Funktsioonimallid, failitöötlus
  • 5. OOP I Klassid
  • 6. OOP II Pärilus ja polümorfism
  • 7. Kontrolltöö 1?

Seitsmendal nädalal toimub 1. kontrolltöö

7.1 1. kontrolltöö näide?
  • 9. Dünaamiline mäluhaldus II
  • 10. Klassimallid
  • 11. STL andmestruktuurid I
  • 12. STL andmestruktuurid II
  • 13. Erindite töötlemine
  • 14. Täiendavad teemad
  • 15. Kontrolltöö 2?

Viieteistkümnendal nädalal toimub 2. kontrolltöö

15.1 2. kontrolltöö näide?
  • 16. Projekti esitlus?
  • Viiteid
  • Vanad materjalid
  • Praktikumid
  • Juhendid
  • Viited

Klassimallid

Pärast selle praktikumi läbimist üliõpilane

  • teab, mis on klassimall ja oskab klassimalli defineerida
  • oskab defineerida klassimalli liikmefunktsioone väljaspool klassi
  • oskab klassimalli abil klassi isendeid luua
  • oskab luua mitme tüübiparameetriga klassimalli

Klassimalli defineerimine

Klassimall on kavand klassi genereerimiseks. Klassimallid erinevad funktsioonimallidest selle poolest, et kompilaator ei saa tuletada malliparameetrite tüüpe klassimalli jaoks. Klassimall muudab klassi definitsiooni üldisemaks. Klassimalli definitsioon algab päisega

template <typename tüübiparameeter>

Võtmesõna typename asemel võib kasutada ka võtmesõna class, aga tuleks eelistada typename. Klassi enda definitsioon on sama nagu varem, ainult tüübi asemel kasutatakse tüübiparameetrit. Tüübiparameetreid võib olla ka mitu. Vaatame näidet klassimallist. Olgu meil klass Paar, mis hoiab kahte sama tüüpi elementi.

NB! Nii funktsioonimallid kui ka klassimallid koos liikmefunktsioonide definitsioonidega paigutatakse päisefaili. Kompilaator teeb klassist malliisendi iga kasutatava tüübi jaoks ja seetõttu on kompilaatoril vaja klassi liikmete kohta kogu infot.

template <typename T>
class Paar{
public:
    Paar();
    Paar(T esimene, T teine);
    void setEsimene(T esimene);
    void setTeine(T teine);
    T getEsimene();
    T getTeine();
private:
    T m_esimene{};
    T m_teine {};
};

Näites olevas klassimallis on mallitüübiks T ja mall sisaldab vaid konstruktorite ja liikmefunktsioonide deklaratsioone. Funktsioonide definitsioonid võivad olla ka klassimalli sees, kuid nii nagu tavalise klassi puhul, kõik klassi sees defineeritud funktsioonid on automaatselt pistikfunktsioonid (inline functions). Väljaspool klassimalli liikmefunktsioonide defineerimisel on eripära, et liikmefunktsioonid on ise mallid ja algavad päisega template <typename T>. Funktsiooni tagastustüüp on enne funktsioonimalli skoobimäärangut Paar<T>:::

template <typename T>
void Paar<T>::setEsimene(T esimene) {
    m_esimene = esimene;
}

Klassimall Paar tervikuna on järgmine (nt failis deklaratsioonid.h):

template <typename T>
class Paar{
public:
    Paar();
    Paar(T esimene, T teine);
    void setEsimene(T esimene);
    void setTeine(T teine);
    T getEsimene();
    T getTeine();
private:
    T m_esimene{};
    T m_teine {};
};

template <typename T>
Paar<T>::Paar(T esimene, T teine) : m_esimene{esimene}, m_teine{teine}{}

template <typename T>
void Paar<T>::setEsimene(T esimene) {
    m_esimene = esimene;
}
template <typename T>
void Paar<T>::setTeine(T teine) {
    m_teine = teine;
}
template <typename T>
T Paar<T>::getEsimene(){
    return m_esimene;
}

template <typename T>
T Paar<T>::getTeine(){
    return m_teine;
}

Kui klassimall on defineeritud, siis saab klassist teha objekte. Objekti loomisel peab ette andma tüübi:

Paar<double> paar{};

Testime klassimallist objektide loomist funktsioonis main:

#include <iostream>
#include <string>
#include "deklaratsioonid.h"
using namespace std;
int main() {
    Paar<int> täisarvupaar{2, 5};
    täisarvupaar.setEsimene(3);
    täisarvupaar.setTeine(6);
    cout << "Paar: (" << täisarvupaar.getEsimene() << ", " << 
    täisarvupaar.getTeine() << ")\n";

    Paar<string> nimepaar{"Madli", "Madis"};
    cout << "Paar: (" << nimepaar.getEsimene() << ", " << 
    nimepaar.getTeine() << ")\n";
    return 0;
}

Paar: (3, 6)
Paar: (Madli, Madis)

Klassimall funktsiooni parameetrina

Klassimalli nime võib kasutada funktsiooni parameetrina. Näiteks, järgmine funktsioon tagastab paari elementide summa

int liida(Paar<int> paar){
    return paar.getEsimene() + paar.getTeine();
}

ja funktsioon

string liida(Paar<string> paar){
    return paar.getEsimene() + paar.getTeine();
}

tagastab ühendatud sõned.

Viimase kahe funktsiooni asemel saab luua funktsioonimalli (paneme faili deklaratsioonid.h):

template <typename T>
T liida(Paar<T> paar){
    return  paar.getEsimene() + paar.getTeine();
}

Katsetame seda funktsioonis main:

    Paar<int> täisarvupaar{3, 6};
    Paar<string> nimepaar{"Madli", "Madis"};
    cout << liida(täisarvupaar) << '\n';
    cout << liida(nimepaar) << '\n';
9
MadliMadis

Mitme tüübiparameetriga klassimall

Klassimallis võib tüübiparameetreid olla rohkem kui üks. Näide kahe tüübiparameetriga klassist:

template <typename T1, typename T2> class MinuKlass{
    T1 m_t1{};
    T2 m_t2{};
public:
    MinuKlass(T1 t1, T2 t2) : m_t1{t1}, m_t2{t2} {}
    void näita() {
        cout << m_t1 << ' ' << m_t2 << '\n';
    }
    friend ostream& operator<<(ostream& os, const MinuKlass m) {
        os << "MinuKlass: " << m.m_t1 << ' ' << m.m_t2;
        return os;
    }

};

Klassi on lisatud ka operaatori << üledefineerimine objekti ekraanile kuvamiseks. Testime klassimalli funktsioonis main:

#include <iostream>
#include <string>
#include "deklaratsioonid.h"
using namespace std;

int main() {
    MinuKlass<string, int> isik{"Pille", 20};
    MinuKlass<double, double> koordinaadid{1.2, 5.6};
    isik.näita();
    koordinaadid.näita();
    cout << isik << '\n';
    cout << koordinaadid << '\n';
    return 0;
}
Pille 20
1.2 5.6
MinuKlass: Pille 20
MinuKlass: 1.2 5.6

Järgmises näites on klassimalli tüübiparameetriks klass. Siin nii klassimallis kui ka kasutatavates klassides peavad kõik liikmefunktsioonid (ka sõbrad) olema defineeritud klassi sees. Funktsioonis main tehakse klassimallist Loom isendid, andes ette tüübiparameetri Kass või Koer. Klassi Loom isenditele rakendatakse funktsioone, mille parameetriks on vastavalt Kass ja Koer isendid.

Fail deklaratsioonid.h

#ifndef N10_0_DEKLARATSIOONID_H
#define N10_0_DEKLARATSIOONID_H
#include <iostream>
#include <string>

class Kass{
public:
    Kass() = default;
    Kass(std::string nimi) : m_nimi{nimi}{}
    std::string m_nimi{};
    void teeHäält(){
        std::cout << "Mjäu\n";
    }
    friend std::ostream& operator<<(std::ostream& os, const Kass k) {
        os << "Kassi nimi: " << k.m_nimi;
        return os;
    }
};
class Koer{
public:
    Koer() = default;
    Koer(std::string nimi) : m_nimi{nimi}{}
    std::string m_nimi{};
    void teeHäält(){
        std::cout << "Auh\n";
    }
    friend std::ostream& operator<<(std::ostream& os, const Koer k) {
        os << "Koera nimi: " << k.m_nimi;
        return os;
    }
};
template <typename T>
class Loom{
public:
    Loom() = default;
    void häälitse(T& loom){
        loom.teeHäält();
    }
    void tee(T& loom){
        std::cout << loom << " Teen midagi\n";
    }
};
#endif //N10_0_DEKLARATSIOONID_H

#include <iostream>
#include <string>
#include "deklaratsioonid.h"
using namespace std;

int main() {
    Kass kass{"Miisu"};
    cout << kass << '\n';
    Loom<Kass> l1{};
    l1.häälitse(kass);
    Koer koer{"Muki"};
    cout << koer << '\n';
    Loom<Koer> l2{};
    l2.häälitse(koer);
    l2.tee(koer);
    return 0;
}
Kassi nimi: Miisu
Mjäu
Koera nimi: Muki
Auh
Koera nimi: Muki Teen midagi

Tüübiparameetrite vaikeväärtused ja lisaparameetrid (non-type parameters)

Nii nagu funktsioonimalli korral, on ka klassimalli korral võimalik tüübiparameetrile anda vaikeväärtus. Järgmises näites on tüübiparameetri T vaikeväärtuseks int ja lisaparameetriks int suurus, mis on klassimallis sisalduva massiivi pikkus. Lisaparameeter võib olla kas int, viit (pointer) või viide (reference). Teised tüübid, nt double ei ole lubatud. Lisaparameetreid tuleb vaadelda kui konstante, nende väärtust ei saa muuta.

// int on fikseeritud parameeter, massiivi pikkus
template <typename T = int, int suurus = 10> class Massiiv {
    T massiiv [suurus]{}; // massiiv 
public:
    Massiiv() { //konstruktor
        int i{};
        for(; i < suurus; i++){
            massiiv[i] = i;
        }
    }
    T& operator[](int i);
};
// operaatori [] üledefineerimine ja piiride kontroll
template <typename T, int suurus>
T& Massiiv<T, suurus>::operator[](int i){
    if(i < 0 || i > suurus - 1) {
        cout << "\nIndeksi väärtus ";
        cout << i << " on väljaspool piire.\n";
        exit(1); //väljumine veaga
    }
    return massiiv[i]; // operaatori [] tagastus
}

Funktsioonis main katsetame erineva parameetritüübiga objekte:

int main() {
    // int massiiv pikkusega 10
    Massiiv<> int_m{};
    Massiiv<int, 10> int_ob{}; 
    // double massiiv pikkusega 15
    Massiiv<double, 15> double_ob{}; 
    int i;
    cout << "Täisarvude massiiv: ";
    for(i = 0; i < 10; i++) cout << int_ob[i] << " ";
    cout << '\n';
    cout << "Ujukomaarvude massiiv: ";
    for(i = 0; i < 15; i++) double_ob[i] = i*2.5;
    for(i = 0; i < 15; i++) cout << double_ob[i] << " ";
    cout << '\n';
    int_m[12] = 100; // tekitab vea
    return 0;

Täisarvude massiiv: 0 1 2 3 4 5 6 7 8 9 
Ujukomaarvude massiiv: 0 2.5 5 7.5 10 
12.5 15 17.5 20 22.5 25 27.5 30 32.5 35 

Indeksi väärtus 12 on väljaspool piire.

Näeme, et omistamine int_m[12] = 100 tekitab vea, sest 12 on väljaspool massiivi int_m piire.

Klassimall määrab klassitüüpide perekonna. Klassimalli eksemplar on klassi definitsioon, mille kompilaator genereerib mallist, kasutades koodis määratud malliargumentide komplekti. Klassimalli tüübiparameetrile vastav argument võib olla põhitüüp, klassi tüüp, viidatüüp või viitetüüp.

Kitsendused tüübiparameetritele <type_traits>

Tüübiparameetritele saab seada ka tüübi osas kitsendusi. Selleks tuleb kaasata päis <type_traits>. Kui lisame klassile Paar kontrolli

template <typename T>
class Paar{
    static_assert(std::is_arithmetic_v<T>, "Parameetrid peavad olema arvud");
public:
...

siis katse luua isendit mittearvuliste parameetritega

Paar<string> paar{"1", "2"};

annab kompileerimisvea.

Täpsemalt saab <type_traits> kohta uurida aadressil

https://en.cppreference.com/w/cpp/header/type_traits

ja

https://en.cppreference.com/w/cpp/types/is_arithmetic

Võtmesõnad typedef ja using

Saab defineerida uue klassi tüübi nime, millel on sama tähendus nagu tüübiga klassimall, nt Paar<int>. Selleks tuleb kasutada võtmesõna typedef:

typedef Paar<int> Täisarvupaar;

Seda saab kasutada isendite loomisel

Täisarvupaar tp1{3, 5}, tp2{4, 8};

Võtmesõnaga typedef peab kasutama konkreetset tüüpi, st ei saa defineerida typedef Paar<T> paar. Uue standardi järgi on võimalik defineerida klassimallile tüübi alias:

template<typename T> using Duo = Paar<T>;

Nüüd Duo on sünonüüm Paar-le.

Duo<int> arvud{}; // arvud on Paar<int>

Klassimallide kohta saab täpsemalt uurida https://en.cppreference.com/w/cpp/language/class_template

  • Institute of Computer Science
  • Faculty of Science and Technology
  • University of Tartu
In case of technical problems or questions write to:

Contact the course organizers with the organizational and course content questions.
The proprietary copyrights of educational materials belong to the University of Tartu. The use of educational materials is permitted for the purposes and under the conditions provided for in the copyright law for the free use of a work. When using educational materials, the user is obligated to give credit to the author of the educational materials.
The use of educational materials for other purposes is allowed only with the prior written consent of the University of Tartu.
Terms of use for the Courses environment