background image

 

 

Wykład 4

Funkcje zaprzyjaźnione 

i konstruktory 

kopiujące

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. Pamietajmy,  ż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

 

 

Destruktor

Destruktorem klasy o nazwie X jest funkcja o nazwie ~X

Jej deklaracja nie jest konieczna.

 Za każdym razem, 

gdy jest likwidowana klasa X automatycznie uruchamiany 
jest destruktor. Destruktor nie likwiduje jednak obiektów 
ani nie zwalnia fizycznie pamięci operacyjnej, która była 
zajmowana przez składniki klasy X. Przygotowuje on 
natomiast składniki klasy X do likwidacji prowadząc do 
zablokowania ich czynności lub dostępu do nich. 

Jeśli  konstruktor  lub  obiekt  klasy  posługiwał  się 
operatorem  new  w  celu  dynamicznego  przydzielenia 
pamięci,  to  destruktor  powinien  zawierać  operator 
delete.
Jako  funkcja  destruktor  nie  może  zwracać  żadnych 
wartości czyli jest typu void.

background image

 

 

Musi  być  wywoływany  bez  argumentów,  czyli  nie  może 
być  przeładowany.    Czyli  do  każdego  konstruktora 
uruchomi się „jego” destruktor.
Jest  uruchamiany  automatycznie  wtedy,  kiedy  obiekt 
wychodzi  ze  swojego  zakresu  ważności,  poza  sytuacją, 
kiedy  obiekt  jest  typu  static  albo  const.  Taki  obiekt  jest 
likwidowany  destruktorem  po  zakończeniu  programu. 
Także  wówczas,  gdy  obiekt  typu  static  jest  wywoływany 
przez  referencje  lub  wskaźnik  wyjście  obiektu  z  jego  z 
zakresu ważności nie uruchamia destruktora.

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

 

 

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.
2.W  sytuacji  kiedy  funkcja  zwraca  przez  wartość  obiekt 
klasy X. Wtedy także tworzona jest kopia obiektu.
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

 

 

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 typu const, czyli nie może zmienić sam siebie.

background image

 

 

 
 
1.#include<iostream.h>
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. (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:
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.

Wiersz 8: analogiczny jak wiersz 5 ale dla klasy Y 
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.  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

 

 

Wiersz  13:  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. 
Wiersz  15:  do  tablicy  wskazywanej  przez  wskaźnik  p 
wpisujemy poprzez kopiowanie łańcucha wartość ’’111”. 

background image

 

 

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.
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 20: modyfikujemy łańcuch w obiekcie d.
Wiersz  21:  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ę. 
Wiersz  22:  wydruk  adresów  obiektów  y  oraz  d.  Powinny 
być różne!!
Wiersz  25-27:  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

 

 

Rezultat na ekranie:
 
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


Document Outline