Informatyka 2 Politechnika Białostocka - Wydział Elektryczny Elektrotechnika, semestr III, studia stacjonarne I stopnia Rok akademicki 2008/2009 Wykład nr 3 (05.11.2008)
Rok akademicki 2008/2009, Wykład nr 3 2/35 Plan wykładu nr 3 Programowanie obiektowe w języku C++ klasy a pliki nagłówkowe funkcje zaprzyjaźnione z klasą konstruktor i destruktor przeładowanie operatorów w klasie dziedziczenie
Rok akademicki 2008/2009, Wykład nr 3 3/35 Przykład nr 1 - Prosta klasa (1/2) /* Name: klasa1.cpp Copyright: Politechnika Białostocka, Wydział Elektryczny Author: Jarosław Forenc (jarekf@pb.edu.pl) Date: 12-06-2007 Description: Przykład prostej klasy */ #include <iostream> #include <string.h> using namespace std; class osoba private: char imie[20]; char nazwisko[30]; int wiek; public: void zapisz(char *i, char *n, int w); void drukuj(); ;
Rok akademicki 2008/2009, Wykład nr 3 4/35 Przykład nr 1 - Prosta klasa (2/2) void osoba::zapisz(char *i, char *n, int w) strcpy(imie,i); strcpy(nazwisko,n); wiek = w; Jan Kowalski 30 void osoba::drukuj() cout << imie << " " << nazwisko; cout << " " << wiek << endl; int main() osoba os1; os1.zapisz("jan","kowalski",30); os1.drukuj(); system("pause"); return 0;
Rok akademicki 2008/2009, Wykład nr 3 5/35 Wskaźnik this funkcje wywoływane są zawsze na rzecz konkretnego obiektu do wnętrza funkcji przekazywany jest niejawnie wskaźnik do tego obiektu - tym adresem funkcja inicjalizuje swój wskaźnik zwany this w rzeczywistości funkcja zapisz(): void osoba::zapisz(char *i, char *n, int w) strcpy(imie,i); strcpy(nazwisko,n); wiek = w; ma następującą postać: void osoba::zapisz(char *i, char *n, int w) strcpy(this->imie,i); strcpy(this->nazwisko,n); this->wiek = w;
Rok akademicki 2008/2009, Wykład nr 3 6/35 Klasy a pliki nagłówkowe zazwyczaj definicje klas umieszcza się w plikach nagłówkowych *.h, natomiast definicje funkcji składowych w plikach *.cpp Przykład:
Rok akademicki 2008/2009, Wykład nr 3 7/35 Klasy a pliki nagłówkowe pliki *.h dołącza się do plików *.cpp dyrektywą #include nazwa.h w plikach nagłówkowych nie mogą występować definicje funkcji, chyba, Ŝe są one umieszczone bezpośrednio w klasie Przykład: osoba.h class osoba private: char imie[20]; char nazwisko[30]; int wiek; public: void zapisz(char *i, char *n, int w); void drukuj(); ;
Rok akademicki 2008/2009, Wykład nr 3 8/35 Klasy a pliki nagłówkowe Przykład: w pliku osoba.cpp umieszczone są definicje funkcji składowych klasy osoba osoba.cpp #include <iostream> #include <string.h> #include "osoba.h" using namespace std; void osoba::zapisz(char *i, char *n, int w) strcpy(imie,i); strcpy(nazwisko,n); wiek = w; void osoba::drukuj() cout << imie << " " << nazwisko; cout << " " << wiek << endl;
Rok akademicki 2008/2009, Wykład nr 3 9/35 Klasy a pliki nagłówkowe Przykład: klasę osoba wykorzystujemy w pliku main.cpp main.cpp #include <iostream> #include <string.h> #include "osoba.h" int main() osoba os1; os1.zapisz("jan","kowalski",30); os1.drukuj(); system("pause"); return 0;
Rok akademicki 2008/2009, Wykład nr 3 10/35 Klasy a pliki nagłówkowe Przykład: w celu uniknięcia wielokrotnego dołączania tego samego pliku nagłówkowego stosuje się dyrektywy kompilacji warunkowej #ifndef, #define #endif osoba.h #ifndef OSOBA_H_ #define OSOBA_H_ class osoba private: char imie[20]; char nazwisko[30]; int wiek; public: void zapisz(char *i, char *n, int w); void drukuj(); ; #endif
Rok akademicki 2008/2009, Wykład nr 3 11/35 Funkcje zaprzyjaźnione z klasą funkcja zaprzyjaźniona z klasą to funkcja, która nie będąc składnikiem klasy ma dostęp do wszystkich (takŝe prywatnych) składników klasy class osoba private: char imie[20]; char nazwisko[30]; int wiek; public: void zapisz(char *i, char *n, int w); friend void funkcja(); ; void funkcja() osoba Nowak; Nowak.wiek = 20; funkcja moŝe przyjaźnić się z więcej niŝ jedną klasą nie ma znaczenia, w którym miejscu w klasie pojawia się deklaracja przyjaźni (sekcja private, protected, public) funkcja zaprzyjaźniona moŝe być funkcją składową innej klasy przyjaźń nie jest dziedziczona
Rok akademicki 2008/2009, Wykład nr 3 12/35 Konstruktor class osoba private: char imie[20]; char nazwisko[30]; int wiek; public: osoba(char *i, char *n, int w); void drukuj(void); ; osoba::osoba(char *i,char *n,int w) strcpy(imie,i); strcpy(nazwisko,n); wiek = w; konstruktor słuŝy do nadania wartości początkowych obiektowi (choć nie tylko) wywoływany jest w momencie tworzenia obiektu nazwa konstruktora jest taka sama jak nazwa klasy dla konstruktora nie określamy typu zwracanej wartości (nie moŝe tam wystąpić nawet void) moŝe mieć kilka wariantów czyli róŝną liczbę parametrów (przeładowanie) bez konstruktora: z konstruktorem: osoba os1; os1.zapisz( Jan, Kowalski,30); osoba os1( Jan, Kowalski,30); lub osoba os1=osoba( Jan, Kowalski,30);
Rok akademicki 2008/2009, Wykład nr 3 13/35 Przeładowanie nazw funkcji przeładowanie nazw funkcji polega na tym, Ŝe w danym zakresie waŝności jest więcej niŝ jedna funkcja o tej samej nazwie: int oblicz(int x); int oblicz(int x, int y); int oblicz(int x, double y); to, która funkcja zostanie w danym przypadku uaktywniona, zaleŝy od liczby i typu argumentów przy przeładowaniu waŝna jest tylko odmienność listy argumentów, natomiast typ zwracany przez funkcję nie jest brany pod uwagę int oblicz(int x); float oblicz(int x); // błąd!!! w rzeczywistości przy przeładowaniu kompilator zmienia nazwy funkcji int oblicz(int x); int oblicz(int x, int y); int oblicz(int x, double y); oblicz_fi oblicz_fii oblicz_fid
Rok akademicki 2008/2009, Wykład nr 3 14/35 Konstruktor Lista inicjalizacyjna konstruktora: class abc int a; int b; int c; abc(int aa, int bb, int cc); ; : a(aa), b(bb) abc::abc(int aa, int bb, int cc): a(aa), b(bb) c = cc; specyfikuje jak naleŝy zainicjować niestatyczne składniki klasy a(aa) - składnik a naleŝy zainicjować wartością wyraŝenia w nawiasie (aa) lista inicjalizacyjna pojawia się tylko przy definicji konstruktora kolejność umieszczania elementów na liście inicjalizacyjnej nie ma znaczenia Konstruktor domniemany: jest to konstruktor, który moŝna wywołać bez Ŝadnego argumentu jeśli w klasie nie ma takiego konstruktora, to kompilator sam go wygeneruje
Rok akademicki 2008/2009, Wykład nr 3 15/35 Konstruktor kopiujący konstruktor kopiujący słuŝy do skonstruowania obiektu, który jest kopią innego, juŝ istniejącego obiektu tej klasy: klasa::klasa(klasa &); klasa::klasa(const klasa &); konstruktor kopiujący nie jest obowiązkowy, jeśli go nie zdefiniujemy wtedy kompilator wygeneruje go sobie sam (kopiowanie składnik po składniku ) konstruktor kopiujący moŝemy sami wywołać, np. klasa obiekt1; klasa obiekt2 = obiekt1; konstruktor kopiujący moŝe być wywołany niejawnie: podczas przesyłania argumentów do funkcji - jeśli argumentem funkcji jest obiekt klasy klasa, a przesyłanie odbywa się przez wartość podczas, gdy funkcja jako swój wynik, zwraca (przez wartość) obiekt danej klasy konstruktor kopiujący jest niezbędny, gdy daną składową w klasie jest wskaźnik
Rok akademicki 2008/2009, Wykład nr 3 16/35 Destruktor class osoba private: char imie[20]; char nazwisko[30]; int wiek; public: osoba(char *i, char *n, int w); ~osoba(void); void drukuj(void); ; osoba::~osoba()... destruktor wywoływany jest wtedy, gdy obiekt jest likwidowany (destruktor moŝna wywołać jawnie - nie spowoduje on jednak usunięcia obiektu) jego nazwa jest taka sama jak nazwa klasy, ale przed jego nazwą umieszcza się znak ~ zadaniem destruktora jest posprzątanie po obiekcie, np. zwolnienie pamięci dla destruktora nie określamy typu zwracanej wartości
Rok akademicki 2008/2009, Wykład nr 3 17/35 Przykład nr 2 - Konstruktor, destruktor (1/2) /* Name: klasa2.cpp Copyright: Politechnika Białostocka, Wydział Elektryczny Author: Jarosław Forenc (jarekf@pb.edu.pl) Date: 12-06-2007 Description: Przykład zastosowania konstruktora i destruktora */ #include <iostream> #include <string.h> using namespace std; class osoba private: char *imie; char *nazwisko; int wiek; public: osoba(char *i, char *n, int w); ~osoba(); void drukuj(); ;
Rok akademicki 2008/2009, Wykład nr 3 18/35 Przykład nr 2 - Konstruktor, destruktor (2/2) osoba::osoba(char *i, char *n, int w) imie = new char[strlen(i)+1]; nazwisko = new char[strlen(n)+1]; strcpy(imie,i); strcpy(nazwisko,n); wiek = w; Jan Kowalski 30 osoba::~osoba() delete [] imie; delete [] nazwisko; void osoba::drukuj() cout << imie << " " << nazwisko; cout << " " << wiek << endl; int main() osoba os1("jan","kowalski",30); os1.drukuj(); return 0;
Rok akademicki 2008/2009, Wykład nr 3 19/35 Przykład nr 3 - Konstruktor, destruktor (1/1) #include <iostream> #include <string.h> using namespace std; class test char name[15]; public: test(char *n) strcpy(name,n); cout << "Konstruktor obiektu: " << name << endl; ~test() cout << "Destruktor obiektu: " << name << endl; ; int main() test t1("t1"), t2("t2"); system("pause"); test t3("t3"); system("pause"); system("pause"); return 0;
Rok akademicki 2008/2009, Wykład nr 3 20/35 Przeładowanie operatorów w w klasie przeładowanie operatora polega na nadaniu mu specjalnego znaczenia w momencie, gdy stoi on obok obiektu jakiejś klasy przeładowanie operatora dokonuje się definiując własną funkcję o postaci: słowo kluczowe nazwa operatora (np. +, -, itp.) typ_zwracany operator @ (argumenty) // ciało funkcji jako co najmniej jeden z argumentów musi wystąpić obiekt klasy zdefiniowanej przez uŝytkownika moŝna przeładować praktycznie wszystkie operatory, nie moŝna wymyślać swoich operatorów, nie moŝna zmieniać priorytetu operatorów automatycznie tworzone są operatory: przypisania ( = ), pobrania adresu ( & ), new, new [], delete i delete [] (tworzenie i usuwanie obiektów) ten sam operator moŝna przeładować wielokrotnie, ale za kaŝdym razem funkcja operatorowa musi mieć inny typ lub kolejność argumentów
Rok akademicki 2008/2009, Wykład nr 3 21/35 Przeładowanie operatorów w w klasie operatory mogą być przeładowane jako funkcja globalna lub jako funkcja składowa klasy Operator jako funkcja globalna: macierz operator - (macierz mac1, macierz mac2) macierz wynik(mac1.size); for (int i=0;i<mac1.size;i++) for (int j=0;j<mac1.size;j++) *(wynik.m+i*wynik.size+j)=*(mac1.m+i*mac1.size+j) -*(mac2.m+i*mac2.size+j); return wynik; aby funkcja globalna mogła korzystać z pól prywatnych klasy musi być funkcją zaprzyjaźnioną z klasą: friend macierz operator - (macierz, macierz); umieszczone w definicji klasy
Rok akademicki 2008/2009, Wykład nr 3 22/35 Przeładowanie operatorów w w klasie Operator jako funkcja składowa klasy: macierz macierz::operator - (macierz mac2) macierz wynik(size); for (int i=0;i<size;i++) for (int j=0;j<size;j++) *(wynik.m+i*wynik.size+j) = *(m+i*size+j) - *(mac2.m+i*mac2.size+j); return wynik; do funkcji przekazywany jest tylko jeden argument (drugi), pierwszy przekazywany jest domyślnie przez wskaźnik this funkcja operatorowa, która jest składową klasy wymaga, aby obiekt stojący po lewej stronie operatora był obiektem tej klasy operatory >> i << moŝna przeładowywać tylko jako funkcje globalne
Rok akademicki 2008/2009, Wykład nr 3 23/35 Przykład nr 4 - Przeładowanie operatorów w w klasie (1/5) /* Description: Klasa wykonująca operacje na macierzach kwadratowych */ /* Błędny wynik przy sumowaniu i róŝnicy elementów o indeksach [0][0]!!! */ /* Gdzie jest błąd??? */ #include <iostream> #include <stdlib.h> #include <time.h> using namespace std; class macierz private: int size; /* rozmiar macierzy */ int *m; /* wskaznik na macierz */ public: macierz(int); /* konstruktor */ macierz(const macierz &); /* konstruktor kopiujacy */ ~macierz(); /* destruktor */ void generuj(int,int); macierz operator + (macierz); friend macierz operator - (macierz, macierz); friend ostream & operator << (ostream &, macierz); ;
Rok akademicki 2008/2009, Wykład nr 3 24/35 Przykład nr 4 - Przeładowanie operatorów w w klasie (2/5) macierz::macierz(int n) size = n; m = new int[n*n]; for (int i=0;i<size;i++) for (int j=0;j<size;j++) *(m+i*size+j) = 0; konstruktor klasy macierz (z dynamicznym przydziałem pamięci) macierz::macierz(const macierz &mac) size = mac.size; m = new int[size*size]; for (int i=0;i<size;i++) for (int j=0;j<size;j++) *(m+i*size+j) = *(mac.m+i*size+j); macierz::~macierz() delete [] m; destruktor klasy macierz konstruktor kopiujący, niezbędny do prawidłowego wykonania przypisania A=B, gdy A i B są obiektami klasy macierz
Rok akademicki 2008/2009, Wykład nr 3 25/35 Przykład nr 4 - Przeładowanie operatorów w w klasie (3/5) void macierz::generuj(int a, int b) srand(time(null)); for (int i=0;i<size;i++) for (int j=0;j<size;j++) *(m+i*size+j) = rand()%(b-a+1)+a; ostream & operator << (ostream &ekran, macierz mac) for (int i=0;i<mac.size;i++) for (int j=0;j<mac.size;j++) cout << *(mac.m+i*mac.size+j) << " "; cout << endl; return ekran; funkcja generująca elementy macierzy z zakresu <a,b> przeładowanie operatora << poza klasą (inaczej nie moŝna!!!)
Rok akademicki 2008/2009, Wykład nr 3 26/35 Przykład nr 4 - Przeładowanie operatorów w w klasie (4/5) macierz macierz::operator + (macierz mac) macierz wynik(size); for (int i=0;i<size;i++) for (int j=0;j<size;j++) *(wynik.m+i*size+j) = *(m+i*size+j) + *(mac.m+i*size+j); return wynik; przeładowanie operatora + w klasie macierz operator - (macierz mac1, macierz mac2) macierz wynik(mac1.size); for (int i=0;i<mac1.size;i++) for (int j=0;j<mac1.size;j++) *(wynik.m+i*wynik.size+j) = *(mac1.m+i*mac1.size+j) - *(mac2.m+i*mac2.size+j); return wynik; przeładowanie operatora - poza klasą
Rok akademicki 2008/2009, Wykład nr 3 27/35 Przykład nr 4 - Przeładowanie operatorów w w klasie (5/5) int main() macierz m1(3), m2(3), m3(3); cout << m1 << endl << m2 << endl; m1.generuj(10,20); m2.generuj(-10,10); cout << m1 << endl << m2 << endl; m3 = m1 + m2; cout << m3 << endl; m3 = m1 - m2; cout << m3 << endl; system ("pause"); return 0; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 20 12 18 11 16 12 10 15 19-7 1 6 1 10-3 4-9 3 0 13 24 12 26 9 14 6 22 0 11 12 10 6 15 6 24 16
Rok akademicki 2008/2009, Wykład nr 3 28/35 Dziedziczenie dziedziczenie jest to technika pozwalającą na definiowanie nowej klasy przy wykorzystaniu klasy juŝ istniejącej dziedziczenie polega na przejmowaniu jednej klasy (klasy bazowej, podstawowej) przez inną klasę (klasę pochodną) przy dziedziczeniu, w skład obiektów klasy pochodnej automatycznie wchodzą pola klasy bazowej, a do obiektów klasy pochodnej moŝemy stosować operacje zdefiniowane przez funkcje składowe klasy bazowej /* klasa podstawowa (bazowa) */ class osoba char *imie; char *nazwisko; int wiek; public: osoba(char *i, char *n, int w); ~osoba() void drukuj(); ; /* klasa pochodna klasy osoba */ class student : public osoba char *wydzial; int semestr; public: student(char *i, char *n, int w, char *wy, int s); ~student() void drukuj(); void promocja(); ;
Rok akademicki 2008/2009, Wykład nr 3 29/35 Dziedziczenie class student : public osoba lista pochodzenia nazwa klasy podstawowej sposób dziedziczenia (specyfikator dostępu) w klasie pochodnej moŝna zdefiniować dodatkowe dane składowe i dodatkowe funkcje składowe w klasie pochodnej moŝna zdefiniować dane i funkcje o takich samych nazwach jak w klasie podstawowej (dane i funkcje z klasy podstawowej są wtedy zasłaniane) do zasłoniętych elementów klasy podstawowej moŝna odwoływać się uŝywając operatora zakresu - ::, np. student Nowak( Jan, Nowak,20, WE,2); - deklaracja obiektu klasy student Nowak.drukuj(); - wywołanie funkcji z klasy pochodnej student Nowak.osoba::drukuj(); - wywołanie funkcji z klasy podstawowej osoba
Rok akademicki 2008/2009, Wykład nr 3 30/35 Dziedziczenie Sposób dziedziczenia: sposób dziedziczenia klasa podstawowa private protected public private - - - protected private protected protected public private protected public jeśli nie podamy sposobu dziedziczenia, to domyślnie będzie to private podczas dziedziczenia nie są dziedziczone: konstruktor, destruktor i operator przypisania - = moŝliwe jest dziedziczenie wielokrotne, tzn. klasa pochodna moŝe być klasą podstawową dla innej klasy
Rok akademicki 2008/2009, Wykład nr 3 31/35 Przykład nr 5 - Dziedziczenie (1/4) #include <iostream> #include <string.h> using namespace std; class osoba private: char *imie; char *nazwisko; int wiek; public: osoba(char*,char *,int); ~osoba(); void drukuj(); ; klasa podstawowa (bazowa) klasa pochodna class student : public osoba private: char *wydzial; int semestr; public: student(char *,char *,int,char *,int); ~student(); void drukuj(); void promocja(); ;
Rok akademicki 2008/2009, Wykład nr 3 32/35 Przykład nr 5 - Dziedziczenie (2/4) osoba::osoba(char *i, char *n, int w) imie = new char[strlen(i)+1]; nazwisko = new char[strlen(n)+1]; strcpy(imie,i); strcpy(nazwisko,n); wiek = w; konstruktor klasy osoba destruktor klasy osoba osoba::~osoba() delete [] imie; delete [] nazwisko; void osoba::drukuj() cout << imie << " " << nazwisko; cout << " " << wiek << endl;
Rok akademicki 2008/2009, Wykład nr 3 33/35 Przykład nr 5 - Dziedziczenie (3/4) student::student(char *i, char *n, int w, char *wy, int s) : osoba(i,n,w) wydzial = new char[strlen(wy)+1]; strcpy(wydzial,wy); semestr = s; student::~student() delete [] wydzial; void student::drukuj() osoba::drukuj(); cout << "Wydzial: " << wydzial << " Semestr: " << semestr << endl; void student::promocja() semestr++; konstruktor klasy student destruktor klasy student lista inicjalizacyjna konstruktora klasy student zawierająca wywołanie konstruktora klasy podstawowej (osoba)
Rok akademicki 2008/2009, Wykład nr 3 34/35 Przykład nr 5 - Dziedziczenie (4/4) int main() student st1("jan","kowalski",20,"we",2); st1.drukuj(); st1.promocja(); st1.drukuj(); st1.osoba::drukuj(); system("pause"); return 0; jako pierwszy zostanie wywołany konstruktor klasy podstawowej (osoba) a po nim konstruktor klasy pochodnej (student) kolejność wywołania destruktorów jest odwrotna w stosunku do konstruktorów jako pierwszy jest wywoływany destruktor klasy student, a po nim destruktor klasy osoba
Rok akademicki 2008/2009, Wykład nr 3 35/35 Koniec wykładu nr 3 Dziękuj kuję za uwagę!