Podstawy
programowania
obiektowego
Ćwiczenia laboratoryjne nr 3
zajęcia zaplanowane na 4 godziny
Temat: Operatory przeciążone, funkcje operatorowe
Prowadzący:
mgr inż. Dariusz Rataj
Koszalin 2001
Podstawy programowania obiektowego ćw nr 2
Strona 2
Spis treści:
1. Przeciążanie operatorów
2. Funkcje operatorowe składowe klasy
3. Funkcje operatorowe zaprzyjaźnione
4. Przykłady
1. Przeciążanie operatorów
Przeciążanie operatora oznacza zdefiniowanie nowego działania operatora dla definiowanej
klasy. W języku C++ mamy możliwość przedefiniowania działania (przeciążenia) prawie
wszystkich operatorów. Wyjątkami są operatory:
. .* ?: :: sizeof
Tworząc nową definicję działania operatora nie zmieniamy jego działania dla typów
standardowych, np.: definiując operator + dla nowotworzonej klasy, działanie tego
operatora dla liczb typu int lub float pozostanie niezmienione. Aby zdefiniować działanie
operatora należy utworzyć funkcję operatorową.
W naszym ćwiczeniu zajmiemy się definicjami podstawowych operatorów dwu
i jednoargumentowych. Funkcje operatorowe możemy zdefiniować jako funkcje składowe
klasy lub jako funkcje zaprzyjaźnione klasy (znane z ćwiczenia nr 2).
2. Funkcje operatorowe składowe klasy
- operatory dwuargumentowe
Operator dwuargumentowy, zdefiniowany jako funkcja składowa klasy, po lewej stronie
zawsze ma argument typu definiowanej klasy. W naszym przykładzie będzie to typ
zespolona.
Przykład:
!
Deklaracja w nagłówku klasy
!
Definicja metody
zespolona zespolona::operator + (zespolona z)
{
zespolona z1(re+z.re, im+z.im);
return z1;
}
zasady stosowania:
Typ zwracany
przez operator
Słowo kluczowe
"operator"
symbol
operatora
Prawy argument
operatora
(typ identyfikator)
zespolona operator + (zespolona z);
Podstawy programowania obiektowego
Strona 3
−
stosujemy wtedy, gdy lewy argument jest tego samego typu co klasa, np. w operatorach
+ , -, *, /, =, = =,
−
definicja metody operatorowej poza klasą posiada identyfikator klasy: zespolona:: , tak
samo jak każda inna metoda klasy.
- operatory jednoargumentowe
!
Deklaracja w nagłówku klasy
zespolona operator * ();
!
Definicja metody
zespolona zespolona::operator * ()
{
return zespolona(re, -im);
}
3. Funkcje operatorowe zaprzyjaźnione
Tak zdefiniowany operator po prawej i lewej stronie ma argumenty dowolnego typu.
Przykład:
!
Deklaracja w nagłówku klasy
!
Definicja metody
ostream& operator << (ostream& o, zespolona z)
{
return o << '(' << z.re << ")+(j" << z.im << ')';
}
zasady stosowania:
−
stosujemy wtedy, gdy lewy argument jest innego typu (może być ten sam typ) co klasa,
np. w operatorach << ,>>. Przeważnie prawy argument jest tego samego typu co
definiowana klasa,
−
deklaracja metody operatorowej w nagłówku klasy posiada słowo kluczowe friend
informujące, że jest to metoda zaprzyjażniona (nie jest metodą składową klasy!). W
definicji metody to słowo nie występuje.
Typ zwracany
przez operator
Słowo kluczowe
"operator"
symbol
operatora
lewy argument
operatora
(typ identyfikator)
prawy argument
operatora
(typ identyfikator)
Słowo kluczowe
"friend" - metoda
zaprzyjaźniona
friend ostream& operator << (ostream& o, zespolona
z);
Podstawy programowania obiektowego ćw nr 2
Strona 4
−
przeważnie typ zwracany przez metodę operatorową jest taki sam jak typ lewego
argumentu (możemy przyjąć to jako zasadę). W naszym przykładzie typ ostream&
(referencja na ostream).
4. Przykłady
Przykład 1. Definicja klasy osoba. Klasa zawiera trzy pola prywatne: nazwisko, imie, pesel typu
tekstowego (tablica znaków), dwie funkcje operatorowe zaprzyjaźnione: operator wyjścia << i
operator wejścia >>.
#include <iostream.h>
// cin, cout, ostream, istream
class
osoba
{
private
:
char
nazwisko[
30
], imie[
20
], pesel[
12
];
public
:
osoba();
// operator wyjscia drukuje dane na konsoli
friend ostream& operator <<(ostream& out, osoba& o);
// operator wejscia pobiera dane z konsoli
friend istream& operator >>(istream& in, osoba& o);
};
osoba::osoba()
{
nazwisko[
0
] =
0
;
// pierwszy znak tablicy = 0 -> tekst pusty
imie[
0
] =
0
;
pesel[
0
] =
0
;
}
ostream& operator << (ostream &out, osoba& o)
// op.wyjscia
{
out << o.nazwisko <<
" "
;
out << o.imie <<
" "
;
out << o.pesel <<
" "
;
return
out;
}
istream& operator >> (istream &in, osoba& o)
// op.wyjscia
{
in >> o.nazwisko >> o.imie >> o.pesel;
return
in;
}
void
main()
{
osoba o;
// deklaracja obiektu osoba
cout <<
"\n podaj nazwisko, imie i pesel\n"
;
cin >> o;
// wprowadzenie danych do obiektu
cout << o;
// wyprowadzenie danych na ekran
}
Podstawy programowania obiektowego
Strona 5
Przykład 2. Definicja klasy Plik umożliwiającą wyprowadzenie zawartości pliku na ekran. Klasa zawiera
jedno pole prywatne plik typu FILE * (struktura opisująca strumień - plik dyskowy), funkcję
operatorową zaprzyjaźnioną definiującą operator wyjścia << .
#include <stdio.h>
// FILE, fopen, fclose, ...
#include <iostream.h>
// cout, cin, ostream
class
Plik
{
private
:
FILE* plik;
public
:
Plik(
char
*NazwaPliku);
// konstruktor otwiera plik dyskowy
~Plik();
// destruktor zamyka plik dyskowy
// operator wyjscia drukuje plik na konsoli
friend ostream& operator <<(ostream& out, Plik & pl);
};
Plik::Plik(
char
*NazwaPliku)
{
if
((plik = fopen(NazwaPliku,
"rt"
)) ==
NULL
)
{
fprintf(stderr,
"Nie moge otworzyc pliku!!!.\n"
);
}
}
Plik::~Plik()
{
if
(plik) fclose(plik);
}
ostream & operator << (ostream &out, Plik &pl)
// op.wyjscia
{
char
ch;
fseek(pl.plik,
0
, SEEK_SET);
// na poczatek pliku
do
{
ch = fgetc(pl.plik);
out << ch;
}
while
(ch != EOF);
return
out;
}
void
main()
{
Plik p(
"autoexec.bat"
);
cout << p;
}
Podstawy programowania obiektowego ćw nr 2
Strona 6
Przykład 3. Definicja klasy zespolona. Przykład ten jest rozszerzeniem przykładu z ćwiczenia nr 2
o szereg operatorów jedno i dwuargumentowych. Funkcje operatorowe zostały zdefiniowane jako
składowe klasy lub jako funkcje zaprzyjaźnione. Przykład do samodzielnej analizy.
#include <iostream.h>
// cin, cout, istream, ostream
#include <math.h>
// fabs, sqrt
#include <conio.h>
// clrscr, getch
typedef enum BOOL { FALSE =
0
, TRUE };
// deklaracja klasy (interfejs klasy)
class
zespolona
{
private
:
double
re, im;
public
:
zespolona() { re =
0
; im =
0
; }
zespolona(
double
r,
double
i =
0
): re(r), im(i) { }
void
ustaw(
double
r,
double
i) { re = r; im = i; }
// przeciazone operatory
zespolona operator * ();
zespolona operator + (zespolona z);
friend zespolona operator - (zespolona z1, zespolona z2);
zespolona& operator += (zespolona z);
friend ostream& operator << (ostream &os, zespolona z);
friend istream& operator >> (istream &is, zespolona &z);
friend BOOL operator == (zespolona z1, zespolona z2);
};
// definicja klasy (implementacja klasy), tzn. definicje funkcji
// skladowych klasy i funkcji zaprzyjaznionych z klasa
zespolona zespolona::operator * ()
{
return
zespolona(re, -im);
}
zespolona zespolona::operator + (zespolona z)
{
return
zespolona(re+z.re, im+z.im);
}
zespolona operator - (zespolona z1, zespolona z2)
{
return
zespolona(z1.re-z2.re, z1.im-z2.im);
}
zespolona& zespolona::operator += (zespolona z)
{
re += z.re; im += z.im;
return
*this;
}
ostream& operator << (ostream &os, zespolona z)
{
return
os <<
'('
<< z.re <<
", "
<< z.im <<
')'
;
}
istream& operator >> (istream &is, zespolona &z)
{
cout <<
"re = "
; is >> z.re;
cout <<
"im = "
; is >> z.im;
return
is;
}
BOOL operator == (zespolona z1, zespolona z2)
Podstawy programowania obiektowego
Strona 7
{
if
( fabs(z1.re-z2.re) <
1e-10
&& fabs(z1.im-z2.im) <
1e-10
)
return
TRUE;
else
return
FALSE;
}
int
main()
{
zespolona z1, z2, z3(
1
), z4(
2
,
3
);
zespolona z5 = z4;
// inicjalizacja
clrscr();
cout <<
"z1 = "
<< z1 <<
"\tz1"
<< endl;
// operator << (cout, z1);
cout <<
"z2 = "
<< z2 <<
"\tz2"
<< endl;
cout <<
"z3 = "
<< z3 <<
"\tz3(1)"
<< endl;
cout <<
"z4 = "
<< z4 <<
"\tz4(2, 3)"
<< endl;
cout <<
"z5 = "
<< z5 <<
"\tz5 = z4"
<< endl << endl;
cout <<
"Podaj z1:"
<< endl;
cin >> z1;
// operator >> (cin, z1);
z2.ustaw(
3
, -
4
);
z3 = *z1;
// z3 = z1.operator * ();
z4 = z1 + z2;
// z4 = z1.operator + (z2);
z5 = z1 - z2;
// z5 = operator - (z1, z2);
cout <<
"z1 = "
<< z1 <<
"\tz klawiatury"
<< endl;
cout <<
"z2 = "
<< z2 <<
"\tustaw(3, -4)"
<< endl;
cout <<
"z3 = "
<< z3 <<
"\tsprzezona do z1"
<< endl;
cout <<
"z4 = "
<< z4 <<
"\t= z1 + z2"
<< endl;
cout <<
"z5 = "
<< z5 <<
"\t= z1 - z2"
<< endl << endl;
z4 = z1 +
2
;
// nie mozna: z4 = 2 + z1;
cout <<
"z4 = "
<< z4 <<
"\t= z1 + 2"
<< endl;
z4 = z1 -
2
;
// mozna: z4 = 2 - z1;
cout <<
"z4 = "
<< z4 <<
"\t= z1 - 2"
<< endl;
z5 = z1 + z2 +
2
-
1
- *z1 + z1 + z2 -
1.5
;
cout <<
"z5 = "
<< z5;
cout <<
"\t= z1 + z2 + 2 - 1 - *z1 + z1 + z2 - 1.5"
<< endl;
z1 = z2;
// podstawienie
cout <<
"z1 = "
<< z1 <<
"\tz1 = z2"
<< endl;
z1 += z2;
// z1.operator += (z2);
cout <<
"z1 = "
<< z1 <<
"\tz1 += z2"
<< endl;
if
(z1 == z2)
// if ( operator == (z1, z2) )
cout <<
"z1 jest rowne z2"
<< endl;
else
cout <<
"z1 jest rozne od z2"
<< endl;
cout << endl <<
"Nacisnij dowolny klawisz..."
;
getch();
return
0
;
}
Podstawy programowania obiektowego ćw nr 2
Strona 8
Zadania do wykonania na zajęciach i w domu:
1. Utwórz klasę wektor - jednowymiarową tablicę wartości typu float. Klasa powinna zawierać:
•
pole prywatne p - wskaźnik na początek tablicy wartości typu float,
•
konstruktor z parametrem typu int - rozmiarem tablicy tworzący dynamicznie tablicę (new) o
odpowiednim rozmiarze,
•
destruktor zwalniający pamięć zarezerwowaną przez konstruktor,
•
funkcję operatorową >>, tak aby można było wprowadzać dane z klawiatury do wektora,
•
funkcję operatorową <<, tak aby można było wyprowadzać dane z wektora na ekran.
2. Utwórz klasę wektor - jednowymiarową tablicę wartości typu int. Klasa powinna zawierać:
•
pole prywatne tab - wskaźnik na początek tablicy wartości typy int,
•
konstruktor z parametrem typu int - rozmiarem tablicy tworzący dynamicznie tablicę (new) o
odpowiednim rozmiarze,
•
destruktor zwalniający pamięć zarezerwowaną przez konstruktor,
•
funkcję operatorową >>, tak aby można było wprowadzać dane z klawiatury do wektora,
•
funkcję operatorową <<, tak aby można było wyprowadzać dane z wektora na ekran.
3. Dla przykładu nr 2 (klasa Plik) rozszerzyć możliwości klasy o funkcje:
•
konstruktor przeciążony tworzący nowy plik dyskowy,
•
funkcje operatorową >>, tak aby można było wprowadzać dane z klawiatury do nowego pliku
dyskowego,
•
funkcję operatorową =, tak aby można było przypisać zawartość pliku,
•
funkcję operatorową +, tak aby można było dodawać zawartości dwóch plików. Wraz z
operatorem = otrzymamy możliwość wykonania działania:
void main()
{
Plik plik1("pl1.txt"), plik2("pl2.txt"), plik1("pl2.txt");
plik1 = plik2 + plik3;
}
4. Dla przykładu nr 2 (klasa Plik) rozszerzyć możliwości klasy o funkcje:
•
konstruktor przeciążony tworzący nowy plik dyskowy,
•
funkcje operatorowe <<, tak aby można było wykonać działanie:
void main()
{
float f = 2.34;
int i = 5;
char tekst[] = "Hallo - to tekst";
Plik plik1("pl1.txt");
plik1 << f;
// zapis do pliku wartości typu
float
plik1 << i; // za
pis do pliku wartości typu
int
plik1 << tekst; // zapis do pliku tekstu z tablicy
}
Uwaga!!! Konieczne jest zdefiniowanie trzech oddzielnych operatorów << dla każdego typu danych.
5. Utwórz klasę okrag - obiekt graficzny. Klasa powinna zawierać:
•
pola prywatne r - promień, x - wsp. x środka, y - wsp. y środka,
•
konstruktor z parametrami: promien, wsp. x i y środka,
•
metodę rysuj rysującą okrag w trybie graficznym systemu DOS,
•
funkcje operatorową ++ przesuwającą okrąg o 1 punkt w prawo,
•
funkcje operatorową -- przesuwającą okrąg o 1 punkt w lewo.
•
funkcje operatorową + dodającą dwa okręgi (działanie dodawania można przyjąć dowolne),
•
funkcje operatorową = umożliwiającą przypisanie,
•
funkcje operatorową == porównującą dwa okręgi.