Kategorie materiałów Informatyka

Przedmiot: C++ Dla Zaawansowanych Wróć do kategorii

LEKCJA 34. Overloading operatorów

plik Pobierz LEKCJA 34. Overloading operatorow.txt

LEKCJA 34 OVERLOADING OPERATORÓW.
________________________________________________________________
Podczas tej lekcji poznasz możliwości dostosowania operatorów 
C++ do własnego "widzimisię" i do potrzeb własnych obiektów. 
________________________________________________________________
 
Niemal od początku niniejszej książki korzystamy z operatorów 
poddanych overloadingowi. Są to operatory << i >> , które 
pierwotnie wykonywały bitowe przesunięcie w lewo i w prawo. 
Owerloading tych operatorów "załatwił" za nas producent 
(Borland, Microsoft, czy inny). Jak widzisz, nie powoduje to w 
dalszym użytkowaniu tych operatorów żadnych zauważalnych 
komplikacji, a często ułatwia tworzenie programów. Zwróć uwagę, 
że overloading operatorów (jak i definicje klas) może znajdować 
się w dołączonych plikach nagłówkowych i po jednorazowym 
wykonaniu może być "niewidoczny" dla programistów tworzących 
programy aplikacyjne.
 
Jeśli projektujemy (definiujemy) nową klasę, dodajemy do C++ 
nowy, lecz pełnoprawny typ danych. Autorzy C++ nie byli w stanie
 
przewidzieć jakie klasy i jakie obiekty mogą wymyślić kolejne 
pokolenia programistów w ramach swojej radosnej twórczości. 
Wprowadzili zatem do C++ jasne i jednoznaczne algorytmy 
postępowania z typami "typowymi". C++ doskonale wie jak dodawać,
 
mnożyć, czy odejmować np. liczby int, long, float itp., nie wie 
jednak jak dodać do siebie obiekty klas CString (CString = Class
 
String = klasa "łańcuch znaków"), TOdcinek (to taki kawałek 
prostej) itp.. A przecież miło byłoby, gdyby rozbudować 
 działanie operatorów tak, by było możliwe ich typowe 
zastosowanie w stosunku do naszych własnych, "nietypowych" 
obiektów: 
 
int x, y;     int z = x + y;      //To operator + załatwia sam
float x, y;   float z = x + y; 
 
Zanim jednak stanie się możliwe postępowanie takie:
 
class CString x, y, z;     z = x + y; 
 
class Nasza_Klasa obiekt1, obiekt2, obiekt3;   
obiekt3 = obiekt1 + obiekt2; 
 
itp., itd. ...
 
musimy "uzupełnić" C++ i "wyjaśnić" operatorom, co właściwie ma 
w praktyce oznaczać operacja   obiekt1 = obiekt2 + obiekt3; . 
Jest wyczuwalne intuicyjnie, że działanie operatorów w stosunku 
do różnych obiektów może być różne. Dla przykładu - wiesz 
zapewne, że inaczej wygląda algorytm mnożenia liczb zespolonych,
 
a inaczej liczb całkowitych rzeczywistych. Dlatego też wykonanie
 
operacji mnożenia wymaga od operatora * podjęcia różnych 
działań: 
 
class Liczba_zespolona x, y, z;         z = x * y; 
 
int x, y, z;                            z = x * y; 
 
Czasem może się zdarzyć, że dla dwu różnych klas działanie 
jakiegoś operatora jest identyczne, częściej jednak (i tak 
należy się spodziewać) działanie operatora dla każdej klasy 
będzie odrębne i unikalne. 
 
Pójdźmy w tym rozumowaniu o krok dalej. Skoro rozszerzenie 
obszaru zastosowań jakiegoś operatora na obiekty nowej 
(nieznanej wcześniej klasy) wymaga zdefiniowania nowego 
algorytmu działania operatora, C++ będzie potrzebował do tego 
celu specjalnych środków, które powinny być łatwo rozpoznawalne.
 
Do opisu algorytmów służą generalnie w C++ funkcje i tu Autorzy 
nie wprowadzili wyjątku. Zastrzegli jednak dla tych specjalnych 
funkcji specjalną nazwę:              operator ...(); 
 
I tak funkcja precyzująca nowy algorytm dodawania (nowy sposób 
działania operatora + ) będzie się nazywać: 
 
operator+(); 
 
a np. funkcja określająca nowy algorytm mnożenia (nowy sposób 
działania operatora * ) będzie się nazywać: 
 
operator*(); 
 
