Skocz do zawartości
  • 👋 Witaj na MPCForum!

    Przeglądasz forum jako gość, co oznacza, że wiele świetnych funkcji jest jeszcze przed Tobą! 😎

    • Pełny dostęp do działów i ukrytych treści
    • Możliwość pisania i odpowiadania w tematach
    • System prywatnych wiadomości
    • Zbieranie reputacji i rozwijanie swojego profilu
    • Członkostwo w jednej z największych społeczności graczy

    👉 Dołączenie zajmie Ci mniej niż minutę – a zyskasz znacznie więcej!

    Zarejestruj się teraz

Rekomendowane odpowiedzi

Opublikowano

Konstruktory

 

W poprzedniej lekcji musieliśmy utworzyć obiekt kowbojskie klasy PaczkaZapalek przed funkcją main, bo tylko obiekty globalne są zerowane przez kompilator na początku programu. Obiekty utworzone lokalnie [np. wewnątrz funkcji main] nie są zerowane i zawierają przypadkowe wartości. Czy oznacza to, że wszystkie składniki takich obiektów trzeba sobie za każdym razem poustawiać ręcznie? No więc, na szczęście nie ;-). Z pomocą przychodzą nam właśnie konstruktory, które poznasz w tym odcinku kursu.

 

 

Co to jest konstruktor?

 

Konstruktor to specjalna funkcja składowa w klasie, którą kompilator wywołuje automatycznie przy tworzeniu każdego obiektu tej klasy. Najważniejszym przeznaczeniem konstruktora jest... uwaga uwaga... nadawanie składnikom obiektu początkowych wartości! :-) Gdy umieścisz w klasie konstruktor, nie będziesz już musieć ręcznie ustawiać składników każdego tworzonego obiektu, bo kompilator zrobi to za ciebie automagicznie! ;-D I tu ważna uwaga! Wbrew temu, co myślą niektórzy, konstruktor nie tworzy obiektu w pamięci. Rezerwacja pamięci dla obiektu to przecież działka kompilatora. Konstruktor wkracza do akcji już po przydzieleniu pamięci dla obiektu, i jedynie nadaje jego składnikom początkową wartość. Jest czymś w rodzaju "dekoratora wnętrz" ;-).

 

Pewnie już nie możesz się doczekać, kiedy powiem ci jak umieścić w klasie taką specjalną funkcję ;-J. Już tłumaczę. Funkcja taka wyróżnia się tym, że nazywa się tak samo jak klasa, oraz nie ma typu zwracanej wartości. I wcale nie chodzi mi o to, że typem tym jest void. Konstruktor wogóle nie ma typu! Zresztą najlepiej wyjaśni to poniższy przykład:

 

 

 

class PaczkaZapalek {

private:

int ile_zapalek;

public:

PaczkaZapalek(); //Konstruktor !!!!!

void Dodaj(int ile);

void Wyjmij(int ile);

int Pokaz() { return ile_zapalek; }

};

 

 

 

 

Umieściliśmy w klasie zapowiedź konstruktora. Jego zadaniem będzie wypełnienie pudełka po brzegi, bo na co komu puste pudełko. Teraz kolej na napisanie definicji tego konstruktora, czyli co on ma robić:

 

 

 

PaczkaZapalek::PaczkaZapalek()

{

ile_zapalek = 40;

}

 

 

 

 

Od tej chwili gdziekolwiek byśmy nie utworzyli obiektu klasy PaczkaZapalek, dostaniemy pudełko zawierające równo 40 zapałek :-). Taki konstruktor programiści nazywają domyślnym, bo nie musimy mu mówić, jakie wartości mają mieć składniki obiektu. Jako dowód możesz sprawdzić, co zrobi poniższy kod umieszczony w funkcji main:

 

 

 

PaczkaZapalek kowbojskie; //Tutaj po cichu zadziała konstruktor

cout << "W paczce jest " << kowbojskie.Pokaz();

cout << " zapalek kowbojskich" << endl;

 

 

 

 

 

 

Konstruktory z parametrami

 

Czasami chcielibyśmy dostać pudełko z inną początkową ilością zapałek. Do tego także przydaje się konstruktor i zaraz dopiszemy sobie taki w naszej klasie.

 

 

 

class PaczkaZapalek {

private:

int ile_zapalek;

public:

PaczkaZapalek(); //Konstruktor domyślny

PaczkaZapalek(int pocz); //Konstruktor z parametrem

void Dodaj(int ile);

void Wyjmij(int ile);

int Pokaz() { return ile_zapalek; }

};

 

 

 

 

Nie, nie widzisz podwójnie ;-). Tam naprawdę są dwa konstruktory. Konstruktor, podobnie jak każda inna funkcja, może być przeładowywany. Kompilator sam się domyśli, którego z nich użyć. Jeśli użyjesz znanego ci już zapisu:

 

 

 

PaczkaZapalek firmowa;

 

 

 

 

