Niejawne funkcje składowe W języku C++ automatycznie tworzone są następujące funkcje składowe (o ile nie zostaną one jawnie zdefiniowane): konstruktor domyślny konstruktor kopiujący operator przypisania destruktor domyślny operator adresu 1
Konstruktor kopiujący Konstruktor kopiujący jest wywoływany wtedy, kiedy nowo tworzony obiekt jest inicjowany już istniejącym obiektem tej samej klasy: o inicjujemy nowy obiekt istniejącym obiektem Punkt a(1,1), b(a); // definicja obiektu b za pomocą a Punkt c=a; Punkt d=punkt(a); Punkt *wpunkt = new Punkt(a); o przekazujemy obiekt do funkcji przez wartość nowy obiekt jest kopią funkcja(a); // przekazanie obiektu a do funkcji o zwracamy obiekt z funkcji przez wartość c=funkcja(); // zwrócenie obiektu z funkcji Jeśli użytkownik takiego konstruktora nie dostarczy, to stosowany jest domyślny konstruktor kopiujący, wykonujący dokładną, bit po bicie kopię obiektu. Przykład: class Ulamek int l; // licznik int m; // mianownik... public: Ulamek(int a=0, int b=1) // Konstruktor inicjujący... ; Ulamek f0, f1(1), f2(6,3), f3(f2); Tutaj będzie użyty odpowiedni konstruktor inicjujący Tutaj będzie użyty konstruktor kopiujący W klasie Ulamek brak jest konstruktora kopiującego. Kompilator utworzy własny konstruktor kopiujący. Pytanie: Czy domyślny konstruktor kopiujący zawsze zrobi to co trzeba? 2
Kiedy potrzebny jest własny konstruktor kopiowania? Przykład: tablica dynamiczna, zdefiniowany jest tylko konstruktor inicjujący.(w3p7) #include <iostream> using namespace std; Programowanie obiektowe Wykład 3 class Tablica int lelem; // liczba elementów tablicy double * T ; // wskaźnik do obszaru przydzielonego dla tablicy public : Tablica (int n) T = new double [lelem = n] ; cout << "++ Konstruktor - adres obiektu: " << this << " - adres tablicy: " << T << "\n" ; ~Tablica () cout << "-- Destruktor - adres obiektu: " << this << " - adres tablicy: " << T << "\n" ; delete [] T ; friend void func(tablica b); ; void func (Tablica b) cout << "*** func: Działam na kopii - adres obiektu: " << &b <<" - adres tablicy: " << b.t <<endl; int main() Tablica a(4) ; cout << "** Zaczynam main **" << endl; cout << "** Wywołuję func **\n" ; func (a) ; cout << "** Kończę main **" << endl; return 0; Jak wygląda przebieg programu? ++ Konstruktor - adres obiektu: 0xbffffc70 - adres tablicy: 0x8049f50 ** Zaczynam main ** ** Wywołuję func ** *** func: Działam na kopii - adres obiektu: 0xbffffc60 - adres tablicy: 0x8049f50 -- Destruktor - adres obiektu: 0xbffffc60 - adres tablicy: 0x8049f50 ** Kończę main ** -- Destruktor - adres obiektu: 0xbffffc70 - adres tablicy: 0x8049f50 Gdzie jest pułapka? 3
Jak działa konstruktor kopiujący? Domyślny konstruktor kopiujący przepisuje kolejno wartość każdej niestatycznej składowej do jej odpowiednika w nowo tworzonym obiekcie. Jest to tzw. kopiowanie płytkie (ang. memberwise copying, shallow copying). W przypadku obiektów o składowych dynamicznych potrzebne jest kopiowanie głębokie zapewniane przez własny konstruktor kopiujący. Konstruktor taki ma prototyp: MojaKlasa(const MojaKlasa &); Przykład (2): #include <iostream> class Tablica int lelem ; double * wsk ; public : Tablica (int n) wsk = new double [lelem = n] ; ~Tablica () delete [] wsk ; //////////////////////////////////////////////////////////////// Tablica (const Tablica & v) // konstruktor kopiujący double *wsk = new double [lelem = v.lelem] ; // przydzielenie pamięci int i ; for (i=0 ; i<lelem ; i++) // skopiowanie do nowej pamięci wsk[i]=v.wsk[i] ; //////////////////////////////////////////////////////////////// ; 4
Inicjalizacja: tworzenie nowego obiektu. Przypisanie: zmiana wartości istniejącego obiektu. Punkt a(1,1), b; // inicjalizacja b=a; // przypisanie Operator przypisania = Występuje podobny problem jak w przypadku kopiowania. W przypadku użycia domyślnego operatora przypisania wykonywana jest kopia składowych bit po bicie, tak jak w przypadku domyślnego konstruktora kopiowania - czyli jest to kopiowanie płytkie. class Tablica int lelem ; int *T ; public : Tablica (int n)... ; int main() Tablica a(3), b(2); Wykonujemy w programie instrukcję przypisania: b = a; b wskazuje na ten sam obszar pamięci co a Pozostaje zajęta pamięć ze sterty, nie ma do niej dostępu Rozwiązanie: przeciążenie operatora przypisania = 5
Prototyp operatora przypisania =: MojaKlasa& operator=(const MojaKlasa &); Przykład (3): #include <iostream> using namespace std; class Tablica int lelem ; int *T ; public : Tablica (int n) T = new int [lelem = n] ; for (int i=0 ; i<lelem ; i++) T[i] = 0 ; ~Tablica () delete T ; // przeciążony operator przypisania Tablica& operator= (const Tablica &) ; ; /////////////////////////////////////////////////// // przeciążony operator przypisania Tablica & Tablica::operator = (const Tablica & v) if (this!= &v) // czy przypisujemy obiekt do siebie samego // 1. zwolnienie pamięci obiektu po lewej stronie = delete [ ] T ; // 2. przydzielenie na nowo pamięci T = new int [lelem = v.lelem] ; // 3. przepisanie do nowej pamięci obiektu po prawej stronie = for (int i=0 ; i<lelem ; i++) T[i] = v.t[i] ; else cout << " nic nie robimy \n" ; return * this ; //////////////////////////////////////////////////// int main() Tablica a(5), b(3), c(4) ; cout << "** przypisanie a=b \n" ; a = b ; // czyli a.operator=(b) cout << "** przypisanie c=c \n" ; c = c ; // czyli c.operator=(c) cout << "** przypisanie a=b=c \n" ; a = b = c ; // czyli a.operator=(b.operator=(c)) 6
Czas życia obiektów Obiekty mogą być tworzone: za pomocą deklaracji zarządza nimi kompilator za pomocą operatorów dynamicznego zarządzania pamięcią zarządza nimi użytkownik w pewnych sytuacjach tworzone są również obiekty tymczasowe W każdym wypadku po utworzeniu obiektu automatycznie wywołany zostanie konstruktor (dostarczony przez użytkownika lub kompilator). Bezpośrednio przed usunięciem obiektu wywoływany jest destruktor (dostarczony przez użytkownika lub kompilator). Obiekty tworzone za pomocą deklaracji Rozróżnia się dwa typy takich obiektów: automatyczne i statyczne. Obiekty automatyczne tworzone są w funkcji lub w wewnętrznym bloku; są usuwane, gdy wychodzi się z danej funkcji lub bloku. Obiekty statyczne tworzone są na zewnątrz wszystkich funkcji lub w funkcji za pomocą deklaracji poprzedzonej słowem static, powstają przed rozpoczęciem wykonywania funkcji main()i są usuwane po jej zakończeniu. Obiekty dynamiczne Obiekty dynamiczne tworzone są przy użyciu operatora new, usuwane są na życzenie za pomocą operatora delete. Użycie operatora new powoduje wywołanie konstruktora inicjującego. Przykład obiektu dynamicznego: Punkt * wsk; wsk = new Punkt(1,1); // wywołany konstruktor Punkt(int,int) wsk->drukuj(); // równoważne (*wsk).drukuj() Punkt * inny; inny = new Punkt; // równoważne new Punkt() 7
Obiekty tymczasowe Obiekty tymczasowe powstają w różnych kontekstach. Po wykorzystaniu usuwane są najszybciej jak to jest możliwe (tak mówi standard). Przykład a=punkt(1,2) ; Wykonanie: 1. Utwórz obiekt tymczasowy typu Punkt 2. Wywołaj konstruktor dla tego obiektu i przekaż mu argumenty. 3. Skopiuj jego zawartość do obiektu a. 4. Wywołaj destruktor. 5. Usuń obiekt tymczasowy. 8
Niszczenie obiektów klasy C++ automatycznie zwalnia zasoby przydzielone obiektowi klasy w chwili zakończenia trwania tego obiektu. Możliwe są trzy przypadki: wychodzimy z zakresu obowiązywania nazwy (wracamy z funkcji, w której został zadeklarowany obiekt lub kończymy blok, w którym został zdefiniowany obiekt), obiekt utworzony za pomocą operatora new jest usuwany za pomocą operatora delete Data *d = new Data(1,1,2002);... delete d; kończymy program - usuwane są wtedy wszystkie obiekty globalne. Przed usunięciem obiektu automatycznie wywoływany jest destruktor klasy. Destruktor oprócz zwalniania zasobów (jeśli są przydzielone dynamicznie), może wykonywać jeszcze inne zadania porządkujące, na przykład aktualizację pewnych liczników. Jeśli nie istnieją żadne zasoby, które należałoby zwolnić przed usunięciem obiektu, nie trzeba dostarczać własnego destruktora, wystarczy destruktor wygenerowany przez kompilator. 9
Stałe i metody stałe Referencja jako stałe Zasada: jeśli wartość parametru przekazywanego przez referencję może zostać zmodyfikowana wewnątrz funkcji, to argumentami mogą być tylko te obiekty, dla których ta modyfikacja jest dozwolona. Przykład: W funkcji main() : f3 = f1+ulamek(2,3); // tworzony obiekt tymczasowy f3 += 2; // stała Która postać jest prawidłowa? Ulamek Ulamek::operator+( Ulamek &b) Ulamek Ulamek::operator+(const Ulamek &b) Ulamek Ulamek::operator += (Ulamek &b) Ulamek Ulamek::operator += (const Ulamek &b) Stałe metody Nie zmieniają obiektów. class X int i; public: X(int ii); int f() const;... ; X::X(int ii) : i(ii) int X::f()const return i; int main() X x1(1); const X x2(2); x1.f(); x2.f();... 10
Funkcja składowa zadeklarowana jako stała jest to informacja dla kompilatora, że może być ona wywołana w stosunku do obiektu będącego stałą. Funkcja składowa nie zadeklarowana jawnie jako stała jest traktowana jako taka, która modyfikuje dane obiektu. Słowo const wchodzi w skład sygnatury funkcji. Kompilator zgłasza błąd, jeśli w funkcji występuje próba zmiany wartości składowej lub wywołanie innej funkcji składowej, która nie została zadeklarowana jako stała. Stałe funkcje składowe mogą być wywoływane zarówno w stosunku do stałych obiektów, jak i obiektów nie będących stałymi. Przykład (w3p4.cpp): #include <iostream> using namespace std; class Wektor int const rozmiar; int * const tab; public: Wektor (int n) : rozmiar(n), tab(new int[n]) ~Wektor() delete [] tab; int t_rozmiar() const return rozmiar; // int &element(int n) return tab[n]; // int element(int n) const return tab[n]; int &operator[](int n) return tab[n]; int operator[](int n) const return tab[n]; ; int main() Wektor a(5); // a.element(2)=1; a[2]=1; cout << "rozmiar: " << a.t_rozmiar()<< endl;; // cout << "wartosc elementu 2: " << a.element(2)<< endl; cout << "wartosc elementu 2: " << a[2]<< endl; 11
Przykład (w3p5.cpp) // Eckel, Thinking in C++, str. 289 #include <iostream> #include <cstdlib> // Generator liczb losowych #include <ctime> // Do inicjacji generatora liczb losowych using namespace std; class Quoter int lastquote; public: Quoter(); // Nie może być stała - modyfikuje obiekt const char* quote() const; // Może być stała nic nie modyfikuje ; Quoter::Quoter() lastquote = -1; srand(time(0)); // Inicjacja generatora liczb losowych const char* Quoter::quote() const static const char* quotes[] = "Czy mamy sie juz smiac?", "Lekarze zawsze wiedza najlepiej", "Sowy nie sa tym, czym sie wydaja", "Strach ma wielkie oczy", "Nie ma naukowego dowodu " "na poparcie tezy " "ze zycie jest na serio", "To, co nas bawi, czyni nas madrzejszymi", ; const int qsize = sizeof quotes/sizeof *quotes; int qnum = rand() % qsize; qnum = rand() % qsize; return quotes[qnum]; int main() const Quoter q1; cout << q1.quote() << endl; Quoter q2; cout << q2.quote() << endl; 12
Przykłady - dynamiczne struktury danych Przykład 1: Stos (w3p8) #include <iostream> using namespace std; class Stos private: int *tab; int wlk; // rozmiar stosu int capacity; //rozmiar tablicy void grow(); public: Stos(); Stos(Stos const&); ~Stos(); Stos& operator=(stos const&); void push(int i); // wkłada liczbę na stos void pop(); // zdejmuje liczbę ze stosu int top() const; // udostępnia wartość liczby z wierzchołka stosu int & top(); // udostępnia referencję do liczby na wierzchołku stosu bool empty() const; // sprawdza czy stos jest pusty? int s_size() const; // zwraca rozmiar stosu ; Stos::Stos () capacity = 1; wlk = 0; tab = new int[capacity]; Stos::~Stos() delete [] tab; Stos::Stos(Stos const& S) capacity = S.capacity; wlk = S.wlk; tab = new int[capacity]; for (int i=0; i<wlk; i++) tab[i]=s.tab[i]; Stos& Stos::operator=(Stos const& S) if (this==&s) return *this; if (capacity<s.wlk) delete [] tab; capacity=s.wlk; tab=new int[capacity]; 13
wlk=s.wlk; for (int i=0; i<wlk; i++) tab[i]=s.tab[i]; return *this; void Stos::push(int i) if (wlk==capacity) grow(); tab[wlk]=i; wlk++; void Stos::pop() if (!empty()) wlk--; int Stos::top() const if (!empty()) return tab[wlk-1]; else cout<<"stos pusty"<<endl; return -1; int& Stos::top() if (!empty()) return tab[wlk-1]; else cout<<"stos pusty"<<endl; return tab[0]; bool Stos::empty() const return wlk==0; int Stos::s_size()const return wlk; void Stos::grow() if (wlk==capacity) capacity *=2; int *p = new int[capacity]; for (int i=0; i<wlk; i++) p[i]=tab[i]; delete [] tab; tab=p; 14
int main() Stos a; if (a.empty()) cout << "empty" << endl; for (int i=1; i<10; i++) a.push(i); while (!a.empty()) cout << a.top() << " "; a.pop(); 15
Przykład 2: obsługa napisów Autor- S.Prata, Szkoła programowania, język C++, wyd.1 (2002), rozdział 12 Klasa String Cechy klasy: powinna umożliwiać przechowywanie napisów zmiennej długości. powinna umożliwiać dostęp do pojedynczych znaków napisu powinna umożliwiać przypisywanie jednego napisu drugiemu powinna umożliwiać porównywanie napisów Programowanie obiektowe Wykład 3 // Plik string1.h -- definicja klasy #include <iostream> using namespace std; #ifndef STRING1_H_ #define STRING1_H_ class String private: char * str; // wskaźnik do napisu - alokacja pamięci // zostanie wykonana w konstruktorze int len; // długość napisu static int num_strings; // ilość obiektów - wprowadzona, aby // pokazac korzystanie ze składowej statycznej static const int CINLIM = 80; // ograniczenie długości danych // wejściowych public: // konstruktory String(const char * s); // constructor String(); // default constructor String(const String &); // copy constructor ~String(); // destructor // przeciążone operatory - metody String & operator=(const String &); String & operator=(const char *); char & operator[](int i); const char & operator[](int i) const; // przeciążone operatory - funkcje zaprzyjażnione friend bool operator<(const String &st, const String &st2); friend bool operator>(const String &st1, const String &st2); friend bool operator==(const String &st, const String &st2); friend ostream & operator<<(ostream & os, const String & st); friend istream & operator>>(istream & is, String & st); // funkcje pomocnicze static int HowMany(); int length () const return len; ; #endif 16
// Plik string1.cpp -- metody klasy String #include <iostream> #include <cstring> #include "string1.h" using namespace std; // inicjalizacja składowych statycznych int String::num_strings = 0; // musi być inicjalizowana na zewnatrz // metody statyczne int String::HowMany() return num_strings; // konstruktory i destruktor // tworzenie obiektu String na podstawie napisu w stylu języka C String::String(const char * s) len = strlen(s); str = new char[len + 1]; strcpy(str, s); num_strings++; // śledzenie tworzonych obiektów // konstruktor domyślny String::String() len = 0; str = new char[1]; str[0] = '\0'; num_strings++; // śledzenie tworzonych obiektów // konstruktor kopiujący String::String(const String & st) num_strings++; len = st.len; str = new char [len + 1]; strcpy(str, st.str); // śledzenie tworzonych obiektów // destruktor String::~String() --num_strings; // śledzenie tworzonych obiektów delete [] str; 17
// przeciążone operatory metody // operator przypisania -- obiekt String do obiektu String String & String::operator=(const String & st) if (this == &st) return *this; delete [] str; len = st.len; str = new char[len + 1]; strcpy(str, st.str); return *this; // operator przypisania -- napis w stylu języka C do obiektu String String & String::operator=(const char * s) delete [] str; len = strlen(s); str = new char[len + 1]; strcpy(str, s); return *this; // operator [] dla obiektu nie-stałego String char & String::operator[](int i) return str[i]; // operator [] dla obiektu stałego String const char & String::operator[](int i) const return str[i]; 18
// przeciążone operatory -- funkcje zaprzyjaźnione bool operator<(const String &st1, const String &st2) return (strcmp(st1.str, st2.str) < 0); bool operator>(const String &st1, const String &st2) return st2.str < st1.str; bool operator==(const String &st1, const String &st2) return (strcmp(st1.str, st2.str) == 0); ostream & operator<<(ostream & os, const String & st) os << st.str; return os; istream & operator>>(istream & is, String & st) char temp[string::cinlim]; is.get(temp, String::CINLIM); if (is) st = temp; while (is && is.get()!= '\n') continue; return is; Programowanie obiektowe Wykład 3 19
Komentarze Konstruktory i destruktory // tworzenie obiektu String na podstawie napisu w stylu języka C String::String(const char * s) len = strlen(s); str = new char[len + 1]; strcpy(str, s); num_strings++; // śledzenie tworzonych obiektów // konstruktor domyślny String::String() len = 0; str = new char[1]; str[0] = '\0'; num_strings++; // śledzenie tworzonych obiektów lub String::String() len = 0; str=0; num_strings++; // śledzenie tworzonych obiektów // destruktor String::~String() --num_strings; // śledzenie tworzonych obiektów delete [] str; Wszystkie konstruktory muszą być zgodne z destruktorem. Ponieważ w destruktorze odwołujemy się do tablicy: delete [] str, musimy w obydwu konstruktorach używać new []. Użycie operatora delete [] w stosunku do obiektu, który nie został utworzony jako tablica obiektów nie jest zdefiniowane. Dopuszczalne jest jednak użycie wskaźnika pustego. Składowe statyczne Inicjalizacja składowej statycznej musi być dokonana na zewnątrz klasy. Instrukcję inicjalizacji należy umieścić w pliku z kodem metod. Pole statyczne może być inicjowane wewnątrz klasy, jeśli jest stałą (const) lub typu wyliczeniowego. Metody statyczne Metoda statyczna nie jest związana z żadnym obiektem Jedyne pola, z których korzysta to pola statyczne Przykład użycia funkcji statycznej: cout << String::HowMany()<< endl; 20
Przeciążony operator przypisania W klasie mamy dwa przeciążone operatory przypisania: String & operator=(const String &); String & operator=(const char *); Pierwszy z operatorów jest wymagany, ponieważ klasa zawiera składową dynamiczną. Drugi z operatorów został wprowadzony po to, aby można było działać bezpośrednio na napisach w stylu języka C, bez konieczności tworzenia obiektów tymczasowych. Przykład 1: Brak przeciążonego operatora = dla przypisywania napisu. String nazwisko; nazwisko="nowak"; Napis w stylu języka C musi być zamieniony na obiekt String. Wykorzystany zostanie konstruktor jednoargumentowy. Powstanie obiekt tymczasowy. Po przypisaniu zostanie usunięty. Przykład 2: Zdefiniowano przeciążony operatora String & operator=(const char *): // operator przypisania -- napis w stylu języka C do obiektu String String & String::operator=(const char * s) delete [] str; len = strlen(s); str = new char[len + 1]; strcpy(str, s); return *this; String nazwisko; nazwisko="nowak"; Użyty zostanie bezpośrednio operator przypisania. Zasada: Konwersje zdefiniowane przez użytkownika (za pomocą konstruktorów lub odpowiednich funkcji konwersji) są brane pod uwagę tylko wtedy, kiedy są niezbędne. Przeciążone operatory porównywania Dzięki temu, że są zdefiniowane jako funkcje zaprzyjaźnione, możliwe jest porównywanie obiektów String z napisami w stylu języka C 21
Przeciążony operator [] Musi umożliwiać korzystanie zarówno z obiektów nie-stałych jak i stałych. W przypadku obiektów nie-stałych: może być użyty zarówno do wyprowadzania jak i przypisywania wartości. // operator [] dla obiektu nie-stałego String // możliwy odczyt -- zapis char & String::operator[](int i) return str[i]; // operator [] dla obiektu stałego String // możliwy tylko odczyt const char & String::operator[](int i) const return str[i]; String nazwisko("nowak"); // obiekt nie jest stały const String odp("tak"); // obiekt stały // obiekt nie jest stały cout << nazwisko[0]; nazwisko[0]='n'; cin >> nazwisko[0]; // obiekt stały cout << odp[0]; 22
Przykład 3: obsługa tablic (w3p6.cpp) Autor- S.Prata, Szkoła programowania, język C++, wyd.1 (2002), rozdział 14 Klasa ArrayDb Cechy klasy: przechowywanie elementów typu double dostęp do elementów za pomocą indeksu możliwość przypisania jednej tablicy drugiej sprawdzanie czy podany indeks jest z zakresu // Plik arraydb.h -- definicja klasy ArrayDb #ifndef ARRAYDB_H_ #define ARRAYDB_H_ #include <iostream> using namespace std; class ArrayDb private: unsigned int size; // ilość elementów tablicy double * arr; // adres pierwszego elementu public: // konstruktory i destruktor ArrayDb(); explicit ArrayDb(unsigned int n, double val = 0.0); ArrayDb(const double * pn, unsigned int n); ArrayDb(const ArrayDb & a); ~ArrayDb(); Programowanie obiektowe Wykład 3 // metody unsigned int ArSize() const return size; // rozmiar tablicy double Average() const; // średnia wartość // przeciążone operatory -- metody double & operator[](int i); const double & operator[](int i) const; ArrayDb & operator=(const ArrayDb & a); // przeciążone operatory -- funkcje zaprzyjaźnione friend ostream & operator<<(ostream & os, const ArrayDb & a); ; #endif 23
// arraydb.cpp -- metody klasy ArrayDb #include <iostream> using namespace std; #include <cstdlib> // exit() prototype #include "arraydb.h" // konstruktory i destruktor // konstruktor domyślny ArrayDb::ArrayDb() arr = 0; size = 0; // konstruktor tworzący tablicę n-elementową, // o wartościach elementów równych val ArrayDb::ArrayDb(unsigned int n, double val) arr = new double[n]; size = n; for (int i = 0; i < size; i++) arr[i] = val; // konstruktor tworzący tablicę n-elementową, // na podstawie tablicy pn ArrayDb::ArrayDb(const double *pn, unsigned int n) arr = new double[n]; size = n; for (int i = 0; i < size; i++) arr[i] = pn[i]; // konstruktor kopiujący ArrayDb::ArrayDb(const ArrayDb & a) size = a.size; arr = new double[size]; for (int i = 0; i < size; i++) arr[i] = a.arr[i]; // destruktor ArrayDb::~ArrayDb() delete [] arr; // przeciążanie operatorów -- metody // operator [] -- nie-stały obiekt double & ArrayDb::operator[](int i) if (i < 0 i >= size) cerr << "Error in array limits: " << i << " is a bad index\n"; 24
exit(1); return arr[i]; // operator [] -- stały obiekt const double & ArrayDb::operator[](int i) const if (i < 0 i >= size) cerr << "Error in array limits: " << i << " is a bad index\n"; exit(1); return arr[i]; // operator przypisania ArrayDb & ArrayDb::operator=(const ArrayDb & a) if (this == &a) // if object assigned to self, return *this; // don't change anything delete [] arr; size = a.size; arr = new double[size]; for (int i = 0; i < size; i++) arr[i] = a.arr[i]; return *this; // przeciążanie operatorów -- funkcje zaprzyjaźnione // operator wyjścia: drukuj po pięć w wierszu ostream & operator<<(ostream & os, const ArrayDb & a) int i; for (i = 0; i < a.size; i++) os << a.arr[i] << " "; if (i % 5 == 4) os << "\n"; if (i % 5!= 0) os << "\n"; return os; 25
// metody // średnia wartość elementów tablicy double ArrayDb::Average() const double sum = 0; int i; int lim = ArSize(); for (i = 0; i < lim; i++) sum += arr[i]; if (i > 0) return sum / i; else cerr << "No entries in score array\n"; return 0; Komentarze Konstruktory i destruktor Konstruktor explicit ArrayDb(unsigned int n, double val = 0.0) może być wywołany z jednym argumentem, pełni więc dodatkowo rolę niejawnej funkcji konwersji. Za pomocą słowa explicit rezygnujemy z tej funkcji. 26
ZADANIA 1. Co to jest konstruktor domyślny? 2. Co to jest konstruktor inicjujący? 3. Co decyduje o kolejności inicjowania składowych obiektu? 4. Co to jest konstruktor kopiujący? 5. Dlaczego konstruktor kopiujący nie może pobierać argumentu przez wartość? 6. Co to jest destruktor? Co robi destruktor? Co powinien robić destruktor? 7. Jakie metody generuje kompilator, gdy nie zostaną jawnie zdefiniowane w klasie? Jak one działają? 8. Co to są i w jaki sposób deklaruje się metody stałe klasy? 9. Opracuj klasę do przetwarzania liczb mieszanych, takich jak na przykład "1 1/3". Zdefiniuj operacje dodawania, odejmowania, mnożenia i dzielenia. Na przykład: "1 1/3 + 2 1/2 = 3 5/6" 10. Opracuj klasę od obsługi tablic rzadkich. Elementy tablicy mogą mieć indeksy z zakresu od 0 do 100000.W tablicy może być do 100 elementów różnych od zera. 11. Stephen Prata, Szkoła programowania. Język C++, wyd.v, 2006, str. 684, pytania sprawdzające 1-5, 12. Stephen Prata, Szkoła programowania. Język C++, wyd.v, 2006, str. 603-605, ćwiczenia programistyczne 1-6. 27