Programowanie obiektowe
w C++
Wykład 05a Temat wiodący:
Static, const, volatile
static, const,
volatile
Static – zmienne klasowe
n
zmienna klasowa o atrybucie static jest
wspólna dla wszystkich obiektów danej klasy
n
istnieje nawet gdy nie ma żadnego obiektu
n
można się do niej odwoływać za pomocą operatora zakresu
class ST {
static int s; // to jest tylko deklaracja int i;
public:
static int ps; // jak wyżej };
int ST::s=0, // to definicja ST::ps=0;
n deklaruje się w klasie, ale definiuje (już bez słowa kluczowego static) poza klasą
n można zdefiniować tylko raz
n dla bibliotek: definicje do *.cpp a dekl. klasy do *.h (nie trzeba i nie mozna dodatkowych extern ST::s)
Static – metody
n
mogą wtedy być wołane, nawet gdy nie ma obiektów (za pomocą operatora zakresu)
n
mogą korzystać jedynie ze statycznych pól i metod klasy
n
nie ma wskaźnika this
Static – metody
class ST {
static int s;
int i;
public:
static int ps;
ST(int i=0) :i(i) { s++;
static void ile_nas() {
cout << s << "\n";
}
void wszystko() // nie wolno static !!!
{
cout << s << " " << i cout << " " << ps << "\n";
} };
void main() {
int i;
// i=s; // nie ma ::s ani ::ps // i=ps;
// i=ST::s; // private i=ST::ps;
ST::ile_nas();
// ST::wszystko(); // niestatyczna metoda klasy ST a;
ST::ile_nas();
a.ile_nas();
a.wszystko();
}
const, volatile
n
const --- stałe
const int ci =7;
n
volatile --- ulotne
volatile int vi=8;
i++;
i--;
i=i+0;
const, volatile
n
obiekty również mogą być stałe bądź ulotne
n
na rzecz obiektów stałych bądź ulotnyvh można aktywować jedynie metody uprzywilejowane
n
metody uprzywilejowane definiujemy podając po nawiasie zamykającym listę argumentów słowa kluczowe const i/lub volatile.
n
UWAGA: uprzywilejowanie metody może powodować jej przeciążenie:
int f(); int f() const;
class CV { int i;
public:
CV(int i=1) :i(i) { };
void out() {
cout << i;
}
void out() const volatile {
cout << i;
}
void inc() { i++;
}
void inc_c() const {
// i++; // i jest const }
void inc_v() volatile {
i++;
}
void inc_cv() const volatile {
// i++;
}
void main() {
CV cv;
const CV cvc;
volatile CV cvv;
const volatile CV cvcv;
cv.inc();
cv.inc_c();
cv.inc_v();
cv.inc_cv();
// cvc.inc();
cvc.inc_c();
// cvc.inc_v();
cvc.inc_cv();
// cvv.inc();
// cvv.inc_c();
cvv.inc_v();
cvv.inc_cv();
// cvcv.inc();
// cvcv.inc_c();
// cvcv.inc_v();
cvcv.inc_cv();
cv.out(); //CV::out();
cvc.out(); //CV::out() const volatile;
cvv.out();
cvcv.out();
}
const, volatile
n
metody uprzywilejowane nie mogą być static.
n
konstruktor ani destruktor nie może być const volatile ani static.
n
deklarowanie metod uprzywilejowanych nie może prowadzić do niejednoznaczności
int f() const; // int f() const volatile;
Przykład: pola const i static
class info {
static int cntr, // licznik istniejacych obiektow serial; // statyczna zmienna pomocnicza const int nr; // unikalny numer seryjny obiektu public:
info()
info(const info & i);
~info();
info & operator=(const info &p);
};
int info::cntr=0, // static info::serial=0; // static
info::info() :nr(++serial) {
cntr++;
cout << "\nkonstruktor bezparametrowy obiektu nr" << nr;
cout << " jest " << cntr << " obiektow info";
cout.flush();
}
info::info(const info & i) :nr(++serial)
{ cntr++;
cout << "\nkonstruktor kopiujacy obiektu nr" << nr << " z obiektu " << i.nr;
cout << " jest " << cntr << " obiektow info";
cout.flush();
}
info::~info() {
cntr--;
cout << "\ndestruktor obiektu nr" << nr;
cout << " zostanie " << cntr << " obiektow info";
cout.flush();
}
info & info::operator=(const info &p) {
cout << "\noperator przypisania do obiektu nr " << nr;
cout << " z obiektu nr" << p.nr;
cout << " jest " << cntr << " obiektow info";
cout.flush();
return *this;
}
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ę
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
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
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)))
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;
}