Diversity w polskim IT
Drishtant Regmi
Drishtant RegmiAssociate Software Engineer @ Reduct Nepal

Jak działają konstruktory w C++

Poznaj definicję i działanie konstruktorów oraz zobacz, jaką rolę pełnią w C++.
14.01.20215 min
Jak działają konstruktory w C++

W tym artykule skupimy się na konstruktorach. Będziemy mówić o ich użyciu oraz o typach konstruktorów w C++, obrazując to wszystko przykładami. 

Czym są konstruktory?

Są to specjalne metody klasy, które inicjują dane składowe obiektu. Konstruktory są automatycznie uruchamiane dla każdego obiektu, a w programowaniu obiektowym są publiczne i często nazywają się tak samo, jak klasa. Nie mają one też typu zwracanego. 

Przyjrzymy się teraz prostej klasie z takim konstruktorem.

#include <iostream>
using namespace std;
class Example
{
   public:  //Access Specifier
   Example() // Constructor
   {
       cout << "I am in a constructor"; //Body of the constructor
   }
};
int main()
{
   Example ExampleObject;    // Create an object(ExampleObject) of  
                             //class(Example), which will call the
                             //constructor.
   return 0;
}


Klasa:
Example
Konstruktor: Example()
Obiekt klasy: ExampleObject

Nasz program ma klasę o nazwie Example, co oznacza, że jej konstruktor będzie miał taką samą nazwę, czyli Example(). Kiedy wywołujemy obiekt (ExampleObject) w funkcji main, to konstruktor jest automatycznie uruchamiany i daje następujący wynik:

I am in a constructor


Konstruktory można również definiować poza klasą, korzystając z jej nazwy i operatora zakresu (::). 

#include <iostream>
using namespace std;
class Example
{
  public:
  Example(); // Constructor declaration
};
Example::Example() // Constructor Definition
{
   cout << "I am in a constructor"; //Body of the constructor
};
int main()
{
  Example ExampleObject;
  return 0;
}


Otrzymamy wtedy taki sam wynik, jak przy pierwszym programie. 

I am in a constructor


Kiedy tworzymy obiekt, to wykonywany jest jeden z jego konstruktorów. Jeśli jednak nie mamy żadnego konstruktora, to wygeneruje go kompilator. Nie będzie on jednak przyjmował żadnego argumentu. 

Parametry w konstruktorze 

Parametr to specjalny rodzaj zmiennej, której używa się do przekazywania argumentów do klasy, lub funkcji. Konstruktory mogą przyjmować parametry w taki sam sposób, jak zwykła funkcja, co jest kluczowe dla konfiguracji początkowych wartości. 

#include <iostream>
using namespace std;
class Player
{
  public:           // Access specifier
  string name;     // Attribute
  string club;     // Attribute
  int age;        // Attribute
  //Constructor with parameters  
  Player(string x, string y, int z): name(x), club(y), age(z){}
};
int main()
{
   Player p1("Messi", "Barcelona", 33); //Object for Constructor
   Player p2("Ronaldo", "Juventus", 35); //Object for Constructor
   // Printing values
   cout << p1.name<< " " << p1.club << " " << p1.age << "\n";
   cout << p2.name<< " " << p2.club << " " << p2.age << "\n";
   return 0;
}


Powyższa klasa ma następujące atrybuty: name, club i age oraz konstruktor z parametrami. Tworzymy obiekt klasy w głównej funkcji i tam też wywołujemy konstruktor. Co więcej, przekazujemy do konstruktora analogiczne dane, aby zainicjalizować atrybuty. 

Output:
Messi Barcelona 33
Ronaldo Juventus 35


Często też widzimy, że pola klasy są inicjalizowane wewnątrz ciała konstruktora tak jak poniżej. Nie jest to niestety dobra praktyka.

// Not the best practice!
fruit()
{
   name="apple"
}

Dlaczego używamy konstruktorów?

Konstruktorów używa się do inicjalizowania pól klasy, szczególnie tych, które są wymagane do działania metod. Na przykład, jeśli mamy obiekt dla koła, to ważne jest, aby podać jego promień, aby metody, takie jak ShowArea(), mogły zadziałać bez błędów. 

Konstruktor zainicjuje wartość i sprawi, że dane podczas wykonywania danej metody będą prawidłowe.  

