Przeładowanie operatory wyjątkowe
" większość operatorów realizowana dowolnie zarówno co
do typu wartości, typu argumentów jak te\ formy realizacji:
o albo jako funkcja globalna
o albo jako niestatyczna metoda klasy
" wyjątki
o operatory które muszą być zrealizowane jako
niestatyczne metody klasy:
operator przypisania =
operator [ ]
operator ( )
operator ->
o operatory o ustalonym typie wyniku i argumentów
operator new
operator delete
Przeładowanie operatora przypisania =
" musi być zrealizowany jako niestatyczna metoda klasy
dla klasy K ma postać:
K & K::operator=(K &)
" cel: przypisanie jednemu obiektowi klasy K zawartości
drugiego
" jeden z operatorów predefiniowanych gdy go dla klasy
nie zdefiniujemy, kompilator wygeneruje go sam (metodą
składnik po składniku )
" standardowy operator daje obiekt identyczny jak
wzorzec co nie zawsze jest po\ądane (patrz klasa
wizytówka, przykład z konstruktorem kopiującym)
" standardowe = nie wystarcza zwłaszcza gdy:
o klasa ma wskazniki jako składowe
o klasa u\ywa operatora new do alokacji pamięci
" analiza problemu przykład klasy wizytówka (wersja
poprawiona, tak by długość napisów była dopasowana do
potrzeb)
class wizytowka {
char* nazw;
char* imie;
public:
wizytowka(char *im, char *na); // zwykły konstruktor
~wizytowka();
};
wizytowka::wizytowka(char *im, char *na)
{
nazw=new char[strlen(na)+1];
strcpy(nazw, na);
imie=new char[strlen(im)+1];
strcpy(imie, im);
}
wizytowka::~wizytowka()
{
delete [ ] nazw;
delete [ ] imie;
}
wizytowka stary( Lech , Walesa );
wizytowka nowy( Aleksander , Kwaśniewski );
Imie A1 Lech
Nazw. A2 Walesa
stary
Imie A3 Aleksander
Nazw. A4 Kwasniewski
nowy
Instrukcja podstawienia wersja standardowa (predefiniowana)
stary=nowy;
Imie A3 Lech
Nazw. A4 Walesa
Imie A3 Aleksander
Nazw. A4 Kwasniewski
Efekty podstawienia składnik po składniku :
o wskazniki obu obiektów są identyczne oba obiekty są
podpięte do tych samych obszarów pamięci (zmiany
dokonane na jednym z obiektów są widoczne w drugim)
o zostały utracone adresy obszarów pamięci zawierających
napisy podczepione oryginalnie do obiektu stary nie
mo\emy ju\ zwolnić (wyciek pamięci)
Poprawna realizacja operatora podstawienia
o musimy zadbać, by ka\dy obiekt wykorzystywał swoje
własne obszary pamięci na napisy
o obszary oryginalnie związane z napisami zmiennej stary
nie nadają się do wykorzystania (mogą być za krótkie)
nale\y je zwolnić (ten fragment to samo co destruktor)
o obliczenie wymaganych rozmiarów nowych tablic;
rezerwacja pamięci na ich pomieszczenie
o skopiowanie tablic z obiektu wzorca do obiektu
podstawianego (ostatnie 2 kroki to samo co konstruktor
kopiujący)
Wynik po poprawnym podstawieniu:
Imie A5 Aleksander
Nazw. A6 Kwasniewski
Imie A3 Aleksander
Nazw. A4 Kwasniewski
" dwa etapy działania:
o usuwanie tablic (równowa\ne destruktorowi)
o alokacja tablic i ich kopiowanie (równowa\ne
konstruktorowi kopiującemu)
" działanie operatora przypisania bardzo bliskie działaniu
konstruktora kopiującego; ró\nice okoliczności
wywołania:
o konstruktor kopiujący gdy powołujemy do \ycia obiekt
na wzór innego (znak = występuje w definicji obiektu)
o operator przypisania gdy zmieniamy ju\ istniejący
obiekt na wzór innego
" rola instrukcji return *this wartość wyra\enia jako
całości instrukcje wielokrotnego podstawienia:
najnowszy=stary=nowy;
" deklaracja const dla argumentu zabezpieczenie przed
jego zmianą
Przykład: ilustracja działania operatora przypisania i konstruktora
kopiującego
#include
#include
class wizytowka {
char *imie;
char *nazw;
public:
wizytowka(char *im, char* na);
wizytowka(const wizytowka &w);
~wizytowka();
wizytowka & operator=(const wizytowka &w);
void wypisz(char *txt);
};
wizytowka::wizytowka(char *im, char *na)
{
imie=new char[strlen(im)+1];
strcpy(imie,im);
nazw=new char[strlen(na)+1];
strcpy(nazw,na);
cout << "Zwykly konstruktor" << endl;
}
wizytowka::wizytowka(const wizytowka &w)
{
imie=new char[strlen(w.imie)+1];
strcpy(imie,w.imie);
nazw=new char[strlen(w.nazw)+1];
strcpy(nazw,w.nazw);
cout << "Konstruktor kopiujacy" << endl;
}
wizytowka::~wizytowka()
{
delete imie;
delete nazw;
}
wizytowka & wizytowka::operator=(const wizytowka &w)
{
delete imie;
delete nazw;
imie=new char[strlen(w.imie)+1];
strcpy(imie,w.imie);
nazw=new char[strlen(w.nazw)+1];
strcpy(nazw,w.nazw);
return *this;
cout << "Operator przypisania" << endl;
}
void wizytowka::wypisz(char *txt)
{
cout << " " << txt << ": " << imie << " " << nazw << endl;
}
main()
{
cout << "Normalne definicje: " << endl;
wizytowka stary("Lech","Walesa"), nowy("Aleksander","Kwasniewski");
cout << "Definicja na wzor innego objektu: " << endl;
wizytowka kopia1=stary, kopia2(nowy);
cout << "Zawartosc obiektow: " << endl;
stary.wypisz("stary");
nowy.wypisz("nowy");
kopia1.wypisz("kopia1");
kopia2.wypisz("kopia2");
cout << "Przypisanie operatorem przypisania: " << endl;
kopia2=kopia1;
kopia1.wypisz("kopia1");
kopia2.wypisz("kopia2n");
kopia1=stary=nowy;
kopia1.wypisz("kopia1n");
stary.wypisz("staryn");
nowy.wypisz("nowyn");
}
_________________________________________________________
Normalne definicje:
Zwykly konstruktor
Zwykly konstruktor
Definicja na wzor innego objektu:
Konstruktor kopiujacy
Konstruktor kopiujacy
Zawartosc obiektow:
stary: Lech Walesa
nowy: Aleksander Kwasniewski
kopia1: Lech Walesa
kopia2: Aleksander Kwasniewski
Przypisanie operatorem przypisania:
kopia1: Lech Walesa
kopia2n: Lech Walesa
kopia1n: Aleksander Kwasniewski
staryn: Aleksander Kwasniewski
nowyn: Aleksander Kwaśniewski
__________________________________________________________
" podstawienie typu:
x=x;
prowadzi do błędu (usunięcie tablic, a następnie próba ich
kopiowania)
" wyjście z sytuacji modyfikacja operatora przypisania:
wizytowka & wizytowka::operator=(const wizytowka &w)
{
if (this=&w) return *this;
// pozostałe linie jak poprzednio
// &
}
" sytuacje, w której wersje predefiniowane operatora
podstawienia nie zostaną wytworzone:
o klasa ma składnik const (nie mo\e być podstawiany
po inicjalizacji, standardowa wersja operatora= by to
robiła)
o jeden ze składników jest referencją (po zainicjowaniu
referencji nie mo\na jej przerzucać na inny obiekt)
o jedna ze składowych klasy jest obiektem klasy, w której
operator przypisania jest prywatny (nie mo\emy
skopiować tego składnika)
Przeładowanie operatora [ ]
" musi być zrealizowany jako niestatyczna metoda klasy
" oryginalnie (w wersji nieprzeładownej):
o wykonuje odwołanie do elementu tablicy
o jest operatorem dwuargumentowym (pierwszy
argument nazwa tablicy, drugi indeks
udostępnianego elementu)
" wersja przeładowana
o zachowuje ilość argumentów
o mo\e zmieniać znaczenie operatora
o mo\e zmieniać typ argumentów (pierwszy argument
obiekt klasy, niekoniecznie tablica; drugi argument
niekoniecznie liczba całkowita)
K k;
k[arg] k.operator[](arg)
Przykład: klasę punkt wyposa\ymy w przeładowanie operatora[ ] z
argumentem typu napis
#include
#include
#include
class punkt {
int x,y;
public:
punkt(int a, int b) : x(a), y(b) {}
void operator[](char *n);
};
void punkt::operator[](char *n)
{
gotoxy(x,y);
printf("%s",n);
}
main()
{
punkt alarm(20,15);
alarm["Uwaga !!! Pali sie lewy silnik !!!"];
}
" uwaga: u\ycie niestandardowej biblioteki conio
" szerokie potencjalne zastosowanie przeładowania do akcji
typu: Zrób coś z obiektem k u\ywając argumentu arg
" wa\na część zastosowań udostępnienie wskazanego
elementu obiektu klasy (nawiązanie do oryginalnego
znaczenia)
Przykład klasa pomiar:
const int ile_pokoi = 100;
class pomiar {
public:
float odczyt[ile_pokoi];
\\ metody klasy
};
pomiar t; // definicja obiektu
cout << t.odczyt[10]; // odwołanie do elementarnej wielkości
" odwołanie poprzez nazwę składowej; musi ona być
publiczna
" czy da się osiągnąć sytuację, w której ten sam element
mo\na osiągnąć przez odwołanie:
cout << t[10];
" odpowiedz tak, w dodatku składowa odczyt mo\e być
prywatna
const int ile_pokoi=100;
class pomiar {
float odczyt[ile_pokoi];
public:
float operator[](unsigned int index)
{
return odczyt[index]; // tu zwykła wersja [ ]
}
// inne metody klasy pomiar
};
pomiar t;
cout << t[10]; // t.operator[](10);
" powy\sza definicja operatora[] nawiązuje do wersji
nieprzeładowanej; ale nie ma wszystkich jej własności
Przykład:
A) typ wbudowany, wersja standardowa [ ]
float x, tab[5]={1.0, 2.0, 3.0};
x=tab[2]; // po prawej stronie =
tab[0]=x; // po lewej stronie = (l-wartość)
tab[4]=tab[1]; // po obu stronach =
B) typ zdefiniowany, wersja przeładowana [ ]
pomiar t(21);
float x;
x=t[0]; // O.K
t[1]=x; // zle, t[1] to nie l-wartość
t[0]=t[2]; // j.w.
t[1]=x oznacza t.operator[](1)=x czyli (21.0)=x
" modyfikacja operator[] ma zawracać obiekt, który:
o reprezentuje wartość
o jest l-wartością
" realizacja modyfikacji typ wyniku to referencja do obiektu
typu takiego, jakiego są udostępniane elementy tablicy
Przykład: klasa pomiar
#include
const int ile_pokoi=100;
class pomiar {
float odczyt[ile_pokoi];
public:
pomiar(float w=0.0);
float & operator[](unsigned int index)
{
return odczyt[index];
}
};
pomiar::pomiar(float w)
{
for(int k=0; k odczyt[k]=w;
}
main()
{
pomiar t(0.0);
for(int k=0; k t[k]=t[k]+0.1*k;
cout<<"Temperatura kolejnych pokoi wynosi: " << endl;
cout<}
___________________________________________________
Temperatura kolejnych pokoi wynosi:
0, 0.1, 0.2, 0.3 ...
___________________________________________________
" lepsza wersja tego przeładowania sprawdzenie zakresy
indeksu:
float & pomiar::operator[](unsigned int index)
{
if (index else blad(); // funkcja obsługi błędu
}
Podsumowanie:
" operator [] przeładowywany zawsze jako metoda klasy
" dwuargumentowy, pierwszy argument obiekt klasy
" mo\e być przeładowany w dowolnym celu
" je\eli ma słu\yć udostępnianiu elementów tablicy (działać
jak typ wbudowany), to powinien zwracać referencję do
obiektu takiego typu, jakiego są elementy tablicy
Przeładowanie operatora ( )
" trzeci z grupy operatorów przeładowywanych jako metoda
klasy
" przeładowanie proste, nie podlega \adnym restrykcjom
" wyjątkowość operatora() mo\e mieć dowolną ilość
argumentów jako jedyny mo\e mieć > 2 argumenty
" przeładowanie dla obiektu k klasy K oznacza:
k(a1, a2, a3, a4) k.operator()(a1,a2,a3,a4)
" mo\liwe równoległe istniejące inne przeładowania
operatora() ró\niące się ilością i typem argumentów
" typowe zastosowania:
o przesłanie do innego operatora większej ilości
argumentów
Przykład: obsługa tablic wielowymiarowych
#include
const int NTAB=5;
class tab3f {
float t[NTAB][NTAB][NTAB];
public:
float & operator()(unsigned int i, unsigned int j, unsigned int k)
{
return t[i][j][k];
}
};
main()
{
tab3f a;
for(int i=0; i for(int j=0; j for(int k=0; k a(i,j,k)=i+j+k;
for(int k=0; k cout << a(k,k,k) << " ";
cout << endl;
}
_________________________________________________
0 3 6 9 12
_________________________________________________
o jedna (lub nawet jedyna) metoda klasy jest b. często
wywoływana przeładowanie pozwoli na
wygodniejszy zapis
Przykład:
#include
class lampa {
int stan;
public:
lampa() : stan(0) {};
void raport(char * txt);
void operator()();
};
void lampa::raport(char * txt)
{
if(stan)
cout << txt << ": lampa zapalona \n";
else
cout << txt << ": lampa zgaszona \n";
}
void lampa::operator()()
{
if(stan)
stan=0;
else
stan=1;
}
main()
{
lampa l1,l2;
l1();
l1.raport("l1");
l2.raport("l2");
}
______________________________________________
l1: lampa zapalona
l2: lampa zgaszona
______________________________________________
Przeładowanie operatorów new oraz delete
" operatory specjalne mają ściśle określone działanie, typ
argumentów i typ wyniku
" słu\ą do rezerwacji i zwolnienia obszarów pamięci
" istnieją wersje globalne (predefiniowane) mo\emy je
u\yć na rzecz dowolnych typów (tak wbudowanych, jak
te\ zdefiniowanych przez u\ytkownika)
" gdy wersja globalna nam nie odpowiada mo\emy dla
pewnej klasy ją przeładować
" przeładowanie zasłania wersje globalne mo\na je u\yć
u\ywając operatora zakresu
" przeładowania realizujemy jako funkcje składowe klasy
" mimo \e jawnie tego nie piszemy funkcje operatorowe
new oraz delete są statycznymi metodami klasy
" nie działają na rzecz konkretnego obiektu klasy; wyra\ają
raczej zdolność klasy do tworzenie nowych i usuwania
istniejących obiektów tej klasy
" funkcja operatorowa new:
o pierwszy argument typu size_t (typ zale\ny od
implementacji, zdefiniowny w stddef.h) potrzebny
rozmiar pamięci; nie przesyłamy go w wywołaniach,
zrobi to automatycznie kompilator na podstawie
znajomości rozmiaru klasy
o typ zwracany: void*; adres początku rezerwowanego
bloku pamięci
" funkcja operatorowa delete:
o pierwszy argument typu void* (wskaznik do
zwalnianego bloku, wypracowany przez stosowne
new)
o typ wyniku: void
" typowe przypadki zastosowań przeładowania tych
operatorów:
o w sytuacji częstej rezerwacji pamięci
(nieefektywność) przeładowanie: hurtowa
rezerwacja większej ilości egzemplarzy obiektu
o gdy chcemy zapobiegać fragmentacji pamięci
o gdy chcemy prowadzić statystykę wykorzystania
zapasu pamięci
" gdy chcemy, aby rezerwacja i zwalnianie tablic obiektów
klasy była dokonywana przeładowanymi operatorami,
musimy zdefiniować przeładowanie operatorów new[]
oraz delete[] (inaczej zostaną u\yte wersje globalne,
nawet gdy przedefiniujemy new oraz delete dla klasy)
Przykład:
#include
#include
const int ile_pokoi=100;
class pomiar {
float odczyt[ile_pokoi];
public:
pomiar(float w=0.0);
float & operator[](unsigned int index);
void * operator new(size_t rozmiar);
void * operator new[](size_t rozmiar);
void operator delete(void * wsk);
void operator delete[](void * wsk);
};
pomiar::pomiar(float w)
{
for(int k=0; k odczyt[k]=w;
}
float & pomiar::operator[](unsigned int index)
{
return odczyt[index];
}
void * pomiar::operator new(size_t rozmiar)
{
cout << "Kreuje nowy obiekt o rozmiarze " << rozmiar << endl;
return (new char[rozmiar]);
}
void * pomiar::operator new[](size_t rozmiar)
{
cout << "Kreuje tablice - obiekt o rozmiarze " << rozmiar << endl;
return (new char[rozmiar]);
}
void pomiar::operator delete(void *wsk)
{
cout << "Kasuje obiekt !!!" << endl;
delete wsk;
}
void pomiar::operator delete[](void *wsk)
{
cout << "Kasuje tablice !!!" << endl;
delete wsk;
}
main()
{
pomiar *p1,*p2;
p1=new pomiar;
p2=::new pomiar;
cout << (*p1)[10] << endl;
delete p1;
::delete p2;
p1=new pomiar[10];
p2=::new pomiar[10];
cout << (p2[3])[3] << endl;
delete p1;
::delete p2;
}
Przeładowanie operatorów << oraz >>
" instrukcje wejścia/wyjścia w C++ - u\ywają standardowych
strumieni (cout, cin) oraz operatorów <<, >>
" operatory <<, >> - operatory przesunięcia bitowego; aby
mo\liwe było ich u\ycie w kontekście wejścia/wyjścia
musiały zostać przeładowane
cout << x; równowa\ne: cout.operator<<(x);
" przeładowania operatora << (>>) dokonane w klasie
ostream (istream) (część biblioteki standardowej)
" tak naprawdę rodzina przeładowań (dla ró\nych typów
drugiego argumentu)
" dla klas te przeładowania nie są określone, np. zapis:
zespol z(1,2);
cout << z;
jest niepoprawny
" wyjście zdefiniowanie własnego przeładowania
operatora << w sytuacji, gdy drugi argument jest zespol
" a priori 3 mo\liwości realizacji takiego przeładowania:
o jako metody klasy ostream
o jako metody klasy zespol
o jako globalnej funkcji
" metoda klasy ostream nie mamy dostępu do zródeł
odpada
" metoda klasy zespol argument typu zespol musiałby
wystąpić jako pierwszy
z.operator<<(cout) równowa\ne zapisowi: z << cout;
sprzeczne z formą wywołania dla typów wbudowanych
" pozostaje : globalna funkcja operatorowa
operator<<(ostream, zespol)
" ustalenie typu funkcji operatorowej: wymóg, by mo\liwe
były składania:
cout << Zmienna z = << z << . << endl; co jest równowa\ne:
(((cout << Zmienna z = ) << z )<< . )<< endl;
" wniosek funkcja operatorowa ma zwracać coś, co jest
równowa\ne cout najlepiej referencję do obiektu klasy
ostream, czyli jej nagłówek ma wyglądać:
ostream & operator(ostream &, zespol &);
" w podobny sposób realizujemy przeładowania >> oraz
przeładowania <<, >> dla innych klas
Przykład: przeładowanie << dla klasy zespol
#include
class zespol {
float rzecz, uroj;
public:
zespol(float r=0.0, float i=0.0) : rzecz(r), uroj(i) {}
float re(){return rzecz;}
float im(){return uroj;}
};
ostream & operator<<(ostream &ekran, zespol &z)
{
ekran << "(" << z.re() << ", " << z.im() << ")";
return ekran;
}
main()
{
zespol z(1,2);
cout << "Zmienna z = " << z << "." << endl;
}
_______________________________
Zmienna z = (1, 2).
_______________________________
Przeładowanie operatorów uwagi ogólne
" wybór: operator globalny metoda klasy
o je\eli operator zmienia obiekt na który działa,
powinna być zrealizowana jako metoda klasy (np. ++,
--, +=)
o operatory które nie zmieniają argumentów powinny
być raczej realizowane jako funkcje globalne (np. +, *,
/, -)
" gdy lewy argument typ wbudowany: nie mo\na
realizować przeładowania jako metody klasy
" gdy operator dwuargumentowy ma być symetryczny
względem swoich argumentów musi być realizowany
jako para funkcji globalnych
" operator realizowany jako metoda klasy nie dopuszcza
niejawnych konwersji w stosunku do pierwszego
argumentu (tego, który jest przekazywany przez wskaznik
this)
Wyszukiwarka
Podobne podstrony:
w17 przeladowanie operatorow
trans operation
m01 operatorchecker sowi
F 15 Układ do pomiaru czasów przełączania diody
Naprawa przełącznika kierunkowskazów
Dodatek C Kolejność operatorów
Cisco Broadband Operating System Appendix A
Operation Peiper
9 Operatory
instrukcja bhp na stanowisku operator koparko ladowarki
Or Operator koparko spycharki
language operators comparison
language operators increment
272?1105 operator koparko ladowarki
1b 2 2 4 11 Lab Konfiguracja aspektów bezpieczeństwa na przełączniku
więcej podobnych podstron