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.

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 przeciążyć operatory istniejące w języku C++, 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:

// Deklaracja znajduje się w pliku punkt.h
#ifndef PUNKT_H
#define PUNKT_H
#include <iomanip>
#include <iostream>
 
using namespace std;
 
class Punkt{
  private:
    double x, y;
  public:
    ...
    //operator wczytywania
    friend istream& operator>>(istream &, Punkt&);
    ...
};
#endif

I definicja w pliku punkt.cpp

//Definicja w punkt.cpp
#include "punkt.h"
#include <iomanip>
#include <iostream>
 
using namespace std;
 
istream& operator>>(istream & input, Punkt& p){
   input.ignore();    // Ignoruj nawias
   input >> p.x;
   input.ignore();    // Ignoruj przecinek
   input >> p.y;
   input.ignore();    // Ignowruj nawias
   return input;      // Umożliwia cin >> a >> b >> c;
}

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

cin >> punkt;

Jeśli operator zadeklarowany byłby jako metoda klasy trzeba by go wywoływać na przykład tak:

punkt >> cin

Jak to działa

Operatory to „zwyczajne” funkcje w jezyku C++. Kompilator zamienia każde pojawienie sie operatora na wywołanie odpowiedniej funkcji.

Zakładajac że operatory + oraz * są składowymi klas, tak będzie wyglądało uzycie tych operatorów dla kompilatora:

Matrix a, b;
a + b; // Dla kompilatora oznacza to a.operator+(b);
a * b; // dla kompilatora oznacza to a.operator*(b);

Można takze zdefiniować niektóre operatory jako funkcje zaprzyjaźnione (jak w przykładzie z poprzedniej sekcji). Zakłądjąc że operatoruy + i * są zadeklarowane jako funkcje zaprzyjaźnione, tak będzie wyglądało uzycie tych operatorów dla kompilatora:

Matrix a, b;
a + b; // Dla kompilatora oznacza to operator+(a,b);
a * b; // dla kompilatora oznacza to operator*(a,b);

Operatory jedno- i dwuargumentowe

Operatory jednoargumentowe można przeciążyć za pomocą niestatycznej funkcji składowej nie pobierającej żadnych argumentów lub za pomocą funkcji nie będącej składową klasy, obierającą jeden argument który jest obiektem lub referencją do obiektu tej klasy. Wyjątkiem jest operator rzutowania, który NIE może być zadeklarowany jako funkcja zaprzyjaźniona.

Załóżmy, ze klasa Napis jest naszą wersją klasy string, przechowującej napisy i umożliwiającą manipulowanie nimi. Chcąc zdefiniować na przykład operator ! 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:

class Napis{
  public:
    // składowa klasy
    bool operator!() const;
    // LUB funkcja zaprzyjaźniona
    friend bool operator!()(const Napis&)
    ...
};

Operatory dwuargumentowe można przeciążyć za pomocą niestatycznej funkcji składowej klasy pobierającej jeden argument lub za pomocą funkcji nie będącej składową klasy, obierającą dwa argumenty z których jeden jest obiektem lub referencją do obiektu tej klasy. Wyjątkami są operatory przypisania, [] oraz , które NIE gogą być zadeklarowane jako funkcje zaprzyjaźniona.

class Napis{
  public:
    // składowa klasy (pomysl dlaczego zwracamy const)
    const Napis operator+=(const Napis&);
    // LUB funkcja zaprzyjaźniona
    friend const Napis operator+=()(Napis&, const Napis&)
    ...
};

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:

double liczba_double = 12.34;
int liczba_int = (int)licczba_double;

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ą:

class Complex{
  public:
    operator Matrix() const;
    ...
};

Dzięki powyższej deklaracji i odpowiedniej definicji możliwe będzie następujące wywołanie:

Complex c("4i5");
//Powinien utworzyć macierz 1x1 z wartością 4i5 w jedynej komórce
Matrix m = (Matrix)c;

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ę:

Complex c(1);
++c;
//kompilator tak anprawdę wywołuje c.operator++()

należy zadeklarować operator ++ w następujcy sposób:

class Complex{
  public:
    Complex &operator++();
};

Operator postfiksowy

Aby możliwe było wywołanie inkrementacji w notacji postfiksowej:

Complex c(1);
c++;
//kompilator tak naprawdę wywołuje  c.operator++(0)

konieczne jest zadeklarowanie operatora ++ w następujący sposób:

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);
};

Tabela operatorów

Operatory, które można przeciążać
+-*/%^&
|~!=<>+=
-=*=/=%=^=&=|=
«»»=«===!=<=
>=&&||++--→*,
[]()newdeletenew[]delete[]
Operatory, których nie można przeciążać
..*::?:sizeof

Ćwiczenia

  1. [1 plus] Przetestować operator wczytywania dla klasy Punkt i dopisz operator wypisania („«”). Sprawdź w jakich przypadkach ten operator się wywołuje.
  2. Do klasy tablica dynamiczna DTab (z laboratorium Klasy i obiekty I):
    1. [1 plus] dopisz operator '[]' pozwalający na pobranie danego elementu tablicy za pomocą konstrukcji
      DTab d(10);
      cout << d[5] << endl;
    2. [1 plus] Jak zrobić żeby dało się ustawić element tablicy za pomocą tego operatora, a nie tylko go pobrać?:
      tab[2] = 7; //ustawia 2 element tablicy na wartość 7
    3. [1 plus] dopisz operator przypisania ( = )
    4. [1 plus] dopisz operatory « i ». Przyjmij format pobierania tablicy jako
      [1,2,3,4]
    5. [1 plus] dopisz operator ++ umożliwiający rozszerzenie tablicy o 1
  3. [1 plus] Wykorzystując klasę Matrix napisz dla klasy Complex operator rzutowania:
    Complex c("5i3");
    Matrix nowa = (Matrix)c;
  4. [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:
      Mapa m("myfile,txt");
      // w zmiennej ilosc powinna znaleźć się ilość powtórzeń
      // słowa "programowanie" w pliku "myfile.txt"
      int ilosc = m["programowanie"];
    • 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ę qsort 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.
pl/dydaktyka/jimp2/2015/labs/operatory.txt · ostatnio zmienione: 2019/06/27 15:50 (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