background image

Wykład 3

Konstruktor – cechy 

główne

background image

Co robi konstruktor?

Wywołanie konstruktora powoduje wykonanie następujących zadań:

• obliczenie rozmiaru obiektu 

• alokacja obiektu w pamięci 

• wyczyszczenie (zerowanie) obszaru pamięci zarezerwowanej dla 

obiektu (tylko w niektórych językach) 

• wpisanie do obiektu informacji łączącej go z odpowiadającą mu 

klasą (połączenie z metodami klasy) poprzez wskaźnik this

• wykonanie kodu klasy bazowej (w niektórych językach nie 

wymagane) 

• wykonanie kodu wywołanego konstruktora 
Z wyjątkiem ostatniego punktu powyższe zadania są wykonywane 

wewnętrznie i są wszyte w kompilator lub interpreter języka, lub 

w niektórych językach stanowią kod klasy bazowej.

W językach programowania w różny sposób oznacza się konstruktor:

• w C++, PHP4, Javie  i in. - jest to metoda o nazwie zgodnej z 

nazwą klasy 

• w Pascalu - metoda której nazwę poprzedzono słowem kluczowym 

constructor

• w PHP 5 - metoda o nazwie __construct 

background image

Uwagi ogólne do konstruktorów:

1. Konstruktor 

NIE  MUSI

  wystąpić  w  opisie  klasy,  czyli 

obiekty  nie  muszą  być  jawnie  wprowadzane 
konstruktorem.

2. Nazwa  konstruktora  może  być  przeładowana

,  czyli 

stosowana  wielokrotnie  w  opisie  klasy  z  różnymi 
listami  argumentów.  Wtedy  kompilator  odróżnia 
konstruktory  po  listach  argumentów,  tak,  jak  w 
przypadku 

przeładowanych 

nazw 

funkcji. 

Konstruktorów może więc być wiele.

3. Konstruktor 

może 

być 

wywoływany 

(a 

nie 

deklarowany!!)  bez  żadnych  argumentów

.  Jest  to  tak 

zwany  konstruktor  domniemany.  Czasem  nazywamy 
go  domyślnym  albo  standardowym.  Ze  względu  na 
istotę  przeładowania  nazwy  konstruktor  domniemany 
czyli  bezargumentowy  może  wystąpić  tylko  raz.  Jeśli 
nie  deklarujemy  w  klasie  żadnego  konstruktora,  to 
kompilator  sam  ustanawia  właśnie  konstruktor 
domniemany do obsługi obiektów w programie. Każdy 
konstruktor  z  argumentami,  którym  nadamy  wartości 
domyślne 

czyli 

niedefiniowalne 

jest 

także 

konstruktorem domniemanym.

background image

Konstruktor jest zwykle deklarowany jako publiczny, 
bo przecież wprowadzane nim obiekty mogą być 
używane przez klasy zewnętrzne, a ponadto jest 
funkcją, która MUSI być dostępna dla składników 
klasy. Możemy jednak dla konstruktora przewidzieć 
ochronę tak, jak dla klas za pomocą etykiet private 
lub protected. Wówczas jednak także 
konstruowane obiekty będą dostępne tylko w 
obrębie klasy z tym konstruktorem jako private albo 
jako protected tylko w zakresie klas dziedziczących.

Konstruktor może zamiast definiować obiekty 
podawać kopie obiektów zawartych w innej klasie. 
Wtedy jest to tak zwany 

konstruktor kopiujący

.

Konstruktor może dokonywać konwersji typu obiekty 
z jednego w drugi. Nazywamy go wtedy 

konstruktorem konwertującym

.

background image

Konstruktor kopiujący 

Przyjrzyjmy się wywołaniu konstruktora klasy o nazwie klasa:
 
klasa::klasa(klasa&)
 
Jego argumentem jest referencja do obiektu danej klasy. Czyli do 

