Wstęp do programowania obiektowego KLASA ISTREAM KLASA OSTREAM MANIPULATORY STRUMIENIOWE STRUKTURY W C++ DOMYŚLNE WARTOŚCI PARAMETRÓW KONSTRUKTORY I DESTRUKTORY KLAS POCHODNYCH KONSTRUKTOR KOPIUJĄCY POLIMORFIZM 1
KLASA ISTREAM 2
Obiekty klasy istream mogą czytać i interpretować sekwencję znaków. Z tej klasy dziedziczą: iostream (strumień uniwersalny), ifstream (wejście z pliku) istringstream (wejście z napisu). 3
Wybrane metody klasy istream int get(); // czyta pojedynczy znak int peek(); // podgląd następnego znaku istream& putback(char c); // zwraca znak do strum. istream& ignore(int n=1); // opuszcza n znaków istream& getline (char* s, streamsize n ); istream& getline (char* s, streamsize n, char delim ); // czyta znaki ze strumienia (maksymalnie n) dopóki nie napotka na delimiter (domyślnie '\n') 4
Przykład użycia get, peek, ignore int kod; if (cin.peek() == @ ) // jeżeli następny znak to @ cin.ignore(); // opuść go, else kod = cin.get(); // wczytaj znak 5
Przykład z putback i getline cout << "Please, enter a number or a word: "; char c = cin.get(); if ( (c >= '0') && (c <= '9') ) { int n; cin.putback (c); cin >> n; cout << "You entered a number: " << n << '\n'; } else { string str; cin.putback (c); getline (cin,str); cout << "You entered a word: " << str << '\n'; } 6
KLASA OSTREAM 7
Obiekty klasy ostream mogą pisać sekwencję znaków. Z tej klasy dziedziczą: iostream (strumień uniwersalny), ofstream (wyjście do pliku) ostringstream (wyjście do napisu). 8
Wybrane metody klasy ostream int put(); //wyprowadza znak ostream& write ( const char* s, streamsize n ); //wyprowadza n znaków z s (tablica char[]) 9
Przykład: maszyna do pisania #include <iostream> #include <fstream> // std::cin, std::cout // std::ofstream int main () { std::ofstream outfile ("test.txt"); char ch; } std::cout << "Type some text (type a dot to finish):\n"; do { ch = std::cin.get(); outfile.put(ch); } while (ch!='.'); return 0; // wszystko co pisze użytkownik zapisuje do pliku test.txt 10
PRZYKŁAD: KOPIOWANIE PLIKU ZNAK PO ZNAKU main() { ifstream we( TEST1.TXT ); ofstream wy( TEST2.TXT ); if (!we!wy) { cerr << Błąd otwarcia pliku ; return 1; } char zn; while (we.get(zn) && wy.put(zn)); } 11
MANIPULATORY STRUMIENIOWE 12
MANIPULATORY STRUMIENIOWE Manipulatory, to funkcje zmieniające stan strumienia Większość zdefiniowana jest w pliku nagłówkowym <iomanip.h>. Można również definiować własne manipulatory. Częściej używane to: endl - przejście do nowego wiersza ends - dodanie znaku \0 do strumienia dec - postać dziesiętna liczby hex - postać szesnastkowa liczby oct - postać ósemkowa liczby flush - opróżnienie bufora strumienia setw(int w) - ustawienie szerokości pola setprecision(int p) - ustawienie liczby miejsc po przecinku setfill(int c) - określenie znaku wypełniającego 13
Przykłady użycia manipulatorów int n= 255; cout << hex << n << endl; // wypisuje liczbę szesnastkową double a=1.2355, b=2.5, c=3.14; cout << setprecision(2) << fixed << a << '\t' << b << '\t' << c << endl; // wypisuje liczby 2 znakami po przecinku, rozdzielone tabulacją cout << scientific << a << '\t' << b << '\t' << c << endl; // wypisuje liczby w formacie wykładniczym, precyzja jak przedtem 14
STRUKTURY W C++ 15
Struktury w C++ są deklarowane tak jak w C, jednak oprócz pól mogą zawierać także funkcje (tzw. metody) i mogą dziedziczyć z innych klas i struktur. Struktura różni się w C++ od klasy wyłącznie domyślnym zakresem widoczności jej pól i metod - dla klasy jest to private a dla struktur public. Używanie struktur zamiast klas nie jest dobrą praktyką programowania, gdyż utrudnia czytanie programu. 16
Przykład struktury w C++ struct miasto { long ludnosc; char* rzeka; miasto(long ll,char *rz): ludnosc(ll), rzeka(strdup(rz)) {}; inline char* get_rzeka() {return rzeka}; ~miasto() { free((void*)rzeka); } }; 17
DOMYŚLNE WARTOŚCI PARAMETRÓW 18
Domyślne wartości parametrów W C++ można zdefiniować domyślne wartości parametru/ów. W tym celu w nagłówku funkcji/metody/konstruktora na parametrze formalnym wykonujemy podstawienie. Parametry domyślne muszą występować na końcu listy argumentów. W wywołaniu możemy opuścić jedną lub więcej wartość parametru aktualnego, dla której zdefiniowano domyślną wartość 19
Przykład domyślnych wartości parametrów class Punkt { float x,y; public: Punkt(float xx = 5, float yy=7):x(xx), y(yy) { } }; Punkt p1(1,3), p2(8), p3; 20
KONSTRUKTORY I DESTRUKTORY KLAS POCHODNYCH 21
Konstruktory i destruktory klas pochodnych Konstruktory nie są dziedziczone. Jeśli w klasie nie zdefiniowaliśmy konstruktora, to zostanie użyty konstruktor domyślny. Aby powstał obiekt klasy pochodnej, musi być najpierw utworzony podobiekt klasy nadrzędnej wchodzący w jego skład. 22
Standardowo wywoływany jest konstruktor bezparametrowy (lub domyślny) klasy nadrzędnej. Aby do konstrukcji podobiektu klasy bazowej użyć konstruktora innego niż bezparametrowy musimy w klasie pochodnej zdefiniować konstruktor, który wywoła odpowiedni konstruktor klasy bazowej poprzez listę inicjalizacyjną 23
Przykład takiego wywołania class Point { protected: int x; int y; Point(int x, int y) : x(x), y(y) { } }; class Pixel: public Point { public: int color; Pixel(int x, int y, int color) : Punkt(x,y), color(color) { } }; 24
Składowe odziedziczone są tu inicjowane w swojej bazowej klasie (przekazywane jako parametr), składowe nowe w klasie pochodnej. Konstruktory wywoływane są od góry struktury dziedziczenia. Takie łańcuchy wywołań konstruktorów są tworzone dla każdego nowo tworzonego obiektu. Jeśli klasa dziedziczy z kilku klas, to konstruktory klas bazowych są wywoływane w kolejności ich wystąpienia na liście dziedziczenia (problematyczne). 25
Destruktory wywoływane są w kolejności odwrotnej do konstruktorów ( od dołu struktury dziedziczenia). 26
KONSTRUKTOR KOPIUJĄCY 27
Definiowanie własnego konstruktora kopiującego jest niezbędne, gdy klasa zawiera jakieś dane dynamiczne (tworzy obiekty dynamiczne, a nawet zwykłego c-stringa) Podobnie niezbędny jest wtedy destruktor Konstruktor kopiujący przyjmuje jako parametr stały obiekt aktualnej klasy (przekazywany przez stałą referencję) 28
Przykład danych dynamicznych class Osoba { char* imie; public: Osoba() : imie(strcpy(new char[9], "nieznane")) { } Osoba(const char* n) : imie(strcpy(new char[strlen(n)+1], n)) { } Osoba(const Osoba& os) : imie(strcpy(new char[strlen(os. imie)+1], os. imie)) { } ~Osoba() { delete [ ] imie; } }; 29
POLIMORFIZM 30
Polimorfizm Polimorfizm - wskaźniki i referencje mogą dotyczyć obiektów różnego typu, a wywołanie metody dla referencji spowoduje zachowanie odpowiednie dla rzeczywistego typu obiektu wywoływanego. Jeśli dzieje się to w czasie działania programu, to nazywa się to późnym wiązaniem lub wiązaniem dynamicznym. Niektóre języki udostępniają bardziej statyczne (w trakcie kompilacji) rozwiązania polimorfizmu - na przykład przeciążanie operatorów i szablony w C++. 31
Typy statyczne i dynamiczne class A { public: void fun() { cout << "Metoda z klasy A"<< endl; } }; class B : public A { void fun() { cout << "Metoda z klasy B"<< endl; } }; A a, *wska = new A, *wskb = new B; A jest obiektem statycznym klasy A. wska jest wskaźnikiem typu A* do obiektu klasy A. Typem statycznym obiektu wskazywanego przez pa jest A i typem dynamicznym również A wskb jest wskaźnikiem typu A* do obiektu klasy B. Typem statycznym obiektu wskazywanego przez wskb jest A, ale typem dynamicznym jego typ prawdziwy, czyli B; 32
Wywołując zwykłą metodę fun() dla obiektów a, *wska, *wskb, decydować będzie typ statyczny obiektu, czyli wywoła się treść z klasy A. Aby wywoływać metodę fun() odpowiednią dla dynamicznego typu obiektu należy w klasie bazowej dodać słówko virtual. Metodę nazywamy wirtualną, a wywołanie polimorficznym. 33
Metoda wirtualna i wywołanie polimorficzne class A { public: virtual void fun() { cout << "Metoda z klasy A"<< endl; } }; class B : public A { void fun() { cout << "Metoda z klasy B"<< endl; } };... A *wskb->fun(); //wypisze "Metoda z klasy B" 34
Warunki wystąpienia polimorfizmu (późnego wiązania) w C++ 1. wywołanie jest poprzez wskaźnik lub referencję typu bazowego (tutaj A*); 2. prawdziwym typem obiektu, na rzecz którego następuje wywołanie, jest typ pochodny (tutaj B); 3. metoda jest wirtualna (virtual); 4. metoda została przedefiniowana w klasie B (ta sama nazwa i sygnatura!). 35
Jawne wywołanie niepolimorficzne funkcji wirtualnej Wywołujemy metodę z klasy bazowej przez nazwę kwalifikowaną: wskb>a::fun(); Nie można jawnie wywoływać polimorficznie: a.b::fun() // błąd! wskb>b::fun(); // błąd! 36
Cena polimorfizmu Ceną za polimorfizm jest pewna utrata wydajności wykonania oraz narzut pamięci. Dla wywołań na rzecz obiektów klas niepolimorficznych odpowiednia metoda jest wybierana (i włączana do kodu wykonywalnego) już w czasie kompilacji na podstawie typu statycznego, jest to tzw. wczesne wiązanie. Późne wiązanie oznacza konieczność sprawdzenia rzeczywistego typu obiektu podczas wykonania programu i wybranie odpowiedniej metody. 37
Inne języki obiektowe W większości języków obiektowych (np. Java, Python) wszystkie wywołania metod są polimorficzne (wirtualne). W C++ mamy możliwość stosowania szybszego i lżejszego pamięciowo statycznego wiązania (kiedy niepotrzebny jest polimorfizm). 38
Polimorfizm i referencje W wywołaniach polimorficznych C++ można używać referencji (analogicznie do wskaźników). A a, *wska = new A, *wskb = new B; A &ref_a = a, &ref_aa = wska, &ref_b = wskb; ref_a.fun(); ref_aa.fun(); ref_b.fun(); //potencjalnie wirtualne 39