Programowanie i struktury danych Programowanie i struktury danych
Wykład 2 Wykład 2
Dr Piotr Cybula <piotr.cybula@wmii.uni.lodz.pl>
Dr Piotr Cybula <piotr.cybula@wmii.uni.lodz.pl>
Enkapsulacja / hermetyzacja Enkapsulacja / hermetyzacja
●
bezpieczeństwo kodu i zapewnienie spójności danych
●
niezależność i separacja danych
●
unikanie «dawania małpie brzytwy» (koncepcja czarnej skrzynki)
●
poziomy dostępu do struktury/klasy (specyfikatory dostępu):
●
public – dostęp publiczny dla innych struktur/klas i funkcji
●
private – dostęp prywatny, tylko dla metod struktury/klasy
●
protected – dostęp chroniony, dla metod struktury/klasy i jej struktur/klas pochodnych (dziedziczenie)
●
friend – deklaracja przyjaźni, dostęp dla wyspecyfikowanych struktur/klas lub funkcji
●
metody stałe (const) gwarantujące zachowanie obiektu w stanie
niezmienionym
Hermetyzacja : struct Hermetyzacja : struct
W strukturze wszystkie składowe (pola i metody) są domyślnie publiczne:
struct Student
{ private: //składowe ukryte string name;
int index;
bool checkIndex(int _index); //metoda wewnętrzna public: //interfejs publiczny
Student(string _name);
Student();
~Student();
int getIndex() const; //selektor
void setIndex(int _index); //modyfikator … //inne metody
};
Student s(”Scott Tiger”);
s.setIndex(12345); //użycie metody publicznej
s.checkIndex(); //błąd kompilacji – metoda prywatna
Hermetyzacja : class Hermetyzacja : class
W klasie wszystkie składowe (pola i metody) są domyślnie prywatne:
class Student
{ //składowe ukryte (słowo kluczowe private jest opcjonalne) string name;
int index;
bool checkIndex(int _index); //metoda wewnętrzna public: //interfejs publiczny
Student(string _name);
Student();
~Student();
int getIndex() const; //selektor
void setIndex(int _index); //modyfikator … //inne metody
};
Student s(”Scott Tiger”);
s.setIndex(12345); //użycie metody publicznej
s.checkIndex(); //błąd kompilacji – metoda prywatna
Niezmienniki klasy Niezmienniki klasy
●
reguły (zdania logiczne) opisujące spójny stan struktury/klasy (poprawne wartości atrybutów)
●
pomocnicze metody prywatne sprawdzające te reguły
●
każda metoda publiczna (w tym konstruktory) zakłada, że obiekt, dla którego została wywołana jest w stanie spójnym i gwarantuje pozostawienie go w takim stanie po jej zakończeniu (projektowanie oparte o kontrakt – design by
contract)
●
metoda publiczna zmieniająca stan obiektu (modyfikator) ma obwiązek sprawdzić zgodność takiej zmiany z regułami opisanymi za pomocą niezmienników
●
w przypadku przekroczenia reguł metoda zobowiązana jest zgłosić sytuację wyjątkową (throw exception)
●
wyjątek zgłaszony w konstruktorze zapobiega utworzeniu obiektu (brak
wywołania destruktora)
Niezmienniki klasy Niezmienniki klasy
#include <stdexcept>
class Student { string name;
int index;
float avg;
//class invariants:
//index is a positive number //avg is between 2.0 and 5.0 public: //interfejs publiczny Student(string _name);
void setIndex(int _index) //modyfikator
{ if(_index <= 0) throw invalid_argument(”Error”); //wyjątek index = _index;
}};
Student s1(”Scott Tiger”), s2(”John Smith”);
try {
s1.setIndex(12345); //ok
s2.setIndex(-1234); //zgłoszenie wyjątku
} catch(exception &e) { cout << e.what(); } //przechwycenie
Przeci ąż enie operatorów Przeci ąż enie operatorów
●
przedefiniowanie wbudowanego operatora dla obiektów własnej klasy (przekazywanych jako operandy)
●
nazwa funkcji zawiera słowo kluczowe operator z przyrostkiem identyfikującym nazwę lub symbol operatora, np. operator+, operator=, itp.
●
przeciążone operatory mogą być metodami klasy lub zaprzyjaźnionymi funkcjami zewnętrznymi (friend)
●
nie można tworzyć nowych operatorów, ani zmieniać ich priorytetów
●
operatory mogą uczynić strukturę bardziej intuicyjną w użyciu, szczególnie jeżeli realizuje pewne operacje matematyczne, ale często stanowią jedynie
«syntaktyczny lukier» – nie należy przeciążać operatora jeżeli jego użycie nie
zwiększa czytelności kodu
Operatory unarne Operatory unarne
●
operują na pojedynczym obiekcie klasy, dla którego są wywoływane
●
przeciążane typowo jako stała bezparametrowa metoda klasy
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } Vector operator-() const;
};
Vector Vector::operator-() const { return Vector(-x, -y);
}
Vector v1(2, -3);
Vector v2(-v1); //wywołanie operatorowe v2 = -v1; //tu też
v2 = v1.operator-(); //wywołanie funkcyjne
Operatory binarne Operatory binarne
●
operują na obiekcie klasy jako pierwszym operandzie oraz drugim operandzie podanego typu (może być tego samego)
●
przeciążane jako stała jednoparametrowa metoda klasy
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } Vector operator+(const Vector &v) const;
};
Vector Vector::operator+(const Vector &v) const { return Vector(x + v.x, y + v.y);
}
Vector v1(2, -3), v2(-1, 4), v3;
v3 = v1 + v2; v3 = v1 + 2; //wywołania operatorowe v3 = v1.operator+(v2); //wywołanie funkcyjne
Operatory binarne Operatory binarne
●
często przeciążane jako dwuparametrowa funkcja zaprzyjaźniona (dla zapewnienia przemienności operandów)
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; }
friend Vector operator+(const Vector &v1, const Vector &v2);
};
Vector operator+(const Vector &v1, const Vector &v2) { return Vector(v1.x + v2.x, v1.y + v2.y);
}
Vector v1(2, -3), v2(-1, 4), v3;
v3 = v1 + v2; v3 = v1 + 2; //wywołania operatorowe
v3 = 2 + v1; //wywołanie operatorowe, możliwa przemienność v3 = v1.operator+(v2); //błąd kompilacji – to nie jest metoda v3 = operator+(v1, v2); //wywołanie funkcyjne
Operator tablicowy [ ] Operator tablicowy [ ]
●
musi być jednoparametrową metodą klasy
●
może zwracać referencję, aby umożliwić modyfikację zwracanej zmiennej
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } double& operator[](int i) const;
};
double& Vector::operator[](int i) const { return (i == 1) ? x : y;
}
Vector v1(2, -3), v2(-1, 4);
double d = v1[1] + v2[2]; //wywołanie do odczytu v1[1] = 5; //wywołanie do modyfikacji
Operatory strumieniowe << i >>
Operatory strumieniowe << i >>
●
przeciążane typowo dla strumieni wejściowych/wyjściowych
●
pierwszy operand jest strumieniem (ostream lub istream), więc operator powinien być funkcją zaprzyjaźnioną i powinien zwracać strumień dla umożliwienia wielokrotnych wywołań kaskadowych
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; }
friend ostream& operator<<(ostream &stream, const Vector &v);
friend istream& operator>>(istream &stream, Vector &v);
};ostream& operator<<(ostream &stream, const Vector &v) { stream << '[' << v.x << ',' << v.y << ']';
return stream;
}Vector v1(2, -3), v2(-1, 4) ;
cout << v1 << ' ' << v2 << endl; //wywołanie kaskadowe
Operator przypisania = Operator przypisania =
●
musi być metodą klasy z obiektem źródłowym jako parametrem i powinien zwracać referencję na obiekt docelowy (*this) dla wywołań kaskadowych
●
automatycznie tworzony przez kompilator jeżeli nie został przeciążony jawnie (musi być przeciążony gdy klasa wymaga napisania własnego konstruktora kopiującego)
class Vector { double x, y;
public:
Vector(double _x = 0, double _y = 0) { x = _x; y = _y; } Vector& operator=(const Vector &v);
};Vector& Vector::operator=(const Vector &v) { if (this != &v) { x = v.x; y = v.y; } return *this;
}Vector v1(2, -3), v2, v3;
v3 = v2 = v1; //wywołanie kaskadowe