Wykład 2
Konstruktory i destruktory
Aby klasa (typ definiowany przez użytkownika) przypominała swoim zachowaniem typy wbudowane, wprowadzono specjalne rodzaje funkcji składowych: W tej części wykładu zajmiemy się pierwszym rodzajem tych funkcji.

Co o konstruktorze wiedzieć należy

  1. Konstruktor jest specjalną funkcją składową, która nazywa się tak samo jak klasa z której pochodzi.
  2. Konstruktor nie zwraca żadnej wartości (nawet void).
  3. Jeśli klasa ma odpowiedni konstruktor, to jest on automatycznie wywoływany przy definiowaniu każdego obiektu tej klasy.
  4. Jesli klasa nie ma ŻADNEGO konstruktora wówczas automatycznie zostanie wygenerowany konstruktor domniemany.
  5. Zawierać może instrukcje nadające wartości początkowe składowym obiektu (w definicji klasy wartości nie można nadawać).
  6. Jeśli obiekt ma być składnikiem uni, to jego klasa nie może mieć żadnego konstruktora.

#include <stdio.h>

class klasa
{
public:
  static int licznik;
  int x;
  int y;
  klasa(void);//konstruktor domniemany
  klasa(int a, int b=2);
  void wypisz();
};

klasa::klasa(void)
{
  licznik++;
  printf("Konstruktor 1\n");
}

klasa::klasa(int a, int b)
{
  x=a;
  y=b;
  licznik++;
  printf("Konstruktor 2\n");
}

void klasa::wypisz()
{
  printf("licznik=%d, x=%d, y=%d\n",licznik,x,y);
}

int klasa::licznik=0;

int main()
{
  klasa k1;
  klasa k2(2);
  klasa k3(3,4);

  k1.wypisz();
  k2.wypisz();
  k3.wypisz();
  
  return 0;
}
Listing 2.1


Konstruktor 1
Konstruktor 2
Konstruktor 2
licznik=3, x=0, y=0
licznik=3, x=2, y=2
licznik=3, x=3, y=4
Output 2.1 Efekt działania programu z listingu 2.1 W linijce
licznik=3, x=0, y=0
zamiast zer mogą pojawić się inne liczby jako, że wartości dla zmiennej x i y są "losowe".


Kiedy i jak wywolujemy konstruktor

Wywołania niejawne

Obiekty lokalne

Konstruktor obiektu uruchamiany jest w momencie, gdy napotykamy na definicję tego obiektu.
{ //rozpoczynamy lokalny blok

  pijazd bicykl; //definicja obiektu bicykl klsy pojazd;
                 //wywołanie niejawne konstruktora
}
Obiekty statyczne istnieją od początku programu aż do jego końca, ale są dostępne tylko w odpowiednim zakresie ważności. Zatem konstruktor obiektu statycznego powinien być uruchamiany jeszcze przed wykonaniem main.

Obiekty globalne

Konstruktor uruchamiany jest przed rozpoczęciem wykonywania main.

Obiekty tworzone przy pomocy operator new

Konstruktor uruchamiany jest w momencie wywołania operatora new.

Wywołania jawne

Obiekt może być stworzony przez jawne wywołanie konstruktora. Otrzymujemy wówczas obiekt, który nie ma nazwy, a czas jego życia ogranicza się do wyrażenia, w którym go użyto.
nazwaKlasy(arg)

#include <stdio.h>

class klasa
{
public:
  klasa(int i);
};

klasa::klasa(int i)
{
  printf("Linia z konstruktorem o numerze %d\n",i);
}

void nie_rob_nic(klasa k)
{
}

klasa k1(1);

int main()
{
  printf("Zaczynam main...\n");
  {
    klasa k2(2);
  }
  {
    static klasa k3(3);
  }

  klasa *wsk;
  wsk=new klasa(4);

  klasa k5(5);

  nie_rob_nic(klasa(6));

  return 0;
}
Listing 2.2


Linia z konstruktorem o numerze 1
Zaczynam main...
Linia z konstruktorem o numerze 2
Linia z konstruktorem o numerze 3
Linia z konstruktorem o numerze 4
Linia z konstruktorem o numerze 5
Linia z konstruktorem o numerze 6
Output 2.2 Efekt działania programu z listingu 2.2

Lista inicjalizacyjna konstruktora

Stały składnik klasy i jego inicjalizacja

Jak wiemy akładników klasy nie można inicjalizować w ciele klasy, czyli poniższy zapis jest niepoprawny
class myClass
{
  int x = 2; //to nie przejdzie :))
}
Z polami typu const jest jeszcze trudniej, bo jako stałe nie możemy zmieniać ich wartości. Do zainicjalizowania ich wartości wykorzystamy konstruktor a dokłanie jego listę inicjalizacyjną
nazwaKlasy  :: nazwaKlasy (argumenty) : listaInicjalizacyjna
{
  //ciało konstruktora
}
gdzie listaInicjalizacyjna przyjmuje postać
składnik1(wartość1), składnik2(wartość2), ... , składnikn(wartośćn)
Za pomocą listy inicjalizacyjnej możemy nadawać wartości także składowym nie-const, ale nie możemy tego zrobić w stosunku do składowych typu static.

#include <stdio.h>

class klasa
{
public:
  int x;
  int y;
  const int z;
  klasa(int a1, int a2, int a3);
  void wypisz();
};

klasa::klasa(int a1, int a2, int a3):x(a1),z(a3)
{
  y=a2;
}

void klasa::wypisz()
{
  printf("x=%d, y=%d, z=%d\n",x,y,z);
}

int main()
{
  klasa k(1,2,3);

  k.wypisz();

  return 0;
}
Listing 2.3


