Podpora výuky algoritmizace

Památce Karla Müllera, dobrého učitele, organizátora a člověka

source

Následující třída je vnitřní třída SafeArray – umístěte ji dovnitř deklarace této třídy:

    //Vnitrni trida iteratoru, ktera umoznuje prochazet bezpecne pole "pointerovou" syntaxi 
    // a pouzivat na nem algoritmy standardni knihovny 
    class Iterator {
 
        //pozice v poli
        size_t index;
 
        //pole se kterym je iterator asociovany
        SafeArray* array;
 
    public:
 
        //vytvori novy iterator asociovany s polem arr, ktery zacina na pozici x
        Iterator(SafeArray* arr, size_t x) {
            index = x;
            array = arr;
        }
 
        /*-----------------------------------------------------------------------------------*/
        //Operatory pro porovnavani iteratoru
 
        bool operator==(const Iterator& other) const {
            if (array == other.array && this->index == other.index) {
                return true;
            } else {
                return false;
            }
        }
        bool operator!=(const Iterator& other) const {
            return !((*this) == other);
        }
        //vrati true pokud tento iterator ukazuje na nizsi prvek v sekvenci
        bool operator<(const Iterator& second) const {
            return this->index < second.index;
        }
 
        //vrati pocet prvku mezi 2 iteratory
        size_t operator-(const Iterator& second) const {
            return this->index - second.index;
        }
 
        /*-----------------------------------------------------------------------------------*/
        //Operatory pro dereferenci iteratoru
 
        //dereference, vrati prvek, na ktery iterator aktualne ukazuje
        T& operator*(void) {
            return (*array)[int(index)];    //pouzijeme pretizeny operator[] v SafeArray
        }
 
        //pretizeny operator pristupu do pole, umoznuje nahodny pristup
        T& operator[](int offset) {
            return (*array)[int(index+offset)];
        }
 
        /*-----------------------------------------------------------------------------------*/
        //Operatory pro manipulaci s iteratorem
 
        Iterator& operator++(void) {
            index++;
            return *this;
        }
        //postfixova varianta, od prefixove je odlisena "dummy" parametrem typu int
        Iterator& operator++(int) { 
            index++;
            return *this;
        }
        Iterator& operator--(void) {
            index--;
            return *this;
        }
        //postfixova varianta, od prefixove je odlisena "dummy" parametrem typu int
        Iterator& operator--(int) {
            index--;
            return *this;
        }
 
        //posun iteratoru o vice prvku dopredu
        Iterator operator+(const size_t x) {
            return Iterator(array, index+x);
        }
        //posun iteratoru o vice prvku dozadu
        Iterator operator-(const size_t x) {
            return Iterator(array, index-x);
        }
 
        //cerna magie (template metaprogramming) pro pouziti s STL
        typedef random_access_iterator_tag iterator_category;
        typedef T value_type;
        typedef size_t difference_type;
        typedef T* pointer;
        typedef T& reference;
    };

Dále potřebujeme 2 metody pro SafeArray, které vrací iterátory na začátek a konec pole:

    //vrati iterator na prvni prvek v sekvenci
    Iterator begin(void) {
        return Iterator(this, 0);
    }
 
    //vrati iterator za posledni prvek v sekvenci. Tento iterator neni platny - nejde 
    //dereferencovat, ovsem manipulovat s nim jinak muzeme, stejne jako jde manipulovat 
    //s pointerem na nevalidni pamet
    Iterator end(void) {
        return Iterator(this, size);
    }

Tato (složitá) definice iterátoru nám umožní s ním pracovat prakticky stejně jako s pointerem:

    SafeArray<int> pole(10);
    for(SafeArray<int>::Iterator i = pole.begin(); i != pole.end(); i++) {
        *i = rand();
    }
 
    //klicove slovo auto ve standardu C++0x deklaruje promennou, ktera prevezme typ vyrazu 
    //na prave strane operatoru =. Doporucujeme pouzivat, abyste se vyhli vypisovani deklaraci
    //typu std::map<std::string, std::vector<std::pair<int, std::string>>>::const_iterator
    for(auto i = pole.begin(); i != pole.end(); i++) {
        cout << *i << endl;
    }

