Professional Documents
Culture Documents
Aleksander Timofiejew
WARSZAWA 2006
Spis treści
1. Wprowadzenie.................................................................................................................................................................4
2. Zasady programowania zorientowanego obiektowo........................................................................................................4
2.1. Tworzenie programu komputerowego ......................................................................................................................4
2.2. Paradygmaty programowania ..................................................................................................................................6
2.3. Języki programowania zorientowanego obiektowo ..................................................................................................6
2.4. Rozwój języków zorientowanych obiektowo.............................................................................................................7
Język C++...................................................................................................................................................................7
2.5. Zalety i wady programowania zorientowanego obiektowo .......................................................................................7
3. Klasy a obiekty programowe............................................................................................................................................8
3.1. Klasa ........................................................................................................................................................................8
3.2. Obiekty klasy............................................................................................................................................................8
3.3. Dziedziczenie klas....................................................................................................................................................8
3.4. Widoczność komponentów klasy .............................................................................................................................9
3.5. Konstruktor i destruktor klasy ...................................................................................................................................9
3.6. Definicja klasy ..........................................................................................................................................................9
3.7. Definicja obiektu klasy............................................................................................................................................10
3.8. Wewnętrzny wskaźnik na obiekt ............................................................................................................................11
3.9. Tworzenie i usuwanie obiektów..............................................................................................................................11
Alokacja obiektu na stosie ........................................................................................................................................11
Alokacja obiektu na stercie .......................................................................................................................................12
Usuwanie obiektów...................................................................................................................................................12
3.10. Odwołanie do komponentów ................................................................................................................................13
3.12. Dostęp do komponentów obiektu .........................................................................................................................13
3.13. Klasy zaprzyjaźnione............................................................................................................................................14
3.14. Informacja o klasie ...............................................................................................................................................14
3.15. Mechanizm RTTI..................................................................................................................................................14
4. Komponenty klasy .........................................................................................................................................................16
4.1. Komponenty statyczne ...........................................................................................................................................16
4.2. Komponenty stałe ..................................................................................................................................................17
4.3. Metody wstawiane..................................................................................................................................................17
4.4. Właściwości klasy ..................................................................................................................................................18
4.5. Włączenie obiektów klas ........................................................................................................................................19
4.6. Rodzaje konstruktorów...........................................................................................................................................19
4.7. Konstruktor statyczny .............................................................................................................................................20
4.8. Konstruktor kopiujący .............................................................................................................................................20
4.9. Konstruktor z argumentami ....................................................................................................................................20
4.10. Inicjalizacja obiektu ..............................................................................................................................................21
4.11. Konstruktory z listą inicjacyjną .............................................................................................................................21
4.12. Destruktory...........................................................................................................................................................23
5. Dziedziczenie klas .........................................................................................................................................................24
5.1. Deklaracja dziedziczenia........................................................................................................................................24
5.2. Dziedziczenie uprawnień dostępu..........................................................................................................................24
5.3. Interfejs obiektu......................................................................................................................................................25
5.4. Hermetyzacja danych składowych .........................................................................................................................25
5.5. Hierarchia klas .......................................................................................................................................................27
5.6. Dziedziczenie od wielu rodziców............................................................................................................................27
5.7. Przesłonięcie metody .............................................................................................................................................28
5.8. Przeciążenie metody ..............................................................................................................................................28
5.9. Polimorfizm nazw ...................................................................................................................................................29
5.10. Metody wirtualne ..................................................................................................................................................29
5.11. Metody dynamiczne .............................................................................................................................................30
5.12. Klasa polimorficzna i klasa abstrakcyjna ..............................................................................................................30
5.13. Lokalna konwersja typów (rzutowanie typów) ......................................................................................................31
5.14. Kolejność wywoływania konstruktorów klas .........................................................................................................31
5.15. Wywołanie konstruktorów z listy inicjacyjnej ........................................................................................................32
5.16. Destruktory wirtualne............................................................................................................................................32
6. Metody operatorowe......................................................................................................................................................34
6.1. Przeciążanie operatorów........................................................................................................................................34
6.2. Metoda operatorowa klasy .....................................................................................................................................35
6.3. Zaprzyjaźniona metoda operatorowa .....................................................................................................................36
6.4. Operatory przeciążone ...........................................................................................................................................36
6.4.1. Operatory unarne............................................................................................................................................36
6.4.2. Operator =.......................................................................................................................................................36
6.4.3. Operator () ......................................................................................................................................................39
6.4.4. Operator [].......................................................................................................................................................39
6.4.5. Operator -> .....................................................................................................................................................40
6.4.6. Operatory new i delete....................................................................................................................................40
7. Wzorce (szablony) klas i funkcji.....................................................................................................................................43
7.1. Wzorce (szablony) klas .........................................................................................................................................43
7.2. Wzorce (szablony) funkcji .....................................................................................................................................44
7.3. Przykłady definicji wzorców....................................................................................................................................44
Literatura ...........................................................................................................................................................................47
LITERATURA GŁÓWNA ...............................................................................................................................................47
LITERATURA POMOCNICZA ......................................................................................................................................47
1. Wprowadzenie
class Nazwa_klasy(Lista_parametrów_formalnych);
value Zbiór_wartości;
Zbiór_specyfikacji_parametrów;
begin
Zbiór_deklaracji_lokalnych;
Lista_instrukcji
end
Grubą czcionką są zaznaczone słowa kluczowe.
1
1) Coad P., Yordan E., 1994: Analiza obiektowa, 2) Coad P., Yordan E., 1994: Projektowanie obiektowe, 3)
Coad P., Nicola J., 1993: Programowanie obiektowe. Warszawa: Oficyna wydawnicza READ ME.
2
PARADYGMAT (nauka) - http://encyklopedia.interia.pl
Paradygmat (nauka) - http://pl/wikipedia.org/wiki/Paradygmat_(nauka)
Opis pewnej klasy „student” wygląda w języku Simula 67 następująco:
3.1. Klasa
Klasa jest konstrukcją składniową języka zorientowanego obiektowo, która zawiera opis
komponentów klasy i implementacje metod klasy.
Komponenty klasy można podzielić na
• pola składowe, zwane także „atrybutami”, „danymi składowymi”, ”zmiennymi klasy”,
• metody klasy, zwane również ”funkcjami składowymi”, ”funkcjami klasy”, ”procedurami klasy”.
Pola składowe (atrybuty) są to zmienne ze wskazanym typem.
Metody klasy są procedurami (funkcjami) zawierającymi ciągi operatorów języka programo-
wania.
W niektórych językach do pól i metod mogą być dołączone jeszcze definicje typów z zakre-
som działania w granicach klasy.
Pola i metody klasy mogą tworzyć trwałe grupy. W środowiskach Delphi, C++ Builder i w ję-
zykach Visual Basic .NET, Visual C# .NET taką grupą nazywana jest właściwością klasy (ang.
class property).
W językach C++, Java i Visual C# struktury programowe są równe klasom. Struktury pro-
gramowe są interpretowane jako klasy z dostępem publicznym do pól i metod, tj. z możliwością
odwołania do pól i metod z każdego miejsca programu.
W języku Java oprócz pól i metod klasa może zawierać jeszcze segment stałego kodu wy-
konywanego przy tworzeniu obiektu danej klasy.
W przykładzie w bloku ze słowem class i nazwą klasy Licznik są opisane komponenty kla-
sy: pole danych liczba, dwie metody Licznik i Plus. Metoda Licznik jest konstruktorem. Poza
opisem klasy umieszczone są implementacje dwóch metod. Do nazw metod jest dodana nazwa
klasy, tj. Licznik::.
Licznik liczn;
O rozmiarze pamięci zajmowanej przez obiekt można dowiedzieć się za pomocą funkcji si-
zeof w języku C++. Do funkcji można przekazać nazwę obiektu lub nazwę klasy.
Alokacja pamięci dla obiektu typu klasa może odbywać się na stosie lub na stercie w zależ-
ności od instrukcji programu. Zwykle obiekty są zaalokowane na stercie, a do odwołania do obiektu
jest wykorzystany wskaźnik lub referencja.
int& ref;
(*pref)++;
gdzie „pref” jest niewidoczny wskaźnik na zmienną (obiekt) „ref”, a znak „*” jest operatorem
wyłuskania (instrukcją pobrania wartości zmiennej z pod adresu).
W języku C (C++) wartość niewidocznego wskaźnika „pref” można wydobyć za pomocą in-
strukcji z operatorem pobrania adresu „&”:
int* p = &ref;
ponieważ operator wyłuskania „*” i operator pobrania adresu „&” niszczą jeden drugiego:
W językach programowania Ada, Visual Basic .NET, Visual C# .NET odwołanie do obiektu
może być tylko przez referencję, a bezpośredni operacje nad wskaźnikami są niemożliwe.
- w języku C++:
void proc()
{
Licznik liczn;
...
}
W językach Smalltalk, Java obiekty alokują się tylko na stercie.
Usuwanie obiektów
W programowaniu zorientowanym obiektowo trzeba zwracać uwagę na usuwanie obiektów
dynamicznych.
System operacyjny typu Windows dla każdego wskaźnika na dynamicznie zaalokowany
obiekt wydziela dodatkową pamięć do rozmieszczenia „stancji wskaźnika”. Stancja wskaźnika za-
wiera fizyczny adres komórki pamięci z obiektem oraz znaczniki systemowe. Stancja wskaźnika
jest potrzebna Windows z powodu dynamicznego przemieszczenia obszarów pamięci zajmowa-
nych aplikacją, w tym z pamięci operacyjnej na dysk i z powrotem w inne miejsce. Typowy rozmiar
stancji wskaźnika wynosi 16 bajtów. Ta pamięć oraz pamięć, którą zajmuje obiekt, zostaje zazna-
czona jako zajęta także po zakończeniu aplikacji. Dlatego aplikacja powinna oczyszczać po sobie
pamięć.
Nie jest potrzebne programowe czyszczenie pamięci w przypadku korzystania z wirtualnych
maszyn Java, Smalltalk i w aplikacjach w językach Visual Basic.NET, Visual C#.NET, które mają
śmieciarza do czyszczenia sterty pamięci z niepotrzebnych obiektów.
Śmieciarz od czasu do czasu sprawdza widoczność każdego referencyjnego obiektu pro-
gramu. Jeżeli nie ma odwołań do obiektu referencyjnego (np. z powodu wyjścia programu poza
granicy widoczności referencji), to śmieciarz zwalnia pamięć, którą zajmuje tymczasowy obiekt
referencyjny. Z wewnątrz programu jest niewiadomo, kiedy będzie działał śmieciarz i kiedy fak-
tyczne będzie miał miejsce proces zwalniania.
Do zwalniania pamięci przydzielonej alokowanemu obiektowi służą specjalne operatory lub
specjalne podprogramy.
Język C++
W języku C++ istnieje specjalny operator delete. Operator delete może mieć postać:
delete wskaźnik_na_obiekt
delete [ ] wskaźnik_na_obiekt
nazwa_obiektu.komponent_obiektu
wskaźnik_na_obiekt->komponent_obiektu;
Application->CreateForm(__classid(TForm1), &Form1);
Główna bazowa klasa TObject, od ktorej dziedziczą wszystkie klasy C++ Buildera, zawiera
metody ClassInfo, ClassName, ClassType, ClassParent, które dają możliwość otrzymania infor-
mację o klasie i jej rodzicu. Na przykład następujace instrukcje demonstrują, jak wydobyć nazwę
klasy formantu (komponentu) pierwszego na formularzu
Metoda name zwraca wskaźnik na wiersz z nazwą klasy. Metoda before porównuje nazwy
klas w schowanej tablicy typów uporządkowanej alfabetyczne i zwraca 1 jeżeli nazwa klasy obiektu
poprzedza nazwę klasy argumentu, lub 0 w przeciwnym przypadku. Metody operatorowe opera-
tor== i operator!= można wykorzystać do porównania typów.
W przykładzie pokazano korzystanie z mechanizmu RTTI w środowisku Borland C++ Buil-
der.
class Licznik
{
static const int maxNumber; //pole stale
public:
Licznik();
int Plus();
bool IsLicznikZero() const; // metoda stala
};
Licznik::Licznik() //konstruktor klasy
{
liczba=0; //inicjalizacja pola
}
int Licznik::Plus() //metoda klasy
{
liczba++; if (liczba>=maxNumber) liczba=0;
return liczba;
}
bool Licznik::IsLicznikZero() const // metoda stala
{
return(liczba==0);
}
const int Licznik::maxNumber=100; //inicjalizacja pola
class Licznik
{
int liczba;
public:
Licznik();
inline int Plus(); //opis wstawianej metody klasy
};
Licznik::Licznik() //konstruktor klasy
{
liczba=0; //inicjalizacja pola klasy
}
inline int Licznik::Plus() //metoda klasy
{
liczba++; if (liczba>=100) liczba=0;
return liczba;
}
W środowisku C++ Builder umieszczenie tekstu metody bezpośrednio za opisem metody
klasy wewnątrz konstrukcji „class {...}” powoduje interpretację tej metody jako metody wstawianą.
W przykładzie metoda Plus klasy Licznik jest metodą wstawianej, chociaż przed nazwą metody nie
jest napisane słowo inline.
Typem właściwości Typ_właściwości może być każdy z typów jak również typ klasy.
Jeżeli w deklaracji właściwości nie ma opcji write, to właściwość staje się właściwością
„tylko do odczytu”. Argument Nazwa_pola_lub_metody w opcjach read i write może być po-
lem lub metodą klasy.
Jeżeli argument w opcjach read i write jest polem, to operacje czytania i zapisywania war-
tości pola wlasciwości są realizowane przez operatory przepisania. Jeżeli argument w opcjach
read i write jest metodą, to do czytania lub zapisu wartości pola kompilator wywołuje wskazaną
metodę.
Niezależnie od rodzaju argumentu w opcjach read i write w tekście programu stosuje się
„prosty” operator przepisania wartości zmiennej typu właściwości. Na przykład:
...
Licznik *pOb=new Licznik;
int liczba=pOb->stan;
liczba++;
pOb->stan=liczba;
...
gdzie właściwość stan jest zdefiniowana następująco:
...
//bez deklaracji właściwości:
Licznik *pOb=new Licznik;
int liczba=pOb->GetNumber();
liczba++;
pOb->SetNumber(liczba);
...
=, *=, /=, %=, +=, -=, <<=, >>=, &=, ^=, |=.
Na przykład przy definicji dwóch obiektów ob1 i ob2 typu klasy cKlass bez konstruktora zdefi-
niowanego jawnie kompilator wywołuje konstruktor domyślny dla obiektu ob1 i konstruktor kopiują-
cy dla obiektu ob2:
cKlass ob1;
cKlass ob2=ob1; lub cKlass ob2(ob1);
Konstruktor kopiujący ma jeden argument typu tej samej klasy przekazywany przez referen-
cję:
Przypomnimy, że słowo const podkreśla, że obiekt - argument nie będzie zmieniony we-
wnątrz konstruktora.
Kompilator języka Object Pascal interpretuje każdą zmienną typu klasa jako referencję. W
związku z tym operator przepisania ob2:=ob1 nie powoduje w języku Object Pascal kopiowania
obiektu ob1:
var
ob1,ob2: cKlass;
begin
ob1:=cKlass.Create;
ob2:=ob1
end;
W przykładzie w wyniku działania operatora przepisania referencje będą równe między sobą,
tj. niewidoczny wskaźnik referencji ob2 będzie wskazywał na ten sam obiekt, co i niewidoczny
wskaźnik referencji ob1.
Wskazana właściwość języka Object Pascal powoduje ograniczenia przy korzystaniu z bi-
blioteki klas VCL (Visual Component Library) w środowisku C++ Builder, ponieważ biblioteka
klas VCL była napisana dla środowiska Delphi w języku Object Pascal.
Przy programowaniu w środowisku Borland C++ Builder można polecić:
1) konstruowanie obiektów związanych z biblioteką klas VCL zawsze za pomocą operatora new,
2) zakaz wykorzystania operatorów przepisania dla obiektów związanych z biblioteką klas VCL.
W razie potrzeby w kopiowaniu obiektów klas VCL lub klas pochodnych od klas VCL należy
napisać tekst konstruktora kopiującego pole po polu. Dla klas pochodnych od klasy TPersistent
można ponowne zdefiniować metodę Assign służącą do kopiowania pól z jednego obiektu do dru-
giego.
class cPoint
{
int x,y,z;
cPoint(int ix=-1,int iy=-1,int iz=-1);
}
cPoint::cPoint(int ix=-1,int iy=-1,int iz=-1)
{
x=ix; y=iy; z=iz;
}
cPoint point;
Licznik ob(1);
W języku C++ przy definicji obiektu z jednoargumentowym konstruktorem klasy (nie pochod-
nej) można określić początkowe wartości pól przez listę inicjacyjną obiektu podobnie jak dla struk-
tury.
Na przykład można napisać
Licznik ob={5};
lub
Licznik ob=5;
class Licznik
{
int liczba; //opis pola klasy
public:
explicit Licznik(int value); //konstruktor klasy
};
Licznik::Licznik(int value) //konstruktor klasy
{
liczba=value; //inicjalizacja pola klasy
}
class cClass1
{
int a, b, c;
public:
cClass1: a(0),b(1),c(2){};
};
//-----------------------------
class cClass2
{
int a, b, &c; //c jest referencją
public:
cClass2(int ia, int ib): a(ia),b(ia+ib),c(a){};
};
//-----------------------------
class cClass3
{
int a;
public:
cClass3(int i) { a=i; };
};
class cClass4
{
int b;
public:
cClass4(int i) : b(i) {};
};
class cClass5 : public cClass3, public cClass4
{
int a, b;
public:
cClass5(int i,int j);
};
cClass5::cClass5(int i,int j):
cClass3(i), cClass4(j+i), a(i)
{
b=j;
}
//-----------------------------
class cClass6
{
cClass2 cc; //cc jest klasą
int x, y;
public:
cClass6(int ix, int iy):
x(ix), y(ix+iy), cc(ix,x) {};
};
Przykład pokazuje, że sposób inicjalizacji może być mieszany: przez listę inicjacyjną i przez
implementację konstruktora.
4.12. Destruktory
Destruktor tak samo jak konstruktor jest podobny do metody (procedurę, funkcji), ale nim nie
jest. Destruktor też nie ma ani typu ani adresu.
Pojęcie „wywołanie destruktora” obejmuje wykonanie instrukcji destruktora i niszczenie
obiektu, tj. uwolnienie obszaru pamięci, w którym kompilator umieścił obiekt przy wywoływaniu
konstruktora.
W przypadku braku destruktora w definicji klasy kompilator wywołuje destruktor domyślny
(ang. default destructor), a mianowicie tylko usuwa obiekt z pamięci.
Przy wielokrotnym dziedziczeniu destruktory są wywoływane w odwrotnej kolejności niż kon-
struktory, tj. wywoływany jest najpierw destruktor danej klasy, a późnej destruktory klas bazowych
w kolejności odwrotnej od tej, w której są one zapisane w definicji klasy.
Jeżeli wśród pól klasy znajdują się wskaźniki na obiekty, trzeba pamiętać o dodawaniu do
destruktora operatorów usuwania tych obiektów. Można polecić napisanie tekstu destruktora bez-
pośrednio po napisaniu tekstu konstruktora.
W języku C++ operator usuwania obiektu, na który wskazuje jakikolwiek wskaźnik pointer,
ma następującą zalecaną postać:
Z tab. 5.1 wynika, że widoczność komponentów klasy bazowej może być tylko zmniejszona.
Dziedziczenie publiczne nie zmienia widoczności każdego z komponentów klasy bazowej.
W przypadku dziedziczenia chronionego publiczne komponenty klasy bazowej są interpreto-
wane w klasie pochodnej jako komponenty chronione (w klasie bazowej), dlatego one są dostępne
w metodach klasy pochodnej i niedostępne dla obiektów klasy pochodnej.
Dziedziczenie prywatne powoduje interpretowanie publicznych oraz chronionych komponen-
tów klasy bazowej jako prywatnych. Z tego powodu metody klasy pochodnej już nie mają dostępu
do publicznych oraz chronionych komponentów klasy bazowej, a dla obiektów klasy pochodnej
zamyka się dostęp do publicznych komponentów klasy bazowej.
W następnym przykładzie w języku C++ klasa B ma typ dziedziczenia chronionego, a klasa
cC - typ dziedziczenia prywatnego od klasy cA. Publiczna metoda plus klasy cA może być wy-
wolywana dla obiektów klas cA, ale nie dla obiektów klasy cB lub cC:
class cA
{
int liczba;
public:
cA(int pocz);
int plus();
};
class cB : protected cA
{
public:
cB(int pocz);
};
class cC : private cA
{
public:
cC(int pocz);
};
cA::cA(int pocz) { liczba=pocz; }
int cA::plus() { liczba++; return liczba;}
cB::cB(int pocz) : cA(pocz){}
cC::cC(int pocz): cA(pocz){}
Na przykład w przypadku hierarchii klas ze strukturą rys. 5.1 dwie klasy cA i cB muszą być
klasami wirtualnymi bazowymi i dlatego klasy należy zadeklarować następująco:
class cA {...};
class cB {...};
class cC: virtual public cA, virtual public cB {...};
class cD: virtual public cA, virtual public cB {...};
class cE: public cC, public cD {...};
W języku C++ można definiować wskaźnik na klasę abstrakcyjną. Z tego powodu jest
dozwolona referencja typu klasy abstrakcyjnej (ale w tym przypadku kompilator nie alokuje tymcza-
sowego obiektu typu klasy abstrakcyjnej).
Przykład z poprawnymi i niepoprawnymi definicjami obiektów:
class cA
{ // klasa abstrakcyjna.
int paramA;
public:
get() { return paramA; }
set(int p) { paramA = p; fA2(); }
virtual void fA1(int)=0;//metoda czysto wirtualna
virtual void fA2()=0; //metoda czysto wirtualna
}
cA x;//błąd
cA* sptr;// poprawnie
cA f(); // błąd
int g(cA s);//błąd
cA& h(cA&);// poprawnie
class сB
{
public:
virtual void f(void) {}; // Metoda wirtualna
};
class cD : public cB { };
cD d; cD* pd=NULL;
cB* pb=&d; //automatyczna konwersja typu wskaźnika
pd=dynamic_cast<cD*>(pb); //konwersja typu wskaźnika
class cA
{
int liczba;
public:
cA() {liczba=0;}
cA(int licz) {liczba=licz;}
...
};
class cB : public cA
{
cB(int licz); ...
};
cB::cB(int licz):cA(licz)
{
...
}
Dla obiektu klasy pochodnej wywoływane są najpierw konstruktory klas bazowych w tej ko-
lejności, w której są zapisane w definicji klasy, a na końcu jest wywoływany konstruktor danej kla-
sy.
Omówiona kolejność może być zmieniona, jeżeli przed bazową klasą będzie napisane słowo
virtual. Klasy z prefiksem virtual mają priorytet przed innymi klasami bazowymi, tj. ich kon-
struktory są wywoływane w pierwszej kolejności.
class cA
{
protected:
int a;
public:
cA(int k);
};
cA::cA(int k) { a=k; }
class cB: private cA
{
int b;
public:
cB(int i): b(2*i),cA(b){};
};
main()
{
cB obB(10);
}
Wynikiem inicjalizacji obiektu obB będzie wartość 20 pola b, a dla pola a - wartość przypad-
kowa, a nie 20, ponieważ kompilator najpierw weźmie element cA(b) listy inicjacyjnej i umieści w
polu a liczbę równą przypadkowej wartości zmiennej b, i tylko w następnym kroku kompilator
weźmie element b(2*i) tej listy.
virtual ~cKlasa();
Po wykonaniu programu z przykładu w tablicy wynik będzie się znajdował tekst BACBA-
Aoooo, co świadczy o poprawnej kolejności wywoływania destruktorów.
6. Metody operatorowe
SumaZespolonych(a,b,c);
с=a+b;
class s_compl
{
double re, im;
public:
s_compl(double re, double im);
s_compl& operator+(s_compl& arg);
};
s_compl::s_compl(double re, double im)
{
this->re=re; this->im=im;
};
s_compl& s_compl::operator+(s_compl& arg)
{
re+=arg.re; im+=arg.im;
};
s_compl a(0.0,1.0),b(1.0,2.0),c;
Liczba argumentów metody operatorowej jest z reguły o jeden mniejsza niż liczba argumen-
tów samego operatora. Za dodatkowy argument operatora służy obiekt, na którego rzecz jest wy-
woływana metoda. W treści _działania metody operatorowej ten argument jest reprezentowany
przez obiekt (*this) lub wewnętrzny wskaźnik this.
Dla jednoargumentowych (unarnych) operatorów argument jest reprezentowany przez
obiekt, na którego rzecz jest wywoływana metoda, a metoda operatorowa jest metodą bezargu-
mentową.
Który z argumentów dwuargumentowego operatora jest reprezentowany przez obiekt, na
rzecz którego jest wywoływana metoda, a który jest argumentem, zależy od kierunku wykonywania
lańcucha podobnych operatorów.
Dla kierunku „lewy” obiektem jest prawy argument operatora, a argumentem - lewy argument
operatora.
Dla kierunku „prawy” obiektem jest lewy argument operatora, a argumentem - prawy argu-
ment operatora.
A więc, argumentem jest zawsze pierwszy dla wskazanego kierunku argument operatora.
Jeżeli wynik wykonania operatora nie jest obiektem, na którego rzecz jest wywoływana me-
toda, to liczba argumentów metody operatorowej musi być równa liczbie argumentów samego ope-
ratora.
W tabeli 6.1 są wskazane kierunek wykonywania operatorów i ich starszeństwo międzygru-
powe w języku C++. Operatory pierwszego wierszu tabelę są wykonywane w pierwszej kolejności.
W zakresie jednej grupy starszeństwo operatorów jest jednakowe i takie operatory są wykonywane
w kierunku analogicznym do kierunku wykonywania osobnego operatora tej grupy.
Można zauważyć, że w tabeli 6.1 powtarzają się operatory, które mogą być i jednoargumen-
towe (unarne) i dwuargumentowe (np. „+”, „-”). Pierwsze od początku tabeli wystąpienie operatora
jest jednoargumentowe (unarne).
Tabela 6.1
Kierunek wykonywania i starszeństwo operatorów
Kompilator języka C++ zabrania przeciążania operatorów „.”, ”.*”, „::”, „?:”.
a.operatorO()
Jeśli klasa ma konstruktor alokujący pamięć dla swoich pól typu wskaźnikowego, to z reguły
klasa powinna mieć przeciążony operator „=”. W przeciwnym razie dla konstrukcji a=b niejawnie
dodany operator kopiuje z b na a tylko pola-wskaźniki bez kopiowania tych obiektów, na które
wskazują.
Dziedziczenie przeciążonego operatora „=” nie jest dopuszczalne. Wywołanie przeciążonego
operatora „=”, np. a=b, jest interpretowane przez kompilator jako
a.operator=(b)
W swoim programie można korzystać z takiej niezwykłej formy zapisu operatora „=”.
W przykładzie 6.1 jest pokazana klasa typu „tablica dynamiczna” z przeciążonym operatorem
„=”.
Przykład 6.1. Klasa z przeciążonymi operatorami
class dynInt
{
protected:
int *m_pData; // wskaźnik do tablicy
int m_nSize; // rozmiar aktualny
int m_nMaxSize; // rozmiar zaalokowany
int m_nGrowBy; // krok przy zwiększeniu
public:
dynInt();
dynInt(dynInt& ob); //konstruktor kopiujący
dynInt(int size,int growBy);
~dynInt();
int fGetSize();
void fSetSize(int newsize, int nGrowBy = -1)
void fSetAtGrow(int nIndex, int newElement);
dynInt& operator=(const dynInt &C);
int& operator[](int nIndex);
const int& operator[](int nIndex) const;
int operator()(int nBegIndex,int nNumber);
};
dynInt::dynInt()
{
m_pData = NULL;
m_nSize = m_nMaxSize = m_nGrowBy = 0;
}
dynInt::dynInt(dynInt& ob)
{//konstruktor kopiujący
m_nSize=m_nMaxSize=ob.fGetSize();
m_pData=(int*) new char[m_nSize*sizeof(int)];
for (int i=0;i<m_nSize;i++)
m_pData[i]=ob[i];
}
dynInt::dynInt(int size,int growBy)
{
m_nSize = m_nMaxSize = size;
m_nGrowBy = growBy;
m_pData=(int*) new char[m_nMaxSize*sizeof(int)];
memset(m_pData,0,size*sizeof(int)); //zerowanie
}
dynInt::~dynInt()
{
delete [] (char*)m_pData;
}
int dynInt::fGetSize()
{
return m_nSize;
}
void dynInt::fSetSize(int newsize,int nGrowBy = -1 )
{
if (nGrowBy != -1)
m_nGrowBy = nGrowBy;
if (newsize<=0)
{// zerować obiekt
delete [] (char*)m_pData;
m_pData = NULL;
m_nSize = m_nMaxSize = 0;
}
else
if (m_pData == NULL)
{ // zaalokować pierwszy raz
m_pData = (int*) new char[newsize*sizeof(int)];
memset(m_pData,0,newsize*sizeof(int)); //zerowanie
m_nSize = m_nMaxSize = newsize;
}
else
if (newsize <= m_nMaxSize)
{// alokacja jest niepotrzebna
if (newsize > m_nSize)
{//inicjalizacja dodatkowa
memset(&m_pData[m_nSize],0,(newsize-m_nSize) * sizeof(int));
}
m_nSize = newsize;
}
else
{// alokacja jest potrzebna
int nNewMax;
if (newsize<m_nMaxSize+m_nGrowBy)
nNewMax=m_nMaxSize+m_nGrowBy; // plus krok
else
nNewMax=newsize;
int *pNew=(int*) new char[nNewMax*sizeof(int)];
// kopiowanie danych dawnych
memcpy(pNew,m_pData,m_nSize*sizeof(int));
// budowanie elementów dodatkowych
if (newsize > m_nSize)
{
memset(&pNewData[m_nSize],0,(newsize-m_nSize) * sizeof(int));
// niszczenie alokacji poprzedniej
delete [] (char*)m_pData;
m_pData = pNewData;
m_nSize = newsize;
m_nMaxSize = nNewMax;
}
}
}
void dynInt::fSetAtGrow(int nIndex, int newElement)
{
if (nIndex>=0)
{
if (nIndex>=m_nSize)
fSetSize(nIndex+1,4);
m_pData[nIndex]=newElement;
}
}
dynInt& dynInt::operator=(const dynInt& ob)
{
if (&ob==this) return *this; //sytuacja “A=A;”
if (m_pData) delete [] (char *)m_pData;
m_nSize=m_nMaxSize=ob.fGetSize();
m_pData=(int*) new char[m_nSize*sizeof(int)];
for (int i=0;i<m_nSize;i++)
m_pData[i]=ob[i];
return *this;
};
int& dynInt::operator[](int nIndex)
{
if (nIndex >= 0 && nIndex < m_nSize)
return m_pData[nIndex];
else
return 0;
}
const int& dynInt::operator[](int nIndex) const
{
if (nIndex >= 0 && nIndex < m_nSize)
return m_pData[nIndex];
else
return 0;
}
int dynInt::operator()(int nBegIndex,int nNumber)
{
if (nBegIndex<0) nBegIndex=0;
int nEnd=nBegIndex+nNumber;
if (nEnd>m_nSize) nEnd=m_nSize;
int suma=0;
for (int i=nBegIndex;i<nEnd;i++)
suma+=m_pData[i];
return suma;
}
6.4.3. Operator ()
Przeciążony operator „()” jest podobny do globalnego operatora wywołania funkcji. Zasadni-
cza różnica między nimi polega na tym, że lewym argumentem operatora wywołania funkcji jest
nazwa funkcji lub wskaźnik na funkcję, podczas gdy lewym argumentom przeciążonego operatora
„()” jest obiekt.
Za pomocą przeciążonego operatora „()” można zbudować własną konstrukcję podobną do
konstrukcji wywołania funkcji.
Na przykład dla obiektów klas typu „tablica dynamiczna” można realizować którąkolwiek
funkcję typową, na przykład funkcję sumowania, przez przeciążony operator „()”. W przykładzie 6.1
pokazana definicja dynamicznej klasy dynInt z przeciążonym operatorem „()”.
Po definicji klasy dynInt można skorzystać z wygodnego zapisu dla funkcji sumowania:
Należy zwrócić uwagę, że w deklaracjach operatorów są wskazane ich typy: void* dla new
oraz void dla delete.
Chociaż jest możliwe przeciążenie operatora referencji „&” i operatora wyłuskania „*” [Kisile-
wicz], nie ma powodów, żeby przeciągać wskazane operatory.
7. Wzorce (szablony) klas i funkcji
cW<typ_faktyczny> ob;
funk<typ_faktyczny>(...);
LITERATURA GŁÓWNA
Kisilewicz J., Język C w środowisku Borland C++. Oficyna Wydawnicza Politechniki Wrocławskiej. Wrocław,
2000
Kisilewicz J., Język C++: programowanie obiektowe, Wrocław: Oficyna Wydawnicza Politechniki Wrocław-
skej, 2002
C++ Builder 5. Vademecum profesjonalisty. Tom 1,2, Wydawnictwo HELION 2001
Porebski W., Języki obiektowe, Gdańsk: Wydawnictwo PG 1993
Youdon E., Argila C., Analiza obiektowa i projektowanie. Przykłady zastosowań, Warszawa 2000
Booch G., UML- przewodnik użytkownika, WNT 2001
LITERATURA POMOCNICZA
Coad P., Nicola J., Programowanie obiektowe, Warszawa 1993.
Deitel H.M., Deitel P.J., Arkana C++. Programowanie, Warszawa 1998.
Grębosz J., Symfonia C++. Programowanie w języku C++ orientowane obiektowo, Kraków 1999.
Kliszewski M., Inżynieria oprogramowania obiektowego, Tomaszów Maz. 1994.
Ledgard H.F., Mała księga programowania obiektowego, Warszawa 1998.
Struzińska-Walczak A., Walczak K., Nauka programowania w języku C++. Wydawnictwo W&W. Warszawa.
2001