Constructori și inițializarea obiectelor
Constructorii reprezintă funcții speciale care au același nume ca și clasa, nu returnează niciun tip de valoare și permit inițializarea unui obiect în momentul creării sale. Astfel, se garantează că variabilele membru (câmpurile) ale clasei vor avea valori bine definite. La fiecare creare a unui nou obiect al clasei, se apelează constructorul.
În lecția anterioară, am folosit următoarea clasă:
#include <iostream>
class Person
{
public:
std::string name;
unsigned age;
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
};
int main()
{
Person person; // apelul constructorului
person.name = "Tom";
person.age = 22;
person.print();
}
Aici, la linia:
Person person;
se apelează constructorul implicit. Dacă nu definim manual un constructor în clasă, compilatorul generează automat unul implicit, fără parametri și care nu face nimic special.
Definirea unui constructor personalizat
Presupunem că dorim ca valorile name și age să fie setate în momentul creării obiectului. Definim un constructor astfel:
#include <iostream>
class Person
{
public:
std::string name;
unsigned age;
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
Person(std::string p_name, unsigned p_age)
{
name = p_name;
age = p_age;
std::cout << "Person has been created" << std::endl;
}
};
int main()
{
Person tom("Tom", 38); // apelul constructorului
tom.print();
}
Constructorul:
Person(std::string p_name, unsigned p_age)
primește doi parametri, le atribuie valorile câmpurilor name și age, apoi afișează un mesaj.
Dacă definim un constructor propriu, compilatorul nu mai generează automat constructorul implicit. Astfel, obiectele trebuie create prin apel explicit:
Person tom("Tom", 38);
Alternativ, putem crea obiecte și așa:
Person tom{"Tom", 38};
Person tom = Person("Tom", 38);
Ieșirea în consolă:
Person has been created
Name: Tom Age: 38
Crearea mai multor obiecte
Constructorii simplifică crearea mai multor obiecte diferite:
#include <iostream>
class Person
{
public:
std::string name;
unsigned age;
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
Person(std::string p_name, unsigned p_age)
{
name = p_name;
age = p_age;
std::cout << "Person has been created" << std::endl;
}
};
int main()
{
Person tom{"Tom", 38};
Person bob{"Bob", 42};
Person sam{"Sam", 25};
tom.print();
bob.print();
sam.print();
}
Ieșirea:
Person has been created
Person has been created
Person has been created
Name: Tom Age: 38
Name: Bob Age: 42
Name: Sam Age: 25
Constructori multipli
Putem defini constructori multipli pentru flexibilitate:
#include <iostream>
class Person
{
std::string name{};
unsigned age{};
public:
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
Person(std::string p_name, unsigned p_age)
{
name = p_name;
age = p_age;
}
Person(std::string p_name)
{
name = p_name;
age = 18;
}
Person()
{
name = "Undefined";
age = 18;
}
};
int main()
{
Person tom{"Tom", 38};
Person bob{"Bob"};
Person sam;
tom.print();
bob.print();
sam.print();
}
Ieșirea:
Name: Tom Age: 38
Name: Bob Age: 18
Name: Undefined Age: 18
Delegarea constructorilor
Pentru a evita repetarea codului, putem apela un constructor din alt constructor:
#include <iostream>
class Person
{
std::string name{};
unsigned age{};
public:
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
Person(std::string p_name, unsigned p_age)
: name(p_name), age(p_age)
{
std::cout << "First constructor" << std::endl;
}
Person(std::string p_name)
: Person(p_name, 18) // apelă primul constructor
{
std::cout << "Second constructor" << std::endl;
}
Person()
: Person("Undefined") // apelă al doilea constructor
{
std::cout << "Third constructor" << std::endl;
}
};
int main()
{
Person sam; // apelează constructorul delegat
sam.print();
}
Ieșirea:
First constructor
Second constructor
Third constructor
Name: Undefined Age: 18
Această tehnică se numește delegare a constructorilor.
Parametri cu valori implicite
Constructorii pot avea valori implicite pentru parametri:
#include <iostream>
class Person
{
std::string name;
unsigned age;
public:
Person(std::string p_name = "Undefined", unsigned p_age = 18)
{
name = p_name;
age = p_age;
}
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
};
int main()
{
Person tom{"Tom", 38};
Person bob{"Bob"};
Person sam;
tom.print(); // Name: Tom Age: 38
bob.print(); // Name: Bob Age: 18
sam.print(); // Name: Undefined Age: 18
}
Inițializarea constantelor și lista de inițializare
Pentru câmpuri const, trebuie folosită lista de inițializare, deoarece acestea trebuie inițializate înainte de intrarea în corpul constructorului:
#include <iostream>
class Person
{
const std::string name;
unsigned age{};
public:
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
Person(std::string p_name, unsigned p_age) : name{p_name}
{
age = p_age;
}
};
int main()
{
Person tom{"Tom", 38};
tom.print(); // Name: Tom Age: 38
}
Listele de inițializare reprezintă enumerări ale inițializatorilor pentru fiecare dintre variabile și constante, plasate după două puncte, după lista de parametri a constructorului:
Person(std::string p_name, unsigned p_age) : name{p_name}
Aici, expresia name{p_name} permite inițializarea constantei cu valoarea parametrului p_name. Valoarea este plasată între acolade, dar se pot folosi și paranteze rotunde:
Person(std::string p_name, unsigned p_age) : name(p_name)
Listele de inițializare pot fi utilizate în mod similar și pentru atribuirea valorilor variabilelor:
class Person
{
const std::string name;
unsigned age;
public:
void print()
{
std::cout << "Name: " << name << "\tAge: " << age << std::endl;
}
Person(std::string p_name, unsigned p_age) : name(p_name), age(p_age)
{ }
};
Când se utilizează liste de inițializare, este important să se țină cont de faptul că valorile trebuie transmise în ordinea în care constantele și variabilele sunt definite în clasă. Adică, în acest caz, în clasă mai întâi este definită constanta name, apoi variabila age. În consecință, valorile trebuie transmise în aceeași ordine. Prin urmare, la adăugarea unor câmpuri suplimentare sau la schimbarea ordinii celor existente, trebuie să aveți grijă ca totul să fie inițializat în ordinea corectă.