Dále nám umožní používat algoritmy z STL:

    std::sort(pole.begin(), pole.end());
 
    for(auto i = pole.begin(); i != pole.end(); i++) {
        cout << *i << endl;
    }

Download

  • Dědičnost (inheritance) vs. skládání (kompozice) – co tyto vztahy vyjadřují, jak se odlišují, jak se implementují, kdy se dají použít (is a, has a)
  • Abstraktní třídy – co modelují, k čemu jsou dobré, jak se používají
  • Pro pokročilé: circle-ellipse problem: http://en.wikipedia.org/wiki/Circle-ellipse_problem

Abychom mohli použít dříve vytvořenou třídu SafeArray umístíme celý její kód (nejen deklarace) do hlavičkového souboru. Je to z důvodu použití šablon.

Řešení začneme navržením základní (bázové) třídy Employee, která bude obsahovat metody společné pro všechny zaměstnance.

class Employee {
public:
 
    /**
     * Returns employee's salary
     */
    virtual int getSalary() = 0;
 
    /**
     * Prints info about employee
     */
    virtual void dumpInfo() = 0;
 
    virtual ~Employee() {
    }
};

Obě metody jsou abstraktní, protože o obecném zaměstnanci toho mnoho nevíme, tedy ani jaký má plat, ani jaké je profese. Z tohoto důvodu je třída abstraktní a nelze přímo instanciovat (nelze vytvářet její instance).

Obě metody jsou pochopitelně “public”, protože chceme, aby byly dostupné všem – představují veřejné rozhraní třídy.

Vytvoříme třídu pro první konkrétní typ zaměstnance – potomka (podtřídu) dědící z třídy Employee.

class Programmer : public Employee {
    // nasledujici promenne deklarujeme jako "protected" protoze nevylucujeme
    // vytvareni podtrid, napr. JavaProgrammer nebo PerlProgrammer, pro ktere
    // muze byt zadouci mit k temto promennym pristup
protected:
 
    int baseSalary;
 
    /**
     * Coefficient marking programmer's effectivity. To be updated quaterly.
     */
    float coef;
 
public:
 
    Programmer() {
        // zde bude ve skutecnosti volan implicitni konstruktor predka
        baseSalary = 50000;
        coef = 1.0;
    }
 
    // deklaraci metody "virtual" v podtridach nemusime opakovat, ale je to konvence
    // zlepsuje citelnost kodu, pokud budeme vytvaret potomka tridy Programmer 
    // nemusime se divat do tridy Employee, zda metoda je virtual ci nikoliv
 
    virtual int getSalary() {
        return int(baseSalary * coef);
    }
 
    virtual void dumpInfo() {
        cout << "Jsem programator a beru " << getSalary() << " Kc mesicne";
    }
 
 
    // nasledujici metody nedeklarujeme jako virtual protoze nepredpokladame, ze by 
    // se v pripadnych podtridach chovani teto metody melo lisit od tohoto
 
    /**
     * Setter for baseSalary
     */
    void setBaseSalary(int bas) {
        baseSalary = bas;
    }
 
    /**
     * Getter for baseSalary
     */
    int getBaseSalary() {
        return baseSalary;
    }
 
    /**
     * Setter for coef
     */
    void setCoef(float coef) {
        this->coef = coef;
    }
 
    /**
     * Getter for coef
     */
    float getCoef() {
        return coef;
    }
};

Jelikož ve třídě Employee nedefinujeme konstruktor s parametrem je kompilátorem vytvořen konstruktor implicitní. Tento implicitní konstruktor je v konstruktoru potomka vyvolán automaticky, nemusíme to dělat ručně. Více k tomuto na 9. cvičení.

Dalším konkrétním typem zaměstnance bude uklízeč(ka). Tato třída nemá žádné nové proměnné, nedefinujeme tedy get- a set- metody, ale pouze dvě virtuální metody deklarované v předkovi.

 
class Janitor : public Employee {
 
static const int MINIMAL_SALARY = 8000;
 
    virtual int getSalary() {
        return MINIMAL_SALARY;
    }
 
