====== Klasy i obiekty I ====== //The great thing about Object Oriented code is that it can make small, simple problems look like large, complex ones.// Do ukończenia ćwiczeń z tego laboratorium przydatne będzie zapoznanie się z informacjami dotyczącymi Makefile znajdującymi się [[http://home.agh.edu.pl/~gjn/dydaktyka/UGLX/node10.html|tutaj]]. Należy to zrobić przed rozpoczęciem zajęć! =====Czym jest klasa a czym obiekt===== Klasa definiuje nowy typ danych podobnie jak struktura, ale w znacznie szerszym znaczeniu. Obiekt to instancja danej klasy: jest to utworzona zmienna o typie określonym przez daną klasę. ====Struktury a klasy=== Struktury są swego rodzaju kontenerami danych, klasy są bytami bardziej samoistnymi. Właściwości klas: * Mogą posiadać **pola**, będące obiektami klas, lub zmiennymi typów prostych * Mogą posiadać **metody** - funkcje związane z daną klasą i mające nieograniczony dostęp do pól danej klasy * Mogą chronić dostępu do pewnych swoich pól i metod * Mogą być rozszerzane i rozbudowywane poprzez mechanizm dziedziczenia. =====Podział na pliki===== Deklaracja i definicja klasy powinny byc rozdzielone. Deklaracja klasy umieszczana jest w **pliku nagłówkowym** o rozszerzeniu **.h** a definicja w osobnym pliku o rozszerzeniu **.cpp**. Definiując metody klasy, używamy operatora zasięgu (**::**), aby wskazać, ze definicja dotyczy metody klasy, a nie zwyczajnej funkcji. Np. ... void Punkt::wyswietl(){ cout << "(" << x << ";" << y << ")";} ... Ponieważ programy składają się często z wielu plików, istnieje zagrożenie, że podłączając pliki nagłówkowe klas kompilator mógłby dołączyć deklaracje klasy kilkukrotnie. Przed takim niebezpieczeństwem służą dyrektywy preprocesora //#ifndef, #define, #endif//. Poniżej: jeśli nie jest zdefiniowane PUNKT_H, to zdefiniuj PUNKT_H. To gwarantuje, że deklaracja klasy zostanie dołączona podczas kompilacji do całego programu tylko raz. Przyjęło się, że dyrektywach preprocesora //#ifndef, #define// używa się nazwy pliku wraz z rozszerzeniem. Gwarantuje to pewną unikalność definiowanych nazw. ====Deklaracja klasy==== // Deklaracja znajduje siew pliku punkt.h #ifndef PUNKT_H #define PUNKT_H class Punkt{ private: double x, y; public: //Konstruktor bezparametrowy Punkt(); //Konstruktor parametrowy Punkt(double _x, double _y); //Destruktor ~Punkt(); double distance(Punkt inny); void wyswietl(); // Krótkie funkcje mogą być zdefiniowane w pliku // nagłówkowym i będą traktowane jako funkcje inline double getX(){return x;} double getY(){return y;} void setX(double _x){x=_x;} void setY(double _y){y=_y;} }; #endif ==== Definicja klasy ==== //Definicja znajduje się w pliku punkt.cpp #include "punkt.h" #include #include using namespace std; /* Aby wskazać, ze definicja funkcji dotyczy metody danej klasy stosujemy tzw. operator zasięgu - "::" */ //Specjalna inicjalizacja zmiennych. Zmienne są inicjowane //nim zostanie wywołane ciało konstruktora Punkt::Punkt():x(0),y(0){ cout << "Konstruktor bezparametrowy" << endl; } Punkt::Punkt(double _x, double _y){ cout << "Konstruktor parametrowy" << endl; x = _x; y = _y; } Punkt::~Punkt(){ cout << "Destruktor! Nic nie robie, bo nie musze zwalniać pamięci!"; cout << endl; } double Punkt::distance(Punkt inny){ return sqrt(pow(x-inny.x,2)+pow(y-inny.y,2)); } void Punkt::wyswietl(){ cout << "(" << x << ";" << y << ")"; } =====Pola, metody, konstruktor, destruktor===== Pola to zmienne znajdujące się wewnątrz definicji klasy. Pola mogą być dowolnego typu. Zadaniem konstruktora jest zainicjowanie ich odpowiednimi wartościami. Metody to funkcje wykonujące jakieś operacje charakterystyczne dla danej klasy. Metody zdefiniowane w pliku nagłówkowym są traktowane przez kompilator jako funkcje //inline//. Może to poprawić wydajność w przypadku małych funkcji, ale pogorszyć w przypadku nagminnego stosowania tego mechanizmu. Do pól i metod dostajemy się dzięki następującym operatorom: * **. (kropka)** - operator kropki umożliwia dostę do pól i metod gdy obiekt jest zwykłą zmienną, lub referencją * **- > (strzałka)** - operator strzałki umożliwia dostęp do pól i metod, gdy działamy na wskaźniku do obiektu. Ponieważ klasy są bytami bardzo złożonymi, a ich polami mogą być w szczególności dynamicznie zaalokowane tablice, konieczne było zaprojektowanie mechanizmu tworzenia i usuwania obiektów danych klas. * **Konstruktor** - specjalna metoda klasy, która jest wywoływana automatycznie podczas tworzenia obiektu danej klasy. Konstruktor musi nazywać się tak jak klasa! Służy przede wszystkim do ustawiania wartości dla pól tworzonego obiektu. * //konstruktor domyślny// - jeśli nie zostanie zdefiniowany żaden konstruktor, obiekt i tak będzie można utworzyć dzięki tzw. konstruktorowi domyślnemu. Nie inicjalizuje on pól klasy żadnymi wartościami - jedynie przydziela danemu obiektowi pamięć. * **Destruktor** - podobnie jak konstruktor jest to specjalna metoda klasy wywoływana podczas niszczenia obiektu. Jeśli nie nie alokujemy dynamicznie pamięci, pisanie destruktora nie jest konieczne. Destruktor musi nazywać się tak jak klasa, ale poprzedzony jest znakiem tyldy. Konstruktor i destruktor nie zwracają żadnego typu. ===== Wskaźnik "this" ===== Każdy obiekt klasy ma pośród swoich pól wskaźnik o nazwie //this//, który wskazuje na niego samego. Wskaźnika //this// nie trzeb deklarować - jest to wbudowany mechanizm. Istnieje kilka zastosowań wskaźnika. Poniżej przedstawione zostały dwa najpopularniejsze: - Odwoływanie się do własnych pól wewnątrz metody, gdy parametry przekazywane do niej przesłaniają zmienne klasy: Punkt::Punkt( double x, double y){ this->x = x; this->y = y; } - Wywołania kaskadowe. W klasie Punkt z poprzedniego laboratorium zaimplementowane zostały dwie metody ustawiające współrzędna x i y punktu. Z użyciem wywołań kaskadowych ustawienie współrzędnych wyglądałoby tak:Punkt p; p.setX(10).setY(12); Pomyśl jak zaimplementować taką funkcjonalność z użyciem wskaźnika //this//. Uwaga: przydadzą się referencje! - Przy przeciążaniu operatorów (patrz laboratorium [[.:operatory|Przeciążanie operatorów]]). =====Modyfikatory dostępu===== Każda klasa może chronić dostępu do swoich pól i metod za pomocą trzech modyfikatorów dostępu: - **public** - pole lub metoda znajdująca się w bloku //public// jest dostępna wszędzie - **protected** - dostęp tylko dla klas dziedziczących po danej klasie i dla klas zaprzyjaźnionych - **private** - pola lub metody znajdujące się w tym bloku są dostępne tylko dla obiektów tej klasy i klas zaprzyjaźnionych ===== Korzystanie z klas i tworzenie obiektów ===== Aby wykorzystać klasę w pisanym programie konieczne jest włączenie jej pliku nagłówkowego za pomocą dyrektywy //#include//. Obiekty tworzymy tak samo jak zwykłe zmienne. Jeśli istnieje konstruktor parametrowy, to parametry podajemy w nawiasach po nazwie zmiennej. #include #include "punkt.h" using namespace std; int main(void){ Punkt p; Punkt p2(12,34); Punkt *ptrP = new Punkt(3,4); p2.wyswietl(); cout << ptrP->distance(p2) << endl; delete ptrP; } =====Wyodrębnianie klas===== Początkujący programiści mają problemy z wyodrębnianiem obiektów. Np. Czy gdybyśmy chcieli napisać program rozwiązujący problem skoczka szachowego z użyciem obiektów - co powinno być obiektem a co nie? Jakie klasy powinny mieć jakie metody? Czy skoczek powinien być obiektem i mieć metodę //skacz// przyjmującą jako parametr szachownicę, czy szachownica powinna być obiektem i mieć metodę //symuluj//? A może i szachownica i skoczek powinny być obiektami? Jeśli coś da się wyodrębnić jako obiekt, to powinno się to zrobić. ====== Ćwiczenia ====== - Przetestuj klasę Punkt. Zobacz kiedy wywołują się destruktory a kiedy konstruktory. Napisz funkcję która przyjmuje dwa obiekty klasy Punkt jako parametry i wyświetla odległość między nimi. Jak wywoływane są konstruktory i destruktory? Czy można bezpośrednio pobrać wartość współrzędnych punktu? - **[1 punkt] Stwórz plik //Makefile// służący do kompilacji projektu z powyższego punktu. Projekt powinien składać się min. z 3 plików: punkt.h, punkt.cpp i pliku zawierającego testy klasy Punkt. Ponadto dopisz do pliku //Makefile// cel clean usuwający zbędne pliki powstające podczas kompilacji (pliki .o). \\ Jak przeprowadzić kompilację projektu z wykorzystaniem takiego //Makefile//?** - **[2 punkty] Zdeklaruj i zdefiniuj klasę DynamicznaTablica** (:!: Uwaga: ta klasa będzie wykorzystywana na kolejnych laboratoriach - lepiej nie omijać tego zadania :-) ). **Napisz program testujący działanie klasy. Deklaracja klasy powinna wyglądać następująco**:#ifndef DTAB_H #define DTAB_H class DTab{ private: double * tab; int length; int last; // Metoda rozszerzajaca rozmiar tablicy do rozmiaru podanego jako parametr void resize(int newSize); public: // Konstruktor bezparametrowy. Powinien tworzyć tablicę o rozmiarze 10. (wykorzystaj metode resize) DTab(); // Tworzy tablice o rozmiarze podanym jako parametr. (wykorzystaj metode resize) DTab(int initLength); // Destruktor. Uwaga! Tablicę tworzymy dynamicznie, czyli tutaj jest wymagany! ~DTab(); // Dodaje element do na koniec tablicy. Jeśli tablica jest za mała // rozszerza ją. (wykorzystaj metode resize) void add(double element); / Pobiera element z tablicy z podanego indexu double get(int index); // Ustawia element o danym indeksie na daną wartość void set(double element, int index); // wyświetla tablice. void print(); }; #endif - [2 plusy] Napisz klasę Kwadrat, która będzie posiadać jako pola obiekty klasy Punkt (4 wierzchołki). Napisz metody obliczające obwód i pole kwadratu. - **[2 punkty] Napisz klasę Complex, która będzie implementować typ określający liczby zespolone. Klasa powinna mieć metody takie jak: //add, sub, mul, div, pow// itd. umożliwiające operacje na liczbach zespolonych.** (:!: Uwaga: ta klasa będzie wykorzystywana na kolejnych laboratoriach - lepiej nie omijać tego zadania :-) )