Spróbujmy zastosować taką filozofię w praktyce programowania. 
 
[!!!] NIESTETY NIE WSZYSTKIE OPERATORY MOŻNA ROZBUDOWAĆ.
________________________________________________________________
Są w C++ operatory, których nie możemy poddać overloadingowi. Są
 
to: 
 
.   ::   .*   ?: 
 
.   operator kropki umożliwia dostęp do pól struktur i obiektów;
 
::  operator "widoczności-przesłaniania" (ang. scope); 
.*  wskazanie członka klasy (ang. pointer-to-member); 
?:  operator warunkowy. 
________________________________________________________________
 
Wszystkie pozostałe operatory możemy poddać overloadingowi i 
przypisywać im potrzebne nam działanie.
 
OVERLOADING OPERATORA [+] (DWUARGUMENTOWEGO).
 
Zaczniemy od operatora + należącego do grupy "dwuargumentowych 
operatorów arytmetycznych" (ang. binary arithmetic operator). 
Zwracamy tu już na początku rozważań uwagę na przynależność 
operatora do określonej grupy, ponieważ overloading różnych 
opertorów należących do tej samej grupy przebiega podobnie. 
Ponieważ znak + może być także operatorem jednoargumentowym 
(ang. unary plus, o czym za chwilę), podkreślamy, że tym razem 
chodzi o plus jako operator dodawania. Overloading operatora 
przeprowadzimy w stosunku do obiektów prostej, znanej Ci już z 
poprzednich przykładów klasy Data, którą (w celu upodobnienia 
się do maniery stosowanej w Windows i bibliotekach klas) 
nazwiemy tym razem CData. "Namówimy" operator + do 
przeprowadzenia operacji na obiektach (dokładniej na polach 
obiektów): 
 
CData nowadata = staradata + 7;       // W tydzien pozniej 
 
Operator + musi oczywiście "wiedzieć", na którym polu obiekty 
klasy CData przechowują liczbę dni i jak związane są (logicznie)
 
pola obiektu dz, mc, rok. Jest rzeczą zrozumiałą, że samo 
dodanie dni do pola dz może nie wystarczyć, ponieważ data 
37.11.93 jest niedopuszczalna. 
 
Jeśli staradata jest obiektem klasy CData z zawartymi wewnątrz 
danymi, to w wyniku działania "nowego" operatora + powinien 
powstać obiekt nowadata klasy CData, którego pola zostaną w 
sensowny sposób powiększone o dodaną liczbę dni. Rozważ 
działanie programu (najlepiej skompiluj i uruchom). 
 
[P120.CPP] 
 
/*  Overloading operatora dwuargumentowego +     */ 
 
# include <iostream.h> 
 
class CData 

  int dz, mc, rok; 
public: 
  CData() {}         //Konstruktor domyslny (pusty)
  CData(int d, int m, int y) { mc = m; dz = d; rok = y; } 
  void Pokazuj() { cout << dz << '.' << mc << '.' << rok; } 
  CData operator+(int);       //TU! overloading operatora + 
}; 
 
static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31}; 
 
/* Definicja funkcji operatorowej: ------------------------ */
 
CData CData::operator+(int n) 

  CData kopia_obiektu = *this; 
  n += kopia_obiektu.dz; 
  while (n > TAB[kopia_obiektu.mc-1])
    { 
      n -= TAB[kopia_obiektu.mc-1]; 
      if (++kopia_obiektu.mc == 13)
         { kopia_obiektu.mc = 1; kopia_obiektu.rok++; } 
    } 
  kopia_obiektu.dz = n; 
  return (kopia_obiektu); 

 
main() 

  CData staradata(31, 1, 94);    //Kostruktor z argumentami
  CData nowadata;                //Pusty konstruktor
  cout << "\n Stara data: "; 
  staradata.Pokazuj(); 
  cout << "\n Podaj ile minelo dni --> "; 
  int n; 
  cin >> n;
  nowadata = staradata + n;
  cout << "\n Jest zatem -->  ";
  nowadata.Pokazuj(); 
  return 0;

 
Do tej pory do danych prywatnych obiektu mogliśmy sięgnąć 
wyłącznie przy pomocy zdefiniowanej wewnątrz klasy 
funkcji-metody. Metodą umożliwiającą nam dostęp do prywatnych 
danych obiektu jest tu zadeklarowana wewnątrz klasy (a więc 
mająca "status prawny" metody) funkcja operatorowa. Przyjrzyjmy 
się tej funkcji dokładniej: 
 