elementu,  który  w  chwili  uruchomienia  tego  konstruktora  już 
istnieje.  Taki  konstruktor  nie  konstruuje  obiektu  tylko  tworzy  kopię 
innego, który już istnieje wśród obiektów klasy. Pozostałe argumenty 
konstruktora są domniemane. Przykładami konstruktora kopiującego 
mogą być:

X::X(X&)
 
lub
 
X::X(X&, float=3.1415, int=0)
 

background image

Konstruktor kopiujący c.d.

• Taki konstruktor wprowadza obiekty identyczne z już 

istniejącymi, czyli ich kopie.

• Taki konstruktor może być wywołany przez program 

niejawnie:

1.W sytuacji gdy do funkcji jest 

przez wartość

 przesyłany 

obiekt klasy X. Wówczas tworzona jest kopia tego obiektu. 

Jest to tzw. kopiowanie płytkie.

2.W sytuacji kiedy funkcja zwraca przez wartość obiekt klasy 

X. Wtedy także tworzona jest kopia obiektu. To także jest 

kopiowanie płytkie.

To, że konstruktor kopiujący podaje obiekt kopiowany przez 

referencję daje mu możliwość 

zmiany zawartości obiektu 

klasy!!

 (patrz przesyłanie argumentu do funkcji przez 

wartość)

background image

Konstruktor kopiujący c.d.

• Nie można pominąć referencji w konstruktorze 

kopiującym, bo gdyby konstruktor X wywoływał 

obiekty swojej klasy X przez wartość, czyli 

wytwarzałby swoją kopię, to powstaje nie zamknięta 

pętla tworzenia kopii. 

• Konstruktor z przyczyn logiki języka otrzymuje więc 

warunki do tego aby uszkodzić oryginał!! 

• Zabezpieczamy się przed taką sytuacją następująco:

X::X(const X&obiekt)

• Teraz konstruktor X wie, że obiekt klasy X musi być 

wywoływany jako stały. Konstruktor kopiujący jest 

domyślnie obdarzony moderatorem const, czyli nie 

może zmienić sam siebie.

background image

Kopiowanie płytkie i głębokie

Wyróżniamy dwa typy kopiowania obiektów 

zawierających pola będące wskaźnikami

• Kopiowanie płytkie
a. Kopiowanie wszystkich składowych (w tym 

wskaźników)

b. Kopiowane są wskaźniki, a nie to, na co wskazują
• Kopiowanie głębokie
a. Alokacja nowej pamięci dla wskaźników
b. Kopiowanie zawartości wskazywanej przez 

wskaźniki w nowe miejsce

 c. Kopiowanie pozostałych pól, nie będących 

wskaźnikami

background image

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:

1.

Sprawdzić przypisanie obiektu do samego siebie, np. A=A:

2.

if (this == &otherObj) // if true, do nothing

3.

Skasować zawartośc obiektu docelowego

4.

delete this->...

5.

Zaalokować pamięć dla kopiowanych wartości

6.

Przepisać kopiowane wartości

7.

Zwrócic *this

background image

Konstruktor kopiujący a operator 

przypisania

Konstruktor kopiujący jest więc używany do 

stworzenia nowego obiektu

• Wydaje się 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.

background image

Konstruktor kopiujący a operator 

przypisania c.d.

• int main() {
• list a;
• //...
• list b(a); //copy constructor  is called
• list c=a; //copy constructor is called
• };

background image

 
 
