Oprócz trzech rodzajów konstruktorów wymienionych na poprzednich laboratoriach: konstruktora domyślnego, konstruktora parometrowego oraz konstruktora bezparametrowego istnieje jeszcze jeden specjalny typ konstruktora, tzw. konstruktor kopiujący.
Konstruktor kopiujący jest przydatny w chwili, kiedy klasy posiadają pola dynamicznie alokowane. Konstruktor kopiujący musi przyjmować jako parametr referencje do obiektu. Przykład konstruktora kopiującego dla klasy Punkt z poprzednich laboratoriów (nie jest on w tej klasie wymagany). Deklaracja:
... Punkt(const Punkt&); ...
Definicja:
... Punkt::Punkt(const Punkt &punkt){ this->x = punkt.x; this->y = punkt.y; cout << "Konstruktor kopiujący!" << endl; } ...
Konstruktor kopiujący jest wywoływany automatycznie w następujących sytuacjach:
... Punkt p(12,34); Punkt p2(p); // Konstruktor kopiujący. Punkt p3 = p; // Konstruktor kopiujący. Punkt p4; //NIE ZADZIAŁA KONSTRUKTOR KOPIUJĄCY //TYLKO DOMYŚLNY OPERATOR PRZYPISANIA p4 = p2;
Słowo kluczowe const ma różne znaczenie w zależności od kontekstu w jakim jest stosowane.
W odniesieniu do metod klasy oznacza, że dana metoda nie może modyfikować elementów składowych klasy jak również nie może wywoływać innych metod niż te zdeklarowane jako const. Jeśli można, warto zadeklarować metodę jako const. Umożliwi to poprawne korzystanie z obiektów tej klasy zadeklarowanych jako stałe.
class Matrix{ ... void wyswietl() const; ... };
W odniesieniu do zmiennych i obiektów i innych zmiennych, oznaczają że nie można modyfikować zmiennej/obiektu. Innymi słowy nie można wywoływać na rzecz danego obiektu innych metod niż zadeklarowane jako const
... const Matrix m(["1 2 3; 1 2 3"]); //Metoda ustawiająca wartość w komórce macierzy o indeksie (1,1) //Wywołanie takiej metody wygeneruje błąd podczas kompilacji. m.set(1,1,12); //Gdyby metoda wyświetl nie była zadeklarowana jako const //takie wywołanie też spowodowałoby błąd m.wyswietl();
Funkcja i klasy zaprzyjaźnione mają nieograniczony dostęp do wszystkich pól i metod klasy której są przyjaciółmi.
Funkcja zaprzyjaźniona definiowana jest poza zasięgiem klasy, ale informacja o tym że jest ona przyjacielem musi znaleźć się w klasie:
//Plkik Matrix.h class Matrix{ double** data; ... friend void wyzeruj(Matrix& m); ... }; // Plik main.cpp // Funkcja może modyfikować prywatne dane klasy // Matrix, ponieważ została określona jako friend void wyzeruj(Matrix& matrix){ for(int r = 0; r < matrix.rows; r++) for(int c = 0; c < matrix.cols; c++) matrix.data[r][c] = 0; }
Analogicznie do funkcji klasy zaprzyjaźnione maja nieograniczony dostęp d wszystkich pól i metod klasy które są przyjaciłmi. Jeśli klasa Node ma być przyjacielem klasy Lista, to w tej drugiej należy umieścić następującą deklarację:
class Lista{ ... friend class Node; ... };
Słowo kluczowe static może być stosowane zarówno w stosunku do pól klasy jak i jej metod, ale w obu przypadkach ma nieco inne znaczenie.
Pola statyczne są swego rodzaju zmiennymi globalnymi, należącymi jednak do zasięgu klasy. Zmienna statyczna jest współdzielona przez wszystkie obiekty klasy. Do zmiennych statycznych można odwoływać się nie mając utworzonego obiektu.
Słowo kluczowe static dodaje się tylko i wyłącznie podczas deklaracji zmiennej. Użycie słowa kluczowego static podczas inicjalizacji zmiennej powoduje błąd składniowy.
Deklaracja zmiennej statycznej w pliku nagłówkowym:
class Matrix{ public: static int licznik; Matrix(){licznik++;} ~Matrix(){licznik--;} };
Zmienna statyczna musi zostać zainicjalizowana w przestrzeni pliku. Nie można inicjalizować statycznej zmiennej w konstruktorze, lub innej funkcji. Inicjalizacja zmiennej statycznej w pliku cpp:
#include "Matrix.h" int Matrix::licznik = 0; ...
Odwoływanie się do pól statycznych:
int main(){ Matrix m; cout << Matrix::licznik <<endl; cout << Matrix.licznik << endl; }
Metody statyczne można wywoływać bez konieczności tworzenia obiektów klasy. Można z tego wywnioskować, że metody statyczne nie posiadają wskaźnika this.
Nie można zatem wewnątrz metod statycznych wywoływać żadnych innych metod niestatycznych, ani odwoływać się do pól niestatycznych.
Metody statyczne należą jednak do zasięgu klasy i mają dostęp do wszystkich pól klasy:
class Matrix{ ... static void wyswietl(Matrix& m); }; void Matrix::wyswietl(Matrix & m){ for(int r = 0; r < matrix.rows; r++){ for(int c = 0; c < matrix.cols; c++) cout << matrix.data[r][c] << endl; cout << endl; } }
DTab wypelniona(int wypelnienie);
Co się stanie gdy w zwróconej DTab będziemy chcieli zmienić jakieś elementy, albo ją rozszerzyć? Dopisz do klasy konstruktor kopiujący.
4.5i6
Co oznacza w zapisie matematycznym
4.5 + 6i
Matrix m("[1i3 2i5 3; 3 4 5; 6 7 8]");
Matrix m("[1 2 3;3 4 5; 2 3 4]"); Matrix m2("[3 2 1; 5 4 3; 7 6 5]"); Matrix wynik = m.add(m2);
#include <iostream> #include "Matrix.h" int main(int argc, char* argv[]){ Matrix m1(argv[1]); Matrix m2(argv[2]); cout << "Macierz pierwsza: " << m1.print() << endl; cour << "Macierz druga: " << m2.print() << endl; cout << "Dodawanie" << (m1.add(m2)).print() << endl; cout << "Odejmowanie" << (m1.sub(m2)).print() << endl; cout << "Mnożenie" << (m1.mul(m2)).print() << endl; cout << "Dzielenie" << (m1.div(m2)).print() << endl; cout << "Potęgowanie" << (m1.pow(2)).print() << endl; cout << "Potęgowanie" << (m2.pow(2)).print() << endl; }
Zabezpieczenie programu przed sytuacjami wyjątkowymi w „podstawowej” wersji, wymaganej na laboratorium, związane jest ze sprawdzeniem odpowiedniej wartości i wypisaniem komunikatu dla użytkownika. Bardziej profesjonalny sposób obsługi takich sytuacji związany jest z mechanizmem wyjątków. Jeżeli chcesz dowiedzieć się o nim czegoś więcej, zapraszam do zapoznania się z nieobowiązkową instrukcją do laboratorium Wyjątki.