CData CData::operator+(int n) 

  CData kopia_obiektu = *this;
 ... 
  return (kopia_obiektu); 
}
 
Funkcja  
* została zdefiniowana dla obiektów klasy CData (z innymi 
postępować nie potrafi); 
Jeśli operator + zostanie umieszczony pomiędzy obiektem klasy 
CData, a liczbą typu int: 
                             .... staradata + n; 
* funkcja pobiera liczbę n jako argument (jawnie); 
* funkcja pobiera obiekt klasy CData jako swój drugi argument 
(niejawnie, dzięki pointerowi this);
* funkcja zwróci obiekt klasy CData (ze zmodyfikowanym polem); 
 
Nowy obiekt zwrócony przez funkcję zostanie przypisany 
 
nowadata = ... ;      // <-- return(kopia_obiektu); 
 
W prawym polu operatora (operator jest dwuargumentowy, ma więc 
swoje lewe i prawe pole) może pojawić także stała. Operacja: 
 
nowadata = staradata + 14; 
 
zostanie wykonana poprawnie. 
 
Ale to nie wszystko. Jeśli wystąpi układ odwrotny - np.: 
 
nowadata = 14 + staradata; 
 
nasz operator "zgłupieje". Doszedłszy do operatora + C++ "nie 
będzie jeszcze wiedział" (analizuje wyrażenia arytmetyczne od 
lewej do prawej), KTÓRY obiekt wystąpi za chwilę. Jedno jest 
pewne, nie zawsze musi być to "własny" obiekt funkcji, do 
którego mamy pointer this. Aby uzyskać jednoznaczność sytuacji, 
funkcja operatorowa powinna tu w jawny sposób pobierać przed 
zadziałaniem dwa argumenty: 
 
CData operator+(int n, CData obiekt); 
 
aby działanie: 
 
CData obiekt_wynik;     obiekt_wynik = n + obiekt; 
 
stało się wykonalne. Pojawia się tu wszakże pewien problem. 
Wskaźnik this wskazuje własny obiekt funkcji-metody, a tym razem
 
funkcja potrzebuje dostępu nie do pola własnego obiektu, lecz do
 
pola "obcego" obiektu przekazanego jej jako argument. Ale w C++ 
możemy: 
 
* zdefiniować dwie (i więcej) funkcji o tej samej nazwie (każda 
na inną ewentualność); 
* możemy nadać funkcji status friend (wtedy nie będąc metodą też
 
uzyska dostęp do danych obiektu). 
 
Definicja naszej klasy CData zawierająca deklaracje dwu funkcji 
operatorowych operator+() różniących się zastosowaniem i (po 
czym rozpozna je C++) liczbą argumentów, będzie wyglądać tak: 
 
class CData 

  int dz, mc, rok; 
public: 
  CData() {} 
  CData(int d, int m, int y) { mc = m; dz = d; rok = y; } 
  void Pokazuj() { cout << dz << '.' << mc << '.' << rok; } 
/* Dwie funkcje operatorowe: ------------------------------ */
  CData operator+(int); 
  friend CData operator+(int, CData&);
}; 
 
Zastosowaliśmy zamiast kopii obiektu bezpośrednio przekazywanej 
funkcji - referencję do obiektu klasy CData - CData&. Klasa 
zawiera: 
* prywatne dane; 
* dwa konstruktory; 
* własną metodę - funkcję operatorową operator+(); 
* deklarację zaprzyjaźnionej z klasą funkcji kategorii friend 
(choć jest to funkcja o tej samej nazwie, jej status i 
uprawnienia są nieco inne). 
 
[!!!] NIE WSZYSTKO, CO WEWNĄTRZ JEST METODĄ. 
________________________________________________________________
Nawet, jeśli wewnątrz definicji klasy zdefiniujemy w pełni 
funkcję (nadając jej status inline), nie stanie się ona metodą! 
Słowo kluczowe friend określa status funkcji jednoznacznie, bez 
względu na to, w którym miejscu w tekście programu umieścimy 
definicję ciała funkcji. 
________________________________________________________________
 
 
W zasadzie ciało funkcji jest na tyle proste (wymagamy od niej 
tylko zwrotu obiektu ze zmodyfikowanym polem danych), że możemy 
skorzystać z rozbudowanego wcześniej operatora + i całe ciało 
zdefiniować tak: 
 
class CData 

  int dz, mc, rok; 
public: 
 ...
  CData operator+(int); 
  friend CData operator+(int n, CData& x) { return (x + n); }
}; 
 
