Programowanie obiektowe III rok EiT 2017-03-28 09:38:18 Programowanie obiektowe III rok EiT dr inż. Jerzy Kotowski Wykład X
Klasówka Program wykładu Język C++ Przykład problemu Klasy, c.d. Wskaźnik this Klasy z konstruktorem i destruktorem Przykład problemu Podstawy języka JAVA Klasówka
Literatura C++ for C programmers, Ira Pohl, The Benjamin/Cummings Publishing Company, Inc. Symfonia C++, Jerzy Grębosz, Oficyna Kallimach, Kraków 1999
Wskaźnik this Słowo kluczowe this oznacza niejawnie deklarowany self-referential poiner. Jednym z obszarów wykorzystania this jest tworzenie konstrukorów i destruktorów zawierających wprowadzane przez programistę funkcje do alokacji pamięci Używanie wskaźnika this może spowodować wzrost efektywności programu Przykład użycia: sztuka dla sztuki class X { char c; public: void init(char b) {c=b;} char valc() {return(this->c);} char val() {return c;} }; Wygoda: return *this; Typ wskaźnika: X const * To znaczy: this pokazuje na obiekty klasy X ale nie wolno nim poruszać. Jest to stały wskaźnik.
Konstruktory i destruktory - start Aby klasa, czyli typ użytkownika przypominała typy wbudowane (J. Grębosz) wymyślono: Konstruktor i destruktor, Funkcje składowe przeładowujące operatory, Operatory konwersji. Konstruktor jest to member function, której nazwa jest identyczna z nazwa klasy. Tworzy on obiekty swojej klasy. Ten proces może uruchamiać procedury inicjalizacji data members i alokacji pamięci z wykorzystaniem operatora new. Destruktor jest to member function, której nazwa jest identyczna z nazwą klasy z poprzedzającym ją znakiem tilde (~). Zwykle jego funkcja polega na zniszczeniu obiektu swojej klasy, najczęściej poprzez wykorzystanie operatora delete. Konstruktor może być przeciążany a destruktor nie. Konstruktor i destruktor nie mogą nic zwracać. Nawet nie mogą zwracać void. Konstruktory i destruktory mogą zawierać słowo return ale tylko w składni return;
Własności konstruktora Konstruktor może być wywoływany do tworzenia obiektów typu const i volatile ale sam nie może być funkcją takiego typu. Konstruktor nie może być typu static. Konstruktor nie może być typu virtual. Nie można posłużyć się adresem konstruktora. Zmienna typu klasa z konstruktorem nie może członkiem unii. Zgoda może bowiem doprowadzić do walki pomiędzy konstruktorami. Konstruktor domniemany to taki, który można wywołać bez żadnego argumentu. Konstruktor domniemany jest tylko jeden. Konstruktor ze wszystkimi argumentami domniemanymi jest konstruktorem domniemanym. Konstruktor domniemany pozwala na array declaration. Jeżeli klasa nie ma żadnego konstruktora to wtedy kompilator sam generuje automatyczny konstruktor domniemany. class Complex{ float Re,Im; public: Complex(float x=0, float y=0) { Re=x; Im=y;} }a,b(-1),c(0,1),z[10];
Lista inicjalizacyjna konstruktora Lista inicjalizacyjna może pojawić się w definicji konstruktora. Deklaracja pozostaje niezmieniona. Lista inicjalizacyjna specyfikuje jak zainicjować niestatyczne składniki klasy. Przykład: było Complex(float x=0, float y=0) { Re=x; Im=y;} może być Complex(float x=0, float y=0): Re(x), Im(y){} Elementy składni: dwukropek i przecinek. W przykładzie mamy do czynienia z definicją inline konstruktora wewnątrz klasy. Lista inicjalizacyjna przydaje się przy przekazywaniu argumentów klasom bazowym przy dziedziczeniu
Konstruktor kopiujący .. \test0.sln Konstruktor typu type::type(type& x) nosi nazwę konstruktora kopiującego. Gwarancja nietykalności wzorca: type::type(const type& x) Konstruktor kopiujący może być wywołany w sposób jawny: type obiekt_nowy = type(obiekt_wzór); Konstruktor kopiujący jest wykorzystywany do kopiowania wartości typu type do innej jeżeli: zmienna typu type jest inicjalizowana przez wartość typu type wartość typu type jest przekazywana jako argument funkcji wartość typu type jest zwracana przez funkcję. Jeżeli w klasie nie ma konstruktora kopiującego to wtedy, gdy zajdzie jedna z sytuacji przedstawionych powyżej, kompilator generuje automatyczny konstruktor kopiujący. Kopiuje on zgodnie z zasadą „składowa po składowej”. Może to być przyczyną niejednej tragedii. konstruktor kopia.cpp
Przykład – nowa wersja klasy stack const int max_len=1000; enum Boolean {False,True}; class stack { char s[max_len]; int top; public: void reset() { top = 0; } // definition void push(char c); // function prototype char pop() { return (s[top--]);} char pop(int n); // overloading, prototype char top_of() { return (s[top]); } Boolean empty() { return ((Boolean)(top == 0)); } Boolean empty(int n); // overloading, prototype Boolean full() { return ((Boolean)(top == max_len - 1 )); } }; enum Boolean {false,true}; class stack { char *s; int max_len; int top; public: stack(void) { s=new char[1000]; max_len=1000; top=0; } stack(int size) {s=new char[size]; max_len=size; top=0; } stack(int size, char *str); ~stack(void) { delete s; } void reset() { top = 0; } void push(char c); char pop() { return (s[top--]); } char pop(int n); char top_of() { return (s[top]); } Boolean empty() { return ((Boolean)(top == 0)); } Boolean empty(int n); Boolean full() {return((Boolean)(top == max_len - 1 )); } };
Nowa wersja klasy stack - różnice Nie ma deklaracji const int max_len=1000; Zamiast tego jest nowy data member int max_len; Nie ma pola char s[max_len]; Zamiast tego jest char *s; Klasa stack ma trzy konstruktory: pierwszy bez argumentów - jego działanie jest równoważne działaniu niejawnego konstruktora, drugi zakłada stos o rozmiarze określonym przez jego parametr, trzeci ma dwa argumenty - zakłada stos o rozmiarze określonym przez pierwszy i kopiuje na stos łańcuch przekazywany przez drugi argument bez znaku końca łańcucha - top jest ustawiony na ostatni element łańcucha. Klasa stack ma destruktor. Interfejsy w obu wersjach pozostały identyczne
Klasa String z dynamiczną alokacją pamięci .. \test0.sln const int max_len=255; class String { char s[max_len]; // dwa skladniki typu private int len; static Stan; // składowa statyczna public: // cztery skladniki typu public void assign(char *st); int lenght() { return(len); } void print(); static void switch_stan(void); }; class String{ char *s; int len; static Stan; public: String(void); String(int n); String(char *p); String(String& str); ~String() { delete s; } int lenght() { return(len); } void assign(char *str); void print(); static void switch_stan(void); }; String dynamiczny.cpp Różnice: Nie ma pola const int max_len=255; Nie ma pola char s[max_len]; jest pole char *s; Pojawiły się cztery konstruktory.
Klasa String z dynamiczną alokacją pamięci – definicje konstruktorów String::String(void) { s=new char[81]; s[0]=0; // przerwanie łańcucha len=80; cout << "\nPracuje konstruktor domniemany:\n"; print(); } String::String(int n) { s=new char[n+1]; s[0]=0; // przerwanie łańcucha len=n; cout << "\nPracuje konstruktor z jednym argumentem:\n"; print(); } String::String(char *p) // WAŻNE { len=(int)strlen(p); // rzutowanie s=new char[len+1]; strcpy(s,p); cout << "\nPracuje operator konwersji:\n"; print(); } String::String(String& str) { len=str.len; s=new char[len+1]; strcpy(s,str.s); cout << "\nPracuje konstruktor kopiujacy:\n"; print(); }
Klasa vect z dynamiczną alokacją pamięci i kontrolą indeksu. \test0 Klasa vect z dynamiczną alokacją pamięci i kontrolą indeksu .. \test0.sln Program ilustruje sposób wykorzystania ADT do stworzenia tablicy elementów (jako klasy), w której będzie można kontrolować wykraczanie indeksów poza dopuszczalny przedział wartości. class vect { int *p; int size; public: int ub; // upper bound = size-1 vect() { size=10; p=new int[size]; ub=size-1; } vect(int n); // konstruktor z argumentem ~vect() { delete p; } int& element (int i); }; int& vect::element(int i) { if(i<0||i>ub) { cerr << "Illegal vector indeks " << i << "\n"; exit(1);} return(p[i]); } // vect.cpp
Klasa vect z dynamiczną alokacją pamięci i kontrolą indeksu Dwa pola typu private: wskaźnik do pola roboczego int *p oraz jego rozmiar int size. W obszarze public jest data member int ub; Klasa ma dwa konstruktory: pierwszy bez argumentów deklarujący tablicę 10 elementową drugi z jednym argumentem int n zakładający tablicę n-elementową. Dostęp do elementów tablicy jest poprzez funkcję element. Najciekawszy element programu.Funkcja jest self indexing - sprawdza, czy indeks nie jest spoza zakresu. Funkcja zwraca referencję do i-tego elementu tablicy, to znaczy udostępnia ten element. Zwracana wartość jest zatem lvalue i może być wykorzystywana jako lewy operand operatora podstawiania. Jest to technika stosowana w C++ jako wygodny mechanizm przy działaniach na bardziej skomplikowanych typach obiektowych. Można napisać: vect vv(8), uu[5]; vv.element(2)=uu[3].element(8); Lepiej by było tak: vv[2]=uu[3][8]; to kiedyś będzie
Klasa multi_v Deklarujemy klasę multi_v do przechowywania wieku, wagi i wzrostu grupy osób. Klasa zawiera trzy obiekty typu vect jako swoje składowe. Klasa ma wyłącznie elementy typu public. Konstruktor klasy multi_v jest konstruktorem z parametrem. Konstruktor ten ma puste wnętrze nie licząc listy inicjalizacyjnej, na której są wywołania konstruktora obiektu typu vect. Konstruktor klasy vect jest wywoływany trzy razy z parametrem i i tworzy trzy obiekty a(i),b(i),c(i). W starszych kompilatorach kolejność zakładania obiektów była nieokreślona. W nowszych kompilatorach kolejność jest zgodna z listą inicjalizacyjną - od lewej do prawej. class multi_v { public: vect a,b,c; multi_v(int i):a(i),b(i),c(i) {} };
Klasa vect i multi_v – kod klienta W segmencie main zakładamy obiekt a_w_h typu multi_v. Składa się on z trzech 5-elementowych tablic obiektów typu vect. Zwraca uwagę sposób użycia pola ub Pętla for(int i=0;i<=a_w_h.a.ub;i++) chodzi 5 razy. Na koniec wykonywane są destruktory w kolejności odwrotnej do wywołań konstruktorów. Są to destruktory elementów klasy vect. multi_v a_w_h(5); // age, weight and height for(int i=0;i<=a_w_h.a.ub;i++) { a_w_h.a.element(i)=21+i; a_w_h.b.element(i)=135+i; a_w_h.c.element(i)=62+i; } for(i=0;i<=a_w_h.a.ub;i++){ cout << a_w_h.a.element(i) << " years "; cout << a_w_h.b.element(i) << " pounds "; cout << a_w_h.c.element(i) << " inches\n";
Klasa matrix – uogólnienie klasy vect Przykład jest uogólnieniem tablicy jednowymiarowej. Istotnym novum jest postać konstruktora i destruktora. class matrix { int **p; int s1,s2; public: int ub1,ub2; matrix(int d1,int d2); ~matrix(); int& element(int i,int j); }; matrix::matrix(int d1,int d2) { s1=d1; s2=d2; p=new int* [s1]; for(int i=0; i<s1;i++) p[i]=new int[s2]; ub1=s1-1; ub2=s2-1; } matrix::~matrix() for(int i=0;i<s1;i++) delete p[i]; delete p; Konstruktor zakłada wpierw s1 elementową tablicę wskaźników do wierszy. Destruktor dealokuje pamięć w odwrotnej kolejności.
Klasa matrix – funkcja element i kod klienta .. \test0.sln int& matrix::element(int i,int j) { return (p[i][j]); } Kod klienta jest izomorficzny do kodu z programu tablica1.cpp tablica2.cpp int sz1,sz2; cout << "\nEnter two sizes: "; cin >> sz1 >> sz2; matrix A(sz1,sz2); // Istotna różnica cout << "\nEnter " << sz1 << " by " << sz2 << " ints:\n"; int i,j; for(i=0;i<sz1;i++) for(j=0;j<sz2;j++) cin >> A.element(i,j); …
Lista jednokierunkowa - a singly linked list Lista jednokierunkowa jest bardzo użytecznym obiektem ADT. Jest to prototyp wielu struktur danych, które noszą nazwę self-referential structures. Obiekty tego typu charakteryzują się w szczególności tym, że jako jedno ze swoich pól mają wskaźnik do danej tego samego typu. Idea listy jednokierunkowej: Elementy dodajemy na początku listy i pierwszy element listy jest jedynym, który można bezpośrednio z listy skasować. Każdy element listy zawiera wszystkie niezbędne informacje, wynikające ze specyfiki programu oraz adres następnego elementu na liście. W przykładzie element znajdujący się na liście jest skromnym obiektem typu char. struct listelem { // element listy char data; // informacje związane z elementem listelem *next; // adres do następnego elementu };
Klasa list Oba pola w strukturze listelem są typu public. Nie ma to specjalnie znaczenia ponieważ tajny jest adres do początku listy. Konstruktor inicjalizuje początek listy na NULL. Jest to klasyczne oznaczenie końca listy, lub faktu, że lista jest pusta. Destruktor wywołuje funkcję składową. Funkcja first zwraca adres do początku listy. Elementy listy są deklarowane jako struktury. Mamy zatem bezpośredni dostęp do ich pól. class list { listelem *h; // wskaźnik do początkowego elementu public: list() { h=0; } // konstruktor ~list() {release();}// destruktor void add(char c); // dodająca nowego element na początku void del(); // kasowanie elementu z początku listy listelem *first() { return h; }// adres do 1 elementu void pr_list(); // wyprowadzanie listy void release(); //zwalnianie pamięci };
Funkcje składowe klasy list void list::add(char c) // dodawanie elementu na początku listy { listelem *temp=new listelem; // przydzielanie pamięci temp->next=h; // dołączanie do listy temp->data=c; h=temp; // aktualizacja początku listy } // zarezerwowana pamięć nie jest zwalniana void list::pr_list() // wydruk całej listy listelem *temp=h; // adres do początku listy nie zmieni się while(temp!=0) // badanie końca listy cout << temp->data << " -> "; temp=temp->next; // przejście do następnego elementu } cout << "Koniec listy.\n";
Funkcje składowe klasy list - c.d. void list::del() // kasowanie jednego elementu z listy { listelem *temp=h; h=h->next; delete temp; // pamięć zajmowana przez pierwszy element } // na liscie wraca do stanu 'free store' void list::release() // kasowanie wszystkich elementów z listy while(h!=0) del(); } Funkcja release zdejmuje bieliznę ze sznurka zostawiając sznurek. Sznurek zdejmuje destruktor. Destruktor o treści delete h; zniszczy tylko pierwszy element na liście i popsuje listę bo nie będziemy w stanie znaleźć następnego elementu.
Klasa list - kod klienta .. \test0.sln list *p; // wskaźnik do listy { // początek wewnętrznego bloku list w; w.add('A'); w.add('B'); // dwa elementy na liście w.pr_list(); w.del(); // kasujemy pierwszy p=&w; p->pr_list(); // powinien byc identyczny wydruk } // niejawne wywołanie destruktora p->pr_list(); // lista została wyczyszczona lista jednokierunkowa.cpp Segment main zawiera inner block. Po wyjściu z niego uruchamiany jest destruktor i lista jest automatycznie kasowana. Przykładowy wydruk: B -> A -> Koniec listy. A -> Koniec listy. Koniec listy.