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
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).
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
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
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[])
Przykład: maszyna do pisania
#include <iostream> // std::cin, std::cout
#include <fstream> // 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
P
RZYKŁAD: K
OPIOWANIE PLIKUZNAK 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));
}
M ANIPULATORY STRUMIENIOWE
12
M ANIPULATORY 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
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++
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);
} };
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ść
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
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ą
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).
Destruktory wywoływane są w
kolejności odwrotnej do konstruktorów („od dołu” struktury dziedziczenia).
26
KONSTRUKTOR
KOPIUJĄCY
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;
} };
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++.
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.
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!).
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.
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