Přetěžování operátorů Ing. Josef Vogel, CSc Katedra softwarového inženýrství Katedra teoretické informatiky, Fakulta informačních technologii, ČVUT v Praze © Karel Müller, Josef Vogel, 2011
Programování a algoritmizace 2, BI-PA2, 2011, Přednáška 4 BI-PA2
Evropský sociální fond Praha & EU: Investujeme do vaší budoucnosti
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
1/31
Přetěžování operátorů – třída Zlomek s operacemi pomocí funkcí – třída Zlomek s operacemi pomocí metod – třída Zlomek s operátory přetíženými pomocí funkcí – třída Zlomek s operátory přetíženými pomocí metod – třída Zlomek s operátory přetíženými pomocí friend-funkcí – třída Zlomek s přetíženým operátorem << – konstruktor uživatelské konverze – přehled přetěžování operátorů
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
2/31
p4\zlomek1\zlomek.h
Třída Zlomek po prvé • Instance třídy Zlomek je objekt obsahující dvě celočíselné položky: čitatel a jmenovatel • Instanční metody jsou zřejmé z následující deklarace třídy. Operace sčítání a odčítání realizujeme pomocí funkcí definovaných mimo třídu: class Zlomek { int cit, jmen; void zkratit(); public: Zlomek(int c = 0, int j = 1); int citatel() const {return cit;} int jmenovatel() const {return jmen;} void vypis() const; }; Zlomek soucet(Zlomek, Zlomek); Zlomek rozdil(Zlomek, Zlomek); bool rovno(Zlomek, Zlomek); Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
3/31
Operace se zlomky • Zkrácení zlomku – čitatele i jmenovatele vydělíme největším společným dělitelem čitatele a jmenovatele • Součet zlomků – zlomky převedeme na společného jmenovatele a pak sečteme • Rozdíl zlomků – zlomky převedeme na společného jmenovatele a pak odečteme
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
4/31
p4\zlomek1\zlomek.cpp
Třída Zlomek po prvé • Definice konstruktoru a metod: Zlomek::Zlomek(int c, int j) { cit = c; if (j>0) jmen = j; else if (j<0) { jmen = -j; cit = -cit; } else throw "jmenovatel je 0"; zkratit(); } vyvolání výjimky int nsd(int x, int y) { int zbytek = x%y; while (zbytek!=0) { x = y; y = zbytek; zbytek = x%y; } return y; }
void Zlomek::zkratit() { int d = nsd(abs(cit), jmen); if (d==1) return; cit /= d; jmen /= d; } void Zlomek::vypis() const { cout << '(' << cit << '/' << jmen << ')'; } Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
5/31
p4\zlomek1\zlomek.cpp
Třída Zlomek po prvé • Definice „netřídních“ funkcí“ Zlomek soucet(Zlomek x, Zlomek y) { int spoljm = x.jmenovatel() * y.jmenovatel(); Zlomek res(x.citatel()*y.jmenovatel() + y.citatel()*x.jmenovatel(), spoljm); return res; } Zlomek rozdil(Zlomek x, Zlomek y) { int spoljm = x.jmenovatel() * y.jmenovatel(); Zlomek res(x.citatel()*y.jmenovatel() – y.citatel()*x.jmenovatel(), spoljm); return res; } bool rovno(Zlomek x, Zlomek y) { return x.citatel()*y.jmenovatel() == x.jmenovatel()*y.citatel(); }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
6/31
p4\zlomek1\main.cpp
Třída Zlomek po prvé • Použití: #include "zlomek.h" int main() { Zlomek a(3,5), b(1), c; a.vypis(); cout << ' '; b.vypis(); cout << endl; c = soucet(a, b); c.vypis(); cout << endl; c = rozdil(a, b); c.vypis(); cout << endl; cout << rovno(a,b) << endl; system("PAUSE"); return 0; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
7/31
p4\zlomek2\zlomek.h
Třída Zlomek po druhé • Binární operace se zlomky můžeme realizovat pomocí metod (metody třídy Zlomek mohou používat privátní položky) class Zlomek { int cit, jmen; void zkratit(); public: Zlomek(int c = 0, int j = 1); int citatel() const {return cit;} int jmenovatel() const {return jmen;} void vypis() const; Zlomek soucet(Zlomek) const; Zlomek rozdil(Zlomek) const; bool rovno(Zlomek) const; };
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
8/31
p4\zlomek2\zlomek.cpp
Třída Zlomek po druhé • V metodě realizující binární operaci se zlomky bude první operand reprezentován objektem, na který se metoda aplikuje, a druhý operand bude dán parametrem metody Zlomek Zlomek::soucet(Zlomek y) const { int spoljm = jmen * y.jmen; Zlomek res(cit*y.jmen + y.cit*jmen, spoljm); return res; } Zlomek Zlomek::rozdil(Zlomek y) const { int spoljm = jmen * y.jmen; Zlomek res(cit*y.jmen - y.cit*jmen, spoljm); return res; } bool Zlomek::rovno(Zlomek y) const { return cit*y.jmen == jmen*y.cit; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
9/31
p4\zlomek2\main.cpp
Třída Zlomek po druhé • V metodě realizující binární operaci se zlomky bude první operand reprezentován objektem, na který se metoda aplikuje, a druhý operand bude dán parametrem metody int main() { Zlomek a(3,5), b(1), c; a.vypis(); cout << ' '; b.vypis(); cout << endl; c = a.soucet(b); c.vypis(); cout << endl; c = a.rozdil(b); c.vypis(); cout << endl; cout << a.rovno(b) << endl; system("PAUSE"); return 0; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
10/31
p4\zlomek3\main.cpp
Třída Zlomek po třetí • Nelíbilo by se vám, kdybychom pro součet zlomků mohli použít operátor + a pro rozdíl zlomků operátor - ? Pak by hlavní funkce mohla mít tento kód: int main() { Zlomek a(3,5), b(1), c; a.vypis(); cout << ' '; b.vypis(); cout << endl; c = a + b; c.vypis(); cout << endl; c = a - b; c.vypis(); cout << endl; cout << (a == b) << endl; system("PAUSE"); return 0; } • Toho lze dosáhnout přetížením operátorů
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
11/31
p4\zlomek3\zlomek.h
Třída Zlomek po třetí • Deklarace třídy Zlomek a funkcí přetěžujících operátory: class Zlomek { int cit, jmen; void zkratit(); public: Zlomek(int c = 0, int j = 1); int citatel() const {return cit;} int jmenovatel() const {return jmen;} void vypis() const; }; Zlomek operator+(Zlomek, Zlomek); Zlomek operator-(Zlomek, Zlomek); bool operator==(Zlomek, Zlomek);
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
12/31
Třída Zlomek po třetí • Přetížení binárních operátorů pomocí funkcí způsobí, že c = a + b; c = a - b; a == b
se provede jako c = operator+(a, b); se provede jako c = operator-(a, b); se vyhodnotí jako operator==(a, b);
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
13/31
p4\zlomek3\zlomek.cpp
Třída Zlomek po třetí • Definice funkcí přetěžujících operátory: Zlomek operator+(Zlomek x, Zlomek y) { int spoljm = x.jmenovatel() * y.jmenovatel(); Zlomek res(x.citatel()*y.jmenovatel() + y.citatel()*x.jmenovatel(), spoljm); return res; } Zlomek operator-(Zlomek x, Zlomek y) { int spoljm = x.jmenovatel() * y.jmenovatel(); Zlomek res(x.citatel()*y.jmenovatel() – y.citatel()*x.jmenovatel(), spoljm); return res; } bool operator==(Zlomek x, Zlomek y) { return x.citatel()*y.jmenovatel() == x.jmenovatel()*y.citatel(); }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
14/31
p4\zlomek4\zlomek.h
Třída Zlomek po čtvrté • Operátor lze přetížit též metodou: class Zlomek { int cit, jmen; void zkratit(); public: Zlomek(int c = 0, int j = 1); int citatel() const {return cit;} int jmenovatel() const {return jmen;} void vypis() const; Zlomek operator+(Zlomek) const; Zlomek operator-(Zlomek) const; bool operator==(Zlomek) const; };
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
15/31
Třída Zlomek po čtvrté • Přetížení binárních operátorů pomocí metod způsobí, že c = a + b; c = a - b; a == b
se provede jako c = a.operator+(b); se provede jako c = a.operator-(b); se vyhodnotí jako a.operator==(b);
• Poznámka: operátor nemůže být současně přetížen funkcí i metodou
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
16/31
p4\zlomek4\zlomek.cpp
Třída Zlomek po čtvrté • Operátor lze přetížit též metodou: Zlomek Zlomek::operator+(Zlomek y) const { int spoljm = jmen * y.jmen; Zlomek res(cit*y.jmen + y.cit*jmen, spoljm); return res; }
Zlomek Zlomek::operator-(Zlomek y) const { int spoljm = jmen * y.jmen; Zlomek res(cit*y.jmen - y.cit*jmen, spoljm); return res; } bool Zlomek::operator==(Zlomek y) const { return cit*y.jmen == jmen*y.cit; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
17/31
p4\zlomek5\zlomek.h
Třída Zlomek po páté • Operátor lze přetížit pomocí friend funkce, která má stejný přístup k private položkám, jako instanční metody třídy: class Zlomek { int cit, jmen; void zkratit(); public: Zlomek(int c = 0, int j = 1); int citatel() const {return cit;} int jmenovatel() const {return jmen;} void vypis() const; friend Zlomek operator+(Zlomek, Zlomek); friend Zlomek operator-(Zlomek, Zlomek); friend bool operator==(Zlomek, Zlomek); };
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
18/31
p4\zlomek5\zlomek.cpp
Třída Zlomek po páté • Definice friend-funkcí vypadají stejně, jako definice jiných funkcí (můžeme v nich však používat private položky)
Zlomek operator+(Zlomek x, Zlomek y) { int spoljm = x.jmen * y.jmen; Zlomek res(x.cit*y.jmen + y.cit*x.jmen, spoljm); return res; } Zlomek operator-(Zlomek x, Zlomek y) { int spoljm = x.jmen * y.jmen; Zlomek res(x.cit*y.jmen - y.cit*x.jmen, spoljm); return res; } bool operator==(Zlomek x, Zlomek y) { return x.cit*y.jmen == x.jmen*y.cit; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
19/31
p4\zlomek6\main.cpp
Třída Zlomek po šesté • A co výstupní konverze do standardního proudu cout? Lze dosáhnout toho, aby výpis zlomku byl předepsán operátorem <<, jak je uvedeno v následující funkci main? int main() { Zlomek a(3,5), b(1), c; cout << a << ' ' << b << endl; c = a + b; cout << c << endl; c = a - b; cout << c << endl; cout << (a == b) << endl; system("PAUSE"); return 0; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
20/31
p4\zlomek6\zlomek.h, zlomek.cpp
Třída Zlomek po šesté • Výstupní konverzi zlomků pomocí operátoru << umožní přetížení tohoto operátoru (pomocí funkce) pro pravý operand typu Zlomek:
class Zlomek { int cit, jmen; void zkratit(); public: Zlomek(int c = 0, int j = 1); int citatel() const {return cit;} int jmenovatel() const {return jmen;} friend Zlomek operator+(Zlomek, Zlomek); friend Zlomek operator-(Zlomek, Zlomek); friend bool operator==(Zlomek, Zlomek); friend ostream& operator<<(ostream&, Zlomek); }; ostream& operator<<(ostream& os, Zlomek x) { os << '{' << x.cit << '/' << x.jmen << ')'; return os; } Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
21/31
p4\zlomek6\main2.cpp
Třída Zlomek po šesté a půl • Podívejme se ještě na funkci main2.cpp: číslo typu int se přiřazuje do proměnné typu Zlomek, sčítá se zlomek s číslem typu int, číslo typu int se zlomkem: int main() { Zlomek a(3,5), b; int i = 1; b = 2; cout << b << endl; b = a + i; cout << b << endl; b = 2 + a; cout << b << endl; system("PAUSE"); return 0; }
Zlomek = int Zlomek = Zlomek + int Zlomek = int + Zlomek
• Kdo za to může? Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
22/31
Konstruktor uživatelské konverze • Konstruktorem uživatelské konverze typu T1 na T je každý konstruktor třídy T, který lze volat s jediným parametrem typu T1 • Je-li ve třídě takový konstruktor, pak všude tam, kde se očekává objekt typu T, může být uveden výraz typu T1. Konverze se provede tak, že se vytvoří dočasný objekt typu T a ten se inicializuje konstruktorem uživatelské konverze • Konstruktor Zlomek(int c = 0, int j = 1); je konstruktorem uživatelské konverze. Proto: b = 2; se provede jako b = Zlomek(2); b = a + 2; se provede jako b = a + Zlomek(2); b = 2 + a; se provede jako b = Zlomek(2) + a; • Uživatelskou konverzi lze zakázat tím, že před deklaraci konstruktoru se uvede explicit: explicit Zlomek(int c = 0, int j = 1);
• Vyzkoušejte Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
23/31
p4\zlomek7\zlomek.h,zlomek.cpp
Přehled přetěžování binárních operátorů • Binární operátory @ (* / % + - << >> < <= > >= == != & ^ | && || += -= ...) zápis operace: @x signatura: @T -> Tv přetížení metodou třídy T1: Tv operator@() ekviv. volání metody: x.operator@() Tv operator@(T) přetížení funkcí: operator@(x) ekviv. volání funkce: • Poznámka: přetížením operátoru např. + není přetížen operátor +=, ten musíme přetížit zvlášť, např. takto: friend Zlomek& operator+=(Zlomek&, Zlomek);
Zlomek& operator+=(Zlomek& x, Zlomek y) { int spoljm = x.jmen*y.jmen; x.cit = x.cit*y.jmen+y.cit*x.jmen; x.jmen = spoljm; x.zkratit(); return x; } Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
24/31
p4\zlomek7\zlomek.h,zlomek.cpp
Přehled přetěžování unárních operátorů a) Unární prefixové operátory @ (! ~ + - ++ -- & *) zápis operace: signatura: přetížení metodou třídy T: ekviv. volání metody: přetížení funkcí: ekviv. volání funkce:
@x @T -> Tv Tv operator@() x.operator@() Tv operator@(T) operator@(x)
Příklad: Zlomek operator++();
Zlomek Zlomek::operator ++() { cit += jmen; zkratit(); return *this; }
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
25/31
p4\zlomek7\zlomek.h,zlomek.cpp
Přehled přetěžování unárních operátorů b) Unární postfixové operátory @ (++ --) zápis operace: signatura: přetížení metodou třídy T: ekviv. volání metody: přetížení funkcí: ekviv. volání funkce:
x@ @T -> Tv Tv operator@(int) x.operator@(0) Tv operator@(T,int) operator@(x,0)
• Příklad: Zlomek operator++(int); Zlomek Zlomek::operator ++(int) { Zlomek vysl = *this; cit += jmen; zkratit(); return vysl; } Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
26/31
p4\zlomek7\main.cpp
Třída Zlomek po sedmé • Použití: int main() { Zlomek a(2,5); cout << a << endl; cout << ++a << endl; cout << a++ << endl; cout << a << endl; Zlomek x(3,4), y(1,8); x += y; cout << x << endl; return 0; }
Karel Müller, Josef Vogel (ČVUT FIT)
// // // //
vypise vypise vypise vypise
(2/5) (7/5) (7/5) (12/5)
// vypise (7/8)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
27/31
Přetížení operátoru = •
Operátor přiřazení (=) zápis operace: signatura: přetížení metodou třídy T: ekviv. volání metody: přetížení funkcí: ekviv. volání funkce:
x = y T = T1 -> T T& operator=(const T1&) x.operator=(y) -
• Poznámky: – Typ argumentu T1 je obvykle stejný s T – Typ výsledku může být též const T& nebo void. Jaký je v tom případě důsledek?
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
28/31
Přetížení operátoru [] •
Operátor [] zápis operace: signatura: přetížení metodou třídy T: ekviv. volání metody: přetížení funkcí: ekviv. volání funkce:
x[y] T[T1] -> Tv Tv& operator[](T1) nebo const Tv& operator[](T1) cons x.operator[](y) -
• První způsob přetížení je určen pro nekonstantní objekty a umožňuje použít výsledek jako l-value (tzn. na levé straně přiřazení) • Druhý způsob přetížení je určen pro konstantní objekty, výsledek není modifikovatelný
Karel Müller, Josef Vogel (ČVUT FIT)
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
29/31
Přetížení operátoru -> • Operátor -> (unární) zápis operace: signatura: přetížení metodou třídy T: ekviv. volání metody: přetížení funkcí: ekviv. volání funkce:
Karel Müller, Josef Vogel (ČVUT FIT)
x -> p T -> Tv* Tv* operator->() (x.operator->()) -> p -
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
30/31
Přetížení operátoru volání funkce •
Operátor () zápis operace: signatura: přetížení metodou třídy T: ekviv. volání metody: přetížení funkcí: ekviv. volání funkce:
Karel Müller, Josef Vogel (ČVUT FIT)
x(x1,x2,…,xn) T(T1,T2,…,Tn) -> Tv Tv operator()(T1,T2,…,Tn) x.operator()(x1,x2,…,xn) -
Přetěžování operátorů
BI-PA2, 2011, Přednáška 4
31/31