to wiadomo, ruszy do pracy konstruktor domyśly. A jak zrobić, żeby zadziałał ten drugi? Przypomnij sobie, w jaki sposób inicjalizowało się zwykłą zmienną typu int:

 

 

 

int liczba = 13;

 

 

 

 

Tak się tworzyło liczbę i od razu nadawało się jej wartość początkową [inicjowało]. Powiedziałem nieco wcześniej, że konstruktor służy do nadawania wartości początkowej składnikom obiektu. Dlatego taki sam zapis zadziała dla naszego pudełka zapałek.

 

 

 

PaczkaZapalek czeskie = 38;

 

 

 

 

Wbrew pozorom to nie jest przypisanie. Przecież obiekt czeskie jest dopiero tworzony! Jeśli pamiętasz jeszcze, co pisałem o inicjalizacji w odcinku Zmienne i typy, ten znak równości na pewno cię nie zmylił ;-). Tak, to jest inicjalizacja. Dlatego rusza do pracy konstruktor klasy PaczkaZapalek. Jeśli nie bardzo to widać, możesz także jawnie go wywołać, stosując taki zapis:

 

 

 

PaczkaZapalek czeskie = PaczkaZapalek(38);

 

 

 

 

Teraz lepiej widać tam ten konstruktor i w jaki sposób dostaje on tą liczbę jako parametr. Dla kompilatora jednak nie ma to znaczenia. Nie musisz wywoływać konstruktora jawnie, bo i tak zostanie on wywołany. Możesz nawet napisać tak:

 

 

 

PaczkaZapalek czeskie(38);

 

 

 

 

Ten właśnie zapis jest najczęściej stosowany, bo jest najbardziej wygodny i pozwala na podawanie konstruktorowi większej ilości parametrów w nawiasie [oddzielonych przecinkami].

 

Zastanówmy się teraz, jak mogłoby wyglądać ciało naszego drugiego konstruktora. Najprościej pewnie jakoś tak:

 

 

 

PaczkaZapalek::PaczkaZapalek(int pocz)

{

ile_zapalek = pocz;

}

 

 

 

 

Takie coś jednak pozwala ustawić początkowo dowolną ilość zapałek, nawet więcej niż się zmieści ;-P. Trzeba dorzucić jakieś sprawdzanie, czy parametr nie przekracza pojemności pudełka. Niepełnosprytny programista pewnie wsadziłby tam jakąś instrukcję if itp., ale po co? Czy nie lepiej wykorzystać to, co już mamy? :-) Looknij se:

 

 

 

PaczkaZapalek::PaczkaZapalek(int pocz)

{

ile_zapalek = 0; Dodaj(pocz);

}

 

 

 

 

Najpierw zerujemy składnik ile_zapalek, a następnie wykorzystujemy funkcję Dodaj, która już potrafi sprawdzić czy nie wsadzamy do pudełka więcej, niż się da. Sprytnie, co nie? ;-) A dla cfaniaków mam jeszcze jeden "wałek": można zupełnie wywalić konstruktor domyślny robiąc taki oto przekręt:

 

 

 

class PaczkaZapalek {

private:

int ile_zapalek;

public:

PaczkaZapalek(int pocz=40); //Konstruktor z parametrem domyśnym

void Dodaj(int ile);

void Wyjmij(int ile);

int Pokaz() { return ile_zapalek; }

};

 

 

 

 

Konstruktor domyślny to przecież taki, który można wywołać bez parametrów. Powyższy konstruktor da się tak wywołać dzięki parametrowi z domyślną wartością. Gdy przy tworzeniu obiektu nie określimy początkowej ilości zapałek, domyślnie w pudełku będzie ich 40.

 

 

Co jeszcze potrafi konstruktor?

 

To jeszcze nie wszystkie zalety stosowania konstruktorów. Drugim ważnym zastosowaniem jest konwersja, czyli zamiana. Jak pewnie pamiętasz, w naszej klasie PaczkaZapalek umieściliśmy konstruktor pobierający typ int. Od tamtej chwili obiekty naszej klasy mogą być budowane na podstawie liczb typu int. Gdy będziemy chcieli utworzyć nasz obiekt tak:

 

 

 

PaczkaZapalek ruskie(36);

 

 

 

 

to konstruktor stworzy obiekt ruskie posługując się tą liczbą. Mówiąc w języku programistów, obiekt klasy int zostanie skonwertowany na postać obiektu naszej klasy. Jeśli w swojej klasie umieścisz konstruktor pobierający obiekt jakiejś innej klasy, to od tej chwili możesz budować obiekty swojej klasy na podstawie obiektów tej innej klasy. Zrób teraz mały eksperyment - wykomentuj zapowiedź i ciało konstruktora i spróbuj skompilować program z taką instrukcją w funkcji main:

 

 

 

PaczkaZapalek nowa = 45;

 

 

 

 

Kompilator wywali błąd, bo nie będzie wiedział, jak zamienić obiekt klasy int na obiekt twojej klasy. Żeby taki zapis był możliwy, musi istnieć konstruktor pobierający int.

×
×
  • Dodaj nową pozycję...