    virtual void dumpInfo() {
        cout << "Jsem uklizec(ka) a beru " << getSalary() << " Kc mesicne";
    }
};

A poslední třída

class CEO : public Employee {
 
    virtual int getSalary() {
        return 1000000; // this is real magic constant :D
    }
 
    virtual void dumpInfo() {
        cout << "Jsem CEO a beru " << getSalary() << " Kc mesicne. Nechci se vas dotknout, ale kdo z vas to ma?";
    }
};
class Company {
    // pole (nase trida SafeArray) obsahuje prvky typu ukazatel na spolecnou tridu, tj. Employee
    // ukazatel je nutny pro to, aby fungoval polymorfismus
 
    SafeArray<Employee *> staff;
 
    int numOfStaff;
 
public:
 
    Company () : staff (10) { // volani konstruktoru SafeArray s hodnotou parametru 10
        numOfStaff = 0;
    }
 
    // metoda bere ruzne potomky tridy Employee a pridava je do spolecneho
    // uloziste (vektoru)
    void add(Employee *e) {
        if (numOfStaff < staff.getSize()) {
            staff[numOfStaff++] = e;
 
        } else {
            cout << "Sorry, ale v nasi firme uz nenabirame" << endl;
        }
    }
 
    /**
     * Prints info about company by printing info about all employees.
     */
    // projde vektor a pro kazdy prvek zavola virtualni metodu, ktera se pro
    // kazdou konkretni instanci bude chovat jinak - a to je prave
    // polymorfismus
 
    void dumpInfo() {
        for (int i = 0; i < numOfStaff; i++) {
            staff[i] -> dumpInfo();
            // BTW: nepracujeme s polem, ale pretizenym operatorem []
            // tridy vector
        }
    }
 
    /**
     * Sums salary of company's employees
     */
    // analogicky jako dumpInfo() - na kazde konkretni instanci je zavolana virtualni
    // metoda getSalary (), ktera vrati vysledek podle typu objektu
 
    int getTotalSalary() {
        int sum = 0;
        for (int i = 0; i < numOfStaff; i++) {
            sum += staff[i] -> getSalary(); // sum = sum + staff[i] -> getSalary ();
        }
        return sum;
    }
};
 
int main() {
    Company microsoft;
 
    int choice;
    do {
        std::cout << "Pridat do firmy:" << std::endl;
        std::cout << "1) CEO" << std::endl;
        std::cout << "2) Programatora" << std::endl;
        std::cout << "3) Uklizecku" << std::endl;
        std::cout << std::endl;
        std::cout << "0) KONEC" << std::endl;
        do {
            std::cin >> choice;
        } while (choice < 0 || choice > 3);
 
        switch (choice) {
            case 1:
                microsoft.add(new CEO);
                break;
            case 2:
                microsoft.add(new Programmer);
                break;
            case 3:
                microsoft.add(new Janitor);
                break;
            default:
                break;
        }
 
    } while (choice != 0);
 
 
    cout << "Informace o firme:" << endl;
    cout << "Zamestnanci:" << endl;
    microsoft.dumpInfo();
    cout << "Soucet mezd: " << microsoft.getTotalSalary() << endl;
 
}

Dědičnost (inheritance)

  • podtřída (subclass) = potomek (descendant) = odvozená třída (derived class)
  • nadtřída (superclass) = předek (ancestor) = bázová třída (base class)

specfikátor přístupnosti protected
uspořádání přístupnosti private < protected < public
vícenásobná dědičnost (multiple inheritance)

Polymorfismus (řídce: mnohoznačnost) (polymorphism)

  • časná, též statická vazba(early, static binding)
  • pozdní, též dynamická vazba(late, dynamic binding)

klíčové slovo virtual
virtuální destruktor - vždy když třída obsahuje virtuální metody
v konstruktoru a destruktoru nefunguje polymorfismus

#include <cstdlib>
#include <iostream>
#include <sstream>
 
using namespace std;
 
/**
 * Trida pro nasi vzjimku. Odvozena od standardni vyjimky.
 */
