JĘZYKI PROGRAMOWANIA Z PROGRAMOWANIEM OBIEKTOWYM Wykład 13 1
KLASY A ZASŁANIANIE NAZW Nazwy danych składowych i funkcji składowych mają zakres klasy, więc w obrębie klasy zasłaniają takie same nazwy spoza klasy. Przykład (wersja 1): #include <iostream> using namespace std; //----------------------------------------------------------------------------- int dana=5; // dana (zmienna) globalna //----------------------------------------------------------------------------- class Obliczenia {public: int dana; int oddaj_1() {return dana;} // dana lokalna klasy (nazwa jak dana globalna) // odniesienie się do danej lokalnej int oddaj_2() {return ::dana;} // odniesienie się do danej globalnej //... // ciąg dalszy ciała klasy //----------------------------------------------------------------------------- int main() { Obliczenia obiekt; // tworzymy obiekt } obiekt.dana=777; // wpisujemy do danej obiektu cout<<obiekt.oddaj_1()<<endl; // wywołania funkcji cout<<obiekt.oddaj_2()<<endl; return 0; 2
KLASY A ZASŁANIANIE NAZW Przykład z poprzedniego slajdu jest poprawny, lecz... o Klasy należy konstruować tak, by były samowystarczalne. o Jeśli klasa ma korzystać ze zmiennej globalnej należy przesłaćją do klasy jako argument stosownej funkcji składowej: void Klasa::odbior(int te) { zmienna=te; } Podsumowując: nienależy z wnętrza klasy odwoływać się do zmiennych globalnychw programie (szczególnie jeśli będziemy chcieli klasę wykorzystać w innym projekcje lub np. komuś udostępnić). 3
KLASY A ZASŁANIANIE NAZW Przykład (wersja 2): #include <iostream> using namespace std; //----------------------------------------------------------------------------- int dana=5; // obiekt globalny //----------------------------------------------------------------------------- class Obliczenia // samowystarczalna klasa... {public: int dana, dana_odebrana; void odbierz(int a) {dana_odebrana=a;} // nowa funkcja do odbierania int oddaj_1() {return dana;} // odniesienie się do danej lokalnej int oddaj_2() {return dana_odebrana;} // odniesienie się do odebranej danej //----------------------------------------------------------------------------- int main() { Obliczenia obiekt; // tworzymy obiekt obiekt.dana=777; // wpisujemy do danej obiektu obiekt.odbierz(dana); // dana globalna jako argument } cout<<obiekt.oddaj_1()<<endl; // wywołania funkcji cout<<obiekt.oddaj_2()<<endl; return 0; 4
Dziedziczenie technika obiektowa, pozwalająca tworzyć nowe klasyna podstawie już istniejącychklas. Dziedziczenie jest jedną z najwspanialszych cech obiektowo orientowanych języków programowania J. Grębosz, Symfonia C++ standard. Po co? 1. Oszczędność czasu. o Załóżmy, że istniejejakaś klasa, która nie spełnia wszystkich oczekiwań. o Można ją poprzez dziedziczenie dopasowaćdo naszych potrzeb tworząc na jej bazie tzw. klasę pochodną. o Np.: mając klasę Telewizor_LCDmożna na jej podstawie stworzyć klasę Telewizor_LCD_3D. o Ważne nie musimyznać kodu źródłowegoklasy podstawowej (a tylko jej definicję w pliku.h) możemy korzystać z klas napisanych przez kogoś innego... 5
2. Tworzenie hierarchii klas. o Często klasy są w pewnej logicznej zależności i tworzą naturalną hierarchię. o Np.: Srodek_transportu Pojazd Statek Samolot Samochod Rower... o Zamiast osobnych klas mamy sytuację: jakaś klasa jest szczególnym rodzajeminnej klasy, o np. Samochodi Rowersą rodzajamiczegoś, co nazwaliśmy Pojazd. 6
o Inne przykłady: -zarówno Prostokat, Trapezjak i Rombto specyficzny Czworokat, - Lewi Myszsą tozwierzęta należące do gromady (tu: klasy) Ssak, itd. o Proces dziedziczenia pozwala w takim przypadku na wprowadzenie relacji między poszczególnymi klasami. Dziedziczenie pozwala na wykorzystanietego, co istniejąca klasa posiada i dostosowanie jejdo innych potrzeb: o dodanie nowych danych składowych i funkcji składowych; o stworzenie nowych wariantów funkcji składowych lub ew. danych składowych. Jeżeli w klasie podstawowej i w klasie pochodnej są składniki (dane lub funkcje) o tych samych nazwach, to w zakresie klasy pochodnej składnik z tej klasy zasłania składnik odziedziczony. 7
Np.: mając klasę Pralka z poprzedniego wykładu: class Pralka // definicja klasy { int nr_programu; int temperatura_prania; public: void pranie(int program, int temperatura); void plukanie(); moglibyśmy chcieć stworzyć (postęp techniczny!) klasę Lepsza_Pralka, która posiada nową daną składową (dla uproszczenia np. tylko godzina): int o_ktorej_zaczac; oraz zmodyfikowaną funkcję: void pranie(int program, int temperatura, bool niski_poziom=false); 8
Realizacja: class Lepsza_Pralka : public Pralka // lista pochodzenia { int o_ktorej_zaczac; public: void pranie(int program, int temperatura, bool niski_poziom); o Klasa Pralka jest w tym przypadku klasą podstawową dla klasy Lepsza_Pralka. o Klasa Lepsza_Pralka jest w tym przypadku klasą pochodną klasy Pralka. o Nowa wersja funkcji składowej pranie zastępuje (zasłania) wersję funkcji pranie z klasy podstawowej. o Wyrażenie po dwukropku to lista pochodzenia. o Specyfikator public przy liście pochodzenia o tym nieco później... 9
o Stwórzmy obiekt moja_nowa_pralka klasy Lepsza_Pralka: Lepsza_pralka moja_nowa_pralka; o Wywołanie funkcji praniedla obiektu klasy Lepsza_Pralkauruchamia funkcję klasy pochodnej: moja_nowa_pralka.pranie(7,40,1); moja_nowa_pralka.pranie(7,40); //ostatni argument domyślny o Gdybyśmy chcieli wywołać funkcję pranie z klasy podstawowej, to należy skorzystać z kwalifikatora zakresu :: moja_nowa_pralka.pralka::pranie(7,40); o Należy pamiętać definiując obiekt klasy pochodnej, że wewnątrz znajduje się fragment odziedziczony po klasie podstawowej... 10
Przykład (publiczne dane składowe): #include <iostream> using namespace std; class Punkt2D // klasa podstawowa { public: float x, y; // dane publiczne void wypisz1() { cout<<x<<" "<<y;} // funkcja publiczna //------------------------------------------------------------------ class Punkt3D: public Punkt2D // klasa pochodna { public: float z; void wypisz2() { cout<<x<<" "<<y<<" "<<z;} //------------------------------------------------------------------ int main() { Punkt2D p2d; // tworzymy obiekt klasy Punkt2D p2d.x=1.1; p2d.y=2.2; // mamy dostęp do danych... p2d.wypisz1(); cout<<endl; Punkt3D p3d; p3d.x=5.5; p3d.y=6.6; p3d.z=7.7; p3d.wypisz2(); cout<<endl; p3d.punkt2d::wypisz1(); cout<<endl; // tworzymy obiekt klasy Punkt3D //wywołanie f. klasy podstawowej return 0;} 11
Przykład (prywatne dane składowe): #include <iostream> using namespace std; class Punkt2D // klasa podstawowa { float x, y; // prywatne dane public: Punkt2D(float a, float b) // konstruktor {x=a; y=b;} void wypisz1() // publiczna funkcja {cout<<x<<" "<<y;} //------------------------------------------------------------------ class Punkt3D: public Punkt2D // klasa pochodna { float z; // prywatna dana public: Punkt3D(float a, float b, float c): Punkt2D(a,b)// konstruktor (o tym później) {z=c;} void wypisz2() {Punkt2D::wypisz1(); //korzystamy z funkcji z klasy podstawowej cout<<" "<<z;} //------------------------------------------------------------------ int main() { Punkt2D p2d (1.1, 2.2); // tworzymy obiekt klasy Punkt2D p2d.wypisz1(); cout<<endl; Punkt3D p3d(5.5,6.6,7.7); p3d.wypisz2(); cout<<endl; return 0;} // tworzymy obiekt klasy Punkt3D 12
Dane składowe często nie są publiczne. Co wtedy z ewentualnymi klasami pochodnymi? Mogą istnieć funkcje dostępowe (jak w poprzednim przykładzie). Można jednak inaczej... Jak to jest z etykietą protected, czyli: rozwiązanie zagadki, po co ona jest... o Etykieta ta została wprowadzona w właśnie na potrzeby dziedziczenia. o Składniki za taką etykietą: - są dostępne dla klas pochodnych(jak gdyby były public); - dla całego świata są niedostępne, (jak gdyby były private). 13
Np.: class Radio // definicja klasy { string obudowa; // składniki prywatne int zakres; protected: // składniki dostępne dla klas pochodnych float dlugosc_fali; void aut_strojenie(); public: // składniki dostępne dla każdego void glosnosc(int vol); o Klasa podstawowa określa odpowiednimi etykietami, które ze składników: - są dostępne tylko dla niej (private); - chce udostępniać tylko klasom pochodnym (protected); - chce, by były ogólnodostępne (public). 14
Przykład (dane składowe protected): #include <iostream> using namespace std; class Punkt2D // klasa podstawowa {protectec: float x, y; // dane protected public: Punkt2D(float a, float b) {x=a; y=b;} // konstruktor void wypisz1() {cout<<x<<" "<<y;} // publiczna funkcja //------------------------------------------------------------------ class Punkt3D: public Punkt2D // klasa pochodna {protectec: float z; // dana protected public: Punkt3D(float a, float b, float c): Punkt2D(a,b) // konstruktor {z=c;} void wypisz2() { cout<<x<<" "<<y <<" "<<z;} // mamy dostęp do danych protected //------------------------------------------------------------------ int main() { Punkt2D p2d (1.1, 2.2); // tworzymy obiekt klasy Punkt2D p2d.wypisz1(); cout<<endl; Punkt3D p3d(5.5,6.6,7.7); p3d.wypisz2(); cout<<endl; return 0;} // tworzymy obiekt klasy Punkt3D 15
o O dostępie do odziedziczonychskładników decyduje też (oczywiście w zakresie ograniczonym do składników public i protected!) klasa pochodna. o Np.: w klasie Lepsza_Pralkajest to słowo public przed nazwą klasy podstawowej: class Lepsza_Pralka : public Pralka { // ciało klasy pochodnej o oznacza to, że: - składniki public z klasy podstawowej będą nadal public; - składniki protected z klasy podstawowej będą nadal protected. o gdyby zamiast publicbyło tam słowo protectedlub privateto: tabelka na następnym slajdzie... 16
słowo na liście dziedziczenia: public protected składowe w klasie podstawowej były: protected public protected public w klasie pochodnej są: protected public protected private protected public private o Klasa pochodna może co najwyżej ograniczyć uprawnienia do odziedziczonych składników! o W ramach naszych rozważań ograniczymy się do wariantu public... 17
Czego się nie dziedziczy: o konstruktorów konstruktor klasy podstawowej niestaje się konstruktorem klasy pochodnej. dlaczego? Klasa pochodna = klasa podstawowa + X gdzie X: składniki zdefiniowane w klasie pochodnej (których klasa podstawowa nie zna) zatem konstruktory klasy pochodnej należy zdefiniować! o destruktorów - z tych samych powodów co powyżej. o dla zainteresowanych operatora przypisania = (jeśli został zdefiniowany w klasie podstawowej). 18
Kolejność uruchamiania konstruktorów: o najpierw uruchamiany jest konstruktor klasy podstawowej; o w następnej kolejności wykonywane są konstruktory obiektów składowych klasy pochodnej (jeśli występują składowe obiekty innych klas); o dopiero na końcu uruchamiany jest konstruktor klasy pochodnej. Klasa uszanuje najpierw starszych, potem swoich gości, a dopiero na samym końcu zajmie się sobą. J. Grębosz: Symfonia C++ standard, str. 808 wydania z 2008 roku 19
Konstruktor klasy pochodnej jest zwykłym konstruktorem, przy czym: o Na jego liście inicjalizacyjnejmożna (a często trzeba) umieścić wywołanie konstruktora klasy podstawowej. Wywołanie takie można pominąć, gdy: o klasa podstawowa nie ma żadnego konstruktora (więc nie ma czego wywołać...); o klasa podstawowa ma konstruktory a wśród nich jest konstruktor domyślny. 20
Przykład: #include <iostream> using namespace std; class Prostokat // klasa podstawowa { protected: float bok_a, bok_b; // dane protected public: Prostokat(float a, float b){bok_a=a; bok_b=b;} // konstruktor void pole() // funkcja składowa { cout<<"\npole prostokata o bokach a=" <<bok_a<<" b="<<bok_b<< " wynosi "<<bok_a*bok_b;} //------------------------------------------------------------------ class Kwadrat: public Prostokat // klasa pochodna { protected: float bok; // dana protected public: Kwadrat(float dl):prostokat(dl, dl) // konstruktor - wywołanie { bok=dl;} // konstruktora klasy podstawowej void pole() // funkcja składowa nowa wersja! { cout<<"\npole kwadratu o boku a="<<bok<<" wynosi "<<bok*bok;} //------------------------------------------------------------------ int main() { Kwadrat k1(2.3); // tworzymy obiekt klasy Kwadrat k1.pole(); // pole kwadratu k1.prostokat::pole(); // pole prostokąta ("schowanego" w klasie Kwadrat) return 0;} 21
Przykład bardziej rozbudowany: class Rolki { protected: string nazwa; public: Rolki(string i):nazwa(i) {} void opis() {cout<<"\nmam rolki: "<<nazwa<<endl;} // klasa podstawowa // konstruktor // f. składowa klasy podstawowej class Fitness: public Rolki // pierwsza klasa pochodna { private: int srednica; string zadanie; // nowe dane składowe public: Fitness (string n, int sr, string za= "jade sobie..."):rolki(n) // konstruktor { zadanie=za; srednica=sr; // przypisania smigaj(zadanie);} // wywołanie funkcji składowej void smigaj(string rob) // funkcja składowa { cout<<"\nto sa rolki do fitnessu: "<<nazwa<<endl; cout<<"kolka "<<srednica<<"mm"<<endl; cout<<"cwiczenie: "<<rob<<endl;} 22
Przykład bardziej rozbudowany: class Speed: public Rolki // druga klasa pochodna {private: int srednica; string lozyska, zadanie; // nowe dane składowe public: Speed (string n, int sr, string lo, string za="rozgrzewka..."):rolki(n) // k. { zadanie=za; srednica=sr; lozyska=lo; // przypisania smigaj(zadanie);} // wywołanie funkcji składowej void smigaj(string rob) // funkcja składowa { cout<<"\n To sa rolki do jazdy szybkiej: "<<nazwa<<endl; cout<<" Kolka "<<srednica<<"mm, lozyska: "<<lozyska<<endl; cout<<" Cwiczenie: "<<rob<<endl;} #include <iostream> using namespace std; // to wstawiamy np. na początku pliku... 23
Przykład bardziej rozbudowany: int main() {Rolki r1("znalezione w piwnicy"); r1.opis(); // funkcja główna // obiekt klasy podstawowej Fitness jakies("pozyczone do nauki", 76); // potem taki obiekt Fitness rekreacyjne("rollerblade Spark", 84, "jazda tylem"); // inne obiekty Speed szybkie("powerslide infinity", 110, "ABEC 7 Freespin"); // 4 arg. domyślny rekreacyjne.smigaj("slalom"); szybkie.smigaj("double push"); return 0;} // fitness: inne ćwiczenie // szybkie: inne ćwiczenie 24