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

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