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 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 = (1+n) + staradata; jeszczejednadata.Pokazuj(); return 0; } Operator + w obu sytuacjach działa poprawnie. Być może wpadłeś na pomysł, że operator - (minus) też mamy już z głowy. Niby tak, ale tylko w takim zakresie, w jakim nasza funkcja operatorowa poprawnie będzie obsługiwać ujemne liczby dni. Jeśli zechcesz podać ujemną liczbę dni (zmuszając funkcję do odejmowania zamiast dodawania), twój dialog z programem będzie wyglądał np. tak: C:\>program Stara data: 31.1.94 Podaj ile minelo dni --> -10 Jest zatem --> 21.1.94 Testuje nowy operator: 22.1.94 lub tak: C:\>program Stara data: 31.1.94 Podaj ile minelo dni --> -150 Jest zatem --> -119.1.94 Testuje nowy operator: -118.1.94 Funkcja operatorowa została napisana w taki sposób, że po przekroczeniu wartości -31 program będzie wypisywał bzdury. Jako zadanie domowe - spróbuj zmodyfikować algorytm w taki sposób, by rozszerzyć zakres poprawnych wartości. [!!!] Możesz dodawać obiekty minusem. ________________________________________________________________ * Należy tu zwrócić uwagę, że dodawanie obiektów może wykonywać nie tylko i nie koniecznie operator + . Jeśli zechcesz, możesz do tego celu zastosować dowolnie wybrany operator (np. -, * itp.). W celu ułatwienia zrozumienia zapisu (i tylko dlatego) większość programistów rozbudowuje działanie operatorów zgodnie z ich pierwotnym zastosowaniem. * DOWOLNOŚĆ, ALE NIE PEŁNA! O tyle, o ile działanie operatora może być zmienione, to ilość argumentów potrzebnych operatorowi pozostaje w C++ "sztywna" (patrz przykład z n!). ________________________________________________________________ W bardzo podobny sposób możesz rozbudowywać inne arytmetyczne operatory dwuargumentowe (*, /, -, itp.) w stosunku także do innych klas. OVERLOADING OPERATORÓW JEDNOARGUMENTOWYCH ++ I -- . Typowe operatory jednoargumentowe to ++ i --. Jako przykładem posłużymy się problemem zlicznia znaków pobieranych ze strumienia wejściowego. Zaczniemy od redefinicji postinkrementacji licznika. Musimy zastosować funkcję operatorową. Funkcja, chcąc operować na obiektach musi w stosunku do tych obiektów posiadać status friend, lub być metodą. Prototyp funkcji operatorowej potrzebnej do wykonania overloadingu operatora jednoargumentowego ++ wygląda w postaci ogólnej tak: typ_zwracany nazwa_klasy::operator++(lista argumentów); Funkcje operatorowe zwracają zwykle wartość zgodną co do typu z typem obiektów z którymi współpracują. Jeśli identyfikatory b, c i d reprezentują obiekty, nic nie stoi na przeszkodzie, by stał się możliwy zapis: class Klasa { ... } x, y, z; ... z = x + y; Dodajemy dwa obiekty x i y tego samego typu (tej samej klasy), a wynik przypisujemy obiektowi z, który także jest obiektem tego samego typu. Jeśli możnaby jeszcze zastosować operator przypisania tak: z = q = x + y; operator przypisania = zwracałby nam w efekcie obiekt tego samego typu. Funkcje operatorowe muszą przestrzegać tych samych zasad, które obowiązują wyrażenia: typ argumentów x, y, z, q, ... powinien być zgodny, rezultat operacji (x + y) powinien być obiektem tego samego typu, co obiekty x, y, z, q. Dokonując overloadingu operatorów powinniśmy precyzyjnie określić typ wartości zwracanej w wyniku działania operatora. Stosowaną poprzednio do inkrementacji liczników metodę Skok_licznika() zastąpimy w definicji klasy funkcją operatorową: class Licznik { public: char moja_litera; int ile; Licznik(char); Licznik operator++(); }; Powinniśmy teraz zdefiniować funkcję operatorową. Ponieważ pole obiektu, które zamierzamy inkrementować nazywa się: obiekt.ile // Licznik::ile; funkcja powinna zadziałać tak: Licznik Licznik::operator++(void) { this->ile++; return (*this); } Przetłumaczmy tę notację na "ludzki język". Funkcja operatorowa: * nie pobiera żadnych jawnych argumentów (void); * jest metodą, zatem w momencie wywołania otrzymuje w niejawny sposób wskaźnik *this do "własnego" obiektu; * posługując się wsakźnikiem this inkrementuje zawartość pola int ile własnego obiektu; * zwraca obiekt (zmodyfikowany) klasy Licznik (tj. dokładniej - zwraca wskaźnik this do własnego-zmodyfikowanego obiektu. Ponieważ funkcja operatorowa jest metodą zadeklarowaną wewnątrz klasy, bez problemu uzyska dostęp do wewnętrznych pól obiektów tej klasy i wykona inkrementację licznika. Możemy zatem zastosować wyrażenie typu: Licznik licznik_a; licznik_a++; Funkcja jest metodą wraz ze wszystkimi właściwymi metodom przywilejami. Zapis możemy zatem uprościć do postaci: Licznik Licznik::operator++(void) { ile++; return (*this); } a tak skrócone ciało funkcji umieścić w definicji klasy obok definicji konstruktora: class Licznik { public: char moja_litera; int ile; Licznik(char z) { ile = 0; moja_litera = z; } Licznik operator++() { ile++; return (this); } }; Aby nie zaciemniać obrazu, przy pomocy licznika będziemy tym razem zliczać wszystkie znaki za wyjątkiem kropki. Ponieważ licznik nie będzie miał swojej ulubionej litery, możemy zastosować pusty konstruktor. [P121.CPP] /* --------------------- POST - inkrementacja ----------- */ # include "iostream.h" class Licznik { public: int ile; Licznik() { ile = 0;} Licznik operator++() { ile++; return (*this); } } obiekt; void main() { cout << "\n Wpisz kilka znakow: "; char znak; for(;;) { cin >> znak; if(znak == '.') break; obiekt++; } cout << "\n Wpisales " << obiekt.ile << " znakow"; } Podobnie jak wcześniej, preinkrementacja i postinkrementacja wymagają odrębnego overloadingu. Dokładnie rzecz ujmując, zgodnie ze standardem ANSI C, odrębny overloading nie jest już niezbędny, wykorzystamy to jednak jako pretekst do wykonania go dwiema różnymi technikami. Ponieważ logika jest bardzo podobna, pomijamy tu (chyba już zbędny) komemtarz. Dla ułatwienia Ci porównania, zestawiliśmy obok siebie różne funkcje operatorowe napisane różnymi technikami (notacja wskaźnikowa i referencyjna). [P122.CPP] /* -------- PRE - inkrementacja ------------------------- */ # include "iostream.h" class Licznik { public: int ile; Licznik() { ile = 0;} Licznik operator+(int n = 1) { this->ile += n; return (*this); } Licznik friend operator++(Licznik& x) { x + 1; return (x); } } obiekt; void main() { cout << "\n Wpisz kilka znakow: "; char znak; for(;;) { cin >> znak; if(znak == '.') break; ++obiekt; } cout << "\n Wpisales " << obiekt.ile << " znakow"; cout << "\n I dodamy jeszcze sto! --> "; obiekt + 100; cout << obiekt.ile; } Poniżej inny przykład tego samego overloadingu odnośnie tej samej klasy Licznik (w trochę inny sposób). [P123.CPP] # include "conio.h" # include "iostream.h" class Licznik { public: char moja_litera; int ile; Licznik() { ile = 0; } //Pusty konstruktor Licznik(char); Licznik operator++(); //Funkcja pre/post-inkrementacji Licznik operator--(); //Funkcja pre/post-dekrementacji }; Licznik::Licznik(char z) { moja_litera = z; ile = 10; } Licznik Licznik::operator++(void) { ile++; return *this; } Licznik Licznik::operator--(void) { ile--; return *this; } void main() { Licznik obiekt1('A'), obiekt2; //obiekt2 - "pusty" cout << "\n Wpisz napis z max. 10 literami [A]: \n "; for(;;) { char litera = getch(); cout << litera; if(obiekt1.ile == 0) break; if(litera == obiekt1.moja_litera) obiekt1--; ++obiekt2; //Ten zlicza wszystkie znaki //metoda PRE - inkrementacji if(obiekt2.ile > 30) cout << "\n NIE PRZESADZAJ \n"; } cout << "\n Koniec: " << obiekt1.ile; cout << " liter " << obiekt1.moja_litera; cout << "\n Wszystkich znakow bylo: " << obiekt2.ile; } Overloading "siostrzanych" operatorów ++ i -- jest bliźniaczo podobny. OVERLOADING OPERATORA ! Z matematyki jesteśmy przyzwyczajenu do zapisu silni n! i wydawałoby się, że mając w C++ do dyspozycji operator ! nie powinniśmy mieć z tym zadaniem najmniejszego kłopotu. Operując znaną Ci klasą Liczba i wyposażając program w funkcję operatorową możemy załatwić ten problem np. tak: [P124.CPP] # include class Liczba { public: long wartosc; Liczba(int x) { wartosc = (long) x; } friend void operator!(Liczba&); }; void operator!(Liczba& obiekt) { long wynik = 1; for(int i = 1; i <= obiekt.wartosc; i++) { wynik *= i; } cout << '\n' << wynik; } int x; main() { for(int k = 0; k < 5; k++) { cout << "\n Podaj liczbe --> "; cin >> x; Liczba a(x); cout << "\n Silnia wynosi: "; !a; } return 0; } Program działa, wyniki kolejnych kilku silni są poprawne. Gdy jednak spróbujemy zastosować operator ! zgodnie z tradycyjnym matematycznym zapisem: a!; okaże się, że C++ zacznie mieć wątpliwości. Komunikaty o błędzie spowodują wątpliwości kompilatora, czy chodzi nam o operator "!=", w którym zapomnięliśmy znaku "=". Jeśli w funkcji operatorowej spróbujemy zmienić operator ! na != , a zapis w programie: z !a; na a!=a; C++ zarząda dwuargumentowej funkcji operatorowej (bo taki operator jest tradycyjnie dwuargumentowy). Możemy oczywiście próbować oszukać C++ przy pomocy argumentu pozornego. Jeśli podamy w funkcji operatorowej dwa argumenty void operator!=(Liczba& obiekt1, Liczba& obiekt2) { long wynik = 1; for(int i = 1; i <= obiekt.wartosc; i++) { wynik *= i; } cout << '\n' << wynik; } program uda się skompilować i kod wynikowy będzie działał poprawnie, C++ zaprotestuje jedynie przy pomocy ostrzeżenia Warning: obiekt2 is never used... Chcąc uniknąć ostrzeżeń należy użyć argument pozorny w dowolny sposób. Zwracamy na to uwagę, ponieważ C++ jest pedantem i: [!!!] DZIAŁANIE OPERATORÓW MOŻE BYĆ DALECE DOWOLNE, ALE LICZBA ARGUMENTÓW MUSI POZOSTAĆ ZGODNA Z "TRADYCJAMI" C++. Stosowanie podczas overloadingu operatorów argumentów pozornych jest techniką często stosowaną przez programistów. Aby wykazać, że korzystanie z gotowych "fabrycznych" zasobów ułatwia życie programiście czasami w zaskakująco skuteczny sposób, przytoczę przykładowy program, który posługując się "fabryczną" klasą ofstream (obiekty - strumień danych do pliku wyjściowego - Output File STREAM): * zakłada w bieżącym katalogu plik dyskowy DANE.TST; * otwiera plik dla zapisu; * zapisuje do pliku tekst "to jest zawartosc pliku"; * zamyka plik; [P125.CPP] # include "fstream.h" void main() { ofstream plik("dane.tst"); plik << "To jest zawartosc pliku"; } I już. O wszystkie szczegóły techniczne tych (wcale przecież nie najprostszych) operacji zadbał producent w bibliotekach klas Wejścia/Wyjścia. Jeśli zechcemy do pliku dopisać coś jeszcze, wystarczy dodać: [P126.CPP] # include "fstream.h" void main() { ofstream plik("dane.tst"); plik << "To jest zawartosc pliku" << " i jeszcze cosik."; } Urzekająca prostota, nieprawdaż? I to wszystko załatwia poddany overloadingowi operator << . Niedowiarek mógłby w tym momencie zapytać "a jeśli plik już istnieje, to chyba nie jest takie proste?". Rzeczywiście, należałoby tu rozbudować program w C++ do postaci: # include "fstream.h" void main() { ofstream plik("dane.tst", ios::app); plik << " Dopiszemy do pliku jeszcze i to..."; } Korzystamy tu dodatkowo z globalnej zmiennej ios::app (ang. append - dołącz) określającej inny niż typowy tryb dostępu do pliku dyskowego i w dalszym ciągu z operatora << . Tworzenie obiektu - pliku dyskowego jest takie proste, dzięki istnieniu konstruktora, który jest tu automatycznie wywoływany po deklaracji: ofstream plik( ... ); [Z] ________________________________________________________________ 1. Wykonaj samodzielnie overloading dowolnego operatora. ________________________________________________________________