PARADYGMATY PROGRAMOWANIA Wykład 3

Transkrypt

PARADYGMATY PROGRAMOWANIA Wykład 3
PARADYGMATY PROGRAMOWANIA
Wykład 3
Definiowanie operatorów i ich przeciążanie
Przykłady zastosowania operatorów:
a) operator podstawienia ( = ) obiektów o złożonej strukturze,
b) operatory działania na łańcuchach:
+ konkatenacja
- wycięcie z łańcucha podanego innego łańcucha,
^ powtórzenie łańcucha zadaną ilość razy,
==, <=, >= <> ... - relacje porządku leksykograficznego dat,
c) operatory do operacji na macierzach
+ dodawanie macierzy
* mnożenie macierzy
^ mnożenie macierzy określoną ilość razy przez siebie
d) operacje na datach:
+ dodawanie liczby do daty
- odejmowanie liczby od daty - wynik: nowa data
- odejmowanie dat - wynik: liczba dni dzieląca daty
==, <=, >= <> ... - relacje pomiędzy datami
e) operacje na zbiorach:
+ dodawanie zbiorów,
- odejmowanie zbiorów (operator dwuragumentowy)
- dopełnianie zbioru (operator jednoargumentowy)
* wyliczanie części wspólnej
==, <=, >= <> ... - relacje pomiędzy zbiorami
Ponieważ do zdefiniowania nowych operatorów używamy tych samych symboli
już zdefiniowanych na poziomie języka (predefiniowanych) to mówimy o
przeciążaniu operatorów.
Dlaczego istnieje potrzeba przedefiniowywania operatora
podstawienia:
class String
{
public:
String( char *_stg );
~String()
private:
char *stg;
int
length;
}
String::String( char *_stg )
{
len = strlen( _stg );
stg = new char[ len + 1 ];
strcpy( stg, _stg );
}
~String()
{
delete [] stg;
}
...
{
String s1("Ala ma kota");
String s2("To kot Ali");
...
s2 = s1;
// ??? - co z zaalokowaną pamięcią w s2
...
}
// ??? - jak wykonuje się destruktor
Operatory a metody wykonywane na obiektach
obiekt_1
operator obiekt_2 => obiekt_3
• obiekty mogą być dowolnych typów - niekoniecznie takich samych
• obiekt_2 lub obiekt_3 może być typu prostego (np. int, BOOL, float,
double char)
• każdemu operatorowi odpowiada funkcja wykonująca odpowiednią operację,
• zapis w postaci infiksowej lub prefiksowej jest równoważny - zwykliśmy
jednak stosować zapis infiksowy, np:
(a * (b + 10) + 15) / (b + 6)
jest równoważny
div( add( mul( a, add( b, 10 )), 15 ), add( b, 6 ))
Operatory podlegające przeciążaniu
+
*
/
%
^
&
|
=
<
>
+= -= *= /= %=
~
^=
!
&=
!=
||
<<
++
>>
--
<<= >>= ==
->* ,
->
|=
[]
<= >= &&
new delete
Zasady przeciążania:
• nie można zmieniać liczby operandów operatora,
• priorytety operatorów oraz zasady ich łączności nie podlegają zmianie,
• nie można modyfikować znaczenia operatorów wobec typów predefiniowanych
języka( int, float, char ...)
Składnia deklaracji i definicji operatorów przeciążanych
<typ wyniku operacji> operator <symbol_operatora>
( <specyfikacja_parametru> );
Przykład:
class String
{
public:
String( char *_stg );
String& operator=( const String& _stg )
private:
char
*str;
int
length;
}
String& String::operator= (const String &_stg )
{
if (this == &_stg)
return *this;
if ( _stg.length > length )
{
delete [] str;
length = _stg.length;
str = new char [length + 1];
}
strcpy( str, _stg.str);
return *this;
}
String& String::operator= ( char *_stg )
{
if (this->str == &_stg)
return *this;
if ( strlen( _stg) > length )
{
delete [] str;
length = strlen( _stg);
str = new char [length + 1];
}
strcpy( str, _stg );
return *this;
}
Przeciążony operator przypisania a konstruktor kopiujący:
konstruktor kopiujący - automatycznie tworzony konstruktor o prototypie
X (const X&)
gdzie: X - identyfikator klasy
Wołany przy:
• Inicjalizacji innym obiektem tej samej klasy,
• przekazaniu obiektu do funkcji przez wartość,
• zwracaniu przez funkcję obiektu.
Różnice pomiędzy operatorem przypisania a konstruktorem kopiującym:
• nie ma potrzeby zwalniania składowych obiektu do którego następuje
przypisanie,
• nie ma potrzeby zabezpieczania się przed kopiowaniem na samego siebie,
• nie ma potrzeby zwracania referencji na obiekty docelowy.
String::String( const String& _stg )
{
length = _stg.length;
str = new char[ length + 1];
strcpy( str, _stg.str );
}
String::String( char *_stg )
{
length = strlen( _stg);
str = new char[ length + 1];
strcpy( str, _stg );
}
Przykłady implementacji operatorów łańcuchowych:
bool String::operator==( const String& _stg )
{
return ( strcmp( str, _stg.str ) == 0 );
}
bool String::operator!=( const String& _stg )
{
return (strcmp( str, _stg.str ) != 0);
}
char& String::operator[]( int index )
{
if ((index >= 0) && (index < lengtgh))
return str[ index ];
else
return str[0];
}
#if 0
String& String::operator+=( const String& _stg )
{
... // ???
}
#endif
String String::operator+( const String& _stg )
{
... // ???
}
String& operator+=( String& left, const String &right )
{
String tymcz;
tymcz = left;
tymcz = tymcz + right;
left = tymcz;
return left;
}
String operator+( const String& left, const String &right )
{
String tymcz;
tymcz = left;
tymcz += right;
return tymcz;
}
Przykłady użycia:
String
s1, s2, s3;
s1
s2
s2
s3
if
= "Jan"; // s1 = String(“Jan”);
= "Anna";
+= "Kowalska";
= s1 + s2;
(s2 != s1)
s3 = (s2 + " ") + "Nowak";
s3 = "Nowak" + s2;
// BŁĄD;
s3 = String("Nowak") + s2;
// OK
Przykład - realizacja operacji na wektorach:
wektor.h:
class Wektor
{
protected:
int liczba;
int *pocz;
public:
Wektor(int);
Wektor(const Wektor &x);
~Wektor();
int Podaj(int);
void Wpisz(int, int);
void Mnoz(int);
void Dodaj(Wektor);
};
class WektorR : public Wektor
{
private:
int przekroczenie;
public:
WektorR(int);
int & operator [] (int i);
void Sprawdz();
WektorR & operator += (WektorR);
WektorR & operator *= (int);
WektorR & operator = (WektorR &);
WektorR & operator -();
WektorR & operator -= (WektorR);
int operator == (WektorR &);
WektorR & operator ++ ();
WektorR & operator -- ();
};
wektor.cpp:
#include "wektor.h"
Wektor::Wektor(int n)
{
pocz = new int[liczba = n];
for (int i=0; i<liczba; i++)
pocz[i] = 0;
}
Wektor::Wektor(const Wektor &x)
{
pocz = new int[liczba = x.liczba];
for (int i=0; i<liczba; i++)
pocz[i] = x.pocz[i];
}
Wektor::~Wektor()
{
delete [liczba] pocz;
}
int Wektor::Podaj(int k)
{
if ((k < liczba) && (k >= 0)) {
return pocz[k];
} else
return pocz[0];
}
void Wektor::Wpisz(int x, int k)
{
if (k < liczba && k >= 0)
pocz[k] = x;
}
void Wektor::Dodaj(Wektor b)
{
int i;
for (i=0; i<liczba; i++)
pocz[i] += b.pocz[i];
}
void Wektor::Mnoz(int k)
{
for (int i=0; i< liczba; i++)
pocz[i] = pocz[i]*k;
}
WektorR::WektorR(int n):Wektor(n)
{
przekroczenie = 0;
}
int WektorR::Sprawdz()
{
return (przekroczenie);
}
int & WektorR::operator [] (int i)
{
if ( i<0 ) {
i = 0;
przekroczenie = 1;
}
if ( i>=liczba) {
i = liczba-1;
przekroczenie = 1;
}
return pocz[i];
}
WektorR & WektorR::operator += (WektorR b)
{
int i;
if (liczba <= b.liczba)
for (i=0; i<liczba; i++)
pocz[i] += b.pocz[i];
return *this;
}
void WektorR::operator *= (int k)
{
for (int i=0; i< liczba; i++)
pocz[i] = pocz[i]*k;
}
WektorR & WektorR::operator = (WektorR &b)
{
if (this != &b) {
if (liczba != b.liczba) {
delete [liczba] pocz;
pocz = new int[liczba = b.liczba];
}
for (int i=0; i<liczba; i++)
pocz[i] = b.pocz[i];
}
return *this;
}
WektorR & WektorR::operator -()
{
for (int i=0; i<liczba; i++)
pocz[i] = -pocz[i];
return *this;
}
WektorR & WektorR::operator -= (WektorR b)
{
for (int i=0; i<liczba; i++)
pocz[i] -= b.pocz[i];
return *this;
}
int WektorR:: operator == (WektorR &b)
{
int i;
if (liczba != b.liczba)
return 0;
else
for (i=0; i<liczba; i++)
if (pocz[i] != b.pocz[i])
return 0;
return 1;
}
WektorR & WektorR:: operator ++ ()
{
for (int i=0; i<liczba; i++)
pocz[i]++;
return *this;
}
WektorR & WektorR:: operator -- ()
{
for (int i=0; i<liczba; i++)
pocz[i]--;
return *this;
}
mian()
{
WektorR
a, b, c;
a[5] = 10;
x = a[5];
a = ++b;
a += b;
b = -a;
a = (b = c);
a = b = c;
Przeciążanie operatorów poza klasami:
void operator += (Wektor &a, Wektor &b)
{
for (int i=0; i < a.liczba; i++)
a.pocz[i] += b.pocz[i];
}
Stosujemy gdy lewy operand nie jest obiektem klasy.
W pozostałych przypadkach zaleca się stosowanie operatora jako metody
Operator ()
Jako jedyny pozwala przekazać większą niż dwa liczbę argumentów
(NIEMOŻLIWE np. Z WYKORZYSTANIEM OPERATORA [] )
Przykład:
class Tablica
{
protected:
int m;
// liczba wierszy
int n;
// liczba kolumn
int *pocz;
public:
Tablica(int, int);
~Tablica();
int & operator() (int ind_w, int ind_k);
};
Tablica::Tablica(int liczbaW, int liczbaK)
{
m = liczbaW;
n = liczbaK;
pocz = new int[m*n];
for (int i = 0; i < m*n; i++)
pocz[i] = 0;
}
Tablica::~Tablica()
{
delete [m*n] pocz;
}
int & Tablica::operator() (int i, int j)
{
if ( i< 0 || i >= m)
i = 0;
if (j < 0 || j >= n)
j = 0;
return pocz[i * n + j];
}
void main(void)
{
Tablica t(10, 10);
t(0,0) = 10;
t(5,7) = 100;
}
Funkcje i klasy zaprzyjaźnione
Stosujemy gdy funkcja nie będąca metodą danej klasy powinna mieć dostęp do jej
składowych prywatnych lub chronionych.
Informację o zaprzyjaźnieniu umieszczamy na początku deklaracji klasy, do której
dostęp otwieramy:
Udostępnienie danych pojedynczej funkcji:
friend <typ funkcji> <ident.funkcji> ( <parametry> )
Udostępnienie danych funkcji przeciążającej operator:
friend <typ wyniku> operator <symbol operatora> ( <parametry> )
Udostępnienie danych pojedynczej metodzie innej klasy
friend <typ metody> <ident.klasy>::<ident.metody> ( <parametry> )
Udostępnienie danych całej innej klasie X:
friend class X;
Przykład - porównywanie zawartości wektorów i tablic:
class Tabl3;
class Wekt3
{
friend void Sprawdz(const Wekt3 &, const Tabl3 &);
private:
int w[3];
public:
Wekt3();
void Wczytaj();
void Drukuj();
};
class Tabl3
{
friend void Sprawdz(const Wekt3 &, const Tabl3 &);
private:
int t[3][3];
public:
Tabl3();
void Wczytaj();
void Drukuj();
};
void Sprawdz(const Wekt3 & x, const Tabl3 & y)
{
int i, j, ident;
for (i=0; i<3; i++) {
ident = 1;
for (j=0; j<3; j++)
if (x.w[j] != y.t[i][j])
ident = 0;
if (ident == 1)
cout << "Wiersz " << i << " jest identyczny" << endl;
}
}