#include <iostream>
using namespace std;
class Area
{
   private:
   int radius;
   public:
   Area(): radius(10.0){ } //Constructor
   void FindRadius()
   {
      cout << "Enter radius: ";
      cin >> radius;
   }
   float FindArea()
   {
      return (3.14 * radius * radius);
   }
   void ShowArea(float rad)
   {
       cout << "Area of circle with user input: " << rad;
   };
int main()
{
   Area A1, A2;
   float rad;
   A1.FindRadius();
   rad = A1.FindArea();
   A1.ShowArea(rad);
   cout << endl << "Area of circle when radius is automatically
                                                 initialized: ";
   rad = A2.FindArea();
   A2.ShowArea(rad);
   return 0;
}


W naszym programie mamy dwa obiekty klasy Area: A1 oraz A2. Następnie wywołujemy funkcję FindRadius(), a ta potrzebuje od użytkownika informacji nt. promienia koła. Dzięki niej ShowArea() może wydrukować powierzchnię okręgu.  

Następnie widzimy, że wartość przypisana w konstruktorze (radius=10) to nasz promień. Wartość ta zostanie użyta do wyliczenia powierzchni. 

Enter radius: 5
Area of circle with user input: 78.5
Area of circle when radius is automatically initialized: 314


Dobra praktyka:
zawsze podawaj wartości domyślne dla pól.

Główne typy konstruktorów w C++

  1. Domyślny konstruktor
  2. Konstruktor
  3. Konstruktor kopiujący 


Domyślny konstruktor

Ten rodzaj konstruktora nie przyjmuje żadnych argumentów. Nie mają one też żadnych parametrów, ale mogą mieć parametry z wartościami domyślnymi. Pierwszy program z tego artykułu to przykład z domyślnym konstruktorem.

#include <iostream>
using namespace std;
class Year
{
  public:
  int date;
  
  Year(): date(2020) {} //Default Constructor
};
int main()
{
  Year y; //Object for constructor
  cout << y.date;
}


Klasa Year ma domyślny konstruktor, który gdy zostaje wywołany, ustawia wartość date na 2020. Kiedy zmienna date z klasy Year jest wywoływana i drukowana, to daje następujący wynik:

2020


Konstruktor

Są to konstruktory posiadające parametry. Przekazując odpowiednie wartości, możemy przypisywać różne wartości do składowych obiektów. Program pokazany w sekcji „Parametry w konstruktorze” to właśnie taki konstruktor. 

#include <iostream>
using namespace std;
class Player 
{        
    public:          // Access specifier
    string name;  // Attribute
    Player(string x): name(x) {} // Constructor with parameter
};
int main() 
{
  Player p1("Messi"); //Object for Constructor
  Player p2("Ronaldo"); 
  Player p3("Bale");
  // Print values
  cout << p1.name<<"\n";
  cout << p2.name<<"\n";
  cout << p3.name<<"\n";
  return 0;
}


Tutaj parametr konstruktora to ciąg znaków zawierający nazwę gracza. Gdy wartość zostaje przekazana, ciąg znaków zostaje przypisany do zdefiniowanej zmiennej.

Output:
Messi
Ronaldo
Bale


Konstruktor kopiujący

Tego konstruktora używa się do tworzenia kopii istniejących już obiektów klas. 


Dla klasy Example z konstruktorem Example() i zdefiniowanym wcześniej obiektem E1 możemy utworzyć obiekt E2 będący dokładną kopią obiektu E1:

class Example
{
    Example()
    {
       cout<<"Object E1"
    }
}
int main()
{
   Example E1;
   Example E2(A1);
     //OR 
   Example E2 = E1; //Copies content of E1 to E2 if there is any
}


W tym przypadku obiekt E2 zawiera te same wartości, co obiekt E1. Ułatwia to sprawę, gdy zachodzi potrzeba duplikowania wartości, ponieważ wszystkie klasy mają domyślnie wbudowany konstruktor kopiujący.

Przeciążanie konstruktora w C++

Konstruktory w C++ można przeciążać. Przeciążone konstruktory występują, gdy domyślny i zwykły konstruktor zostają zdefiniowane w klasie - jeden ma parametr, a drugi nie. Konstruktory można również przeciążać, gdy posiadamy ich więcej i różnią się od siebie parametrami.

#include <iostream>
using namespace std;
class Player
{
    public:
    int no;
    string name;
    // first constructor
    Player(int x): no(x)
    {
        name = "None";
        cout<<no<<" "<<name;
    }
    // second constructor
    Player(int x, string str): no(x), name(str)
    {
        cout<<endl<<no<<" "<<name;
    }
};
int main()
{
    // Player P1 initialized with no 105 and name None
    Player P1(105);
    
    // Player P2initialized with roll no 11 and name Bale
    Player P2(11, "Bale");
}


W powyższym kodzie mamy dwa konstruktory z różnymi parametrami, co powoduje ich przeciążenie.

Output:
105 None
11 Bale


Gdy jawnie definiujemy dowolny konstruktor bez zdefiniowania konstruktora domyślnego, to kompilator go nam nie stworzy. W takim przypadku będziemy musieli samodzielnie dokonać definicji konstruktora domyślnego.


Oryginał tekstu w języku angielskim możesz przeczytać tutaj

<p>Loading...</p>