230 likes | 395 Views
Programowanie obiektowe III rok EiT. dr inż. Jerzy Kotowski Wykład X. Program wykładu. Język C++ Klasy, c.d. Wskaźnik this Klasy z konstruktorem i destruktorem Przykład problemu Podstawy języka JAVA Klasówka. Literatura.
E N D
Programowanie obiektoweIII rok EiT dr inż. Jerzy Kotowski Wykład X
Program wykładu • Język C++ • 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{ floatRe,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 constint 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; } voidreset() { 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 memberint 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 - topjest 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 constint 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(); staticvoid 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(); staticvoid 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=newchar[81]; s[0]=0; // przerwanie łańcucha len=80; cout << "\nPracuje konstruktor domniemany:\n"; print(); } String::String(int n) { s=newchar[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=newchar[len+1]; strcpy(s,p); cout << "\nPracuje operator konwersji:\n"; print(); } String::String(String& str) { len=str.len; s=newchar[len+1]; strcpy(s,str.s); cout << "\nPracuje konstruktor kopiujacy:\n"; print(); }
Klasa vectz 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=newint[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 vectz dynamiczną alokacją pamięci i kontrolą indeksu • Dwa pola typu private: wskaźnik do pola roboczego int *poraz jego rozmiar int size. • W obszarze public jest data memberint 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=newint* [s1]; for(int i=0; i<s1;i++) p[i]=newint[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 • Kod klienta jest izomorficzny do kodu z programu tablica1.cpp • tablica2.cpp int& matrix::element(int i,int j) { return (p[i][j]); } 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. • Funkcja releasezdejmuje 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. 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(); }
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 w.pr_list(); 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. A -> Koniec listy. Koniec listy.