Programowanie obiektowe C++ Programowanie zorientowane obiektowo Wykład 5 Witold Dyrka witold.dyrka@pwr.wroc.pl 19/12/2011
Prawa autorskie itp. Wiele slajdów do tego wykładu powstało w oparciu o slajdy Bjarne Stroustrupa do kursu Foundations of Engineering II (C++) prowadzonego w Texas A&M Universityhttp://www.stroustrup.com/Programming (tym razem większość wykładu:-)
Program wykładów 0. Bezpieczeństwo typów. Kontener vector. Obsługa błędów (26/09/2011) 1. Abstrakcja i enkapsulacja: klasy i struktury. Typ wyliczeniowy (10/10/2011) 2. Polimorfizm: przeładowanie funkcji i operatorów (24/10/2011) 3. Zarządzanie pamięcią: konstruktory, destruktory, przypisanie Konwersje (21/11/2011) 4. Dziedziczenie (05/12/2011) 5. Programowanie uogólnione: szablony (19/12/2011) 6. Projektowanie programów orientowanych obiektowo (02/01/2012) 7. Kolokwium zaliczeniowe (16/01/2012)
Plan na dziś Szablony funkcji definicja, konkretyzacja, specjalizacja Szablony klas Programowanie uogólnione Standardowa biblioteka szablonów STL Iteratory
Klasa vector liczb double class vector { // an almost real vector of doubles int sz; // the size double* elem; // a pointer to the elements int space; // size+free_space public: vector() : sz(0), elem(0), space(0) { } // default constructor explicit vector(int s) :sz(s), elem(new double[s]), space(s) { } // constructor vector(const vector&); // copy constructor vector& operator=(const vector&); // copy assignment ~vector() { delete[ ] elem; } // destructor double& operator[ ](int n) { return elem[n]; } // access: return reference int size() const { return sz; } // current size void resize(int newsize); void push_back(double d); void reserve(int newalloc); int capacity() const { return space; } }; // grow (or shrink) // add element // get more space // current available space
Szablony vector liczb double jest świetny... Ale potrzebujemy wektorów elementów dowolnego typu: vector<double> vector<int> vector<month> vector<record*> // vector of pointers vector< vector<record> > // vector of vectors vector<char> Chcemy by typ elementu był parametrem vector-a by vector mógł przechowywać elemeny typów wbudowanych oraz typów użytkownika Nie jest to żadna magia kompilatora możemy definiować swoje własne typy i funkcje sparametryzowane, czyli szablony
Zacznijmy od szablonów funkcji Przykład 1 max dwóch zmiennych: opcja 1: polimorfizm long maks(long a, long b) { return (a>b?a:b); } double maks(double a, double b) { return (a>b?a:b); } char maks(char a, char b) { return (a>b?a:b); } opcja 2: szablon (template) template <class T> T maks(t a, T b) { return (a>b?a:b); }
Szablony funkcji - składnia Definicja szablonu: template <class T> T maks(t a, T b) {... } lub template <typename T> T maks(t a, T b) {... } Konkretyzacja: cout << maks('a','b'); cout << maks(1,2); cout << maks<int>(1,2); UWAGI: T może być dowolnym typem: prostym strukturą klasą szablonem class i typename używamy zamiennie
Konkretyzacja szablonu funkcji Przykłady: cout << maks('a','b'); # b cout << maks(1,2); # 2 cout << maks(1.1,2.2); # 2.2 cout << maks(1,2.2); cout << maks<float>(1,2.2); # 2.2 # Błąd: niejednoznaczny typ cout << maks( leon, jan ); # wynik zależny od... adresów łańcuchów!
Specjalizacja szablonu funkcji template <class T> T maks(t a, T b) { return (a>b?a:b); } template <> char* maks(char* a, char* b) { return (strcmp(a,b)>0?a:b); } cout << maks( leon, jan ); # bez zmian! czy ktoś ma pomysł? cout << maks<char*>( leon, jan ); # leon - działa! kompilator odróżnia const char* od char*
Szablony funkcji Kiedy? te same operacje na różnych typach danych przeważnie służące do operowania na zbiorach, np. sortowanie, porównywanie itp. szeroko stosowane w standardowej bibliotece szablonów STL Dlaczego? elastyczne i szybkie krótszy, łatwiejszy do pielęgnacji kod bezpieczniejsze niż rzutowanie typów
Wróćmy do klasy vector, czyli szablony klas // an almost real vector of Ts: template <class T> class vector { // for all types T int sz; // the size T* elem; // a pointer to the elements int space; // size+free_space public: vector() : sz(0), elem(0), space(0) { } // default constructor explicit vector(int s) :sz(s), elem(new T[s]), space(s) { } // constructor vector(const vector&); // copy constructor vector& operator=(const vector&); // copy assignment ~vector() { delete[ ] elem; } // destructor }; T& operator[ ] (int n) { return elem[n]; } // access: return reference int size() const { return sz; } // the current size //
Szablon vector-a i jego konkretyzacja // an almost real vector of Ts: template<class T> class vector { // }; vector<double> vd; // T is double vector<int> vi; // T is int vector< vector<int> > vvi; // T is vector<int> // in which T is int vector<char> vc; // T is char vector<double*> vpd; // T is double* vector< vector<double>* > vvpd; // T is vector<double>* // in which T is double
vector<double> to po prostu // an almost real vector of doubles: class vector { int sz; // the size double* elem; // a pointer to the elements int space; // size+free_space public: vector() : sz(0), elem(0), space(0) { } // default constructor explicit vector(int s) :sz(s), elem(new double[s]), space(s) { } // constructor vector(const vector&); // copy constructor vector& operator=(const vector&); // copy assignment ~vector() { delete[ ] elem; } // destructor }; double& operator[ ] (int n) { return elem[n]; } // access: return reference int size() const { return sz; } // the current size //
A vector<char> to: // an almost real vector of chars: class vector { int sz; // the size char* elem; // a pointer to the elements int space; // size+free_space public: vector() : sz(0), elem(0), space(0) { } // default constructor explicit vector(int s) :sz(s), elem(new char[s]), space(s) { } // constructor vector(const vector&); // copy constructor vector& operator=(const vector&); // copy assignment ~vector() { delete[ ] elem; } // destructor char& operator[ ] (int n) { return elem[n]; } // access: return reference int size() const { return sz; } // the current size // };
Parametry szablonów klas kilka parametrów typu inne parametry (nie typu) template <typename T, typename S = char, int rozmiar=10> class dwulista { T kolumna1[rozmiar]; S kolumna2[rozmiar]; parametry nie typu w klasie są stałe public: void wloz(int poz, T pole1, S pole2) { if (poz>=0&&poz<rozmiar) { kolumna1[poz] = pole1; kolumna2[poz] = pole2; } else throw runtime_error("poza zakresem!"); }; T wez1(int poz) { if (poz>=0&&poz<rozmiar) return(kolumna1[poz]); else throw runtime_error("poza zakresem!"); }; S wez2(int poz) { if (poz>=0&&poz<rozmiar) return(kolumna2[poz]); else throw runtime_error("poza zakresem!"); }; }; wszystkie parametry mogą posiadać wartości domyślne
Inne cechy szablonów klas Szablony klas można specjalizować: szablon ogólny: template <typename T> class element{ }; specjalizacja: template <> class element<char> { }; Szablony klas można częściowo specjalizować: template <class gatunek1, class gatunek2> class krzyzowka { }; template <class gatunek1> class krzyzowka<gatunek1, ecoli> { };...jeśli kompilator pozwala... Każda wygenerowana w wyniku instancjacji klasa ma oddzielną kopię składników statycznych: static int element<char>::ileobiektow oraz są od siebie niezależne!! static int element<double>::ileobiektow
Szablony klas Kiedy? obsługa klas będących kontenerami (pojemnikami) obiektów lub innych danych Przykłady: zbiór łańcuchów różnego typu stos dowolnych elementów kolejki, wektory, listy, tablice...
Szablony klas (2) sparametryzowana klasa w momencie podstawienia parametrów (konkretyzacji szablonu) kompilator generuje klasę lub jej metodę szablony klas można specjalizować dla parametru stosować do pisania klas obsługujących kontenery (pojemniki) obiektów różnych typów krótszy i łatwiejszy do pielęgnacji kod w zasadzie komplementarne do obiektowości
Każda róża ma kolce Problemy kiepska diagnostyka błędów często naprawdę kiepskie komunikaty o błędach często dopiero w momencie konsolidacji wszystkie szablony muszą być w pełni zdefiniowane w każdej jednostce translacji umieszczaj definicje szablonów w plikach nagłówkowych Bjarne S. radzi używaj bibliotek opartych na szablonach takich jak standardowa biblioteka szablonów (STL) C++ np.., vector, sort() początkowo pisz tylko proste szablony aż nie zdobędziesz doświadczenia
Idźmy dalej... Umiemy już pisać klasy i funkcje, które zachowują się bardzo podobnie niezależnie od używanych typów danych używanie int-a nie różni się zbyt wiele od używania double-a używanie vector<int> nie różni się zbyt wiele od używania vector<string> Chcielibyśmy pisać programy w taki sposób, że nie musielibyśmy powtarzać pracy za każdym razem, kiedy stworzymy nowy rodzaj kontenera albo nieco zmodyfikujemy sposób interpretacji danych
Ideały Chcielibyśmy pisać programy w taki sposób, że nie musielibyśmy powtarzać pracy za każdym razem, kiedy stworzymy nowy rodzaj kontenera albo nieco zmodyfikujemy sposób interpretacji danych szukanie wartości w wektorze nie różni się zbyt wiele od szukania wartości w liście lub w tablicy szukanie łańcucha bez względu na wielkość liter nie różni się zbyt wiele od szukania łańcucha z uwzględnieniem wielkości liter rysowanie dokładnych wartości danych eksperymentalnych nie różni się zbyt wiele od rysowania wartości zaokrąglonych Kopiowanie pliku nie różni się zbyt wiele od kopiowania wektora
Ideały (2) Kod ma być: łatwy do czytania i łatwy do modyfikacji regularny, krótki, szybki Spójny dostęp do danych niezależnie jak są przechowywane, niezależnie od ich typu bezpieczeństwo typów Zwarte przechowywanie danych Szybkie operacje dostęp do danych, dodawanie i usuwanie danych Standardowe wersje najbardziej popularnych algorytmów kopiowanie, szukanie, sortowanie, sumowanie itp.
Programowanie ogólne Pisanie kodu, który może działać z różnymi typami przekazywanymi do niego jako argumenty, pod warunkiem, że typy te spełniają określone wymagania syntaktyczne I semantyczne Celem (dla użytkownika takiego kodu) jest Większa poprawność dzięki lepszej specyfikacji Szerszy zakres zastosowań możliwość ponownego wykorzystania Lepsza wydajność dzięki szerszemu użyciu optymalnych bibliotek pozbycie się niepotrzebnie wolnego kodu Od konkretu do abstrakcji W przeciwnym wypadku kod często puchnie 24
Przykład uogólniania (konkretne algorytmy) double sum(double array[], int n) // one concrete algorithm (doubles in array) { double s = 0; for (int i = 0; i < n; ++i ) s = s + array[i]; return s; } struct Node { Node* next; int data; }; int sum(node* first) { int s = 0; while (first) { s += first->data; first = first->next; } return s; } // another concrete algorithm (ints in list) 25
Przykład uogólniania (abstrakcyjna struktura danych) // pseudo-code for a more general version of both algorithms int sum(data) // somehow parameterize with the data structure { int s = 0; // initialize while (not at end) { // loop through all elements s = s + get value; // compute sum get next data element; } return s; // return result } Potrzebujemy trzech operacji na strukturze danych: sprawdzenie czy doszliśmy do końca (not at end) pobieranie wartości (get value) pobieranie kolejnego elementu (get next data element) 26
Przykład uogólniania (wersja STL) // Concrete STL-style code for a more general version of both algorithms template<class Iter, class T> T sum(iter first, Iter last, T s) { while (first!=last) { s = s + *first; ++first; } return s; } Niech użytkownik inicjuje akumulator: float a[] = { 1,2,3,4,5,6,7,8 }; double d = 0; d = sum(a,a+sizeof(a)/sizeof(*a),d); // Iter should be an Input_iterator // T should be something we can + and = // T is the accumulator type 27
Standardowa biblioteka szablonów Część biblioteki standardowej C++ Główny cel najbardziej ogólna, wydajna i elastyczna reprezentacja koncepcji (idei, algorytmów) oddzielne koncepcje oddzielnie w kodzie dowolne łączenie koncepcji tam gdzie ma to sens Aby programowanie było jak matematyka w myśl zasady: dobre programowanie jest matematyką twórcą jest Rosjanin, Aleksander Stiepanow
STL ok. 10 kontenerów i 60 algorytmów plus dodatkowe kontenery i algorytmy Boost.org, Microsoft, SGI, dokumentacja: SGI: http://www.sgi.com/tech/stl/ (jasny opis) Dinkumware: http://www.dinkumware.com/refxcpp.html (uwaga na kilka wersji biblioteki) Rogue Wave: http://www.roguewave.com/support/docs/sourcepro/stdlibug/index.html Microsoft: http://msdn.microsoft.com/en-us/library/y097fkab.aspx C++ Templates Tutorial: http://www.iis.sinica.edu.tw/~kathy/vcstl/templates.htm
Podstawowy model STL Algorytmy sort, find, search, copy, Kontenery iterators vector, list, map, hash_map, Separacja Algorytmy operują na danych, ale nie wiedzą nic o kontenerach Kontenery przechowują dane, ale nie wiedzą nic o algorytmach Algorytmy i kontenery współpracują poprzez iteratory Każdy kontener ma swoje własne typy iteratorów
Iteratory Para iteratorów definiuje sekwencję Początek (wskazuje na pierwszy element jeśli istnieje) Koniec (wskazuje na element za ostatnim elementem) begin: end: Iterator jest typem, który udostępnia operacje iteratorowe: ++ idź do następnego elementu * pobierz wartość == sprawdź czy dwa iteratory wskazują na ten sam element Niektóre iteratory udostępniają więcej operacji (np. --, + i [ ])
Najprostszy algorytm: find() begin: // Find the first element that equals a value template<class In, class T> In find(in first, In last, const T& val) { while (first!=last && *first!= val) ++first; return first; } void f(vector<int>& v, int x) // find an int in a vector { vector<int>::iterator p = find(v.begin(),v.end(),x); if (p!=v.end()) { /* we found x */ } // } end: Możemy pominąć różnice pomiędzy kontenerami
find() ogólny zarówno dla typu elementów jak i kontenera void f(vector<int>& v, int x) // works for vector of ints { vector<int>::iterator p = find(v.begin(),v.end(),x); if (p!=v.end()) { /* we found x */ } // } void f(list<string>& v, string x) // works for list of strings { list<string>::iterator p = find(v.begin(),v.end(),x); if (p!=v.end()) { /* we found x */ } // } void f(set<double>& v, double x) // works of set of doubles { set<double>::iterator p = find(v.begin(),v.end(),x); if (p!=v.end()) { /* we found x */ } // }
Algorytmy i iteratory Iterator wskazuje (odnosi się do, oznacza) element w sekwencji Koniec sekwencji to jeden za ostatnim elementem nie ostatni element konieczne by elegancko reprezentować pustą sekwencję jeden-za-ostatnim-elementem nie jest elementem możesz porównać iterator odnoszący się do niego nie możesz wyłuskać go (przeczytać jego wartości) Zwrócenie końca sekwencji oznacza nieznalezienie lub niepowodzenie operacji jakiś iterator: the end: Pusta sekwencja 0 1 2 3
Dziś najważniejsze było... Szablony funkcji i klas pojęcia: parametryzacja, konkretyzacja, specjalizacja Programowanie ogólne piękno matematyki w programowaniu Używaj bibliotek opartych na szablonach np. STL Iteratory
A po Nowym Roku... 2.01 godziny rektorskie (wykład się nie odbędzie) materiały do wykładu 6 na stronie www nie będą jednak obowiązywały do kolokwium 16.01 kolokwium zaliczeniowe przykładowe zadania pojawią się na początku roku