1.#include<iostream>
2.#include<string.h>
3.#include<conio.h>
4.class X
5.{public:char*p; X(char*);
6.};
7.class Y
8.{public:
9.char*p; Y(char*);
10.

Y(Y&);

// deklaracja konstruktora kopiajacego obiekty klasy Y

11.};
12.void main()
13.{
14.X x("xxx"); X j=x;

//powolanie do zycia obiektow x,j klasy X

15.cout<<"\nx="<<x.p<<",  j="<<j.p;    //  wydruk  wskaznika  czyli  adresu  do 

obiektow x,j

16.strcpy(j.p,"111"); // skopiowanie pod wskaznik obiektu j lancucha 111
17.cout<<"\nx="<<x.p<<", j="<<j.p;

Przykład: konstruktor kopiujący będzie kopiował wskaźnik do 

obiektu. ( czy to tzw. kopiowanie głębokie ?)

background image

18.cprintf("\n\rx.p=%p, j.p=%p,x.p,j.p);
19.Y y("yyy"); Y d=y; //

powołanie obiektów klasy Y

20.cout<<"\ny="<<y.p<<", d="<<d.p;
21.strcpy(y.p,"222");
22.cout<<"\ny="<<y.p<<", d="<<d.p;
23.cprintf("\n\ry.p=%p, d.p=%p,y.p,d.p);
24.getch();
25.}
26.X::X(char*s)
27.{p=new char[80]; if(p)strcpy(p,s);
28.}
29.Y::Y(char*s)
30.{p=new char[80]; if(p)strcpy(this->p,s);
31.}
32.Y::Y(Y&y)
33.{p=new char[80]; if(p)strcpy(p,y.p);
34.}

background image

Omówienie przykładu:

5.{public:char*p; X(char*);

Wiersz  5:  etykieta  public  dla  klasy  X  oraz  deklaracje 
zmiennej  własnej  p,  która  jest  wskaźnikiem  do  zmiennej 
znakowej oraz konstruktor obiektów klasy X oczekującego 
na  liście  parametrów  formalnych  wskaźnika  do  zmiennej 
typu  string  lub  charakter.  Ciało  tego  konstruktora  jest 
podane w wierszu 26-28:

26.X::X(char*s)
27.{p=new char[80]; if(p)strcpy(p,s);
28.}

Wiersz 8-9: analogiczny jak wiersz 5 ale dla klasy Y 

8.{public:
9.char*p; Y(char*);

background image

Wiersz 9: konstruktor kopiujący klasy Y. Będzie on 
kopiował wskaźnik do zmiennej znakowej, którą 
wskaże. Może to być zmienna z innej klasy czyli z 
innego typu zmiennej obiektowej. Na tym polega 
kopiowanie głębokie. W klasie X funkcjonuje konstruktor 
kopiujący domyślny tworzony podczas kompilacji. Daje 
on kopiowanie płytkie, czyli dotyczące tylko składników 
własnej klasy X.

background image

13.{
14.X x("xxx"); X j=x; //powolanie do zycia obiektow

x,j klasy X

Wiersz  13-14:  tworzymy  obiekt  x  oraz  obiekt j klasy X.  Do  obiektu  x 
wpisywany  jest  element  tablicy  zarezerwowanej  dla  niego  przez 
konstruktor  w  wierszu  26.  Obiekt  j  jest  inicjalizowany  obiektem  x. 
Kopiowanie x do j jest realizowane przez konstruktor domyślny klasy 
X.  Przepisuje  on  wskaźnik  do  obiektu  x  do  wskaźnika  do  obiektu  j. 
Dlatego wskaźnik p w obiekcie j będzie wskazywał to samo miejsce co 
wskaźnik  p  w  obiekcie  x.  Dlatego  wydruk  w  wierszu  14  powinien 
podać ten sam wynik dla każdego z tych obiektów.
Zauważmy, że obiekt j nie ma zarezerwowanej swojej przestrzeni na 
tablice  znakową,  korzysta  natomiast  ze  zmiennej  wskaźnikowej 
własnej p z klasy X do podłączenia się do tej samej tablicy co obiekt 
x.  Dlatego  pojawia  się  szczególny  zapis  obiektów  x  oraz  j  połączony 
ze zmienną własną wskaźnikową p. 
15.cout<<"\nx="<<x.p<<",  j="<<j.p;    //  wydruk  wskaznika  czyli 
adresu do obiektow x,j
16.strcpy(j.p,"111");  //  skopiowanie  pod  wskaznik  obiektu  j  lancucha 
111
17.cout<<"\nx="<<x.p<<", j="<<j.p;

background image

15.cout<<"\nx="<<x.p<<", j="<<j.p;  // wydruk wskaznika 
czyli adresu do obiektow x,j
16.strcpy(j.p,"111");  //  skopiowanie  pod  wskaznik  obiektu  j 
lancucha 111
17.cout<<"\nx="<<x.p<<", j="<<j.p;

Wiersz 15-17: do tablicy wskazywanej przez 
wskaźnik p wpisujemy poprzez kopiowanie 
łańcucha wartość ’’111” ale przedtem 
sprawdzamy adresy obiektów.
Wiersz  16:  wydruk  wartości  obiektu  x  oraz  j 
wskazywanych przez zmienną p
Wiersz 17: wydruk adresów wskazywanych przez p 
dla  obiektu  x  oraz  j.  Te  adresy  powinny  być 
jednakowe, czy nie?

background image

18.cprintf("\n\rx.p=%p, j.p=%p,x.p,j.p);
19.Y y("yyy"); Y d=y; 

powołanie obiektów klasy Y

20.cout<<"\ny="<<y.p<<", d="<<d.p;
21.strcpy(y.p,"222");
22.cout<<"\ny="<<y.p<<", d="<<d.p;

Wiersze 18-22: powtórzenie takich samych działań ale dla 
klasy Y. Wprowadzamy obiekty y oraz d, które grają takie 
same role jak poprzednio x oraz j.
Wiersz 21: modyfikujemy łańcuch w obiekcie d.
Wiersz  22:  drukujemy  wartości  obiektów  y  oraz  d  nie   
spodziewając  się  ich  identyczności  jak  poprzednio  dla  x 
oraz  j.  Dlaczego?  Dlatego,  że  konstruktor  Y  działa  przez 
referencję,  a  nie  poprzez  przypisanie  jak  konstruktor 
kopiujący  domyślny.  Łańcuch  d  jest  modyfikowany 
tylko  w  miejscu  d
.  Konstruktor  Y  zapewnia  modyfikację 
poprzez referencję. 
Wydruk adresów obiektów y oraz d. Powinny być różne!!

background image

26.X::X(char*s)
27.{p=new char[80]; if(p)strcpy(p,s);
28.}

Wiersz 26-28: ciało konstruktora obiektów klasy X.  
Operatorem new jest dynamicznie przydzielona 
pamięć dla tablicy 80cio  znakowej.  Kopiowanie 
łańcucha z listy parametrów formalnych konstruktora 
do tablicy nastąpi tylko wtedy, kiedy operator new tę 
pamięć przydzieli.

background image

29.Y::Y(char*s)

30.{p=new char[80]; if(p)strcpy(this->p,s);

31.}

32.Y::Y(Y&y)

33.{p=new char[80]; if(p)strcpy(p,y.p);

34.}

Konstruktory klasy Y. Konstruktor kopiujący powiela 
postać konstruktora poza wskazaniem, że dozwala 
na kopiowanie obiektów klasy Y do wskaźnika p spod 
adresy każdego obiekty klasy Y.

background image

Rezultat na ekranie (przykładowy):
 
x=xxx, j=xxx
x=111, j=111
x.p=2707:0004, j.p=2707:0004
y=yyy, d=yyy
y=yyy, d=222
y.p=270D:0004, d.p=2713:0004

background image

Jakie mamy więc metody 

tworzenia obiektów?

Zmienne 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) {...} …..

• Atype a; //konstruktor domyślny

• 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() {

• Atype a; //konstruktor domyślny

• return a; //konstruktor kopiujący

• }

background image

Cechy (zalecane) poprawnie 

napisanej klasy

Jawny konstruktor

• Gwarantuje, że każdy zadeklarowany egzemplarz obiektu zostanie 

w kontrolowany sposób zainicjalizowany

Jeżeli obiekt zawiera wskaźniki do dynamicznie zaalokowanej 

pamięci:

A. Jawny destruktor:

• Zapobiega wyciekom pamięci. Zwalnia zasoby podczas usuwania 

obiektu.

B. 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ą).

C. 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.

background image

Wykład 4

Przeciw pełnej 

hermetyzacji

background image

Ukrywanie informacji - etykiety private 

i public 

Jeśli klasa ma etykietę private, to jej składniki będą dostępne tylko w 
zakresie  wnętrza  klasy.  Jeśli  etykiet  nie  ma,  to  w  trybie 
domyślnym  wszystkie  składniki  klasy  są  private
.  Są  lokalne  w 
zakresie  ważności  klasy.  Jeśli  etykieta  jest  public,  to  składniki  klasy 
mogą  być  wywoływane  także  spoza  tej  klasy.  Etykietami  możemy 
określać  także  dostęp  do  wybranych  składników  klasy  wybiórczo 
umieszczając  etykietę  przed  nazwą  składnika  klasy.  Wtedy  private  i 
public mają sens podany wyżej. Dodatkowo stosuje się także etykietę 
protected.  Oznacza  ona,  że  taki  składnik  jest  dostępny  tylko  w 
ramach  dziedziczenia  tj.  w  klasach,  które  są  potomkami  klasy 
zawierającej ten składnik.
Tak  więc  etykiety  właśnie  zapewniają  nam  ochronę  dostępu  do 
składników  klasy.  Dane  najczęściej  umieszczamy  w  klasach 
chronionych czyli private. Do ustawiania wartości i pobierania danych 
korzystamy z funkcji składowych klasy. To do nich stosujemy etykiety 
ochrony. Etykiety te są jednym z narzędzi hermetyzacji klasy.

background image

Przykład: 
class chamidlo
{
int a;
float b;
void fun1(int);
protected:
char m;
void fun2(void);

public:

int v;

void fun3(char*);
private:

int d;
void fun4(float b);

}
 
W  tej  klasie  składniki  prywatne,  czyli  dostępne  tylko  w  obrębie 

klasy  chamidlo  to:  a,b,fun1,d,  fun4.  Składniki  protected,  czyli 
zastrzeżone  dla  tej  klasy  i  jej  potomków  to:  m,fun2.  Pozostałe 
składniki  są  publiczne,  czyli  dostępne  dla  wszystkich  elementów 
programu. Są to: v, fun3.

background image

Funkcje zaprzyjaźnione

 
To takie funkcje, które, mimo, że nie są składnikami klasy, to mają 

dostęp  do  jej  składników  czyli  innych  funkcji,  zmiennych  i  obiektów. 
Mają dostęp także do tych składników klasy, które są hermetyzowane 
etykietą  private.  Pamiętajmy,  że  jeśli  nie  ma  innych  etykiet,  to 
wszystkie  składniki  są  private.  Funkcja  zaprzyjaźniona  jest 
wprowadzana instrukcją  friend.

 

Sposób stosowania:
class figura{
 
int x,y;
…….
friend void goniec(figura&)
};

background image

Sama funkcja goniec(figura&) jest zdefiniowana gdzieś w 
programie w całkowicie innym miejscu nie powiązanym z 
klasą pionek. W klasie figura {} chcemy z niej skorzystać 
nawet, jeśli przynależy ona do innej klasy. Wtedy poprawnie 
jest taką funkcję zaznaczyć etykietą  public w jej klasie.
 
Cechy funkcji zaprzyjaźnionych:
*Funkcja może być zaprzyjaźniona z kilkoma klasami.
*Na argumentach jej wywołania może wykonywać operacje 
zgodnie ze swoją definicją.
*Może być napisana w zupełnie innym języku niż C++ i 
dlatego może nie być funkcją składową klasy.

background image

*Ponieważ  funkcja  typu  friend  nie  jest  składnikiem  klasy  to  nie  ma 
wskaźnika  this,  czyli  musi  się  posłużyć  operatorem  wskaźnika,  albo 
przypisania  aby  wykonać  działania  (także  te    na  składniku  klasy,  z 
którą jest zaprzyjaźniona).
*Jest deklarowana w klasie ze słowem instrukcji friend i nie podlega 
etykietom hermetyzcji (public, private, protected).
*Może  być  cała  zdefiniowana  w  klasie  i  wtedy  jest  typu  inline  ale 
nadal jest funkcją zaprzyjaźnioną.
*Nie musi być funkcją składową żadnej klasy ale może nią być.
*Klasa  może  się  przyjaźnić  z  wieloma  funkcjami,  które  są  lub  nie  są 
składnikami innych klas.
*Funkcje zaprzyjaźnione nie są przechodnie, to znaczy, że „przyjaciel 
mego  przyjaciela  nie  jest  moim  przyjacielem”  czyli  zaprzyjaźnienie 
nie przenosi się od klasy do klasy.
*Zaprzyjaźnienie nie podlega mechanizmowi dziedziczenia.
*Z  zasady  umieszcza  się  funkcje  zaprzyjaźnione  na  początku 
wszystkich deklaracji w klasie.

background image

Zaprzyjaźnienie klas

#include <string>
using namespace std;
class Pies {

string kolor, przedmiot; 

int wiek; friend class Kot;

public:

Pies(string, int, string); void drukuj(); 

void zamien(Kot *);
};
class Kot {  string kolor, przedmiot; int wiek; 
friend class Pies;
public:

Kot(string, int, string); void drukuj(); 

void  zamien(Pies *);
void clear() { przedmiot=""; }
};

background image

Zaprzyjaźnienie klas cd.1

Pies::Pies(string aKolor, int aWiek, string aPrzedmiot)
{

•     kolor = aKolor;

•     wiek = aWiek;

•     przedmiot = aPrzedmiot;
}

Kot::Kot(string aKolor, int aWiek, string aPrzedmiot)
{

•     kolor = aKolor;

•     wiek = aWiek;

•     przedmiot = aPrzedmiot;
}

background image

Zaprzyjaźnienie klas cd.2

• void Pies::zamien(Kot *b)

• {

•     string old = b->przedmiot;

•     b->przedmiot = przedmiot;

•     przedmiot = old;

• }    

• void Kot::zamien(Pies *b)

• {

•     string old = b->przedmiot;

•     b->przedmiot = przedmiot;

•     przedmiot = old;

• } 

background image

Zaprzyjaźnienie klas cd.3

• void

• Pies::drukuj()

• {

•     printf("Kolor [%s], wiek [%d], przedmiot [%s]\n", 

kolor.c_str(), wiek,

•         przedmiot.c_str());

• }

• void

• Kot::drukuj()

• {

•     printf("Kolor [%s], wiek [%d], przedmiot [%s]\n", 

kolor.c_str(), wiek,

•         przedmiot.c_str());

• }

background image

Zaprzyjaźnienie klas cd.4

int main(int argc, char* argv[])
{

•     Pies a("czarny", 3, "kosc");     Kot b("bialy", 5, "pilka");

•     a.drukuj();  b.drukuj();

•     printf("\nZamiana...\n\n");     a.zamien(&b);     a.drukuj();  

b.drukuj();

•     printf("\nA zabiera B...\n\n");     a.zamien(&b);     b.clear();

•     a.drukuj();  b.drukuj();

•     printf("\n");     system("PAUSE");

return 0;

}

background image

Cechy zaprzyjaźniania klas

• W klasie możemy deklarować przyjaźń z 

funkcjami składowymi innej klasy bez ograniczeń. 
Oznacza to, że obiekty klasy zaprzyjaźnionej i jej 
składniki otrzymują dostęp do wszystkich – także 
prywatnych – składników klasy zaprzyjaźnionej.

• Jedną klasę możemy zaprzyjaźnić z wieloma 

innymi klasami.

• Przyjaźń nie jest przechodnia.
• Zaprzyjaźnianie klas jest wyłączone z procesu 

dziedziczenia, czyli zaprzyjaźnienie nie jest 
dziedziczone.


Document Outline