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ę 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 <math.h>
#include <iostream>
 
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:

  1. 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;
    }
  2. 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!

  3. Przy przeciążaniu operatorów (patrz laboratorium 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:

  1. public - pole lub metoda znajdująca się w bloku public jest dostępna wszędzie
  2. protected - dostęp tylko dla klas dziedziczących po danej klasie i dla klas zaprzyjaźnionych
  3. 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 <iostream>
#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

  1. 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?
  2. [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?
  3. [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 
  4. [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.
  5. [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 :-) )
pl/dydaktyka/jimp2/2016/labs/klasy1.txt · ostatnio zmienione: 2017/07/17 08:08 (edycja zewnętrzna)
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0