Programowanie obiektowe w C++ Wykład 03 Temat wiodący: Konstruktory i deskruktory Konstruktory i Destruktory 1
Konstruktor Konstruktor co to? jest metodą służącą do inicjowania obiektów danej klasy jest przepisem na utworzenie obiektu danego typu Konstruktor czy jest potrzebny? czy dane zawarte w obiekcie wymagają inicjalizacji? Konstruktor deklarowanie Deklaracja konstruktora dla klasy T: T(args); lub T::T(args); class point double x, y; public: // point(double, double); point(double); point(); ; // nie zwraca nawet void // można go przeciążać 2
Konstruktor nietypowa metoda Nie wolno określać typu wartości zwracanej (nawet void). Nie można wywołać konstruktora na rzecz już istniejącego obiektu. Nie można pobrać adresu konstruktora. Nie jest widoczny w zakresie klasy i nie ma nazwy (wg. opisu języka). Wywoływanie Konstruktor bezargumentowy (domyślny): point p1; Pozostałe: point p3(10.0, 20.0); // 2 arg. point p2(1.0); // 1 arg. // point p1(); // tak wygląda deklaracja funkcji p1 point p2=1.0; // tak tylko dla k. 1 arg. 3
Wywoływanie można jawnie określić konstruktor point p3=point(10.0, 20.0); point p2=point(1.0); point p1=point(); albo: point p3=point::point(10.0, 20.0); point p3=point::point(1.0); point p1=point::point(); obiekty anonimowe Wywoływanie point pu; // obiekt nieanonimowy pu pu = point (3); // tymczasowy obiekt anonimowy, // usunięcie (zaraz po przypisaniu lub // później, gdy będzie niepotrzebny ) point(20); // tymczasowy anonimowy i bezużyteczny point * ppu; ppu=&point(); // błąd trudny do wyśledzenia, składniowo poprawne 4
Definiowanie class point double x, y; public: // point(double, double); point(double); point() x=y=0.0; ; ; inline point::point(double d) x=y=d; point::point(double x0, double y0) x=x0; y=y0; Lista inicjacyjna w definicji konstruktora (i tylko tu) można wykorzystać listę inicjacyjną T() : składowa(inicjalizator) [, składowa(inicjalizator) ] class point double x, y; public: // ; point():x(0.0), y(0.0) ; 5
Lista inicjacyjna inline point::point(double d) :x(d), y(d) // zrobione point::point(double x, double y) :x(x), y(y) // to jest jednoznaczne!!! // zrobione Lista inicjacyjna tylko tak można inicjalizować pola będące referencjami i pola ustalone. w liście inicjacyjnej oprócz niestatycznych składowych klasy (zadeklarowanych w klasie, a nie odziedziczonych) można określić sposób wywołania konstruktorów bezpośrednich przodków i przodków wirtualnych. kolejność na liście inicjacyjnej nie ma znaczenia, wykonywanie w kolejności: klasy bazowe (wirtualne i bezpośredni przodkowie w kolejności deklaracji), składowe w kolejności deklaracji, ciało konstruktora. 6
Lista inicjacyjna point::point(int x, int y):x(x), y(y) ; //OK. inline point::point(int i) :x(i), y(x) ; //OK. inline point::point(int i) :y(x), x(i) ; //OK. // point::point(int i) :y(i), x(y) oops!!! Konstruktor domyślny jeżeli żaden konstruktor nie został zadeklarowany to kompilator dostarcza domyślny konstruktor (bezargumentowy) o pustym ciele, dla klasy T: T::T() jeżeli my zadeklarujemy konstruktor (nawet tylko argumentowy) to kompilator nie dostarczy domyślnego bezargumentowego. 7
Konstruktory Kolejność wywoływania konstruktorów klasa (klasy) bazowa (bazowe w kolejności deklaracji) składowe klasy w kolejności deklaracji ciało konstruktora Uwaga: obiektowe składowe klasy (oraz klasy bazowe), jeżeli nie zostaną zainicjalizowane w liście inicjacyjnej, to będą inicjalizowane 2 razy: konstruktorem bezargumentowym swojej klasy następnie kodem z ciała konstruktora tworzonego obiektu class segment point p1; int number; point p2; ; Przykład segment::segment() :p1(1.0, 1.0) p2=point(1.0); ; // jak to zadziała??? 8
Przykład class segment point p1; int number; point p2; ; segment::segment() :p1(1.0, 1.0) // konstr. p1 (2-arg), konstr. p2 (domyślny) // number niezainicjalizowany p2=point(1.0); // konstr. temp. (1 arg.), przypisanie ; // destr. temp. Konstruktor kopiujący służy do inicjalizacji obiektów danej klasy innymi obiektami tej samej klasy, dla klasy T ma postać: T::T(const T &); parametr musi być referencją, a nie zmienną (bo powstawałyby obiekty tymczasowe, które też trzeba zainicjalizować, też konstruktorem kopiującym) parametr powinien być const aby można było wywołać konstruktor z argumentem const 9
Konstruktor kopiujący np. k.k. dla klasy point: point(const point & p) :x(p.x), y(p.y) Konstruktorem kopiującym może być też inny kostruktor który można wywołać w taki sposób, np.: point(const point &, int=7); Konstruktor kopiujący generowany automatycznie K.k. jest generowany automatycznie przez kompilator, gdy nie programista go nie zdefiniuje (inne konstruktory są nieistotne). generowany automatycznie k.k. skopiuje obiekt pole po polu (klasy bazowe oraz pola obiektowe ich konstruktorami kopiującymi analogicznie jak dla listy inicjalizacyjnej będzie to działać też gdy klasa ma składowe ustalone (const)) np.: point(const point & p) :x(p.x), y(p.y) 10
Co to jest destruktor? łatwo zgadnąć Destruktor Kiedy jest przydatny? jeszcze łatwiej Destruktor klasy T: ~T(); Destruktor np.: ~point() // nie ma argumentów ani typu zwracanego cout << \njestem sobie point (x: << x << y: << y; << ) a zaraz mnie już nie będzie ; 11
Destruktor przeciwnie do konstruktora jest widoczny w zakresie klasy destruktor można wywołać jawnie destruktor domyślny generowany gdy nie zdefiniowano destruktora ma ciało puste, ale Kolejność wywoływania konstruktorów i destruktorów Konstruktory klasa (klasy) bazowa (bazowe w kolejności deklaracji) składowe klasy w kolejności deklaracji ciało konstruktora Destruktory po prostu odwrotnie ciało destruktora destruktory obiektów składowych (kolejność przeciwna do deklaracji w klasie) destruktor klasy (klas) bazowych (kolejność przeciwna do deklaracji w klasie) 12
Kolejność wywoływania konstruktorów i destruktorów obiekty zdefiniowane w blokach (lokalne, automatyczne) konstruktory są wywoływane gdy sterowanie napotyka na definicję obiektu destruktory po opuszczeniu bloku w kolejności odwrotnej do konstruktorów obiekty globalne (statyczne) konstruktory w kolejności definicji, przed wywołaniem funkcji main() destruktory w kolejności odwrotnej, po zakończeniu main(). Kolejność wywoływania konstruktorów i destruktorów obiekty dynamiczne kontrolowane są przez programistę za pomocą new i delete. przydział pamięci i wywołanie konstruktora w momencie zastosowania operatora new destruktor i dealokacja po delete 13
Konstrukcja i destrukcja obiektów dynamicznych point *pp0=new point; point *pp1=new point(1.0); point *pp2=new point(10.0, 20.0); point *tablica=new point[10]; // tablica 10 punktow // inicjalizowanych konstr. bezarg. // w kolejności rosnących adresów // przy new T[] wyłącznie konstruktor bezargumentowy delete pp1; delete pp2; delete pp0; delete [] tablica; point global=777; int f(int) segment o; Przykład void main() point local; point *p_local; local=global; local=point(10,20); int a=f(1); for (int i=0; i<2; i++) point w_bloku=local; p_local = new point(1.0); point local2(10,20); delete p_local; p_local = new point(); 14
point global=777; int f(int) segment o; Przykład k4, k5, d5, d4 k1 void main() point local; k2 point *p_local; local=global; local=point(10,20); k3, d3 int a=f(1); for (int i=0; i<2; i++) point w_bloku=local; k6, i==1 k7 d6, i==1 d7 p_local = new point(1.0); k8 point local2(10,20); k9 delete p_local; d8 p_local = new point(); k10 d9, d2, d1,???10 Przykład zdefiniować klasę person opisującą osoby class person int age; // wiek char *name, // imię *lastname; // nazwisko public: person(const char *name, const char *lastname, const int age); person(const person & o); ~person(); ; 15
Przykład inline person::person(const char *name, const char *lastname, const int age) :age(age) person::name=new char[strlen(name) + 1]; strcpy(person::name, name); person::lastname=new char[strlen(lastname) + 1]; strcpy(person::lastname, lastname); inline person::person(const person & o) :age(o.age), name(new char[strlen(o.name) + 1]), lastname(new char[strlen(o.lastname) + 1]) strcpy(name, o.name); strcpy(lastname, o.lastname); inline person::~person() delete name; delete lastname; Przykład 16
Przykład zdefiniować klasę queue reprezentującą kolejkę osób (FIFO) class queue person **persons; // tablica wskaźników do osób w kolejce const capacity; // pojemność kolejki int length; // aktualna długość kolejki public: queue(int capacity); ~queue(); int insert(const person &o); // wstaw, 1-brak miejsca, 0-wstawiono int collect(person &o); // pobierz, 1-kolejka pusta, 0-pobrano ; Przykład queue::queue(int capacity) :capacity(capacity), length(0), persons((person **) new (person*) [capacity]) // rzutowanie dla bc31 queue::~queue() for (int i=0; i<length; i++) delete persons[i]; delete [] persons; // elementy // tablica 17
Przykład int queue::insert(const person &o) if (length==capacity) return 1; persons[length++]=new person(o); // tworzymy kopie argumentu metody return 0; int queue::collect(person &o) if (length==0) return 1; Przykład o.~person(); o=*persons[0]; // jeszcze nie wiemy // jak przeciążyć operator przypisania length--; for(int i=0; i<length; i++) persons[i]=persons[i+1]; return 0; 18
int queue::collect(person &o) if (length==0) return 1; Przykład o.~person(); o=*persons[0]; // jeszcze nie wiemy // jak przeciążyć operator przypisania length--; for(int i=0; i<length; i++) persons[i]=persons[i+1]; return 0; // wytęż wzrok i znajdź wyciek pamięci!!! 19