Konstruktory w języku C++. Szczegółowa analiza krok po kroku

Cześć!

Zgodnie z obietnicą z poprzedniego postu, dzisiaj zajmiemy się konstruktorami. Operatorem przypisania(którego efekt działania jest podobny do konstruktora kopiującego) zajmiemy się innym razem. Od czego należy zacząć ?

Od definicji. Jest to specjalna metoda w ciele klasy, wywoływana na samym początku istnienia obiektu.

Na razie wystarczy definicji. Teraz wyjaśnijmy sobie jej znaczenia. Jak widać konstruktor będzie dosyć podobny do zwykłych metod. Ma on jedną tą różnicę że wywołuje się na samym początku istnienia instancji klasy(czyli obiektu).

Jak wygląda zapis ?

#include <iostream>
class MyClass
{

public:
MyClass()
{

std::cout<<"To ja konstruktor.\n";

}

};

int main(){

MyClass myClass;

}

I oto mamy Nasz konstruktor. No w sumie fajnie, ale po co mi konstruktor ?

Konstruktor wywołuje się na samym początku istnienia obiektu, przez co jest możliwe zainicjowanie zmiennych w klasie bez narażenia się na ich odczyt, gdy jeszcze nie istnieją. Jak to zrobić ?
class MyClass
{
int first,second,last;
public:
MyClass()
{
this->first=10;
this->second =20;
this->last =30;
std::cout<<"To ja konstruktor.\n";

}

};

Dlaczego tak ważne jest inicjowanie zmiennych za pomocą kontruktora ?
Wyobraźmy sobie sytuację, że piszemy program mający mnóstwo linii kodu, który wykonuje się w wielu miejscach jednocześnie(na wielu rdzeniach procesora, czyli na niejako kilku procesorach w jednej obudowie. Komputery, które mamy w domu mają zazwyczaj więcej niż 1 rdzeń. Często mają po 4 rdzenie), mogą używać zmiennych jeszcze niezainicjalizowanych. Czyli mających śmieci z pamięci. Proponuję zrobić eksperyment.

int n;
std::cout<<n<<'\n';

Wynik tego typu operacji(jeśli zmienna nie jest zmienną globalną, mającą domyślnie 0) będzie zmienną która wydaje się być losowa(tak naprawdę to nie ma w komputerze losowości, ale o tym może kiedy indziej).


Lista inicjalizująca

Lista inicjalizująca jest rozszerzeniem konstruktora. Nie ma listy inicjalizującej bez konstruktora.
Pozwala ona na inicjalizowanie zmiennych stałych typu const, oraz używanie konstruktorów w klasach bazowych. Czyli tych, z których klasa dziedziczy.

Pokażmy to na przykładzie

class A
{
int n;
public:
A(int n)
{

this->n=n;

}
};
class B
{
const int y;
A a;
public:
B() :y(10), a(3)
{

}
};

To co w powyższym przykładzie jest dla Nas dotychczas nieznane to:
B() :y(10), a(3)
{

}
Jest to zapis listy inicjalizacyjnej. Zapis ma postać:
Nazwa(argumenty),zmienne do zainicjowania rozdzielone przecinkiem. To czym mają być zainicjowane podajemy w nawiasach. Warto wspomnieć, że listy inicjalizujące są nieco szybsze niż zwykłe konstruktory(https://stackoverflow.com/questions/13214241/c-constructors-vs-initialization-lists-speed-comparison)

Konstruktor kopiujący

Z pojęciem konstruktora kopiującego wiąże się bezpośrednio pojęcie operatora przypisania. Na początku wyjaśnijmy jaki jest cel stworzania konstruktora kopiującego oraz operatora przypisania. Jak sama nazwa mówi, oba będą coś kopiowały lub przypisywały. Mimo różnic w nazwie pełnią one tę samą rolę. Przypisują jeden obiekt do drugiego. Ale dlaczego nie mogę napisać sobie po prostu:


a=b;

tak było to robione przy zwykłym przypisaniu ? Po co mi to ?

Z prostego względu. Nie można po prostu przypisać jednego obiektu do drugiego. A już zwłaszcza jeśli mamy dwie różne klasy. W związku z tym ażeby przypisać obiekt dom drugiego należy podać co chemy w Nim zmienić. Co daje wygodę. Nie zawsze chcemy przypisywać wszystkie dane do innego obiektu. Co ciekawe gdy nie stworzymy kontruktora kopiującego, lub operatora przypisania, one są tworzone automatycznie i domyślnie przez kompilator. Konstruktor kopiujący ma postać: klasa(obiekt klasy). Często spotyka się również dodatkowo przesyłanie w postaci const, oraz przesyłanie oryginałów, dzięki czemu zaoszczędza się czas. Pokażmy działanie konstruktora kopiującego na przykładzie:

#include <iostream>

class myClass
{
int a,b,c;
  public:
      myClass():a(0),b(3),c(4){

      }
      myClass(int a,int b,int c):a(a),b(b),c(c)
      {
///konstruktor mozna ustawiac
      }
myClass(const myClass &myObject)
      {
          std::cout<<"Hello!\n";
          this->a=myObject.a;
          this->b=myObject.b;
          this->c=myObject.c;

      }
void showValue()
      {
          std::cout<<a<<" "<<b<<" "<<c<<'\n';
      }
};
int main()
{

myClass my1;
myClass my2(10,20,30);
my1.showValue();
my1=my2;
my1.showValue();

return 0;
}

Powyższy kod pokazuje, że istotnie zmieniliśmy wartości. No, ale Szymon, ja po prostu zamiast męczyć się z konstruktorami kopiującymi i tego typu, postanowiłem, że nie napiszę tego i po prostu my1=my2 i bez tworzenia w klasie konstruktorów kopiujących. I co ? Działa. No... nie do końca zawsze Ci będzie działało. Przy alokacji pamięci dynamicznie konstruktor kopiujący tworzony dynamicznie może nie zadziałać najlepiej, a czasami może po prostu nie przypisać zmiennych. Nie będę pokazywać jak to wygląda, gdyż materiał przeciąga się w nieskończoność, a dodatkowy kod i jego wyjaśnienie zostawmy na inną okazję. Kwestia operatora przypisania i innych operatorów zostanie poruszona przy przeciążaniu operatorów.

Komentarze

Popularne posty z tego bloga

Co dokładnie oznacza std::cout<<"Witaj Swiecie!"<<'\n''; w C++ ?

Klasy, Przestrzenie nazw, Nonlocal, Global i Local. Czy Python ma zmienne prywatne ?

Przeciążanie operatorów w C++