Jeśli w operacji dodawania argumenty zastosujemy we 
wcześniejszej kolejności: 
 
  return (obiekt + liczba); 
 
to zostanie tu wykorzystany operator + rozbudowany poprzednio 
przez metodę  CData::operator+(int). Program w całości może 
zatem wyglądać tak: 
 
[P121.CPP] 
 
# include "iostream.h" 
 
class CData 

  int dz, mc, rok; 
public: 
  CData() {} 
  CData(int d, int m, int y) { mc = m; dz = d; rok = y; } 
  void Pokazuj() { cout << dz << '.' << mc << '.' << rok; } 
  CData operator+(int); 
  friend CData operator+(int n, CData& x) { return (x + n); }
}; 
 
static int TAB[] = {31,28,31,30,31,30,31,31,30,31,30,31}; 
 
CData CData::operator+(int n) 

  CData kopia_obiektu = *this; 
  n += kopia_obiektu.dz; 
  while (n > TAB[kopia_obiektu.mc-1])
    { 
      n -= TAB[kopia_obiektu.mc-1]; 
      if (++kopia_obiektu.mc == 13)
         { kopia_obiektu.mc = 1; kopia_obiektu.rok++; } 
    } 
  kopia_obiektu.dz = n; 
  return (kopia_obiektu); 

 
main() 

  CData staradata(31, 1, 94);    //Kostruktor z argumentami
  CData nowadata, jeszczejednadata; 
  cout << "\n Stara data: "; 
  staradata.Pokazuj(); 
  cout << "\n Podaj ile minelo dni --> "; 
  int n; 
  cin >> n;
  nowadata = staradata + n;
  cout << "\n Jest zatem -->  ";
  nowadata.Pokazuj(); 
  cout << "\n Testuje nowy operator:  ";
  jeszczejednadata ...

Ciąg dalszy w pliku do pobrania.

Wkuwanko.pl jako podmiot świadczący usługę hostingu materiałów edukacyjnych nie ponosi odpowiedzialności za ich zawartość.

Aby zgłosić naruszenie prawa autorskiego napisz do nas.

ikona Pobierz ten dokument

Wróć do kategorii

wkuwanko.pl

Wasze komentarze: dodaj komentarz

  • Nie ma jeszcze komentarzy do tego materiału.

Materiały w kategorii C++ Dla Zaawansowanych [22]

  • podgląd pobierz opis LEKCJA 25. Przykład obiektu
  • podgląd pobierz opis LEKCJA 27. Dziedziczenie
  • podgląd pobierz opis LEKCJA 28. Dziedziczenie złożone
  • podgląd pobierz opis LEKCJA 29. Funkcje i overloading
  • podgląd pobierz opis LEKCJA 30. Wymiana danych między obiektami
  • podgląd pobierz opis LEKCJA 31. Przekazanie obiektów jako argumentów do funkcji
  • podgląd pobierz opis LEKCJA 33. Wskaźniki do obiektów
  • podgląd pobierz opis LEKCJA 34. Overloading operatorów
  • podgląd pobierz opis LEKCJA 35. O zastosowaniu dziedziczenia
  • podgląd pobierz opis LEKCJA 36. Funkcje wirtualne i klasy abstrakcyjne
  • podgląd pobierz opis LEKCJA 37. Jak komputer dysponuje swoimi zasobami w środowisku tekstowym (DOS)
  • podgląd pobierz opis LEKCJA 38. Programowanie dla Windows
  • podgląd pobierz opis LEKCJA 39. Wykorzystanie standardowych zasobów Windows
  • podgląd pobierz opis LEKCJA 40. Struktura programu proceduralno-zdarzeniowego
  • podgląd pobierz opis LEKCJA 41. Jak tworzy się aplikację dla Windows
  • podgląd pobierz opis LEKCJA 42. Kompilaroty specjalnie dla Windows
  • podgląd pobierz opis LEKCJA 43. Elementy sterujące i zarządzanie programem
  • podgląd pobierz opis LEKCJA 44. Okna dialogowe
  • podgląd pobierz opis LEKCJA 45. Dołączanie zasobów - menu i okienka dialogowe
  • podgląd pobierz opis LEKCJA 46. Programy obiektowo-zdarzeniowe
[ Misja ] [ Regulamin ] [ Kontakt ] [ Reklama ]   © wkuwanko.pl 2008-2019 właściciel serwisu SZLIFF

Partnerzy: matzoo.pl matmag.pl batmat.pl onlinefm.pl pisupisu.pl Matematyka radio online