|
|
pl:dydaktyka:jimp2:2017:labs:operatory [2017/04/04 02:07] mwp [Konwersja typów] |
pl:dydaktyka:jimp2:2017:labs:operatory [2019/06/27 15:50] |
====== Przeciążanie operatorów ====== | |
=====Wykorzystanie przeciążania operatorów===== | |
| |
Pisząc program w C%%++%% można korzystać zarówno z typów wbudowanych jak i z typów zdefiniowanych przez siebie (jako nowe klasy). | |
| |
Wszystkie typy wbudowane mają zdefiniowane operatory porównania i przypisania, inkrementacji czy dekrementacji; tablice, wskaźniki czy referencje również posiadają zestaw operatorów ułatwiających (czy nieraz wręcz umożliwiających korzystanie z ich własności). | |
| |
Dla typów zdefiniowanych przez programistę można zdefiniować (przeciążyć) operatory istniejące w języku C%%++%% dla danego typu, dzięki czemu stosowanie ich w kontekście tych typów ma sens. Jednym z typowych przykładów przeciążania operatorów jest przeładowanie operatora wstawiania i pobierania danych ze strumienia:<file cpp Point.h>// Deklaracja znajduje się w pliku Point.h | |
#ifndef POINT_H | |
#define POINT_H | |
#include <iostream> | |
| |
class Point { | |
public: | |
| |
... | |
void SetX(double x); | |
void SetY(double y); | |
| |
//opcjonalna definicja pozwalająca na uzyskanie | |
//dostępu do prywatnych pól z wewnątrz definicji | |
//funkcji zadeklarowanej poniżej | |
// friend std::istream& operator>>(std::istream &, Punkt&); | |
... | |
| |
private: | |
double x_, y_; | |
}; | |
| |
//właściwa deklaracja, przeciążająca | |
//operator >> dla strumienia wejściowego | |
//i klasy punkt | |
std::istream& operator>>(std::istream &is, Point& point); | |
#endif | |
</file> | |
| |
I definicja w pliku Point.cpp | |
<file cpp Point.cpp> | |
//Definicja w Point.cpp | |
#include "Point.h" | |
#include <iomanip> | |
#include <iostream> | |
| |
using ::std::istream; | |
| |
//Helper functions: | |
void CheckNextChar(char c, istream *is) { | |
int next_char = is.peek(); | |
if (next_char != c) { | |
throw runtime_error("invalid character"); | |
} | |
is.ignore(); | |
} | |
| |
void IgnoreWhitespace(istream *is) { | |
is >> ws; | |
} | |
| |
double ReadNumber(istream *is) { | |
double d; | |
is >> d; | |
return d; | |
} | |
| |
| |
// Właściwa definicja, obydwa argumenty funkcji nie | |
//są zadeklarowane jako const, bo obydwa są modyfikowane | |
//wewnątrz funkcji (STL nie używa naszej konwencji z przekazywaniem | |
//przez wskaźnik) | |
istream& operator>>(istream & input, Punkt& p){ | |
CheckNextChar('(', &input); | |
p.SetX(ReadNumber(&input)); | |
CheckNextChar(',', &input); | |
IgnoreWhitespace(&input); | |
p.SetY(ReadNumber(&input)); | |
CheckNextChar(')', &input); | |
return input; // Umożliwia cin >> a >> b >> c; | |
} | |
</file> | |
| |
Powyższy przykład umożliwia wczytanie punktu podanego w formacie //(12, 22)//. Należy zwrócić uwagę, że operator ">>" nie może być metodą klasy a funkcją zaprzyjaźnioną jeśli chcemy wywołać go jako <code cpp>cin >> punkt;</code> Jeśli operator zadeklarowany byłby jako metoda klasy trzeba by go wywoływać na przykład tak:<code cpp>punkt >> cin</code> | |
| |
===== Jak to działa ===== | |
Operatory to "zwyczajne" funkcje w języku C%%++%%. Kompilator zamienia każde pojawienie się operatora na wywołanie odpowiedniej funkcji. | |
| |
Zakładając że operatory + oraz * są składowymi klas, tak będzie wyglądało użycie tych operatorów dla kompilatora: | |
<code cpp> | |
Matrix a, b; | |
a + b; // Dla kompilatora oznacza to a.operator+(b); (argument a zostanie przesłany jako this do funkcji) | |
a * b; // dla kompilatora oznacza to a.operator*(b); (j.w.) | |
</code> | |
| |
Można także zdefiniować niektóre operatory jako funkcje nieskładowe (jak w przykładzie z poprzedniej sekcji). Zakłądjąc że operatory + i * są zadeklarowane jako funkcje zaprzyjaźnione, tak będzie wyglądało użycie tych operatorów dla kompilatora: | |
<code cpp> | |
Matrix a, b; | |
a + b; // Dla kompilatora oznacza to operator+(a,b); | |
a * b; // dla kompilatora oznacza to operator*(a,b); | |
cin >> a; // dla kompilatora oznacza to operator>>(cin, a) | |
cin >> a >> b; // dla kompilatora oznacza to operator>>( operator>>(cin, a), b) | |
//stąd też wynika, że wartość zwracana przez funkcję operator>> musi być taka sama jak pierwszy argument tej funkcji | |
</code> | |
| |
===== Operatory jedno- i dwuargumentowe===== | |
Operatory **jednoargumentowe** można przeciążyć za pomocą niestatycznej funkcji składowej nie pobierającej żadnych argumentów (jeden niejawny argument jest przekazywany jako **this**) lub za pomocą funkcji nie będącej składową klasy, pobierającą jeden argument, który jest obiektem (przekazywanie przez wartość) lub referencją do obiektu tej klasy. **Wyjątkiem** jest operator rzutowania, który NIE może być zadeklarowany jako funkcja nieskładowa. | |
| |
Funkcje nieskładowe mogą być dodatkowo deklarowane jako zaprzyjaźnione z klasą dla której są zdefiniowane wtedy mają dostęp do prywatnych składników klasy. | |
| |
Załóżmy, że klasa Text jest naszą wersją klasy //string//, przechowującej napisy i umożliwiającą manipulowanie nimi. Chcąc zdefiniować na przykład operator **!** (logiczne zaprzeczenie) dla klasy Napis, który będzie zwracał //true// jeśli obiekt będzie zawierał pusty napis, a w każdym innym przypadku //true//, możemy to zrobić na da sposoby: | |
| |
<code cpp> | |
class Text{ | |
public: | |
// składowa klasy | |
bool operator!() const; | |
// LUB (opocjonalnie) funkcja nieskładowa zaprzyjaźniona | |
// friend bool operator!()(const Text&) | |
... | |
}; | |
| |
//LUB funkcja nieskładowa, która dodatkowo może być zaprzyjaźniona | |
//bool operator!()(const Text &text) | |
</code> | |
| |
Operatory **dwuargumentowe** można przeciążyć za pomocą niestatycznej funkcji składowej klasy pobierającej jeden argument (pierwszy argument jest przekazywany niejawnie jako **this**) lub za pomocą funkcji nie będącej składową klasy, pobierającą dwa argumenty z których jeden jest obiektem lub referencją do obiektu tej klasy. **Wyjątkami** są operatory przypisania, **[]** oraz **->**, które NIE mogą być zadeklarowane jako funkcje zaprzyjaźnione. | |
| |
<code cpp> | |
class Text{ | |
public: | |
// składowa klasy (pomysl dlaczego zwracamy const) | |
const Text operator+=(const Text& txt); | |
// LUB funkcja zaprzyjaźniona | |
friend const Text operator+=()(Text& lhs, const Text& rhs); | |
... | |
}; | |
const Text operator+=()(Text& lhs, const Text& rhs); | |
</code> | |
| |
=====Konwersja typów===== | |
Specyficznym rodzajem operatorów są tzw. operatory rzutowania. Wykorzystywane one są do konwersji typów, czy inaczej mówiąc do rzutowania. W prosty sposób można rzutować pomiędzy typami wbudowanymi: | |
<code cpp> | |
double number = 12.34; | |
int integer_value = static_cast<int>(number); | |
</code> | |
Aby możliwe było rzutowanie pomiędzy typami zdefiniowanymi przez programistę, konieczna jest definicja operatorów realizujących taką funkcjonalność. | |
Operatory rzutowania NIE mogą być zadeklarowane jako funkcje zaprzyjaźnione - muszą być składowymi klasy. Dodatkowo niczego nie zwracają: | |
<code cpp> | |
//Complex.h | |
class Complex{ | |
public: | |
operator Matrix() const; | |
... | |
}; | |
//Complex.cpp | |
Complex::operator Matrix() const { | |
return Matrix {{*this}}; | |
} | |
</code> | |
| |
Dzięki powyższej deklaracji i odpowiedniej definicji możliwe będzie następujące wywołanie:<code cpp> | |
Complex c("4i5"); | |
//Powinien utworzyć macierz 1x1 z wartością 4i5 w jedynej komórce | |
Matrix m = (Matrix)c; | |
</code> | |
| |
===== Operatory inkrementacji i dekrementacji===== | |
Operatory **%%++%%** i **- -** mogą również być przeciążane. Mogą one być jednak stosowane zarówno w wersji prefiksowej i postfiksowej. | |
| |
==== Operator prefiksowy==== | |
Chcąc przeładować operator inkrementacji dla klasy //Complex// tak aby można było wykonać następującą operację:<code cpp> | |
Complex c(1); | |
++c; | |
//kompilator tak anprawdę wywołuje c.operator++() | |
</code> | |
należy zadeklarować operator **%%++%%** w następujcy sposób: | |
<code cpp> | |
class Complex{ | |
public: | |
Complex &operator++(); | |
}; | |
</code> | |
| |
==== Operator postfiksowy==== | |
| |
Aby możliwe było wywołanie inkrementacji w notacji postfiksowej: | |
<code cpp> | |
Complex c(1); | |
c++; | |
//kompilator tak naprawdę wywołuje c.operator++(0) | |
</code> | |
konieczne jest zadeklarowanie operatora **%%++%%** w następujący sposób: | |
<code cpp> | |
class Complex{ | |
public: | |
// parametr "int" jest "dowolnym argumentem" który jest wykorzystywany | |
// przez kompilator do odróżnienia operatora postfiksowego od | |
// operatora prefiksowego. Podczas wywołania c++ | |
// kompilator wywoła tak naprawdę c.operator++(0) | |
Complex operator(int); | |
}; | |
</code> | |
| |
=====Tabela operatorów===== | |
^Operatory, które można przeciążać^^^^^^^ | |
|+|-|*|/|%|<nowiki>^</nowiki>|&| | |
|<nowiki>|</nowiki>|~|!|=|<|>|+=| | |
|-=|*=|/=|<nowiki>%=</nowiki>|<nowiki>^=</nowiki>|&=|<nowiki>|=</nowiki>| | |
|<<|>>|>>=|<<=|==|!=|<nowiki><=</nowiki>| | |
|>=|&&|<nowiki>||</nowiki>|%%++%%|<nowiki>--</nowiki>|->*|,| | |
|->|[]|()|new|delete|new[]|delete[]| | |
| |
| |
^Operatory, których nie można przeciążać^^^^^ | |
|.|.*|::|?:|sizeof| | |
| |
======Ćwiczenia====== | |
- [1 plus] Przetestować operator wczytywania dla klasy Punkt i dopisz operator wypisania ("<<"). Sprawdź w jakich przypadkach ten operator się wywołuje. | |
- Do klasy tablica dynamiczna DTab (z laboratorium [[.:klasy1|Klasy i obiekty I]]): | |
- [1 plus] dopisz operator '[]' pozwalający na pobranie danego elementu tablicy za pomocą konstrukcji <code cpp>DTab d(10); | |
cout << d[5] << endl;</code> | |
- [1 plus] Jak zrobić żeby dało się ustawić element tablicy za pomocą tego operatora, a nie tylko go pobrać?:<code cpp>tab[2] = 7; //ustawia 2 element tablicy na wartość 7</code> | |
- [1 plus] dopisz operator przypisania ( = ) | |
- [1 plus] dopisz operatory << i >>. Przyjmij format pobierania tablicy jako <code>[1,2,3,4]</code> | |
- [1 plus] dopisz operator <nowiki>++</nowiki> umożliwiający rozszerzenie tablicy o 1 | |
- [1 plus] Wykorzystując klasę //Matrix// napisz dla klasy //Complex// operator rzutowania:<code cpp>Complex c("5i3"); | |
Matrix nowa = (Matrix)c; | |
</code> | |
- **[5 punktów] Napisz klasę Mapa, która będzie zawierać listę obiektów typu Para. Klasa Para powinna posiadać dwa pola jedno typu //string// a drugie typu //integer//. Klasa Mapa powinna mieć konstruktor przyjmujący jako parametr ścieżkę do pliku tekstowego. W konstruktorze powinno nastąpić odczytanie pliku i zbudowanie indeksu wyrazów, tak aby każdy wyraz z pliku miał odpowiadający sobie obiekt klasy Para w liście. Pole typu //string// wewnątrz obiektów Para powinno odpowiadać danemu słowu, natomiast pole typu //integer// ilości powtórzeń tego słowa we wczytanym pliku. Plik jest __dowolnym plikiem tekstowym!__ Analizując go, ignorujemy znaki interpunkcyjne, spacje tabulatory, etc. i wczytujemy tylko słowa. ** | |
* Przeładuj operator klasy Mapa **[]** tak aby możliwe było poniższe wywołanie:<code cpp>Mapa m("myfile,txt"); | |
// w zmiennej ilosc powinna znaleźć się ilość powtórzeń | |
// słowa "programowanie" w pliku "myfile.txt" | |
int ilosc = m["programowanie"];</code> | |
* Przeładuj operator **%%<<%%** dla klasy Mapa, aby możliwe było wyświetlenie raportu o ilości słów i ich liczebności w danym pliku. Dane wyświetlane powinny być posortowane malejąco. Do tego celu wykorzystaj metodę //[[http://www.cplusplus.com/reference/list/list/sort/|sort]]// klasy //list// - wykorzystaj listę z biblioteki standardowej!. | |
* Przeładuj operatory porównania (//<,>,==//) i przypisania (//=//) dla klasy Para (porównywanie względem liczebności). | |
* Przeładuj operator **%%++%%** dla klasy Para, tak aby można było szybko inkrementować liczebność danego słowa podczas budowania mapy. | |
| |