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
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
Typ zwracany
symbol
przez operator
operatora
zespolona operator + (zespolona z);
Słowo kluczowe
Prawy argument
"operator"
operatora
(typ identyfikator)
! Definicja metody
zespolona zespolona::operator + (zespolona z)
{
zespolona z1(re+z.re, im+z.im);
return z1;
}
zasady stosowania:
Strona 2
Podstawy programowania obiektowego
− 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
prawy argument
Typ zwracany
symbol
operatora
przez operator
operatora
(typ identyfikator)
friend ostream& operator << (ostream& o, zespolona z); Słowo kluczowe
Słowo kluczowe
lewy argument
"friend" - metoda
"operator"
operatora
zaprzyjaźniona
(typ identyfikator)
! 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.
Strona 3
Podstawy programowania obiektowego ćw nr 2
− 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
}
Strona 4
Podstawy programowania obiektowego
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;
}
Strona 5
Podstawy programowania obiektowego ćw nr 2
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)
Strona 6
Podstawy programowania obiektowego
{
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;
}
Strona 7
Podstawy programowania obiektowego ćw nr 2
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; // zapis 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.
Strona 8