Programowanie obiektowe
w C++
Wykład 05 Temat wiodący:
Funkcje zaprzyjaźnione i operatory
this
n
dostępny w każdej niestatycznej metodzie
n
jest to wskaźnik do obiektu na rzecz którego wywołano metodę
n
można go wykorzystać jeżeli mamy zwrócić wskaźnik bądź referencję do obiektu na rzecz którego wywoływana jest metoda
n
nie zajmuje pamięci
n
nie wymaga deklarowania
this
person & person::timeTravel(int years) {
age += years;
return *this;
}
person Boss(40, „John”, „Kovalsky”);
( Boss.timeTravel(+20) ).timeTravel(-30)
friend
n
po polsku zaprzyjaźnianie
n
jest to deklaracja znosząca ograniczenia
dostępu do składowych klasy przez ściśle
określony kod
friend
class A {
friend C::theMethodNoArg();
// dotyczy konkretnej metody, tu bezargumentowej friend class B;
// dotyczy całej deklaracji klasy B
// również deklaracje wewnątrz B mają dostęp // do pól prywatnych A
friend fun();
// dotyczy konkretnej funkcji, tu bezargumentowej }
friend
n
zaprzyjaźnianie nie podlega dziedziczeniu
n
zaprzyjaźnianie nie jest przechodnie
n
deklarowane w dowolnej sekcji (nawet
prywatnej) klasy
friend
class M { friend void f() { …
} };
jest równoważne class M {
friend void f();
};
void f() { … }
Operatory
n
Co to jest operator?
a = b.val >> 4 + c * d[ e++ ]
Operatory
n
operator definiuje operacje wykonywane na swoich argumentach i typ wyniku takiej operacji – analogicznie do funkcji.
n
w C++ można definiować znaczenie
operatorów w przypadku wykorzystania ich do obiektów (przynajmniej jeden argument
obiektowy).
Operatory
n
Można przeciążać większość operatorów
n
Przeciążanie może poprawić wydajność
n
Nie ma obowiązku a zwykle nie ma i potrzeby
definiowania wielu operatorów, jeżeli operator
byłby sporadycznie używany to zaleca się
deklarowanie go jako metody bądź zwykłej
funkcji.
Operatory które można przeciążać
() [] ->
+ - ++ -- ! ~ * & new delete (typ) // jednoargumentowe ->*
* / % + -
<< >>
< <= > >=
== !=
&
^
|
&&
||
= += -= *= /= %= &= ^= |= <<= >>= // op. przypisania
Operatory
n
nie można przeciążać operatorów:
. .* :: ?: sizeof
n
operatory, które mogą być jedynie metodami klasy:
() [] -> new delete (typ) =
n
operatory domniemane, jeżeli nie zdefiniowane:
= &
Łączność
n
operatory przypisania i jednoargumentowe są prawostronnie łączne
a=b=c oznacza a=(b=c)
n
pozostałe lewostronnie
a+b+c oznacza (a+b)+c
Kolejność wartościowania
n
kolejność wartościowania argumentów niezdefiniowana, za wyjątkiem:
, || &&
n
„ , ”, „ || ”, „ && ” najpierw wartościowany lewy argument, potem prawy
n
dla operacji logicznych stosowane jest
skrócone wartościowanie.
Operatory a metody
n
inna składnia deklaracji, definicji i wywołania
n
operatory nie mogą mieć argumentów domyślnych (wyjątek: operator wywołania funkcji)
n
określona jest liczba argumentów (wyjątek: operator wywołania funkcji)
n
przyjęte jest zwyczajowe znaczenie operatorów
Operatory
n
Chcesz += to zdefiniuj go, nie wystarczy zdefiniować + i =
n
Chcesz ++ to zdefiniuj go,
kompilatorowi nie wtstarczy + i =
n
Zachowaj wierność zwyczajowemu znaczeniu operatorów
n niech jednoarg. & zwraca adres
n
Zachowaj konsekwencję
n ++i; i++; i+=1; i=i+1; // powinny dawać taki sam efekt.
Operatory jako metody
n
pierwszy argument będzie obiektem klasy
n
metoda ma mniej argumentów niż operator
n
deklarowanie
int point::operator<=(point);
n
wywoływanie
point point1, point2;
int i= point1 <= point2;
int j= point1.operator<=(point2);
Operatory jako funkcje
n przynajmniej jeden z argumentów musi być obiektem
n wszystkie argumenty są parametrami funkcji operatorowej
n nie ma this
n deklarowanie
int operator<=(point, point);
n wywoływanie
point point1, point2;
int i = point1 <= point2;
int j= operator<=(point1, point2);
Konwencja dla operatorów ++ i --
n
operator++() i operator--() są przedrostkowe
++i --i
n
operatory przyrostkowe deklarujemy i
definiujemy z dokładnie jednym nienazwanym parametrem int:
operator++(int); operator--(int);
i++ i--
Konwencja dla operatorów ++ i --
class X { public:
X& operator++(); // prefix ++a X operator++(int); // postfix a++
};
class Y { public:
};
Y& operator++(Y&); // prefix ++b
Y operator++(Y&, int); // postfix b++
Konwencja dla operatorów ++ i --
void f(X a, Y b) {
++a; // a.operator++();
a++; // a.operator++(0);
++b; // operator++(b);
b++; // operator++(b, 0);
a.operator++(); // odpowiada ++a;
a.operator++(0); // odpowiada a++;
operator++(b); // odpowiada ++b;
operator++(b, 0); // odpowiada b++;
}
Operatory
class X // składowe z niejawnym this {
X* operator&(); // przedrostkowy, jednoargumentowy &
X operator&(X); // dwuargumentowy X operator++(int); // przyrostkowy
// X operator&(X, X); // blad trzyargumentoowy // X operator/(); // blad jednoarg.
};
// funkcje globalne, bez this, często zaprzyjazniane X operator-(X); // przedrostkowy X operator-(X, X); // dwuargumentowy
X operator--(X&, int); // przyrostkowy dekrementacja // X operator-(X, X, X); // blad trzyargumentoowy // X operator/(); // blad jednoarg.
Operator przypisania
n
Musi być zdefiniowany jako metoda (niestatyczna) a nie jako funkcja
T& T::operator=(const T &)
n Nie ma listy inicjalizacyjnej bo to nie jest konstruktor !!!
Operator przypisania
person& person::operator=(const person &o) {
if (this == &o) // Tom = Tom;
return *this;
delete name;
name=new char[strlen(o.name) + 1];
strcpy(name, o.name);
delete lastName;
lastName=new char[strlen(o.lastName) + 1];
strcpy(lastName, o.lastName);
age=o.age;
return *this; // zwraca obiekt, będzie przekazany przez referencje }
Operator przypisania
n dlaczego operator przypisania zwraca referencję?
n chcemy napisać o1=o2=o3;
person& operator=(const person&);
n przypisania do: o2 z: o3
n operator przypisania do: o1 z: (o2=o3)
person operator=(const person&);
n przypisania do: o2 z: o3
n konstruktor kopiujący (powstaje tymczasowy obiekt t1)
n operator przypisania do: o1 z: t1
n konstruktor kopiujący (powstaje tymczasowy obiekt t2)
n destruktor t2
n destruktor t1
Operator przypisania
n
dlaczego operator przypisania zwraca referencję?
n
chcemy napisac o1=o2=o3;
n
void operator=(const person&);
n
można o2=o3;
n
nie da się o1=o2=o3;
Operator przypisania generowany automatycznie
n
jeżeli się nie zdefiniuje operatora przypisania to kompilator dostarczy własny operator przypisania, który skopiuje obiekt pole po polu.
n
nie będzie działał dla pól stałych
n
nie będzie działał dla dla referencji
n
dla wskaźników skopiuje wartość
Operatory strumieniowe
n
Wejście
friend istream & operator >>(istream &, T &);
n
Wyjście
friend ostream &operator <<(ostream &, const T &);
Operatory strumieniowe
class point {
int x, y;
public:
…
friend istream &operator >>(istream & s, point & p);
friend ostream &operator <<(ostream & s, const point &);
}
point p, p2;
cin>>p;
cin>>p>>p2;
cout<<p2<<p;
Operatory strumieniowe
istream & operator >>(istream & s, point & p) {
s >> p.x >> p.y;
return s;
}
ostream &operator <<(ostream & s, const point & p) {
s << p.x << p.y;
return s;
}
Operator wyłuskania
T* operator->();
n
T t;
n
t->m jest interpretowane jako:
( t.operator-> )->m
n
można używać zmiennej jak wskaźnika
n
jeżeli operator-> nie zwraca wskaźnika to
n
można użyć go: a=t.operator->();
n
nie można: a=t->; bo to błąd składniowy
Operator wywołania funkcji
wynik T::operator()(args);
n
Dowolna liczba argumentów, można przeciążać
n
Wyjątek: tylko ten operator może mieć domyślne argumenty
n
potem można obiekt używać tak, jak funkcję
T ob;
ob();
n
stosuje się gdy
n nie istnieje odpowiedni operator, a by się przydał
n jest jakś metoda dominująca dla klasy (będzie bez nazwy ale łatwiejsza do wywołania – np. klasa counter i metoda inkrementacja)
operator indeksowania
wynik T::operator[](index);
n
typy wyniku i indeksu dowolne ale zdrowy rozsądek nakazuje indeks całkowity
n
można tworzyć inteligentne agregaty,
Operator konwersji
n
operator konwersji tworzy obiekt określonego typu lub klasy z obiektu na rzecz którego jest
wywoływany
T::operator X();
n
operator konwersji klasy T do typu X:
n
nie określa się jawnie typu wartości zwracanej,
n
jest bezargumentowy
n
musi być metodą (nie funkcją)
Operator konwersji
n
np.:
person::operator int();
n
konwersja może następować niejawnie:
person o;
int i=o+5;
n
W wyrażeniach
n dla danego obiektu konwersja jest stosowana tylko jeżeli jest konieczna,
n może być tylko jedna konwersja danego składnika definiowana przez użytkownika
n nie może być niejednoznaczności
Operator konwersji
n
np. dla klas: A, B i C, zdefiniowano operatory konwersji B::operator A() i C::operator B();
int f(A a);
A a; B b; C c;
f(a); // ok..
f(b); // f(A(b))
// f(c); // nie ma operatora C::operator A()
// nie zrobi się niejawnie f(A(B(c)))
// (2 konwersje skladnika)
Operator konwersji
n
aby przekształcić obiekt jednego typu w obiekt innego typu można jawnie użyć konstruktora, ale:
n
nie da się przeprowadzić przekształcenia do typu podstawowego (np. int, bo to nie klasa),
n
nie da się przekształcić do starej klasy bez modyfikowania jej definicji.
Przykład
class counter {
int cnt;
public:
counter(int = 0):cnt(0){};
int operator()(int =1); // increment by arg. or by 1 operator int(); // convert to int
operator double(); // convert to double counter &operator+=(int);
friend counter operator+(int, counter); // int+counter, friend counter operator+(int); // counter+int
int operator++(); // ++counter
int operator++(int); // counter++
};
Przykład
inline int counter::operator ()(int i) {
return cnt+=i;
};
inline counter::operator int() {
return cnt;
};
inline counter::operator double() {
return double(cnt);
};
Przykład
counter & counter::operator +=(int i) {
cnt+=i; // operator()(i); (*this)(i)
return *this;
};
counter operator+(int i, counter l) {
l+=i; // zaprzyjaźnianie nie było konieczne return l;
};
Przykład
counter counter::operator +(int i) {
counter l=*this;
l+=i;
return l;
};
int counter::operator ++() {
cnt+=1;
return cnt;
};
int counter::operator ++(int) {
int ret=cnt;
cnt+=1;
return ret;
};
Przykład
class complex {
double re, im;
public:
complex(double re, double im):re(re), im(im) {};
complex & operator = (const complex &c);
complex & operator += (const complex &c);
complex operator + (const complex &);
friend complex & operator -= (complex &, const complex &);
friend complex operator - (const complex &, const complex &);
friend istream & operator >>(istream &, complex &);
friend ostream & operator <<(ostream &, const complex &);
};
Przykład
complex & complex::operator = (const complex &c) {
re=c.re;
im=c.im;
return *this;
};
Przykład
complex & complex::operator += (const complex &c) {
re+=c.re;
im+=c.im;
return *this;
};
inline complex complex::operator + (const complex & c) {
return complex(re+c.re, im+c.im); // complex(double, double) }
Przykład
inline complex & operator-=(complex & c1, const complex & c2) //friend {
c1.re-=c2.re;
c1.im-=c2.im;
return c1;
}
complex operator-(const complex & c1, const complex & c2) //friend {
complex c=c1;
c-=c2;
return c;
}
Przykład
istream & operator >>(istream & s, complex & c) {
s >> c.re >> c.im;
return s;
}
ostream &operator <<(ostream & s, const complex & c) {
s << c.re << c.im;
return s;
}