1 Klasa listy jednokierunkowej Przekazywanie parametrów do funkcji Dzisiejszy wykład Wskaźniki Klasa listy jednokierunkowej Przekazywanie parametrów do funkcji Płytkie i głębokie kopiowanie Konstruktor kopiujący Operator przypisania Przeciążenie operatorów
2 Cztery atrybuty zmiennejWskaźniki Cztery atrybuty zmiennej nazwa typ wartość lokacja (adres) Wskaźnik jest typem wartości przechowywany w zmiennej jest to pewna liczba Operator * oznacza: pobierz wartość przechowywaną w zmiennej i użyj jej jako adresu innej zmiennej Operator & oznacza: pobierz adres zmiennej (NIE wartość zmiennej) int x = 5;
3 Zmienna W programie W pamięciWskaźniki Zmienna nazwa, typ, wartość, lokacja (adres) W programie W pamięci Która zmienna pod jakim adresem? Ile pamięci zajmuje? Kto o tym decyduje? lokacja 001000 int x = 5; 5 wartość typ nazwa
4 Wskaźniki Jaka jest wartość poniższych wyrażeń? Czy są one poprawne?x &x *x W programie W pamięci lokacja 001000 int x = 5; 5 wartość typ nazwa
5 Wskaźniki Jaka jest wartość poniższych wyrażeń? Czy są one poprawne?x, &x, *x p, &p, *p q, &q, *q ip, &ip, *ip W programie W pamięci lokacja wartość nazwa int x=5; char *p="hello"; char *q; int *ip; ip=&x; int x char*p ? char*q ? int* ip hello\0
6 Wskaźniki do funkcji int a,b,*c; c=f1(&a,&b); c=fp1(&a,&b);int* f1(int*, const int*); int* (*fp1)(int*, const int*); int* (*f2(int))(int*, const int*); int* (*(*fp2)(int))(int*, const int*); fp1=f1; fp1=&f1; fp1=&fp1; /* zle */ fp2=f2; fp2=&f2; int a,b,*c; c=f1(&a,&b); c=fp1(&a,&b); c=(*fp1)(&a,&b); c=*fp1(&a,&b); /* zle */ c=(f2(3))(&a,&b); c=(*f2(3))(&a,&b); c=(fp2(3))(&a,&b); c=(*fp2(3))(&a,&b); c=(*(*fp2)(3))(&a,&b);
7 Lista jednokierunkowa z dowiązaniamiKlasa List Lista jednokierunkowa z dowiązaniami next=0x1234 val=1 next=0x1334 val=2 next=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head;
8 Klasa List - konstruktor i destruktornext=0x1234 val=1 next=0x1334 val=2 next=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } list::list() { head = NULL; } list::~list() { while(head) node* t=head->next; delete head; head=t; }; }
9 Klasa List - destruktornext=0x1334 val=2 next=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } list::list() { head = NULL; } list::~list() { while(head) node* t=head->next; delete head; head=t; }; }
10 Klasa List - destruktornext=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } list::list() { head = NULL; } list::~list() { while(head) node* t=head->next; delete head; head=t; }; }
11 Klasa List - destruktorhead=NULL class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } list::list() { head = NULL; } list::~list() { while(head) node* t=head->next; delete head; head=t; }; }
12 Klasa List - insert l head next=0x1234 val=1 next=0x1334 val=2next=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } next=0x1134 val=1 void list::insert(int a) { node* nowy=new node; nowy->next=head; head = nowy; head->val = a; }
13 Klasa List - insert l head next=0x1234 val=1 next=0x1334 val=2next=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } next=0x1134 val=1 void list::insert(int a) { node* nowy=new node; nowy->next=head; head = nowy; head->val = a; }
14 Klasa List - insert l head next=0x1234 val=1 next=0x1334 val=2next=NULL val=3 l head class list { private: struct node node *next; int val; }; node * head; public: list (); ~list (); void insert (int a); } next=0x1134 val=1 void list::insert(int a) { node* nowy=new node; nowy->next=head; head = nowy; head->val = a; }
15 Klasa List - iterator next=0x1234 val=1 next=0x1334 val=2 next=NULLhead current class list { private: ... node * head; node *current; public: void goToHead (); int getCurrentData (); void advance (); bool moreData (); }; #include
16 Przekazywanie argumentów do funkcjiPrzekazywanie przez wartość parametry formalne są kopią parametrów aktualnych Przekazywanie przez referencję parametry formalne są referencją do parametrów aktualnych, tj. wszystkie operacje na parametrach formalnych odnoszą się do parametrów aktualnych C i C++ domyślnie przekazują parametry przez wartość void d1(int x) { x = 10; } void d2(int *p) { (*p) = 10;} void d3(int *p) { p = new int(4);} int main() { int y = 2; d1(y); cout << y; d2(&y); cout << y; d3(&y); cout << y; }
17 Przekazywanie argumentów do funkcjiPrzez wartość parametry formalne są kopią parametrów aktualnych Przez referencję parametry formalne są referencją do parametrów aktualnych, tj. wszystkie operacje na parametrach formalnych odnoszą się do parametrów aktualnych Przez stałą referencję do funkcji przekazywana jest referencja do parametru w celu uniknięcia kosztów kopiowania, ale wartość nie może być w funkcji modyfikowana (co jest sprawdzane przez kompilator) void f1(int x) { x = x + 1; } void f2(int& x) { x = x + 1; } void f3(const int& x) { x = x + 1; } void f4(int *x) { *x = *x + 1; } int main() { int y = 5; f1(y); f2(y); f3(y); f4(&y); } Która metoda w którym przykładzie? Ile wynosi y po każdym wywołaniu? Co jest przekazywane do f4? Czy to wartość, czy referencja? Czy można przekazać wskaźnik przez referencję?
18 Przekazywanie argumentów do funkcjiPrzekazywanie obiektów do funkcji nie różni się koncepcyjnie od przekazywania typów prostych klasy umożliwiają modyfikację zachowania w tym przypadku Trzy metody: przez wartość, przez referencję, przez stałą referencję Przez wartość parametr formalny jest kopią parametru aktualnego. Użyty konstruktor kopiujący. Przez referencję parametry formalne są referencją do parametrów aktualnych, tj. wszystkie operacje na parametrach formalnych odnoszą się do parametrów aktualnych Przez stałą referencję do funkcji przekazywana jest stała referencja do argumentu. Tylko metody ze specyfikatorem const mogą być wywoływane wewnątrz metody na rzecz argumentu.
19 Przekazywanie obiektów jako parametrówKiedy obiekt jest użyty jako parametr wywołania funkcji, rozróżnienie między kopiowaniem płytkim a głębokim może spowodować tajemnicze problemy void PrintList (list & toPrint, ostream & Out) { int nextValue; Out << "Printing list contents: " << endl; toPrint.goToHead (); if (!toPrint.moreData ()) Out << "List is empty" << endl; return; } while (toPrint.moreData ()) nextValue = toPrint.getCurrentData (); Out << nextValue << " "; toPrint.advance (); Out << endl; Obiekt toPrint jest przekazywany przez referencję, gdyż może być duży i kopiowanie byłoby nieefektywne Czy można użyć stałej referencji? Co by się stało, gdybyśmy przekazali toPrint przez wartość?
20 Przekazywanie obiektów jako parametrówW poprzednim przykładzie obiekt nie może być przekazany przez stałą referencję, gdyż wywołana funkcja zmienia obiekt (wskaźnik current) Z tego powodu możemy chcieć wyeliminować możliwość przypadkowej modyfikacji listy i przekazywać ją przez wartość Będzie to rozwiązanie nieefektywne Może spowodować problemy, jeżeli brak konstruktora kopiującego
21 Przekazywanie obiektów przez wartośćvoid PrintList (list toPrint, ostream & Out) { // identyczna implementacja } int main() list BigLst; // initialize BigList with some data nodes PrintList(BigList, cout); BigList next=0x1234 val=1 next=0x1334 val=2 next=NULL val=3 l head toPrint BigList i toPrint są tym samym obiektem Funkcja PrintList dalej może modyfikować BigList Konsekwencje są jeszcze poważniejsze l head
22 Przekazywanie obiektów przez wartośćPo zakończeniu PrintList czas życia zmiennej toPrint kończy się i wywoływany jest jej destruktor Jest to ta sama lista, która jest zawarta w BigList. Po powrocie do funkcji main(), BigList została zniszczona, ale BigList.head wskazuje na zwolnioną pamięć BigList l head next=0x1234 val=1 next=0x1334 val=2 next=NULL val=3 toPrint Dealokacja toPrint powoduje dealokację listy węzłów, na którą wskazuje toPrint.head l head
23 Przypisanie obiektów Obiekty posiadają domyślny operator przypisania (identyczny jak struktury) Domyślny operator przypisania kopiuje pole po polu wartości z obiektu źródłowego do obiektu docelowego W wielu przypadkach jest to zadowalające. Niestety, jeżeli obiekt zawiera wskaźnik do dynamicznie zaalokowanej pamięci, rezultat zwykle nas nie zadowoli. class DateType { public: // constructor DateType(); DateType(int newMonth, int newDay, int newYear); ... }; . . . DateType A(1, 22, 2002); DateType B; B = A; // copies the data members of A into B
24 Problem z przypisaniem wskaźnikówclass Wrong { private: int *table; // some data here public: // constructor Wrong() {table = new int[1000]; } ~Wrong() { delete [] table; } }; . . . Wrong A; Wrong B; B = A; // copies the data members of A into B Jaki typ danych przechowuje Wrong? Czy int * table to to samo co int table[] ? Co się stanie przy kopiowaniu? Jakie napotkamy problemy? Jak temu zapobiec? A B table ?
25 Kopiowanie płytkie i głębokieWyróżniamy dwa typy kopiowania obiektów zawierających pola będące wskaźnikami Kopiowanie płytkie Kopiowanie wszystkich składowych (w tym wskaźników) Kopiowane są wskaźniki, a nie to, na co wskazują Kopiowanie głębokie Alokacja nowej pamięci dla wskaźników Kopiowanie zawartości wskazywanej przez wskaźniki w nowe miejsce Kopiowanie pozostałych pól, nie będących wskaźnikami A B table A B table table
26 Problemy z płytkim kopiowaniemlist myList; myList.insert (3); myList.insert (2); myList.insert (1); myList next=0x1234 val=1 next=0x1334 val=2 next=NULL val=3 l head
27 Problemy z płytkim kopiowaniemlist myList; myList.insert (3); myList.insert (2); myList.insert (1); list anotherList; anotherList=myList; myList next=0x1234 val=1 next=0x1334 val=2 next=NULL val=3 l head anotherList l head
28 Głębokie kopiowanie Kiedy obiekt zawiera wskaźnik do dynamicznie zaalokowanego obszaru, należy zdefiniować operator przypisania wykonujący głębokie kopiowanie W rozważanej klasie należy zdefiniować operator przypisania: AType& AType::operator=(const AType& otherObj) Operator przypisania powinien uwzględnić przypadki szczególne: Sprawdzić przypisanie obiektu do samego siebie, np. A=A: if (this == &otherObj) // if true, do nothing Skasować zawartośc obiektu docelowego delete this->... Zaalokować pamięć dla kopiowanych wartości Przepisać kopiowane wartości Zwrócic *this
29 Konstruktor kopiujący i operator przypisaniaKonstruktor kopiujący jest używany do stworzenia nowego obiektu Jest prostszy od operatora przypisania - nie musi sprawdzać przypisania do samego siebie i zwalniać poprzedniej zawartości Jest użyty do skopiowania parametru aktualnego do parametru formalnego przy przekazywaniu parametru przez wartość Przy tworzeniu nowego obiektu, można go zainicjalizować istniejącym obiektem danego typu. Wywołany jest wówczas konstruktor kopiujący. int main() { list a; //... list b(a); //copy constructor called list c=a; //copy constructor called };
30 Obiekty anonimowe Obiekt anonimowy to obiekt bez nazwy UżytecznyTworzony jest obiekt, ale nie ma nazwanej zmiennej, która go przechowuje Użyteczny do użytku tymczasowego (parametr przy wywołaniu funkcji, zwracaniu wartości, fragment wyrażenia) jako domyślna wartość parametru będącego obiektem Rozważmy metodę pobierającą obiekt typu Address: Argument może zostać przekazany w sposób następujący: Zamiast: void Person::setAddress(Address addr); Person joe; joe.setAddress(Address("Disk Drive"...)); Person joe; Address joeAddress("Disk Drive"...); joe.setAddress(joeAddress);
31 Przykład: obiekty anonimowe jako parametryBez obiektów anonimowych mamy nieporządek Użycie obiektów anonimowych zmniejsza zanieczyszczenie lokalnej przestrzeni nazw Name JBHName("Joe", "Bob", "Hokie"); Address JBHAddr("Oak Bridge Apts", "#13", "Blacksburg","Virginia", "24060"); Person JBH(JBHName, JBHAddr, MALE); . . . Person JBH(Name("Joe", "Bob", "Hokie"), Address("Oak Bridge Apts", "#13", "Blacksburg", "Virginia", "24060"),MALE); . . .
32 Przykład: obiekty anonimowe jako wartości domyślneUżycie obiektów anonimowych jest relatywnie prostą metodą na kontrolowanie inicjalizacji i zmniejszenie liczby metod klasy Person::Person(Name N = Name("I", "M", "Nobody"), Address A = Address("No Street", "No Number", "No City", "No State", "00000"), Gender G = GENDERUNKNOWN) { Nom = N; Addr = A; Spouse = NULL; Gen = G; }
33 Wybrane metody tworzenia obiektówZmienne automatyczne Atype a; //konstruktor domyślny Zmienne automatyczne z argumentami Atype a(3); //konstruktor z parametrem int Przekazywanie parametrów funkcji przez wartość void f(Atype b) {...} ... f(a); //konstruktor kopiujący Przypisanie wartości zmiennym Atype a,b; a=b; //operator przypisania Inicjalizacja nowych obiektów Atype b; //konstruktor domyslny Atype a=b; //konstruktor kopiujący (NIE operator przypisania) Zwracanie wartości z funkcji Atype f() { return a; //konstruktor kopiujący }
34 Cechy dobrze napisanej klasyJawny konstruktor domyślny Gwarantuje, że każdy zadeklarowany egzemplarz obiektu zostanie w kontrolowany sposób zainicjalizowany Jeżeli obiekt zawiera wskaźniki do dynamicznie zaalokowanej pamięci: Jawny destruktor Zapobiega wyciekom pamięci. Zwalnia zasoby podczas usuwania obiektu. Jawny operator przypisania Używany przy przypisywaniu nowej wartości do istniejącego obiektu. Zapewnia, że obiekt jest istotnie kopią innego obiektu, a nie jego aliasem (inną nazwą). Jawny konstruktor kopiujący Używany podczas kopiowania obiektu przy przekazywaniu parametrów, zwracaniu wartości i inicjalizacji. Zapewnia, że obiekt jest istotnie kopią innego obiektu, a nie jego aliasem.
35 Przeciążenie - istnienie wielu definicji tej samej nazwy Wiele funkcji noszących tę samą nazwę W C++, przeciążone nazwy są rozróżniane na podstawie liczby i typu argumentów z uwzględnieniem dziedziczenia Powyższe parametry zwane są sygnaturą funkcji typy wartości zwracanej nie są rozróżniane, poniższy przykład jest niepoprawny double fromInt(int x) float fromInt(int x) Częste zastosowanie przeciążenia to przeciążenie operatorów
36 Przeciążenie i polimorfizmPrzeciążenie to forma polimorfizmu Pozwala na zdefiniowanie nowego znaczenia (funkcjonowania) operatorów dla wybranych typów. Kompilator rozpoznaje, której implementacji użyć, po sygnaturze funkcji (typach operandów użytych w wyrażeniu) Przeciążenie jest wspierane dla wielu wbudowanych operatorów 17 * 42 4.3 * 2.9 cout << 79 << 'a' << "overloading is profitable" << endl; Użyta implementacja zależy od typów operandów
37 Przyczyny przeciążania operatorówWsparcie dla naturalnego, sugestywnego użycia: Complex A(4.3, -2.7), B(1.0, 5.8); Complex C; C = A + B; // '+' oznacza dodawanie dla tego typu, tak samo jak dla np. typu int Integralność semantyczna: przypisanie dla typów obiektowych musi zapewnić wykonanie głębokiej kopii Możliwość użycia obiektów w sytuacjach, w których oczekiwany jest typ prosty
38 Operatory, które mogą być przeciążaneJedynie poniższe operatory mogą być przeciążane Operatory =, ->, [], () muszą być metodami niestatycznymi delete [] delete new[] new () [] -> , ->* -- ++ || && >= <= != == <<= >>= >> << &= ^= %= /= *= -= += > < = ! ~ | & ^ % / * - +
39 Wskazówki dotyczące przeciążania operatorówOperator powinien zachowywać się zgodnie z oczekiwaniami użytkownika Należy dostarczyć pełen zestaw pokrewnych operatorów: a = a + b i a+=b powinny mieć taki sam efekt i należy dostarczyć użytkownikowi klasy oba operatory Należy zdefiniować operator jako składową jeśli inne rozwiązanie nie jest konieczne Jeżeli przeciążony operator nie może być składową, powinien być funkcją zaprzyjaźnioną, a nie używać akcesorów, dodanych do klasy wyłącznie w tym celu Complex Complex::operator~() const { return ( Complex(Imag, Real) ); }
40 Składnia przeciążania operatorówDeklarowane i definiowane tak samo jak inne metody i funkcje, różnią się użyciem słowa kluczowego operator Jako metoda klasy: bool Name::operator== (const Name& RHS) { return ((First == RHS.First) && (Middle == RHS.Middle) && (Last == RHS.Last) ); } Jako funkcja zaprzyjaźniona: bool operator==(const Name& LHS, const Name& RHS) { return ((LHS.First == RHS.First) && (LHS.Middle == RHS.Middle) && (LHS.Last == RHS.Last) ); Bardziej naturalne jest użycie metody
41 Użycie operatorów przeciążonychJeżeli Name::operator= jest zdefiniowany jako metoda klasy Name, wówczas nme1 == nme2 jest równoważne nme1.operator==(nme2) Jeżeli operator== nie jest zdefiniowany jako składowa, wówczas operator==(nme1, nme2)
42 Przeciążenie operatorówSkładowe przeciążające operatory są definiowane z użyciem słowa kluczowego operator Odpowiednio użyte przeciążenie operatorów pozwala na traktowanie obiektów typu zdefiniowanego przez użytkownika w sposób tak samo naturalny, jak typów wbudowanych. // add to DateType.h: bool operator==(Datetype otherDate) const ; Interfejs // add to DateType.cpp: bool dateType::operator==(Datetype otherDate) const { return( (Day == otherdate.Day ) && (Month == otherDate.Month ) && (Year == otherDate.Year )); } Implementacja DateType aDate(10, 15, 2000); DateType bDate(10, 15, 2001); if (aDate == bDate) { . . . Klient
43 Operator binarny jako składowaOperator odejmowania dla klasy Complex jako składowa: Lewy operand operatora musi być obiektem Complex X(4.1, 2.3), Y(-1.2, 5.0); int Z; OK: X + Y; Not OK: Z + X; Zazwyczaj przekazuje się operand przez stałą referencję, żeby uniknąć narzutu kopiowania Complex Complex::operator-(const Complex& RHS) const { return ( Complex(Real - RHS.Real, Imag - RHS.Imag) ); }
44 Operator binarny jako funkcja nie będąca składowąOperator odejmowania dla klasy Complex jako funkcja nie będąca składową: Operator odejmowania musi używać interfejsu publicznego w celu dostępu do danych prywatnych klasy... ...chyba że klasa Complex zadeklaruje funkcję jako funkcję zaprzyjaźnioną Funkcja zaprzyjaźniona ma dostęp do składowych prywatnych klasy tak samo, jak składowa. Complex operator-(const Complex& LHS, const Complex& RHS) { return ( Complex(LHS.getReal() - RHS.getReal(), LHS.getImag() - RHS.getImag()) ); } class Complex { ... friend Complex operator+ (const Complex&, const Complex&); };
45 Operatory jednoargumentowe (unarne)Operator negacji dla klasy Complex Składowa implementująca unarny operator nie ma parametrów. Complex Complex::operator-() const { return ( Complex(-Real, -Imag) ); } Complex A(4.1, 3.2); // A = i Complex B = -A; // B = i
46 Operatory pre- i postinkrementacjiOperator preinkrementacji Operator postinkrementacji class Value { private: int x; public: Value(int i = 0) : x(i) {} int get() const { return x; } void set(int x) ( this->x = x; } Value& operator++(); Value operator++(int Dummy); } Value& Value::operator++() { x = x + 1; return *this; } Value Value::operator++(int Dummy) { x = x + 1; return Value(x-1); // return previous value }
47 Wielokrotne przeciążenieW klasie może być kilka operatorów dodawania Napiszmy kilka wyrażeń mieszanych: Sygnatura funkcji zostanie użyta do wyboru właściwej funkcji Complex Complex::operator+(double RHS) const { return (Complex(Real + RHS, Imag)); } Complex Complex::operator+(Complex RHS) const { return (Complex(Real + RHS.Real, Imag + RHS.Imag)); Complex X(4.1, 2.3); double R = 1.9; Complex Y = X + R; // Y.Real is 6.0 Complex Z = Y + R; // complex plus double Complex W = Y + X; // complex plus complex
48 Wielokrotne przeciążenieKonstruktor może służyć jako operator konwersji Nie zadziała, gdy lewy argument jest typu double Lepiej zdefiniować operator dwuargumentowy jako funkcję zaprzyjaźnioną Complex Complex::operator+(Complex RHS) const { return (Complex(Real + RHS.Real, Imag + RHS.Imag)); } Complex:: Complex (double co) { Real = co; Imag = 0; }; Complex X(4.1, 2.3); double R = 1.9; Complex Y = X + R; // Y = X.operator+(Complex(R)); Complex X(4.1, 2.3); double R = 1.9; Complex Y = R + X; // syntax error
49 Wielokrotne przeciążenieFunkcja zaprzyjaźniona działa również, gdy argument typu double jest po lewej stronie Kiedy implementować operatory jako funkcje zaprzyjaźnione: Przy operacjach na podstawowych typach danych, np. Complex operator+(int LHS, const Complex& RHS); Kiedy nie można zmodyfikować klasy oryginalnej, np. ostream friend Complex operator+(Complex LHS, Complex RHS) { return (Complex(LHS.Real + RHS.Real, LHS.Imag + RHS.Imag)); } Complex X(4.1, 2.3); double R = 1.9; Complex Y = X + R; // Y = operator+(X,Complex(R)); Complex Z = R + X; // Y = operator+(Complex(R),X);
50 Implementowany zbiór operatorówW wielu przypadkach dla danego typu cała kategoria operatorów ma sens Na przykład, dla klasy Complex ma sens przeciążenie wszystkich operatorów arytmetycznych. Dla klasy Name ma sens przeciążenie wszystkich operatorów relacji Często implementacje jednych operatorów mogą korzystać z implementacji innych operatorów, np: Complex operator + (Complex s1, Complex s2) { Complex n (s1); return n += s2; }
51 Operatory wejścia/wyjściaNie mamy dostępu do kodu klas istream i ostream, a więc nie możemy przeciążyć operatorów << i >> jako składowych tych klas. Nie możemy ich także uczynić składowymi klasy danych, ponieważ obiekt klasy danych musiałaby wówczas znajdować się po lewej stronie operatora, a nie o to nam chodzi. Dlatego musimy zdefiniować operator<< jako osobną funkcję Funkcja ta musi mieć dostęp do składowych klasy danych, a więc zazwyczaj będzie to funkcja zaprzyjaźniona. Rozwiązaniem alternatywnym, często nieakceptowalnym, jest zdefiniowanie akcesorów dla wszystkich pól prywatnych. Sygnatura funkcji będzie miała następującą postać: ostream& operator<<(ostream& Out, const Data& toWrite)
52 operator<< dla obiektów ComplexPrzeciążony operator<< drukuje sformatowany obiekt klasy Complex do strumienia wyjściowego ostream& operator<<(ostream& Out, const Complex& toWrite) { const int Precision = 2; const int FieldWidth = 8; Out << setprecision(Precision); Out << setw(FieldWidth) << toWrite.Real; if (toWrite.Imag >= 0) Out << " + "; else Out << " - "; Out << setw(FieldWidth) << fabs(toWrite.Imag); Out << "i"; Out << endl; return Out; }
53 operator>> dla obiektów ComplexPrzeciążony operator>> wczytuje ze strumienia wejściowego obiekt klasy Complex sformatowany w sposób używany przez operator<< istream& operator>>(istream& In, Complex& toRead) { char signOfImag; In >> toRead.Real; In >> signOfImag; In >> toRead.Imag; if (signOfImag == '-') toRead.Imag = -toRead.Imag; In.ignore(1, 'i'); return In; } Funkcja zależy od sposobu formatowania obiektów Complex w strumieniu wejściowym. Może być znacznie bardziej skomplikowana, jeżeli chcemy wczytywać różne formaty.
54 Przeciążenie operatora indeksowaniaDostarcza spodziewaną funkcjonalność, pozwalając na napisanie class vector { int *dane; unsigned int size; public: vector(int n); //creates n-element vector ~vector(); int& operator[] (unsigned int pos); int operator[] (unsigned int pos) const //copy constructor, assignment operator, ... }; int& vector::operator[] (unsigned int pos) if (pos >= size) abort (); return dane[pos]; } int vector::operator[] (unsigned int pos) const vector a(10); a[5]=10; cout << a[4]<
55 Przeciążenie operatorów relacjiJeżeli zamierzamy przechowywać obiekty danej klasy jako elementy kolekcji, przynajmniej część operatorów relacji powinna zostać przeciążona W celu efektywnego wyszukiwania elementów i sortowania kolekcja musi mieć możliwość porównywania przechowywanych obiektów. Może ona: Użyć funkcji akcesorów i porównywać bezpośrednio pola Użyć specjalnej metody porównującej zdefiniowanej w danej klasi Użyć przeciążonych operatorów relacji (==, !=, <, <=, >, >= w zależności od potrzeb) Pierwsze podejście wymaga, aby kolekcja posiadała szczegółowe informacje o swoich elementach Drugie podejście wymaga dostarczenia specjalnych składowych Trzecie podejście jest najbardziej naturalne i pozwala na niezależne projektowanie klas danych i kolekcji.