Dr hab. inż. Lucyna Leniowska, prof. UR, Zakład Mechatroniki, Automatyki i Optoelektroniki, IT Programowanie obiektowe, wykład nr 7 Przegląd typów strukturalnych - klasy i obiekty - c.d. Klasa - powtórzenie Klasa pozwala grupowad w programach - w jednej zmiennej - dane obiektu i metody (funkcje), które operują na tych danych. Aby zdefiniowad klasę, należy podad w programie słowo kluczowe class, nazwę klasy, składowe danych klasy i funkcje (metody) klasy. Definicja klasy tworzy wzorzec, przy użyciu którego można w programach tworzyd obiekty typu tej klasy, analogicznie jak zmienne typów char, int itd. Aby przypisywad w programach wartości składowym danych klasy, należy używad operatora wyboru składowej oznaczanego kropką. Do wywoływania funkcji składowych klasy także służy operator wyboru składowej oznaczany kropką. Aby zdefiniowad funkcję na zewnątrz definicji klasy, należy w programie poprzedzid definicję funkcji nazwą klasy i operatorem widoczności (::) w nastepujący sposób: typ_wartosci nazwa_klasy::nazwa_funkcji (parametry) // Instrukcje Składowe klasy mogą byd albo publiczne, albo prywatne. Programy mają bezpośredni dostęp do składowych publicznych przy użyciu operatora wyboru składowej (kropka). Natomiast dostęp do składowych prywatnych mogą realizowad jedynie za pośrednictwem metod klasy. Jeśli słowo kluczowe public nie występuje jawnie, to C++ przyjmuje, że wszystkie składowe są prywatne.
Konstruktory i destruktory c.d. Konstruktory i destruktory to specjalne funkcje klasy, które są automatycznie wywoływane podczas tworzenia lub usuwania obiektów. Tworząc w programie obiekt, można przekazywad parametry funkcji konstruktor w deklaracji obiektu. Destruktor jest specjalną funkcją, którą program automatycznie wywołuje za każdym razem, gdy obiekt jest usuwany. Destruktor ma taką samą nazwę jak klasa obiektu, poprzedzoną falką (~). Podawanie domyślnych wartości funkcjom konstruktor W C++ można podawać domyślne wartości parametrów funkcji. Jeśli nie podamy w wywołaniu funkcji/metody wartości żadnego parametru, to będą wykorzystywane wartości domyślne, o ile zostały zdefiniowane. W konstruktorach można podawać w definicjach domyślne wartości parametrów tak samo jak dla innych funkcji. Konstruktor o wartościach domyślnych pracownik::pracownik (std::string imie_nazwisko, long ident_pracownika, float zarobki=2000.0) Możliwe i poprawne jest wywołanie: 1) pracownik sekretarka ("Anna Kowal", 101); 2) pracownik sekretarka ("Anna Kowal", 101, 2000.0); 3) pracownik sekretarka ("Anna Kowal", 101, 5000.0);
Przeciążanie funkcji konstruktor W C++ można przeciążad funkcje, definiując funkcje o tych samych nazwach, ale o różnych typach parametrów. W taki sam sposób można przeciążad funkcję konstruktor. W przedstawionym poniżej programie występuje przeciążona funkcja konstruktor. Przykład 7.1. #include <iostream> class pracownik public: pracownik (std::string, long, float); pracownik (std::string, long); ~pracownik (void); void inf_o_prac (void); int zmien_zarobki (float); long podaj_id (void); private: std::string imie_nazwisko; long ident_pracownika; float zarobki; ; pracownik::pracownik (std::string imie_nazwisko, long ident_pracownika, float zarobki) pracownik::imie_nazwisko = imie_nazwisko; pracownik::ident_pracownika = ident_pracownika; if (zarobki < 7000.00) pracownik::zarobki = zarobki; else pracownik::zarobki = 0.0; //Podano niepoprawne zarobki
pracownik::pracownik (std::string imie_nazwisko, long ident_pracownika) pracownik::imie_nazwisko = imie_nazwisko; pracownik::ident_pracownika = ident_pracownika; do std::cout << "Podaj zarobki pracownika " << imie_nazwisko << "mniejsze od 7 000: " ; std::cin >> pracownik::zarobki; while (pracownik::zarobki >= 7000.0); pracownik::~pracownik () std::cout << "Usuwam obiekt " << imie_nazwisko <<std::endl; void pracownik::inf_o_prac (void) std::cout << "Imię i nazwisko: " << imie_nazwisko <<std::endl; std::cout << "Ident: " << ident_pracownika << std::endl; std::cout << "Zarobki: " << zarobki << std::endl; int main(void) pracownik sekretarka ("Anna Kowal", 101, 3000.0); pracownik ksiegowa ("Katarzyna Kwiatek", 200, 5000.0); pracownik menager ("Jan Kowalski", 102); //Tworzenie obiektu3 sekretarka.inf_o_prac (); ksiegowa.inf_o_prac (); menager.inf_o_prac(); return 0; W przypadku inicjacji obiektu menager za pomocą konstruktora o dwóch parametrach (przeciążonego), na ekranie zobaczymy pytanie o wartośd trzeciego parametru:
Podaj zarobki pracownika Jan Kowalski mniejsze od 7 000: 4500 Imię i nazwisko: Anna Kowal Ident: 101 Zarobki: 3000 Imię i nazwisko: Katarzyna Kwiatek Ident: 200 Zarobki: 5000 Imię i nazwisko: Jan Kowalski Ident: 102 Zarobki: 4500 Usuwam obiekt Jan Kowalski Usuwam obiekt Katarzyna Kwiatek Usuwam obiekt Anna Kowal Porównanie typów złożonych: struktura struct i klasa class podobieostwa i różnice W przypadku stosowania zmiennych typu struct funkcje nie są związane ze strukturą. #include <cmath> #include <iostream> struct wektor2d ; double dlugosc(const wektor2d& wektor) return sqrt(wektor.x*wektor.x + wektor.y*wektor.y); Gdy stosujemy typy class to funkcje(metody) przynależą do klasy. class wektor2d public: double dlugosc() const
return sqrt(x*x + y*y); Uwaga: Słowo kluczowe const sygnalizuje, czy w metodzie możemy zmieniad składowe obiektu, na rzecz którego została wywołana metoda. Teoretycznie nie ma żadnej szczególnej różnicy pomiędzy kodem umieszczonym w metodzie należącej do klasy i kodem zwykłej funkcji, która współpracuje ze strukturą. Efekty działania są takie same. Podstawowe różnice istnieją w czytelności zapisu i w różnych udogodnieniach, które umożliwiają klasy. Praktyczne różnice istnieją np. przy optymalizacjach, które kompilator potrafi zastosowad, a także przy użyciu metod wirtualnych i wskaźników do funkcji. Stosujemy następującą składnię: o dla struktur: naz_funkcji(ob, parametry), o dla klas: ob.naz_funkcji(parametry), Przykład 7.2. a)gdy wektor2d jest strukturą struct wektor2d ; wektor2d suma(const wektor2d& a, const wektor2d& b) wektor2d wynik; wynik.x = a.x + b.x; wynik.y = a.y + b.y; return wynik;
void negacja(wektor2d& wektor) wektor.x = -wektor.x; wektor.y = -wektor.y; b)gdy wektor2d jest klasą class wektor2d public: double dlugosc() const return sqrt(x*x + y*y); wektor2d suma(const wektor2d& b) const wektor2d wynik; wynik.x = x + b.x; wynik.y = y + b.y; return wynik; void negacja() // bez 'const' x = -x; y = -y; void wypisz(std::ostream& out) const out << "(" << x << "," << y << ")"; ; Jeżeli parametr opisujący strukturę na której działa funkcja posiada modyfikator const, to w klasie piszemy const za nawiasami zawierającymi parametry, np. o Dla struktur: double dlugosc(const wektor2d& wektor) o Dla klas: double dlugosc() const W programach - jeżeli można i ma to sens, warto dodawad modyfikatory const, bo w ten sposób:
o metody będą ogólniejsze, o będzie można je wywoład dla większej liczby obiektów, o np. metodę dlugosc()const możemy wywoład na rzecz obiektu const wektor2d, ale już metodę negacja() nie. Przyjrzyjmy się jeszcze metodzie wypisz() Dla struktur: void wypisz(const wektor2d wektor, std::ostream& out) out << "(" << wektor.x << "," << wektor.y << ")"; Dla klas: void wektor2d::wypisz(std::ostream& out) const out << "(" << x << "," << y << ")"; W tej funkcji parametr out jest strumieniem wyjścia, tak aby dało się wykorzystad metodę zarówno z std::cout, jak i z plikami. Wywołanie wygląda zatem następująco: dla struktur: wypisz(c, std::cout); a dla klas: c.wypisz(std::cout); A gdyby dane pochodziły z pliku, to wywołanie przyjęłoby postad: Przykład 7.3 dla struktur: std::fstream f ("log.txt"); wypisz(c, f); a dla klas std::fstream f ("log.txt"); c.wypisz(f); Poniżej przedstawiony jest przykład implementacji wektora na płaszczyźnie za pomocą struktury i za pomocą klasy. Dla wektora zaplanowano następujące operacje: dodawanie, za pomocą funkcji suma zmiana znaku składowych, za pomocą funkcji negacja obliczenie długości wektora, za pomocą funkcji dlugosc wypisanie wektora na dowolny strumieo (np. na std::cout lub do pliku).
#include <cmath> #include <iostream> struct wektor2d ; double dlugosc(const wektor2d& wektor) return sqrt(wektor.x*wektor.x + wektor.y*wektor.y); wektor2d suma(const wektor2d& a, const wektor2d& b) wektor2d wynik; wynik.x = a.x + b.x; wynik.y = a.y + b.y; return wynik; void negacja(wektor2d& wektor) wektor.x = -wektor.x; wektor.y = -wektor.y; void wypisz(const wektor2d wektor, std::ostream& out) out << "(" << wektor.x << "," << wektor.y << ")"; int main() wektor2d a, b, c; a.x = 4; a.y = 6; b.x = 1; b.y = 2; negacja(b); c = suma(a, b); std::cout << "Dlugosc wektora "; wypisz(c, std::cout); std::cout << " wynosi: " << dlugosc(c) << std::endl; #include <cmath> #include <iostream> class wektor2d public: double dlugosc() const return sqrt(x*x + y*y); wektor2d suma(const wektor2d& b) const ; wektor2d wynik; wynik.x = x + b.x; wynik.y = y + b.y; return wynik; void negacja() // bez 'const' x = -x; y = -y; void wypisz(std::ostream& out) const out << "(" << x << "," << y << ")"; int main() wektor2d a, b, c; a.x = 4; a.y = 6; b.x = 1; b.y = 2; b.negacja(); c = a.suma(b); std::cout << "Dlugosc wektora "; c.wypisz(std::cout); std::cout << " wynosi: " << c.dlugosc() << std::endl; return 0; return 0; Uwaga: następny przykład gdy klasa wektor2d ma dodatkowo różne konstruktory będzie na następnym wykładzie.