class InvalidParameterException : public std::exception {
public:
    std::string paramName;
    std::string paramValue;
 
    InvalidParameterException(std::string paramName, std::string paramValue) :
    paramName(paramName), paramValue(paramValue) {
 
    }
 
    // z duvodu zdedeni od std:exception
    // throw () specifikuje ze z destrukturu se nesmi sirit zadna vyjimka
    ~InvalidParameterException() throw () {
 
    }
};
 
/**
 * Trida pro bezny bankovni ucet. Pro jednoduchost obsahuje jediny atribut,
 * a to zustatek
 *
 */
class BankAccount {
    // protected specifikuje, ze atribut bude pristupny nejen ve vlastni tride, ale i
    // v jejich primych potomcich
protected:
    int zustatek;
 
public:
 
    BankAccount(void) {
        zustatek = 0;
    }
 
    /**
     * @param hodnota vyse vkladane castky
     * @throws InvalidParameterException pokud byla vkladana castka zaporna
     */
    bool vloz(int hodnota) {
        if (hodnota < 0) {
            // konverze int na string
            ostringstream stream;
            stream << hodnota;
            string str = stream.str();
 
            throw InvalidParameterException("hodnota", str);
        }
        zustatek += hodnota;
        return true;
    }
 
    BankAccount(int zustatek) {
 
        // v tomto konstrukturu volame metodu pro vlozeni penez na ucet, protoze
        // v ni mame osetreni zaporneho zustatku. Protoze ale tato metoda penize
        // pridava, musime nejprve nastavit hodnotu atributu zustatek na nulu
        this->zustatek = 0;
 
        vloz(zustatek);
 
    }
 
 
    /**
     *
     * @param hodnota vyse vybirane castky
     * @return true pokud bylo na uctu dostatek peneze, jinak false
     * @throws InvalidParameterException pokud byla vybirana castka zaporna
     */
    // metoda je virtualni, protoze budeme chtit v potomkovi predefinovat jeji
    // chovani
 
    virtual bool vyber(int hodnota) {
 
        if (hodnota < 0) {
 
            // konverze int na string
            ostringstream stream;
            stream << hodnota;
            string str = stream.str();
 
            throw InvalidParameterException("hodnota", str);
        }
 
        // pouzivame ostry operator <, protoze banka nam nikdy nepovoli vybrat vsechny penize... :-/
        if (hodnota < zustatek) {
            zustatek -= hodnota;
            return true;
        } else {
            return false;
        }
    }
 
 
};
 
/**
 * Trida je potomkem (odvozena od) tridy BankAccount. Pridava atribut limit
 * stanovujici, o kolik se muze jit do minusu.
 */
class DebetBankAccount : public BankAccount {
    // pokud budeme predpokladat, ze z DebetBankAccount budeme odvozovat nejakou
    // dlasi specialni tridu pro debetni typ uctu, dame radeji limit jako protected
private:
    int limit;
 
public:
    // konstanta - vyznam static vysvetlime pozdeji
    static const int DEFAULT_DEBET = 10000;
 
    // konstruktor potomka - nejprve se vola konstruktor predka s parametrem zustatek
    // a pote se provadi nastaveni atributu pridanych v potomkovi - zde limit
 
    DebetBankAccount(int zustatek) : BankAccount(zustatek) {
        limit = DEFAULT_DEBET;
    }
 
    // abychom zmenili chovani metody vyber u debetniho uctu, musime ji v potomkovi
    // predefinovat
 
    /**
     *
     * @param hodnota vyse vybirane castky
     * @return true pokud byl vyber uspesny, jinak false
     * @throws InvalidParameterException pokud byla vybirana castka zaporna
     */
    virtual bool vyber(int hodnota) {
        if (hodnota < 0) {
 
            // konverze int na string
            ostringstream stream;
            stream << hodnota;
            string str = stream.str();
 
            throw InvalidParameterException("hodnota", str);
        }
 
        if ((zustatek + limit) < hodnota) {
            return false;
        } else {
            zustatek -= hodnota;
            return true;
        }
    }
 
    // ostatni metody nepotebuji predefinovavat (menit chovani), proto je zde
    // nemusime znovu uvadet - zdedili jsme je
};
 
