7146
Szczegóły |
Tytuł |
7146 |
Rozszerzenie: |
PDF |
Jesteś autorem/wydawcą tego dokumentu/książki i zauważyłeś że ktoś wgrał ją bez Twojej zgody? Nie życzysz sobie, aby podgląd był dostępny w naszym serwisie? Napisz na adres
[email protected] a my odpowiemy na skargę i usuniemy zabroniony dokument w ciągu 24 godzin.
7146 PDF - Pobierz:
Pobierz PDF
Zobacz podgląd pliku o nazwie 7146 PDF poniżej lub pobierz go na swoje urządzenie za darmo bez rejestracji. Możesz również pozostać na naszej stronie i czytać dokument online bez limitów.
7146 - podejrzyj 20 pierwszych stron:
prof. Jan Bielecki
Visual C++ 6.0
Programowanie obiektowe
Dodatki
B��dy programowania
Klasy pierwotne
Za przyk�ad klasy pierwotnej niech pos�u�y klasa definiuj�ca poj�cie liczba zespolona. Jak wiadomo (albo nie!), liczba zespolona jest uporz�dkowan� par� liczb rzeczywistych. Do zapisu liczby zespolonej o sk�adnikach a i b u�ywa si� notacji
a + bi
o tak dobranym i, �e i2 = -1.
St�d �atwo ju� wynika, �e
(a+bi) + (c+di) == (a+c) + (b+d)i
oraz �e
(a+bi) * (c+di) == (a*c - b*d) + (a*d + b*c)i
Sk�adnik a jest nazywany cz�ci� rzeczywist� (re), a sk�adnik b cz�ci� urojon� (im) liczby zespolonej.
#include <iostream.h>
struct Cplx {
double re, im;
};
Cplx add(Cplx &parL, Cplx parR);
int main(void)
{
Cplx one = { 1.0, 3.0 },
two = { 2.0, 4.0 };
Cplx sum = add(one, two);
char *plus = "+";
if(sum.im < 0)
plus = "";
cout << sum.re << plus << sum.im << 'i' << endl;
return 0;
}
Cplx add(Cplx &parL, Cplx parR)
{
double re = parL.re + parR.re,
im = parL.im + parR.im;
Cplx sum = { re, im };
return sum;
}
Definicja typu strukturowego Cplx jest opisem poj�cia liczba zespolona. Egzemplarzami typu s� zmienne one, two i sum.
Program wyznacza sum� dw�ch liczb zespolonych i wyprowadza j� w postaci 3 + 7i.
Hermetyzacja
Przytoczone rozwi�zanie ma t� wad�, �e inicjowanie i przetwarzanie zmiennych struktury Cplx jest uci��liwe, a ka�dy u�ytkownik klasy mo�e bez ogranicze� pos�ugiwa� si� nazwami jej p�l re i im.
Dlatego podczas definiowania klas stosuje si� hermetyzacj�. Polega ona na tym, �e s�owo kluczowe struct zast�puje si� s�owem class, a zestawy sk�adnik�w klasy umieszcza w sekcjach private (prywatny), public (publiczny) i protected (chroniony).
Uwaga: Typy zadeklarowane z u�yciem s�owa kluczowego class s� nazywane klasami, a opisane przez nie zmienne s� nazywane obiektami. R�nica mi�dzy typami obiektowymi i strukturowymi polega jedynie na domniemaniach hermetyzacji. Dlatego ka�dy obiekt jest struktur�, a ka�da struktura jest obiektem.
dla dociekliwych
Ka�da deklaracja
struct Any ...{
// ...
};
jest r�wnowa�na deklaracji
class Any ... {
public:
// ...
};
a ka�da deklaracja
class Any ...{
// ...
};
jest r�wnowa�na deklaracji
class Any ... {
private:
// ...
};
a zatem r�nice mi�dzy strukturami i klasami s� niemal �adne.
Podzia� na sekcje
Umieszczenie sk�adnika w sekcji prywatnej oznacza, �e b�d� mog�y si� nim pos�ugiwa� tylko sk�adniki jego klasy oraz funkcje zaprzyja�nione z jego klas�.
Uwaga: W celu zaprzyja�nienia funkcji z klas�, nale�y w dowolnej sekcji klasy umie�ci� deklaracj� funkcji poprzedzon� s�owem kluczowym friend.
Umieszczenie sk�adnika w sekcji publicznej oznacza, �e b�d� mog�y si� nim pos�ugiwa� wszystkie funkcje i wszystkie sk�adniki.
Umieszczenie sk�adnika w sekcji chronionej oznacza, �e b�d� mog�y si� nim pos�ugiwa� tylko sk�adniki jego klasy, funkcje zaprzyja�nione z jego klas� oraz sk�adniki jego klasy pochodnej.
Uwaga: Umieszczenie sk�adnika (np. konstruktora albo operatora przypisania) w sekcji prywatnej, uniemo�liwia u�ywanie go poza klas�. Ten prosty zabieg funkcjonuje jak zakaz u�ywania wybranych sk�adnik�w klasy.
#include <iostream.h>
class Cplx {
friend Cplx add(Cplx &parL, Cplx parR);
protected:
double re, im;
public:
Cplx(double r, double i) : re(r), im(i)
{
}
void show(void)
{
cout << re << '+' <<
im << 'i' << endl;
}
};
Cplx add(Cplx &parL, Cplx parR)
{
double r = parL.re + parR.re,
i = parL.im + parR.im;
return Cplx(r, i);
}
int main(void)
{
Cplx one(1.0, 3.0),
two(2.0, 4.0);
Cplx sum = add(one, two);
sum.show();
return 0;
}
Sk�adniki re i im s� prywatne, a sk�adniki Cplx i show s� publiczne.
Funkcja show nie jest sk�adnikiem, ale jako zaprzyja�niona z klas� Cplx, mo�e odwo�ywa� si� do wszystkich jej sk�adnik�w, w tym do prywatnych sk�adnik�w re i im.
Sk�adniki klasy
Sk�adnikami klasy s� pola, konstruktory, destruktory i metody. Deklaracja sk�adnika mo�e by� umieszczona w sekcji prywatnej, publicznej albo chronionej.
Konstruktory
Konstruktorem jest sk�adnik, kt�ry s�u�y do inicjowania element�w struktury. Nazwa konstruktora jest identyczna z nazw� klasy. Deklaracja konstruktora ma posta� deklaracji funkcji, ale nie mo�e zawiera� okre�lenia typu rezultatu.
class Cplx {
// ...
protected:
double re, im;
public:
Cplx(double r, double i)
{
re = r;
im = i;
}
Cplx(double r)
{
re = r;
im = 0;
}
Cplx(void)
{
re = im = 0;
}
// ...
};
Dzi�ki zdefiniowaniu konstruktor�w staj� si� poprawne nast�puj�ce deklaracje
Cplx numA(3,4);
Cplx numB(5);
Cplx numC;
Konstruktor domy�lny
Konstruktor, kt�ry mo�e by� u�yty bez podania argument�w jest nazywany domy�lnym. Zazwyczaj jest nim konstruktor bezparametrowy.
Uwaga: Je�li w klasie nie zdefiniuje si� ani jednego konstruktora, to jest ona niejawnie wyposa�ana w konstruktor bezparametrowy o pustym ciele. Jest on w�wczas konstruktorem domy�lnym.
class Cplx {
// ...
protected:
double re, im;
public:
Cplx(void) // konstrukor domy�lny
{
re = im = 0;
}
// ...
};
Argumenty domniemane
Konstruktory mog� mie� argumenty domniemane. Ich u�ycie upraszcza definicj� klasy.
class Cplx {
// ...
protected:
double re, im;
public:
Cplx(double r =0, double i =0) // konstruktor domy�lny
{
re = r;
im = i;
}
// ...
};
Mimo zdefiniowania tylko jednego konstruktora, s� poprawne nast�puj�ce deklaracje
Cplx numA(3, 4);
Cplx numB(5);
Cplx numC;
Lista inicjacyjna
Za nag��wkiem konstruktora, po znaku : (dwukropek), wyst�puje lista inicjacyjna. Jej elementami s� napisy
fld(exp, exp, ... , exp)
w kt�rych fld jest identyfikatorem pola, a ka�de exp jest list� wyra�e� (zazwyczaj jednoelementow�).
Opracowanie elementu listy inicjacyjnej powoduje u�ycie wyra�e� exp do zainicjowania tego elementu obiektu, kt�ry jest opisany przez pole fld.
Je�li w miejscu wyst�pienia wyra�enia exp, nie jest widoczny identyfikator pola (gdy� zosta� przes�oni�ty przez identyfikator parametru konstruktora), to nazw� pola fld klasy Name jest Name::fld.
Uwaga: Je�li konstruktor nie ma listy inicjacyjnej, to domniemywa si� j� pustymi wyra�eniami exp. W takim wypadku, elementy typ�w nieobiektowych (np. int) s� inicjowane warto�ciami nieokre�lonymi, a elementy typ�w obiektowych s� inicjowane przez konstruktory domy�lne.
class Cplx {
// ...
protected:
double re, im;
public:
Cplx(double re =0, double im =0) : re(re), im(im)
{
}
// ...
};
W inicjatorze re(re) pierwsze re jest identyfikatorem pola, a drugie jest identyfikatorem parametru.
Gdyby u�yto inicjatora im(re * Cplx::re), to argumentami operacji by�aby nazwa parametru i nazwa pola.
Konstruktor kopiuj�cy
Konstruktorem kopiuj�cym klasy Name jest konstruktor u�ywany do inicjowania jej obiektu elementami innego obiektu klasy Name. Konstruktor kopiuj�cy jest jednoparametrowy, a jego parametr jest typu const�Name�&.
Cplx numA(3, 4),
numB(numA),
numC = numB;
Je�li definicja klasy nie zawiera konstruktora kopiuj�cego, to jest niejawnie uzupe�niana definicj� takiego publicznego konstruktora, kt�ry inicjuje elementy obiektu elementami obiektu inicjuj�cego. Oznacza to, �e dla klasy Cplx domniemany konstruktor kopiuj�cy jest zdefiniowany przez funkcj�
Cplx(const Cplx &par) : re(par.re), im(par.im)
{
}
Uwaga: Inicjowanie realizowane przez domniemany konstruktor kopiuj�cy polega na kopiowaniu-p�ytkim. Je�li klasa zawiera pola wska�nikowe lub odno�nikowe, to kopiowanie-p�ytkie mo�e okaza� si� niezadowalaj�ce. W takim wypadku nale�y zdefiniowa� w�asny konstruktor kopiuj�cy, kt�ry wykona kopiowanie-g��bokie (uwzgl�dniaj�ce klonowanie zmiennych identyfikowanych przez wska�niki i odno�niki).
class Cplx {
// ...
protected:
double re, im;
public:
Cplx(double re =0, double im =0) : re(re), im(im)
{
}
Cplx(const Cplx &par) : re(par.re), im(par.im)
{
}
// ...
};
U�ycie w�asnego konstruktora kopiuj�cego jest zbyteczne, poniewa� domniemany konstruktor kopiuj�cy dla klasy Cplx ma dok�adnie tak� sam� definicj�.
dla dociekliwych
Mo�e powsta� pytanie, kiedy do nadawania warto�ci pocz�tkowych elementom obiektu u�ywa� listy inicjacyjnej, a kiedy u�ywa� instrukcji w ciele konstruktora.
Je�li pola s� typu nieobiektowego (np. int albo char *), to skutek jest taki sam. Ale je�li elementy obiektu s� typu obiektowego, to r�nica mo�e by� istotna. Wynika to st�d, �e nadawanie warto�ci za pomoc� listy ma semantyk� inicjowania, a nadawanie warto�ci za pomoc� instrukcji, ma semantyk� przypisywania. Dlatego zaleca si� u�ywanie listy inicjacyjnej.
Destruktory
Destruktorem jest sk�adnik, kt�ry s�u�y do wykonania czynno�ci poprzedzaj�cych zniszczenie obiektu. Nazw� destruktora jest po��czenie znaku ~ (tylda) i nazwy klasy. Deklaracja konstruktora ma posta� deklaracji funkcji, ale nie mo�e zawiera� okre�lenia typu rezultatu. Destruktor jest bezparametrowy i nie mo�e by� przeci��ony.
Uwaga: Destruktor jest wywo�ywany niejawnie. Mo�na go wywo�a� jawnie, ale w�wczas nale�y naprawd� dobrze wiedzie� co si� robi. Dlatego taki spos�b post�powania lepiej zostawi� zawodowcom.
#include <iostream.h>
class Cplx {
friend Cplx add(Cplx &parL, Cplx parR);
protected:
double re, im;
public:
Cplx(double re, double im) : re(re), im(im)
{
cout << "Constructing: ", show();
}
~Cplx(void)
{
cout << "Destructing: ", show();
}
void show(void)
{
cout << re << '+' <<
im << 'i' << endl;
}
};
int main(void)
{
Cplx numA(1, 2);
{
Cplx numB(3, 4);
// ...
}
return 0;
}
Wykonanie programu powoduje wyprowadzenie napisu
Constructing: 1+2i
Constructing: 3+4i
Destructing: 3+4i
Destructing: 1+2i
Potwierdza si� fakt, �e obiekty automatyczne s� niszczone w kolejno�ci odwrotnej do kolejno�ci ich tworzenia.
Metody
Je�li sk�adnikiem klasy Name jest metoda fun, a obiektem tej klasy wskazanym przez ptr jest obj, to
ptr->fun(arg, arg, ... , arg)
oraz
obj.fun(arg, arg, ... , arg)
jest wywo�aniem metody fun na rzecz obiektu obj.
Uwaga: Metoda mo�e by� wywo�ana tylko na rzecz obiektu. Je�li wyra�enie *ptr albo obj nie jest nazw� obiektu, to nie poddaje si� go niejawnej konwersji. Dlatego poprawna mo�e by� m.in. operacja obj�+�"!", ale nie jest poprawna operacja "!"�+�obj, chyba �e operator + zdefiniowano za pomoc� funkcji globalnej.
W chwili wywo�ania metody jest tworzony wska�nik this typu Name�*, zainicjowany wskazaniem obiektu obj. Podczas wykonywania metody fun, trwa�� nazw� tego obiektu jest *this, a nazw� jego sk�adnika m jest (*this).m albo pro�ciej: this->m.
Uwaga: Je�li w miejscu wyst�pienia this->m jest widoczna deklaracja sk�adnika m, to this->m mo�na upro�ci� do m.
#include <iostream.h>
class Cplx {
protected:
double re, im;
public:
Cplx(double r, double i) : re(r), im(i)
{
}
Cplx add(Cplx &par)
{
double re = this->re + par.re,
im = this->im + par.im;
return Cplx(re, im);
}
void show(void)
{
cout << this->re << '+' <<
this->im << 'i' << endl;
}
};
int main(void)
{
Cplx one(1.0, 3.0),
two(2.0, 4.0);
Cplx sum = one.add(two);
sum.show(); // 3+7i
return 0;
}
W ciele funkcji add obiekt one ma nazw� *this, a jego elementy maj� nazwy this->re i this-im.
Funkcje operatorowe
Funkcj� operatorow� jest funkcja o nazwie
operator@
w kt�rej @ jest jednym operator�w wymienionych w tabeli Operatory.
Tabela Operatory
new delete
+ - * / % ^ & | ~
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- , () []
-> ->*
Funkcja operatorowa mo�e by� funkcj� globaln� albo metod� klasy. Operator mo�e by� zdefiniowany przez funkcj� globaln� tylko w�wczas, gdy ma ona co najmniej jeden argument obiektowy. Operatory =, [], () i -> mog� by� definiowane tylko przez metody.
Operatory jednoargumentowe
Je�li w pewnym miejscu programu wyst�puje operacja
@a
w kt�rej a jest wyra�eniem typu obiektowego, to jest traktowana jak wywo�anie
operator@(a)
jednoparametrowej funkcji globalnej operator@.
Je�li takiej funkcji nie ma, to jest traktowana jak wywo�anie
a.operator@()
bezparametrowej metody nale��cej do klasy obiektu a.
Je�li w pewnym miejscu programu wyst�puje operacja
a--
albo
a++
to, dla odr�nienia od operacji poprzednikowej, jest traktowana tak, jak wywo�anie funkcji z dodatkowym parametrem typu int.
Uwaga: Je�li istnieje funkcja globalna, to zabrania si�, aby w klasie obiektu a istnia�a metoda bezparametrowa.
#include <iostream.h>
class Cplx {
friend Cplx operator~(Cplx &par);
friend void operator++(Cplx &par, int);
friend ostream &operator<<(ostream &out, Cplx &par);
protected:
double re, im;
public:
Cplx(double r, double i) : re(r), im(i)
{
}
Cplx operator-(void)
{
return Cplx(-re, -im);
}
void operator++(void)
{
++re;
}
};
Cplx operator~(Cplx &par)
{
return Cplx(par.re, -par.im);
}
void operator++(Cplx &par, int)
{
++par.im;
}
ostream &operator<<(ostream &out, Cplx &par)
{
if(par.re)
out << par.re;
if(par.re && par.im > 0)
out << '+';
return out << par.im << 'i';
}
int main(void)
{
Cplx one(1,2);
cout << ~-one << endl; // -1+2i
Cplx two(3,4);
++two;
two++;
cout << two << endl; // 4+5i
return 0;
}
Operacja -one jest traktowana jak wywo�anie operator-(one) i jest realizowana przez funkcj� globaln�, zaprzyja�nion� z klas�.
Operacja ~-one jest traktowana jak wywo�anie one.operator<<(-one) i jest realizowana przez metod� klasy.
Operacja ++two zwi�ksza o 1 warto�� cz�ci rzeczywistej, a operacja two++ zwi�ksza o 1 warto�� cz�ci urojonej. Operacja ++two jest traktowana tak, jak wywo�anie two.operator++(), a operacja two++ jest traktowana tak, jak wywo�anie operator ++(two,�0).
Operatory dwuargumentowe
Je�li w pewnym miejscu programu wyst�puje operacja
a @ b
w kt�rej a albo b jest wyra�eniem typu obiektowego, to jest traktowana jak wywo�anie
operator@(a, b)
dwuparametrowej funkcji globalnej operator@.
Je�li takiej funkcji nie ma, to jest traktowana jak wywo�anie
a.operator@(b)
jednoparametrowej metody nale��cej do klasy obiektu a.
Uwaga: Je�li istnieje funkcja globalna, to zabrania si�, aby w klasie obiektu a istnia�a metoda, kt�r� mo�na by wywo�a� z argumentem b.
#include <iostream.h>
class Cplx {
friend ostream &operator<<(ostream &out, Cplx &par);
protected:
double re, im;
public:
Cplx(double r, double i) : re(r), im(i)
{
}
Cplx operator+(Cplx &par)
{
double re = Cplx::re + par.re,
im = Cplx::im + par.im;
return Cplx(re, im);
}
};
ostream &operator<<(ostream &out, Cplx &par)
{
if(par.re)
out << par.re;
if(par.re && par.im > 0)
out << '+';
return out << par.im << 'i';
}
int main(void)
{
Cplx one(1.0, 3.0),
two(2.0, 4.0);
Cplx sum = one + two;
cout << sum << endl; // 3+7i
return 0;
}
Operacja one�+�two jest traktowana jak wywo�anie one.operator+(two) i jest realizowana przez metod� klasy.
Operacja cout�<<�sum jest traktowana jak wywo�anie operator<<(cout,�sum) i jest realizowana przez funkcj� globaln� zaprzyja�nion� z klas�.
Operatory specjalne
Niekt�re operatory musz� by� implementowane przez metody. S� nimi =�(przypisanie), []�(indeksowanie), ->�(wybranie) i () (wywo�anie). Ostatni z nich mo�e by� wieloargumentowy.
Je�li w klasie nie zdefiniowano operatora przypisania, to jest domniemywany w postaci publicznej metody, realizuj�cej kopiowanie-p�ytkie.
Uwaga: Rezultatem domniemanego przypisania jest odno�nik do lewego argumentu. Oznacza to, �e dla klasy Cplx domniemana operacja przypisania jest zdefiniowana przez funkcj�
Cplx &operator=(const Cplx &par)
{
re = par.re;
im = par.im;
return *this;
}
Uwaga: Inicjowanie realizowane przez domniemany operator przypisania polega na kopiowaniu-p�ytkim. Je�li klasa zawiera pola wska�nikowe lub odno�nikowe, to mo�e okaza� si� niezadowalaj�ce. W takim wypadku nale�y zdefiniowa� w�asny operator przypisania, kt�ry wykona kopiowanie-g��bokie (uwzgl�dniaj�ce klonowanie zmiennych identyfikowanych przez wska�niki i odno�niki).
Konwertery
Konwerterem jest metoda, kt�ra definiuje konwersj� obiektu jej klasy na obiekt typu docelowego.
Je�li w pewnym miejscu wyst�puje operacja
(Type)a
w kt�rej a wyra�eniem typu obiektowego, a Type jest nazw� typu docelowego, to jest traktowana jak wywo�anie
a.operator Type()
bezparametrowej metody operator Type zdefiniowanej w klasie obiektu a.
#include <iostream.h>
#include <math.h>
class Cplx {
friend ostream &operator<<(ostream &out, Cplx &par);
protected:
double re, im;
public:
Cplx(double r, double i) : re(r), im(i)
{
}
operator double(void)
{
return sqrt(re * re + im * im);
}
};
ostream &operator<<(ostream &out, Cplx &par)
{
if(par.re)
out << par.re;
if(par.re && par.im > 0)
out << '+';
return out << par.im << 'i';
}
int main(void)
{
Cplx one(3,4);
cout << one << endl; // 3+4i
cout << (double)one << endl; // 5
return 0;
}
Operacja (double)one jest traktowana jak wywo�anie one.operator�double().
Niejawne konwersje
W ka�dym miejscu, gdzie typ wyra�enia jest niezgodny z typem inicjowanej nim zmiennej, mo�e by� zastosowana niejawna konwersja standardowa, konstruktorowa albo konwerterowa. Konwersja konstruktorowa jest okre�lona przez konstruktor, a konwerterowa przez konwerter. Wymaga si�, aby zastosowana konwersja by�a jednoznaczna.
#include <iostream.h>
class Cplx {
private:
double re, im;
public:
Cplx(double re =0, // =(double)0
double im =0) // =(double)0
: re(re), im(im)
{
}
operator double(void)
{
return re; // (double)re
}
};
Cplx num = 12; // Cplx(12)
int main(void)
{
cout << num << endl; // num.operator double()
return 0;
}
Sk�adniki statyczne
Sk�adnik statyczny klasy jest zadeklarowany ze specyfikatorem static. Jest on w istocie elementem klasy, a nie obiektu i istnieje nawet w�wczas, gdy nie utworzono ani jednego obiektu klasy. Nazw� statycznego sk�adnika mem klasy Class jest Class::mem.
Funkcja statyczna nie ma wska�nika this, a wi�c nie mo�e odwo�ywa� si� do p�l nie-statycznych. Pole statyczne nale�y zadeklarowa� w klasie i zdefiniowa� poza klas� (obecnie tak�e i w klasie). Domy�lnym inicjatorem pola statycznego jest =�0.
#include <iostream.h>
class Real {
private:
static int count; // deklaracja
protected:
double re;
public:
Real(double re =0) : re(re)
{
count++;
}
~Real(void)
{
count--;
}
static int getCount(void)
{
return count;
}
};
int Real::count = 0; // definicja
int main(void)
{
cout << Real::getCount() << endl; // 0
Real r1;
{
Real r2, r3;
cout << Real::getCount() << endl; // 3
}
cout << Real::getCount() << endl; // 1
return 0;
}
Metoda statyczna getCount dostarcza informacji o liczbie obiekt�w klasy Real.
Dystrybucja klasy
Przetestowana definicja klasy, uzupe�niona o wspomagaj�ce j� funkcje globalne jest dystrybuowana w postaci �r�d�owego pliku nag��wkowego i binarnego pliku implementacyjnego. U�ytkownik klasy w��cza do swoich modu��w nag��wek klasy i do��cza do swojego projektu plik implementacyjny.
Klasa Cplx
Przytoczona tu klasa zawiera przyk�adowy zestaw sk�adnik�w i funkcji wspomagaj�cych. Poniewa� sk�adniki funkcyjne zdefiniowane w ciele klasy s� domy�lnie otwarte (inline), wi�c aby uczyni� je zamkni�tymi mo�na je zdefiniowa� poza cia�em klasy.
Uwaga: Identyfikator sk�adnika zdefiniowanego poza cia�em klasy musi by� poprzedzony kwalifikatorem zakresu Name::, w kt�rym Name jest nazw� jego klasy.
#include <iostream.h>
#include <math.h>
class Cplx {
friend inline double sqrt(Cplx &par);
protected:
double re, im;
public:
Cplx(double re =0, double im =0) : re(re), im(im)
{
}
~Cplx(void)
{
}
operator double(void)
{
return sqrt(re * re + im * im);
}
Cplx operator+(Cplx &par);
Cplx operator~(void)
{
return Cplx(re, -im);
}
void putCplx(ostream &out)
{
if(re)
out << re;
if(re && im > 0)
out << '+';
out << im << 'i';
}
void getCplx(istream &inp)
{
inp >> re;
if(inp.peek() == 'i')
im = re, re = 0;
else {
inp >> im;
if(inp.peek() != 'i') {
inp.clear(ios::badbit);
return;
}
}
char drop;
inp >> drop;
}
};
Cplx Cplx::operator+(Cplx &par)
{
return Cplx(re + par.re, im + par.im);
}
double sqrt(Cplx &par)
{
return (double)par;
}
ostream &operator<<(ostream &out, Cplx &par)
{
par.putCplx(out);
return cout;
}
istream &operator>>(istream &inp, Cplx &par)
{
par.getCplx(inp);
return inp;
}
int main(void)
{
Cplx one, two;
cin >> one >> two;
cout << "Sum = " << one + two << endl;
cout << "sqrt(" << one << ") = " <<
double(one) << endl;
cout << "sqrt(" << two << ") = " <<
sqrt(two) << endl;
return 0;
}
Aby nie zaprzyja�nia� funkcji operatorowych operator<< i operator>> z klas� Cplx u�yto w nich publicznych metod putCplx i getCplx.
Plik nag��wkowy
W pliku nag��wkowym pozostawia si� deklaracje jej sk�adnik�w i funkcji wspomagaj�cych oraz definicje sk�adnik�w i funkcji wspomagaj�cych zadeklarowanych (jawnie albo niejawnie) ze specyfikatorem inline.
W nag��wku pozostawia si� argumenty domniemane, ale usuwa si� z niego listy inicjacyjne (wraz z dwukropkami).
Uwaga: Deklaracje nag��wka obudowuje si� zazwyczaj dyrektywami #ifndef, #define i #endif. Dzi�ki temu unika si� b��d�w spowodowanych wi�cej ni� jednokrotnym w��czeniem nag��wka do tego samego pliku �r�d�owego.
#ifndef CPLX
#define CPLX
#include <iostream.h>
#include <math.h>
class Cplx {
friend inline double sqrt(Cplx &par);
protected:
double re, im;
public:
Cplx(double re =0, double im =0);
~Cplx(void);
operator double(void);
Cplx operator+(Cplx &par);
Cplx operator~(void);
void putCplx(ostream &out);
void getCplx(istream &inp);
};
double sqrt(Cplx &par);
ostream &operator<<(ostream &out, Cplx &par);
istream &operator>>(istream &inp, Cplx &par);
#endif
Plik implementacyjny
Plik implementacyjny zawiera dyrektyw� #include w��czaj�c� nag��wek oraz definicje tych wszystkich funkcji, kt�re zadeklarowano (tylko zadeklarowano!) w nag��wku.
W pliku implementacyjnym pozostawia si� listy inicjacyjne i specyfikatory virtual, ale usuwa si� z niego argumenty domniemane.
#include "cplx.h"
#include <iostream.h>
#include <math.h>
Cplx::Cplx(double re, double im) : re(re), im(im)
{
}
Cplx::~Cplx(void)
{
}
Cplx::operator double(void)
{
return sqrt(re * re + im * im);
}
Cplx Cplx::operator~(void)
{
return Cplx(re, -im);
}
void Cplx::putCplx(ostream &out)
{
if(re)
out << re;
if(re && im > 0)
out << '+';
out << im << 'i';
}
void Cplx::getCplx(istream &inp)
{
inp >> re;
if(inp.peek() == 'i')
im = re, re = 0;
else {
inp >> im;
if(inp.peek() != 'i') {
inp.clear(ios::badbit);
return;
}
}
char drop;
inp >> drop;
}
Cplx Cplx::operator+(Cplx &par)
{
return Cplx(re + par.re, im + par.im);
}
double sqrt(Cplx &par)
{
return (double)par;
}
ostream &operator<<(ostream &out, Cplx &par)
{
par.putCplx(out);
return cout;
}
istream &operator>>(istream &inp, Cplx &par)
{
par.getCplx(inp);
return inp;
}
Program testuj�cy
#include <iostream.h>
#include <math.h>
#include "cplx.h"
int main(void)
{
Cplx one, two;
cin >> one >> two;
cout << "Sum = " << one + two << endl;
cout << "sqrt(" << one << ") = " <<
double(one) << endl;
cout << "sqrt(" << two << ") = " <<
sqrt(two) << endl;
return 0;
}
Klasy pochodne
Klas� pochodn� jest klasa, kt�ra wywodzi si� od klasy bazowej wyszczeg�lnionej na li�cie dziedziczenia.. Klas� pochodn� tworzy si� w�wczas, gdy jej klasie bazowej brakuje pewnych sk�adnik�w, ale gdy nowej klasy nie chce si� definiowa� od pocz�tku.
class Derived
: public Base1, public Base2
{
// ...
};
Klasa Derived pochodzi od klas Base1 i Base2 wyszczeg�lnionych na li�cie dziedziczenia.
Dziedziczenie i inicjowanie
Klas pochodna dziedziczy wszystkie sk�adniki klasy bazowej, ale nie dziedziczy konstruktor�w i operator�w przypisania.
Ka�dy obiekt klasy pochodnej sk�ada si� z takich samych element�w z jakich sk�ada si� obiekt klasy bazowej oraz z tych dodatkowych element�w, kt�re s� opisane przez pola klasy pochodnej.
Inicjowanie element�w klasy bazowej odbywa si� za pomoc� konstruktora klasy bazowej, wywo�anego z listy inicjacyjnej konstruktora klasy pochodnej.
Uwaga: T� cz�� obiektu klasy pochodnej, kt�ra jest obiektem klasy bazowej nazywa si� podobiektem klasy pochodnej.
class Real {
protected:
double re;
public:
Real(double re) : re(re)
{
}
};
class Cplx : public Real {
protected:
double im;
public:
Cplx(double re, double im) : Real(re), im(im)
{
}
};
Obiekt klasy Cplx sk�ada si� z element�w opisanych przez pole re klasy Real i pole im klasy Cplx.
Element listy inicjacyjnej Real(re) s�u�y do zainicjowania tej cz�ci obiektu klasy Cplx, kt�ra jest podobiektem klasy Real.
Identyfikowanie podobiekt�w
Je�li klasa Derived wywodzi si� od klasy Primary, a obiekt objD klasy Derived zawiera obiekt objP klasy Primary, to
(Primary &)objD jest nazw� podobiektu objP
(Derived &)objP jest nazw� obiektu objD
(Primary *)&objD jest wska�nkiem na podobiekt objP
(Derived *)&objP jest wska�nikiem na obiekt objD
*(Primary *)&objD jest nazw� podobiektu objP
*(Derived *)&objP jest nazw� obiektu objD
Uwaga: Konwersja odno�nika-do-obiektu na odno�nik-do-jego-podobiektu oraz konwersja wska�nika-na-obiekt we wska�nik-na-podobiekt jest konwersj� standardow� i jako taka mo�e by� wykonana niejawnie.
#include <iostream.h>
class Real {
friend ostream &operator<<(ostream &out, Real &par);
private:
double *pRe;
public:
Real(double re =0) : pRe(new double)
{
*pRe = re;
}
Real(const Real &par) : pRe(new double)
{
*pRe = *par.pRe;
}
~Real(void)
{
delete pRe;
}
Real &operator=(Real &par)
{
*pRe = *par.pRe;
return *this;
}
};
ostream &operator<<(ostream &out, Real &par)
{
return out << *par.pRe;
}
class Cplx : public Real {
friend ostream &operator<<(ostream &out, Cplx &par);
private:
double *pIm;
public:
Cplx(double re, double im) : Real(re), pIm(new double)
{
*pIm = im;
}
Cplx(const Cplx &par) : Real(par), pIm(new double)
{
*pIm = *par.pIm;
}
Cplx(void)
{
delete pIm;
}
Cplx &operator=(Cplx &par)
{
(Real &)*this = (Real &)par;
*pIm = *par.pIm;
return *this;
}
};
ostream &operator<<(ostream &out, Cplx &par)
{
out << (Real &)par;
if(par.re && *par.pIm > 0)
out << '+';
return out << *par.pIm << 'i';
}
int main(void)
{
Cplx num(3,4);
cout << num << endl; // 3+4i
return 0;
}
W wyra�eniu (Real�&)*this�=�(Real�&)par napis (Real�&)*this jest nazw� podobiektu obiektu *this, a napis (Real &)par jest nazw� podobiektu obiektu par. Pierwszy z tych napis�w jest r�wnowa�ny napisowi *(Real�*)this, a drugi jest r�wnowa�ny napisowi *(Real�*)&par.
Zast�pienie rozpatrywanego wyra�enia wyra�eniem *pRe�=�*par.pRe jest niemo�liwe, poniewa� pole pRe jest prywatne, a metoda operator= klasy Cplx nie jest zaprzyja�niona z klas� Real.
Gdyby nie zdefiniowano funkcji operator<< dla klasy Cplx, to operacja cout�<<�num zosta�aby niejawnie zast�piona operacj� cout�<<�(Real�&)num i zosta�aby wyprowadzona liczba 3.
dla dociekliwych
Po utworzeniu obiektu i jego podobiekt�w jest wywo�ywany konstruktor obiektu. Podczas opracowywania jego listy inicjacyjnej s� wywo�ywane konstruktory podobiekt�w. Nast�pnie odbywa si� inicjowanie w�asnych element�w obiektu. Na zako�czenie jest wykonywane cia�o konstruktora obiektu.
Uwaga: Inicjowanie podobiekt�w odbywa si� w kolejno�ci wyszczeg�lnienia klas na li�cie dziedziczenia. Inicjowanie w�asnych element�w obiektu odbywa si� w kolejno�ci zadeklarowania ich p�l.
Tu� przed zniszczeniem obiektu jest wywo�ywany destruktor obiektu. Po wykonaniu jego cia�a s� wywo�ywane destruktory w�asnych element�w obiektu, a nast�pnie destruktory podobiekt�w.
Uwaga: Destruktory element�w s� wywo�ywane w kolejno�ci odwrotnej do zadeklarowania ich p�l. Destruktory podobiekt�w s� wywo�ywane w kolejno�ci odwrotnej do wyszczeg�lnienia klas na li�cie dziedziczenia.
#include <iostream.h>
class Real {
private:
double re;
public:
Real(double re) : re(re)
{
cout << "Real-C: " << re << endl;
}
~Real(void)
{
cout << "Real-D: " << re << endl;
}
};
class Cplx : public Real {
private:
Real im;
public:
Cplx(double re, double im) : im(im), Real(re)
{
cout << "Cplx-C: " << endl;
}
~Cplx(void)
{
cout << "Cplx-D: " << endl;
}
};
int main(void)
{
Cplx num(3,4);
cout << endl;
return 0;
}
Wykonanie programu powoduje wyprowadzenie napisu
Real-C: 3
Real-C: 4
Cplx-C:
Cplx-D:
Real-D: 4
Real-D: 3
Potwierdza si�, �e kolejno�� element�w listy inicjacyjnej jest nieistotna.
Studium programowe
Je�li kto� dysponuje klas� Cplx, z definicj� zawart� w nag��wku cplx.h i implementacj� w pliku binarnym cplx.obj (por. poprzedni rozdzia�), ale chce mie� klas� do niej podobn�, kt�rej ka�dy obiekt zawiera dodatkowy element, okre�laj�cy ile razy cz�� urojona mia�a warto�� 0, to taka klas�, nazwan� tu Cplx2, mo�e zdefiniowa� nast�puj�co.
#include "cplx.h"
class Cplx2 : public Cplx {
friend ostream &operator<<(ostream &out, Cplx2 &par);
protected:
int count;
public:
Cplx2(double re =0, double im =0) : Cplx(re, im), count(0)
{
}
Cplx2 &operator=(const Cplx2 &par)
{
if(par.im == 0)
count++;
(Cplx &)*this = par;
return *this;
}
};
ostream &operator<<(ostream &out, Cplx2 &par)
{
out << (Cplx &)par;
if(par.count != 0)
out << ':' << par.count;
return out;
}
istream &operator>>(istream &inp, Cplx2 &par)
{
inp >> (Cplx &)par;
if(inp.peek() == ':') {
char chr;
int num;
inp >> chr >> num;
}
return inp;
}
Podobnie jak uczyniono to uprzednio, klas� Cplx2 mo�na dystrybuowa� w postaci pliku nag��wkowego cplx2.h i pliku implementacyjnego cplx2.obj.
Plik nag��wkowy
#ifndef CPLX2
#define CPLX2
#include <iostream.h>
#include "cplx.h"
class Cplx2 : public Cplx {
friend ostream &operator<<(ostream &out, Cplx2 &par);
protected:
int count;
public:
Cplx2(double re =0, double im =0);
Cplx2 &operator=(const Cplx2 &par);
};
ostream &operator<<(ostream &out, Cplx2 &par);
istream &operator>>(istream &inp, Cplx2 &par);
#endif
Plik implementacyjny
#include "cplx2.h"
#include <iostream.h>
Cplx2::Cplx2(double re, double im) : Cplx(re, im), count(0)
{
}
Cplx2 &Cplx2::operator=(const Cplx2 &par)
{
if(par.im == 0)
count++;
(Cplx &)*this = par;
return *this;
}
ostream &operator<<(ostream &out, Cplx2 &par)
{
out << (Cplx &)par;
if(par.count != 0)
out << ':' << par.count;
return out;
}
istream &operator>>(istream &inp, Cplx2 &par)
{
inp >> (Cplx &)par;
if(inp.peek() == ':') {
char chr;
int num;
inp >> chr >> num;
}
return inp;
}
Program testuj�cy
#include <iostream.h>
#include "cplx2.h"
int main(void)
{
Cplx2 num(3,4);
cout << num << endl; // 3+4i
num = 0;
num = Cplx2(1,2);
num = 0;
cin >> num; // 5i
cout << num << endl; // 5i:2
return 0;
}
Metody wirtualne
Metod� wirtualn� jest metoda zadeklarowana ze specyfikatorem virtual. Je�li wywo�anie metody wirtualnej odbywa si� na rzecz obiektu kompletnego (a nie na rzecz jego podobiektu!), albo zawiera operator zakresu (np. obj.Cplx::abs()), to skutek wywo�ania b�dzie taki sam jak dla metody nie-wirtualnej. Je�li wywo�anie odbywa si� na rzecz podobiektu obiektu kompletnego, to zostanie wykonana metoda o r�wnowa�nej sygnaturze, widoczna w klasie obiektu kompletnego. Taki spos�b wywo�ania jest polimorficzny, poniewa� jest inny podczas kompilowania, a inny podczas wykonania programu.
Uwaga: Sygnatur� funkcji uzyskuje si� po usuni�ciu z jej deklaracji specyfikator�w typu funkcji, identyfikator�w jej parametr�w i opis�w argument�w domniemanych. W szczeg�lno�ci funkcja o deklaracji const�int�fun(int par[3]) ma sygnatur� fun(int�[3]).
#include <iostream.h>
#include <math.h>
class Real {
protected:
double re;
public:
Real(double re) : re(re)
{
}
virtual double abs(void)
{
return re < 0 ? -re : re;
}
};
class Cplx : public Real {
protected:
double im;
public:
Cplx(double re, double im) : Real(re), im(im)
{
}
double abs(void)
{
return sqrt(re * re + im * im);
}
};
int main(void)
{
Cplx num(3,4);
Cplx &ref = num;
Cplx *ptr = #
cout << num.abs() << endl; // 5
cout << ref.abs() << endl; // 5
cout << ptr->abs() << endl; // 5
return 0;
}
Wywo�anie num.abs() odbywa si� na rzecz obiektu kompletnego, wi�c zostanie wywo�ana metoda abs jego klasy, czyli Cplx::abs.
Wywo�anie ref.abs() odbywa si� na rzecz podobiektu klasy Real, identyfikowanego przez ref. Poniewa� w klasie Real metoda abs jest wirtualna, wi�c faktycznie zostanie wywo�ana metoda abs widoczna w klasie obiektu kompletnego do kt�rego nale�y podobiekt, czyli Cplx::abs.
Wywo�anie ptr->abs() odbywa si� na rzecz podobiektu *ptr klasy Real, wskazanego przez ptr. Poniewa� w klasie Real metoda abs jest wirtualna, wi�c faktycznie zostanie wywo�ana metoda abs widoczna w klasie obiektu kompletnego, kt�rego podobiektem jest *ptr, czyli Cplx::abs.
Gdyby z definicji funkcji Real::abs usuni�to specyfikator virtual, to wywo�ania ref.abs() i ptr->abs() dotyczy�yby metody Real::abs, co spowodowa�oby wyprowadzenie liczb 4.
Gdyby z klasy Cplx usuni�to metod� abs, to w klasie obiektu kompletnego by�aby widoczna metoda abs odziedziczona z klasy Real, a wi�c w ka�dym z rozpatrzonych przypadk�w zosta�aby wywo�ana metoda Real::abs.
Czyste metody wirtualne
Je�li nie przewiduje si� wykonania metody wirtualnej, to mo�na usun�� jej cia�o, a w deklaracji umie�ci� inicjator =�0. Tak zdefiniowana metoda jest czyst� metod� wirtualn�.
virtual double abs(void) = 0;
Klasa, kt�ra zawiera przynajmniej jedn� czyst� metod� wirtualn� jest klas� abstrakcyjn�. Klasa abstrakcyjna jest przydatna tylko do definiowania klas pochodnych (nie mo�na tworzy� obiekt�w takiej klasy).
Je�li w klasie pochodnej klasy abstrakcyjnej nie zdefiniuje si� wszystkich odziedziczonych przez ni� czystych metod wirtualnych, to taka klasa tak�e b�dzie klas� abstrakcyjn�.
Studium programowe
Studium po�wi�cono zdefiniowaniu klasy z metod� wirtualn� oraz klasy pochodnej. Pokazano, jak nie zmieniaj�c pierwotnej definicji operatora wyj�cia mo�na, dzi�ki przedefiniowaniu metody wirtualnej, zmieni� spos�b wyprowadzania wynik�w.
Klasa String
Niech b�dzie dana nast�puj�ca klasa String do wykonywania operacji na �a�cuchach o rozmiarze nie przekraczaj�cym 256 element�w.
#include <iostream.h>
#include <string.h>
class String {
friend ostream &operator<<(ostream &out, String &par);
private:
int len;
char str[256];
public:
String(char *ptr) : len(strlen(ptr))
{
strcpy(str, ptr);
}
String &operator+=(char *ptr)
{
strcpy(str + len, ptr);
return *this;
}
String &operator+=(String &par)
{
return *this += par.str;
}
virtual char operator[](int pos)
{
return str[pos];
}
};
ostream &operator<<(ostream &out, String &par)
{
for(int i = 0; i < par.len ; i++)
out << par[i];
return out;
}
U�ycie
Je�li klas� String jest dystrybuowana w postaci pliku nag��wkowego string.h i pliku implementacyjnego string.obj, to mo�na jej u�y� w nast�puj�cym programie.
#include <iostream.h>
#include "string.h"
int main(void)
{
String h("Hello"),
w("World");
cout << h << endl; // Hello
cout << (h += w) << endl; // HelloWorld
cout << ((h += " ") += w) << endl; // Hello World
return 0;
}
Operacja += musi by� uj�ta w nawiasy. Wynika to st�d, �e jej priorytet jest ni�szy od priorytetu operacji <<.
Klasa String2
Je�li u�ytkownik klasy String nie jest zadowolony ze sposobu wykonywania operacji wyj�cia, to korzystaj�c z tego, �e metoda operator[] jest wirtualna, mo�e zdefiniowa� nast�puj�c� klas� String2 i przedefiniowa� w niej t� metod� tak, aby dostarcza�a nie kody liter �a�cucha, ale kody odpowiadaj�cych im du�ych liter.
#include <ctype.h>
class String2 : public String {
public:
String2(char *ptr) : String(ptr)
{
}
char operator[](int pos)
{
return toupper(String::operator[](pos));
}
};
Uwaga: Argumentowi funkcji toupper nie nadano postaci str(pos), poniewa� identyfikator str jest prywatny, a wi�c w klasie String2 jest niedost�pny. Nie nadano mu postaci ((String�&)*this)[pos], r�wnowa�nej (*this)[pos], poniewa� spowodowa�oby to rekurencyjne wywo�anie metody String2::operator[], to jest wpadni�cie programu w p�tl�.
U�ycie
Je�li klas� String2 jest dystrybuowana w postaci pliku nag��wkowego string2.h i pliku implementacyjnego string2.obj, to mo�na jej u�y� w nast�puj�cym programie.
#include <iostream.h>
#include "string2.h"
int main(void)
{
String2 h("Hello"),
w("World");
cout << h << endl; // HELLO
cout << (h += w) << endl; // HELLO WORLD
cout << ((h += " ") += w) << endl; // HELLO WORLD
return 0;
}
Uzasadnienie
Definicja klasy String ma posta�
class String {
// ...
virtual char operator[](int pos)
{
return str[pos];
}
};
ostream &operator<<(ostream &out, String &par)
{
for(int i = 0; i < par.len ; i++)
out << par[i];
return out;
}
a definicja klasy String2 ma posta�
class String2 : public String {
// ...
char operator[](int pos)
{
return toupper(String::operator[](pos));
}
};
Poniewa� nie zdefiniowano operatora wyj�cia dla obiekt�w klasy String2, wi�c w zasi�gu deklaracji
String2 h("Hello");
wykonanie operacji
cout << h
powoduje niejawne zastosowanie konwersji standardowej odno�nika-do-obiektu na odno�nik-do-podobiektu
cout << (String &)h
a nast�pnie wywo�anie operatora wyj�cia dla obiekt�w klasy String.
W ciele tego operatora parametr par jest odno�nikiem do podobiektu, a wi�c wywo�anie par[i] metody wirtualnej operator[] klasy String zostaje zast�pione wywo�aniem metody operator[] klasy String2. A zatem w miejscu wywo�ania par[i] jest dostarczany kod du�ej litery.
Projektowanie kolekcji
Klas� kolekcyjn� jest klasa, kt�rej obiekty s� przystosowane do przechowywania obiekt�w wybranej rodziny klas. Zazwyczaj stawia si� wymaganie, aby klasy rodziny wywodzi�y si� od wsp�lnej klasy pierwotnej.
Klas� iteracyjn� jest klasa, kt�ra umo�liwia otrzymywanie wska�nik�w albo odno�nik�w na kolejne obiekty znajduj�ce si� w kolekcji. Klasa iteracyjna jest zazwyczaj definiowana jako klasa wewn�trzna jej klasy kolekcyjnej.
W nast�puj�cym programie zdefiniowano klas� kolekcyjn� Numbers umo�liwiaj�c� tworzenie kolekcji oraz klas� iteracyjn� Numbers::Scanner umo�liwiaj�c� przegl�danie kolekcji.
Klasa Numbers umo�liwia dodawanie obiekt�w do kolekcji (operator+=) oraz ujawnianie i wyznaczanie sumy warto�ci bezwzgl�dnych wszystkich obiekt�w znajduj�cych si� w kolekcji (metody showAll i getSum).
W kolekcji mo�na przechowywa� obiekty numeryczne dowolnych klas pochodnych od klasy Root, byle tylko ka�d� z nich wyposa�ono w metody wirtualne showVal i getAbs.
Uwaga: Klas� Numbers zdefiniowano w taki spos�b, �e �adna z jej metod nie zale�y od typu obiekt�w umieszczanych w kolekcji. W�a�ciwo�� ta umo�liwia umieszczanie w kolekcjach Numbers, obiekt�w takich klas, kt�re nie by�y znane podczas definiowania klasy kolekcyjnej.
#include <iostream.h>
#include <math.h>
#include <stdlib.h>
class Root {
friend class Numbers;
public:
virtual void showVal(ostream &out) = 0;
virtual double getAbs(void) = 0;
};
const int Size = 1000;
class Numbers {
public:
class Scanner {
private:
Numbers &dataBase;
int pos;
public:
Scanner(Numbers &dataBase)
: dataBase(dataBase), pos(0)
{
}
Root *operator++(int)
{
if(pos == Size)
return pos = 0, 0;
else
return dataBase.pItem[pos++];
}
};
friend Root *Numbers::Scanner::operator++(int);
private:
Root *pItem[Size];
int count;
public:
Numbers(void) : count(0)
{
}
Numbers &operator<=(Root &ref)
{
if(count == Size) {
cout << "Numbers overflow" << endl;
exit(0);
}
pItem[count++] = &ref;
return *this;
}
void showAll(ostream &out)
{
out << "Data base contains:" << endl;
for(int i = 0; i < count ; i++) {
Root *ptr = pItem[i];
ptr->showVal(out);
out << endl;
}
out << endl;
}
double getSum(void)
{
double sum;
for(int i = 0; i < count ; i++) {
Root *ptr = pItem[i];
sum += ptr->getAbs();
}
return sum;
}
};
class Real : public Root {
protected:
double re;
public:
Real(double re =0) : re(re)
{
}
double getAbs(void)
{
return re < 0 ? -re : re;
}
void showVal(ostream &out)
{
out << re;
}
};
class Cplx : public Real {
protected:
double re, im;
public:
Cplx(double re =0, double im =0) : re(re), im(im)
{
}
double getAbs(void)
{
return sqrt(re * re + im * im);
}
void showVal(ostream &out)
{
out << '(' << re << ',' << im << ')';
}
};
int main(void)
{
Real r1(1), r2(2);
Cplx c1(3,4);
Numbers dataBase;
dataBase <= r1 <= r2 <= c1;
dataBase.showAll(cout); // 1 2 (3,4)
cout << "Sum = " <<
dataBase.getSum() << // 8
endl;
cout << endl << "Scanner output:" << endl;
Numbers::Scanner scan(dataBase);
Root *pItem;
while(pItem = scan++) {
pItem->showVal(cout);
cout << endl;
}
return 0;
}
Studium programowe
Klas� Numbers mo�na dystrybuowa� w postaci pliku nag��wkowego numbers.h oraz pliku implementacyjnego numbers.obj. Mimo, i� kod �r�d�owy klasy jest w�wczas niedost�pny, mo�na jej u�y� w programie pos�uguj�cym si� klas� Fract, nie znan� w chwili gdy powstawa�y klasy Numbers i Scanner.
Uwaga: Klasa Fract implementuje liczby u�amkowe. Ich liczniki i mianowniki s� przechowywane w postaci znormalizowanej.
#include <iostream.h>
#include "numbers.h"
class Frac : public Root {
private:
int num, den;
public:
Frac(int num =0, int den =1) : num(num), den(den)
{
norm();
}
int gdc(int n, int d)
{
int t;
while(d) {
t = n % d;
n = d;
d = t;
}
return n;
}
void norm()
{
int g = gdc(num, den);
num /= g;
den /= g;
if(den < 0) {
num = -num;
den = -den;
}
}
Frac operator+(Frac &par)
{
Frac f;
f.num = num * par.den + par.num * den;
f.den = den * par.den;
f.norm();
return f;
}
operator double(void)
{
return double(num) / den;
}
double getAbs(void)
{
double val = double(num) / den;
return val < 0 ? -val : val;
}
void showVal(ostream &out)
{
out << num << '/' << den;
}
};
int main(void)
{
Frac f1(4,8),
f2(3,4);
Numbers dataBase;
dataBase.add(f1).add(f2);
dataBase.showAll(cout); // 1/2 3/4
cout << endl;
cout << "Sum = " <<
dataBase.getSum() << // 1.25
endl;
cout << f1 + f2 << endl; // 1.25
cout << endl;
cout << "Scanner output:" << endl;
Numbers::Scanner scan(dataBase);
Root *pItem;
while(pItem = scan++) {
pItem->showVal(cout);
cout << endl;
}
return 0;
}
Metoda gdc wyznacza najwi�kszy-wsp�lny-podzielnik, a metoda norm normalizuje liczb� u�amkow� w taki spos�b, aby licznik i mianownik nie mia�y wsp�lnego podzielnika r�nego od 1.
Studium projektowe
Przedstawione tu studium projektowe ilustruje istotne problemy jakie powstaj� podczas projektowania klas. Dokonano go na przyk�adzie klasy String, kt�rej obiekty umo�liwiaj� reprezentowanie i wykonywanie operacji na �a�cuchach. Po umieszczeniu jej definicji w nag��wku string.h, klasa String mo�e by� u�yta m.in. w nast�puj�cy spos�b.
#include <iostream.h>
#include "string.h"
int main(void)
{
String h("Hello"),
w = "World";
cout << "The length of \"" << h + w <<
"\" is: " << !(h + w) << endl;
return 0;
}
Operacja dodawania (+) s�u�y do sklejania �a�cuch�w, a operacja wykrzyknik (!) dostarcza rozmiar �a�cucha.
Za�o�enia projektowe
Obiekt klasy String sk�ada si� z 2 element�w: ze zmiennej typu int okre�laj�cej rozmiar �a�cucha oraz ze zmiennej typu char�* wskazuj�cej pierwszy element �a�cucha przydzielonego na stercie. Z ca�� klasa jest zwi�zana zmienna statyczna count okre�laj�ca aktualn� liczb� �a�cuch�w.
class String {
int len;
char *ptr;
static int count;
};
Klasa String o podanej definicji umo�liwia tworzenie obiekt�w, inicjowanie ich za pomoc� konstruktora domy�lnego i kopiuj�cego oraz przypisywanie obiekt�w. Po uwzgl�dnieniu domniema�, tak zdefiniowana klasa jest r�wnowa�na klasie
class String {
private:
int len;
char *ptr;
static int count;
public:
String(void) : len(), ptr()
{
}
String(const String &par)
: len(par.len), ptr(par.ptr)
{
}
~String(void)
{
}
String &operator=(const String &par)
{
len = par.len;
ptr = par.ptr;
return *this;
}
};
Poniewa� nie zdefiniowano ani jednego jawnego konstruktora, wi�c obiekty nie s� w�a�ciwie zainicjowane i ich elementy maj� warto�ci nieokre�lone. Wobec braku operatora wyj�cia (albo konwertera), na obiektach klasy nie mo�na wykonywa� operacji wej�cia-wyj�cia.
#include <iostream.h>
class String {
int len;
char *ptr;
static int count;
};
int String::count = 0;
int main(void)
{
String s1,
s2(s1),
s3 = s2;
s1 = s3;
cout << s1 << endl; // b��d
cout << String::count << endl; // b��d
return 0;
}
Wyposa�enie w konstruktor
Inicjowanie element�w obiektu danymi o warto�ciach okre�lonych jest mo�liwe tylko w�wczas, gdy jego klas� wyposa�ono w konstruktor. Jego definicji mo�na nada� posta�
String(char *ptr ="")
: len(strlen(ptr)), ptr(new char [len+1])
{
strcpy(String::ptr, ptr);
count++;
}
Tak zdefiniowany konstruktor, poniewa� mo�e by� wywo�any bez argument�w, jest zarazem konstruktorem domy�lnym.
#include <iostream.h>
#include <string.h>
class String {
friend os