Programowanie obiektowe
w C++
Wykład 03 Temat wiodący:
Konstruktory i deskruktory
Konstruktory
i Destruktory
Konstruktor
n
Konstruktor — co to?
n
jest metodą służącą do inicjowania obiektów danej klasy
n
jest przepisem na utworzenie obiektu danego typu
n
Konstruktor — czy jest potrzebny?
n
czy dane zawarte w obiekcie wymagają inicjalizacji?
Konstruktor — deklarowanie
n Deklaracja konstruktora dla klasy T:
T(args); lub T::T(args);
class point {
double x, y;
public:
// …
point(double, double); // nie zwraca nawet void
point(double); // można go przeciążać
point();
};
Konstruktor — nietypowa metoda
n
Nie wolno określać typu wartości zwracanej (nawet void).
n
Nie można wywołać konstruktora na rzecz już istniejącego obiektu.
n
Nie można pobrać adresu konstruktora.
n
Nie jest widoczny w zakresie klasy i nie ma nazwy (wg. opisu języka).
Wywoływanie
n Konstruktor bezargumentowy (domyślny):
point p1;
n 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.
Wywoływanie
n
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();
Wywoływanie
n
obiekty anonimowe
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
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
n 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) {};
};
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
n
tylko tak można inicjalizować pola będące referencjami i pola ustalone.
n
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.
n
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.
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
n
jeżeli żaden konstruktor nie został zadeklarowany to kompilator dostarcza domyślny konstruktor
(bezargumentowy) o pustym ciele, dla klasy T:
T::T() { }
n
jeżeli my zadeklarujemy konstruktor (nawet tylko argumentowy) to kompilator nie dostarczy
domyślnego bezargumentowego.
Kolejność wywoływania konstruktorów
n
Konstruktory
n klasa (klasy) bazowa (bazowe w kolejności deklaracji)
n składowe klasy w kolejności deklaracji
n ciało konstruktora
n
Uwaga: obiektowe składowe klasy (oraz klasy bazowe), jeżeli nie zostaną zainicjalizowane w liście inicjacyjnej, to będą inicjalizowane 2 razy:
n konstruktorem bezargumentowym swojej klasy
n następnie kodem z ciała konstruktora tworzonego obiektu
Przykład
class segment {
point p1;
int number;
point p2;
};
segment::segment() // jak to zadziała???
:p1(1.0, 1.0) {
p2=point(1.0);
};
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
n
służy do inicjalizacji obiektów danej klasy innymi obiektami tej samej klasy, dla klasy T ma postać:
T::T(const T &);
n
parametr musi być referencją, a nie zmienną (bo powstawałyby obiekty tymczasowe, które też trzeba zainicjalizować, też konstruktorem kopiującym)
n
parametr powinien być const aby można było
wywołać konstruktor z argumentem const
Konstruktor kopiujący
n
np. k.k. dla klasy point:
point(const point & p) :x(p.x), y(p.y) {}
n
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
n
K.k. jest generowany automatycznie przez kompilator, gdy nie programista go nie zdefiniuje (inne
konstruktory są nieistotne).
n
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) {}
Destruktor
n
Co to jest destruktor?
n
łatwo zgadnąć
n
Kiedy jest przydatny?
n
jeszcze łatwiej
Destruktor
n
Destruktor klasy T:
~T();
n
np.:
~point() // nie ma argumentów ani typu zwracanego {
cout << ”\njestem sobie point (x:”
<< x << „ y:” << y;
<<„) a zaraz mnie już nie będzie”;
}
Destruktor
n
przeciwnie do konstruktora jest widoczny w zakresie klasy
n
destruktor można wywołać jawnie
n
destruktor domyślny – generowany gdy nie zdefiniowano destruktora
n
ma ciało puste, ale …
Kolejność wywoływania konstruktorów i destruktorów
n
Konstruktory
n klasa (klasy) bazowa (bazowe w kolejności deklaracji)
n składowe klasy w kolejności deklaracji
n ciało konstruktora
n
Destruktory – po prostu odwrotnie
n ciało destruktora
n destruktory obiektów składowych (kolejność przeciwna do deklaracji w klasie)
n destruktor klasy (klas) bazowych (kolejność przeciwna do deklaracji w klasie)
Kolejność wywoływania konstruktorów i destruktorów
n
obiekty zdefiniowane w blokach (lokalne, automatyczne)
n konstruktory są wywoływane gdy sterowanie napotyka na definicję obiektu
n destruktory po opuszczeniu bloku w kolejności odwrotnej do konstruktorów
n
obiekty globalne (statyczne)
n konstruktory w kolejności definicji, przed wywołaniem funkcji main()
n destruktory w kolejności odwrotnej, po zakończeniu main().
Kolejność wywoływania konstruktorów i destruktorów
n
obiekty dynamiczne kontrolowane są przez programistę za pomocą new i delete.
n
przydział pamięci i wywołanie konstruktora w momencie zastosowania operatora new
n
destruktor i dealokacja po delete
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;
Przykład
point global=777;
int f(int) { segment o; }
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();
}
Przykład
point global=777;
int f(int) { segment o; } 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
n 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();
};
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);
}
Przykład
inline person::~person() {
delete name;
delete lastName;
}
Przykład
Przykład
n 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 };
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]; // elementy delete [] persons; // tablica }
Przykład
int queue::insert(const person &o) {
if (length==capacity) return 1;
persons[length++]=new person(o); // tworzymy kopie argumentu metody return 0;
}
Przykład
int queue::collect(person &o) {
if (length==0) return 1;
o.~person(); // jeszcze nie wiemy
o=*persons[0]; // jak przeciążyć operator przypisania
length--;
for(int i=0; i<length; i++) persons[i]=persons[i+1];
return 0;
}
Przykład
int queue::collect(person &o) {
if (length==0) return 1;
o.~person(); // jeszcze nie wiemy
o=*persons[0]; // 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 !!!