x=1, y=2, z=3
Output 2.3 Efekt działania programu z listingu 2.3

Konstruktor dla obiektu, którego składnikiem są obiekty innych klas

Obiekt jakiejś klasy, będący składnikiem innej klasy, może być inicjalizowany jedynie za pomocą listy inicjalizacyjnej. Kolejność jest następująca: najpierw konstruktor dla obiektów składowych, potem konstruktor klasy zawierającej te obiekty.

#include <stdio.h>

class klasa1
{
public:
  int x;
  int y;
  klasa1(int a1, int a2);
  void wypisz();
};

class klasa2
{
public:
  int x;
  int y;
  klasa1 z;
  klasa2(int b1, int b2, int b3);
  void wypisz();
};

klasa1::klasa1(int a1, int a2)
{
  printf("Rusza konstruktor klasy 1...\n");
  x=a1;
  y=a2;
}

klasa2::klasa2(int b1, int b2, int b3):y(b2),z(b3,4)
{
  printf("Rusza konstruktor klasy 2...\n");
  x=b1;
}

void klasa1::wypisz()
{
  printf("Zawartosc klasy 1: x=%d, y=%d\n",x,y);
}

void klasa2::wypisz()
{
  printf("Zawartosc klasy 2: x=%d, y=%d\n",x,y);
  z.wypisz();
}

int main()
{
  klasa1 k1(1,2);
  klasa2 k2(6,7,8);

  k1.wypisz();
  k2.wypisz();

  return 0;
}
Listing 2.4


Rusza konstruktor klasy 1...
Rusza konstruktor klasy 1...
Rusza konstruktor klasy 2...
Zawartosc klasy 1: x=1, y=2
Zawartosc klasy 2: x=6, y=7
Zawartosc klasy 1: x=8, y=4
Output 2.4 Efekt działania programu z listingu 2.4

Konstruktor kopiują


#include <stdio.h>

class klasa
{
public:
  int x;
  klasa(void);
  klasa(int i);
  klasa(klasa & k);
  void wypisz();
};

klasa::klasa(void)
{
  printf("Konstruktor 1\n");
}

klasa::klasa(int i)
{
  printf("Konstruktor 2\n");
  x=i;
}

klasa::klasa(klasa & k)
{
  printf("Konstruktor 3\n");
  x=k.x;
}

void klasa::wypisz()
{
  printf("Zawartosc klasy: x=%d\n",x);
}

klasa funkcja(klasa k)
{
  return k;
}

int main()
{
  klasa k1;
  klasa k2(7);
  klasa k3=klasa(k2);//jawne wywolanie konstr. kop
  klasa k4=k2;//prawie jawne wywolanie konstr. kop

  k1.wypisz();
  k2.wypisz();
  k3.wypisz();
  k4.wypisz();

  k1=funkcja(k4);

  k1.wypisz();
  k2.wypisz();
  k3.wypisz();
  k4.wypisz();

  return 0;
}
Listing 2.5


Konstruktor 1
Konstruktor 2
Konstruktor 3
Konstruktor 3
Zawartosc klasy: x=0
Zawartosc klasy: x=7
Zawartosc klasy: x=7
Zawartosc klasy: x=7
Konstruktor 3
Konstruktor 3
Zawartosc klasy: x=7
Zawartosc klasy: x=7
Zawartosc klasy: x=7
Zawartosc klasy: x=7
Output 2.5 Efekt działania programu z listingu 2.5

#include <stdio.h>
#include <string.h>

class klasa
{
public:
  char * tekst;
  klasa(char * t);
  void wypisz();
  void zamien_napis(char * t);
};

klasa::klasa(char * t)
{
  tekst = new char[30];
  strcpy(tekst,t);
}

void klasa::wypisz()
{
  printf("Zawartosc klasy: tekst=%s\n",tekst);
}

void klasa::zamien_napis(char * t)
{
  strcpy(tekst,t);
}

int main()
{
  klasa k1("tescik");
  klasa k2=k1;

  k1.wypisz();
  k2.wypisz();

  k1.zamien_napis("zamiana");

  k1.wypisz();
  k2.wypisz();
  
  return 0;
}
Listing 2.6


Zawartosc klasy: tekst=tescik
Zawartosc klasy: tekst=tescik
Zawartosc klasy: tekst=zamiana
Zawartosc klasy: tekst=zamiana
Output 2.6 Efekt działania programu z listingu 2.6
Co się stało? Popatrzmy na rysunek poniżej:


#include <stdio.h>
#include <string.h>

class klasa
{
public:
  char * tekst;
  klasa(char * t);
  klasa(klasa & k);
  void wypisz();
  void zamien_napis(char * t);
};

klasa::klasa(char * t)
{
  tekst = new char[30];
  strcpy(tekst,t);
}

klasa::klasa(klasa & k)
{
  tekst = new char[30];
  strcpy(tekst,k.tekst);
}

void klasa::wypisz()
{
  printf("Zawartosc klasy: tekst=%s\n",tekst);
}

void klasa::zamien_napis(char * t)
{
  strcpy(tekst,t);
}

int main()
{
  klasa k1("tescik");
  klasa k2=k1;

  k1.wypisz();
  k2.wypisz();

  k1.zamien_napis("zamiana");

  k1.wypisz();
  k2.wypisz();
  
  return 0;
}
Listing 2.7


Zawartosc klasy: tekst=tescik
Zawartosc klasy: tekst=tescik
Zawartosc klasy: tekst=zamiana
Zawartosc klasy: tekst=tescik
Output 2.7 Efekt działania programu z listingu 2.7

Destruktor