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; }
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; }
specfikátor přístupnosti protected
uspořádání přístupnosti private < protected < public
vícenásobná dědičnost (multiple inheritance)
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)