int main(int argc, char** argv) {
 
    bool status;
    BankAccount *ba = new BankAccount(10000);
    DebetBankAccount *dba = new DebetBankAccount(5000);
 
    try {
        // vybirame z normalniho uctu castku mensi nez zustatek
        status = ba -> vyber(1000);
        if (status) {
            cout << "vyber OK" << endl;
        } else {
            cout << "vyber neproveden" << endl;
        }
 
        // vybirame z normalniho uctu castku vetsi nez zustatek
        status = ba -> vyber(11000);
        if (status) {
            cout << "vyber OK" << endl;
        } else {
            cout << "vyber neproveden" << endl;
        }
 
        // "potomek muze zastoupit predka"
        // ukazatel na potomka muzeme priradit do promenne typu ukazatel na predka
 
        ba = dba;
 
        // vybirame z debetniho uctu castku mensi nez zustatek
        status = ba -> vyber(1000);
        if (status) {
            cout << "vyber OK" << endl;
        } else {
            cout << "vyber neproveden" << endl;
        }
 
        // vybirame z debetniho uctu castku vetsi nez zustatek (ale v ramci
        // povoleneho kontokorentu)
        status = ba -> vyber(11000);
        if (status) {
            cout << "vyber OK" << endl;
        } else {
            cout << "vyber neproveden" << endl;
        }
        // vidime ze se musela vyvolat metoda vyber z potomka, nikoliv z predka
 
        // vybirame z debetniho uctu castku vetsi nez zustatek a nad ramec
        // povoleneho kontokorentu
        status = ba -> vyber(20000);
        if (status) {
            cout << "vyber OK" << endl;
        } else {
            cout << "vyber neproveden" << endl;
        }
 
        // vybirame zapornou castku - vznikne vyjimka
        ba -> vyber(-1000);
    } catch (InvalidParameterException e) {
        // atributy vyjimky nam umozni zjistit pro jaky parametr byla nepovolena hodnota pouzita
        cout << "Nelze vkladat nebo vybirat zapornou castku: " << e.paramName << " " << e.paramValue << endl;
    }
 
 
    return 0;
}

Cílem příkladu je vytvořit “silně zjednodušenou” personální agendu nadnárodní korporace. V programu budou existovat různé typy zaměstnanců, kteří se budou lišit platy. Zaměstnanci nebudou mít jména, to není (pro naši aplikaci) podstatné. Program umožní uživateli zadat libovolné množství různých typů zaměstnanců (programátor, CEO, uklízečka), vypsat informace o nich a celkovou mzdu (neřeší se zdravotní a sociální pojištění). Údaje o zaměstnancích jsou uloženy v třídě Company v jejíž implementaci je využito třídy SafeArray z dřívejška (pozn. z výukových důvodů, abychom viděli, jak snadno použít dobře navrženou třídu. Jinak by bylo vhodnější pro tento účel použít například třídu std::vector).

int main() {
    Company microsoft;
 
    int choice;
    do {
        std::cout << "Pridat do firmy:" << std::endl;
        std::cout << "1) CEO" << std::endl;
        std::cout << "2) Programatora" << std::endl;
        std::cout << "3) Uklizecku" << std::endl;
        std::cout << std::endl;
        std::cout << "0) KONEC" << std::endl;
        do {
            std::cin >> choice;
        } while (choice < 0 || choice > 3);
 
        switch (choice) {
            case 1:
                microsoft.add(new CEO ());
                break;
            case 2:
                microsoft.add(new Programmer ());
                break;
            case 3:
                microsoft.add(new Janitor ());
                break;
            default:
                break;
        }
 
    } while (choice != 0);
 
 
    cout << "Informace o firme:" << endl;
    cout << "Zamestnanci:" << endl;
    microsoft.dumpInfo();
    cout << "Soucet mezd: " << microsoft.getTotalSalary() << endl;
 
}
#include <iostream>
#include <exception>
#include <string>
 
// Vyjimka vyhozena pri pristupu na chybny index v poli
class InvalidIndexException : public std::exception {
 
