Programowanie obiektowe, wykład nr 8 Konstruktory i destruktory
Transkrypt
Programowanie obiektowe, wykład nr 8 Konstruktory i destruktory
Dr hab. inż. Lucyna Leniowska, prof. UR, Zakład Mechatroniki, Automatyki i Optoelektroniki, IT Programowanie obiektowe, wykład nr 8 Konstruktory i destruktory – c.d. Przeciążanie operatorów Zestawienie wiadomości o konstruktorach Przykład 8.1. class wektor2d { public: double x, y; }; Gdy piszemy: wektor2d wynik; wynik.x = 1; wynik.y = 2; to pole x w obiekcie przechowywanym w zmiennej wynik zostanie zainicjalizowane dwa razy: pierwszy raz na 0, drugi raz na 1. Z tego powodu warto używad konstruktorów z listą inicjalizacyjną, bo wtedy pola klasy inicjalizowane są tylko raz. 1. Konstruktor wieloargumentowy Służy do zainicjowania obiektów danej klasy, dla których jest możliwe podanie wszystkich wartości składowych. Przykład 8.2. class wektor2d { public: double x, y; wektor2d(double nowyX, double nowyY) { x = nowyX; y = nowyY; } double dlugosc() const { return sqrt(x*x + y*y); } } Wywołanie konstruktora: int main() { wektor2d p(2, 5); return 0; } 2. Konstruktor inicjujący wartościami domyślnymi Umożliwia inicjowanie składowych klasy ustalonymi ‘z góry’ wartościami. Najczęściej służy do zainicjowania tablic obiektów danej klasy, dla których nie jest możliwe podanie wszystkich wartości. Przykład 8.3. class wektor2d { public: double x, y; wektor2d(double x=4, double y=5) {this->x=x; this->y=y;} double dlugosc() const { return sqrt(x*x + y*y); } } Wywołanie konstruktora: int main(void) { wektor2d p1[10], p2; return 0; } 3. Konstruktor kopiujący Jako argument otrzymuje tylko obiekt danej klasy. Jego działanie polega na kopiowaniu wartości pól klasy, czyli tworzeniu kopii obiektu. Przykład 8.4. class wektor2d { public: double x, y; //konstruktor kopiujący wektor2d(wektor2d &wkt) {x=wkt.x; y=wkt.y;} double dlugosc() const { return sqrt(x*x + y*y); } Wywołanie konstruktora: int main(void) { wektor2d a(1,2), b(3,4); //zainicjowane konstruktorem parametrycznym wektor2d Kopia = a; wektor2d w(b); } 4. Konstruktory z listą inicjalizacyjną Istnieje możliwośd zdefiniowania konstruktora z tzw. listą inicjalizacyjną: o Konstruktor bez parametrów: wektor2d() : x(0), y(0) { } o Konstruktor z dwoma parametrami i listą: wektor2d(double nowyX, double nowyY) : x(nowyX), y(nowyY) { } Dzięki listom inicjalizacyjnym, które wywołują konstruktory dla składowych klasy możemy uniknąd podwójnego nadpisywania pól klasy, tzn. lepiej napisad: wektor2d(double nx, double ny) : x(nx), y(ny) { } niż wektor2d(double nx, double ny) { x = nx; y = ny; } Inicjalizacja z użyciem konstruktora z listą: wektor2d a(4, 6), b(1, 2); Obiekty bez nazwy Obiekty bez nazwy tworzymy zgodnie ze schematem: klasa(parametry_konstruktora) np. wektor2d(1, 2); Obiekty bez nazwy pozwalają na efektywniejsze zwracanie parametrów. Aby zwrócid więcej niż jedną wartośd na raz można np. ‘zamknąd’ wszystkie wartości, które chcemy zwrócid w strukturę lub klasę. Dlatego możemy przerobid funkcję negacja (przykł z poprzedniego wykładu): void wektor2d::negacja() { x = -x; y = -y; } na taką, która zamiast zmieniad obiekt, na rzecz którego została wywołana, tworzy nowy, już zanegowany wektor (obiekt): wektor2d negacja() const { return wektor2d(-x, -y); } Pozwala to później napisad wywołanie: a.suma(b.negacja()), w którym wynik zwracany przed funkcję (typu wektor2d) jest parametrem innej funkcji. Przykład 8.5. Porównanie implementacji wektora na płaszczyźnie (przykład z poprzedniego wykładu) za pomocą klasy z dwoma konstruktorami i klasy bez konstruktorów. //bez konstruktora #include <cmath> #include <iostream> //z konstruktorami #include <cmath> #include <iostream> class wektor2d { public: double x; double y; class wektor2d { public: double x; double y; wektor2d() : x(0), y(0) { } wektor2d(double nowyX, double nowyY) : x(nowyX), y(nowyY) { } double dlugosc() const { return sqrt(x*x + y*y); } double dlugosc() const { return sqrt(x*x + y*y); } wektor2d suma(const wektor2d& b) const { wektor2d suma(const wektor2d& b) const { wektor2d wynik; wynik.x = x + b.x; wynik.y = y + b.y; return wynik; } return wektor2d(x + b.x, y + b.y); } wektor2d negacja() const { void negacja() { x = -x; y = -y; } void wypisz(std::ostream& out) const { out << "(" << x << "," << y << ")"; } return wektor2d(-x, -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; int main() { wektor2d a(4, 6), b(1, 2); wektor2d c = a.suma(b.negacja()); std::cout << "Dlugosc wektora "; c.wypisz(std::cout); std::cout << " wynosi: " << c.dlugosc() << std::endl; return 0; return 0; } } Przeciążanie operatorów Definiując w programie klasę, definiujemy nowy typ, dla którego można określid nowe operacje. Przeciążanie operatorów to proces modyfikowania znaczenia operatorów, np. operatora plus (+), na potrzeby określonej klasy. Dzięki przeciążaniu operatorów można uprościd typowe operacje na obiektach klasy i poprawid czytelnośd programu. Słowo kluczowe operator służy do oznaczania przedefiniowania operatora. Przeciążając operator definiujemy funkcję, którą kompilator ma wywoływad za każdym razem, gdy klasa korzysta z przeciążonego operatora. Ta funkcja realizuje odpowiednią operację. Jeśli program przedefiniowuje operator na potrzeby konkretnej klasy, to znaczenie tego operatora zostaje zmienione tylko w obrębie tej klasy. W pozostałych miejscach programu operator będzie wykonywad operacje zgodnie z jego standardowym znaczeniem. Można przedefiniowywad większośd operatorów, jednak są też operatory których nie można przeciążad. Zamiast używad nowych nazw funkcji, możemy przeciążyd znane operatory i w ten sposób uzyskad większą czytelnośd kodu, np. zamiast funkcji suma, możemy napisad operator+ i dalej w kodzie stosowad a + b zamiast a.suma(b). Przykład 8.6. Operatory przeciążone class wektor2d { public: double x; double y; wektor2d() : x(0), y(0) { } wektor2d(double nowyX, double nowyY) : x(nowyX), y(nowyY) { } double dlugosc() const { return sqrt(x*x + y*y); } wektor2d operator+(const wektor2d& b) const { return wektor2d(x + b.x, y + b.y); } wektor2d operator-() const { return wektor2d(-x, -y); } void wypisz(std::ostream& out) const { out << "(" << x << "," << y << ")"; } }; int main() { wektor2d a(4, 6), b(1, 2); wektor2d c = a + (-b); std::cout << "Dlugosc wektora "; c.wypisz(std::cout); std::cout << " wynosi: " << c.dlugosc() << std::endl; return 0; } Na ekranie zobaczymy: Dlugosc wektora (3,4) wynosi: 5 Funkcje zaprzyjaźniowe – gdy parametr operatora nie jest składową klasy Zauważmy, że lewa strona operatora+ (tzn. "a") jest obiektem klasy wektor2d. Gdyby tak nie było, nie moglibyśmy zastosowad tej notacji. Jeżeli jednak zachodzi koniecznośd operacji nie na zmiennych klasy wektor2d to stosujemy funkcje zaprzyjaźnione. Funkcja zaprzyjaźniona (friend) to taka funkcja, która ma dostęp do prywatnych składowych klasy. Tworzymy zatem friend void operator<<(std::ostream& out, wektor2d& wektor) { ... } Zauważmy, że dla np. dla operator+ możemy pisad a+b+c co oznacza (a + b) + c Jest to możliwe bo operator+ zwraca obiekt typu wektor2d. Dla operator<< możemy uzyskac ten sam efekt zwracajac lewy (pierwszy) argument funkcji operator<<, czyli: friend std::ostream& operator<<(std::ostream& out, wektor2d& wektor) { ... } i teraz możemy napisad std::cout << a << b << std::endl; Do programu dodajemy zatem funkcję zaprzyjaźnioną: friend std::ostream& operator << (std::ostream& out, wektor2d& wektor) { out << "(" << wektor.x << "," << wektor.y << ")"; return out; } int main() { wektor2d a(4, 6), b(1, 2); wektor2d c = a + (-b); std::cout << "Dlugosc wektora " << c; std::cout << " wynosi: " << c.dlugosc() << std::endl; return 0; } Przeciążony operator << ’potrafi’ wyświetlid składowe wektora c. Jakich operatorów nie wolno przeciążać W tabeli wymieniono operatory, których nie wolno przeciążać. Operator Opis Przykład . Wybór składowej klasy obiekt.skladowa .* Wskaźnik składowej obiekt.*skladowa :: Widoczność nazwa_klasy::skladowa ?: Wyrażenie warunkowe c = (a>b) ? a : b;