    // index na ktery se volajici pokousel pristoupit
    int index;
 
public:
    InvalidIndexException(int index) {
        this->index = index;
    }
 
    int getIndex() const {
        return this->index;
    }
};
 
// obecnejsi vyjimka vyhozena pri zavolani funkce s chybnou hodnotou parametru
class InvalidParameterException : public std::exception {
 
    // ktery parametr byl chybny
    std::string param;
 
public:
    InvalidParameterException(const std::string& param) {
        this->param = param;
    }
 
    const std::string& getParam() const {
        return this->param;
    }
};
 
// Jednoducha trida reprezentujici bezpecne pole obecneho typu, s kontrolou mezi, automatickou 
// alokaci a dealokaci
template <class T>
class SafeArray  {
 
private:
 
    // naalokovana velikost pole
    int size;
 
    // pointer na data
    T* data;
 
public:
 
    // konstruktor ktery vytvori pole velikosti size a vyplni jej nulami, zaroven funguje jako
    // vychozi (aby slo alokovat pole poli)
    SafeArray(const int size = 0) {
        if(size < 0) {
            throw  InvalidParameterException ("size");
        }
        data = new T[size];
        this->size = size;
    }
 
    //kopirovaci konstruktor, pouzije se mimo jine pokud nekam objekt predavame hodnotou
    SafeArray(const SafeArray& other) {
        this->size = other.size;
        this->data = new T[size];
        for(int i = 0; i < size; i++) {
            this->data[i] = other.data[i];
        }
    }
 
    // operator prirazeni, pouzije se pri zapise instance1 = instance2
    SafeArray& operator=(const SafeArray& other) {
        // ochrana proti samoprirazeni, ktere by zpusobilo segfault
        if(this == &other) {
            // pokud se this a other uz sobe rovna, tak nemusime nic delat
            return *this;   
        }
        //narozdil od kopirovaciho konstruktoru zde musime nejdriv smazat existujici data
        delete [] this->data;
 
        this->size = other.size;
        this->data = new T[size];
        for(int i = 0; i < size; i++) {
            this->data[i] = other.data[i];
        }
        return *this;
    }
 
    // destruktor, ktery uklidi vsechnu alokovanou pamet
    ~SafeArray() {
        delete [] data;
    }
 
    // operator indexace, ktery vraci referenci na ulozeny prvek. Reference jde cist, i 
    // modifikovat, takze funkcnosti je identicky s puvodnim operatorem pro pointery
    T& operator[](int index) {
        if(index<0 || index>=size){
            //pokus o pristup na neexistujici index
            throw InvalidIndexException (index);
        }
        return data[index];
    }
 
    // operator indexace, ktery vraci konstantni referenci na ulozeny prvek. Reference jde 
    // pouze cist. Tuto variantu operatoru jde volat i pro konstantni instanci, narozdil od 
    // predchozi
    const T& operator[](int index) const {
        if(index<0 || index>=size){
            //pokus o pristup na neexistujici index
            throw InvalidIndexException(index);
        }
        return data[index];
    }
 
    //vrati velikost pole
    int getSize() const {
        return size;
    }
};
 
 
int main() {
    SafeArray <int> x(10);
    SafeArray <long> y(10);
    //x = y; //tato konstrukce zpusobi chybu, protoze jde o naprosto odlisne datove typy
 
    try {
        int i = 0;
        while(true) {
            x[i++] = i*i;
        }
    } catch(InvalidIndexException& ex){
        std::cout << "Pristup na chybny index: " << ex.getIndex() << std::endl;
    }
 
 
    try {
        SafeArray<std::string> stringArray(-1);
    } catch(InvalidParameterException& ex){
        std::cout << "Chybna hodnota parametru: " << ex.getParam() << std::endl;
    }  catch(InvalidIndexException& ex){
        std::cout << "Pristup na chybny index: " << ex.getIndex() << std::endl;
    }
 
    // pole poli
    SafeArray<SafeArray<int>> twoDimensionalArray(10);
 
 
    system("pause");
}

Šablona (template) funkce, třídy= generická (parametrizovatelná) funkce, třída
Výjimka (exception)