Перегрузка конструкторов, методов и операторов
Как было сказано ранее метод и конструктор по сути своей являются функциями, а функции как мы уже рассматривали можно перегружать, т.е. создавать несколько функций с одинаковыми именами, но разными аргументами или типом функции (const/не const).
Рассмотрим перегрузку конструкторов и методов на примере:
class Person
{
public:
Person(string Name,unsigned int age)
{
this->Name=Name;
this->age= new unsigned int (age); //создание переменной в динамической памяти
}
Person(string Name) //перегрузка конструктора
{
this->Name=Name;
this->age= new unsigned int (18); //создание переменной в динамической памяти
}
Person(const Person &AnotherPerson) //конструктор копирования
{
this->Name=AnotherPerson.Name;
this->age= new unsigned int (0); //создаем переменную
*(this->age)=*AnotherPerson.age; //копируем ЗНАЧЕНИЕ переменной
}
Person(Person &&AnotherPerson) //конструктор перемещения
{
this->Name=AnotherPerson.Name;
this->age=AnotherPerson.age; //копируем указатель на переменную
AnotherPerson.age=nullptr;
}
~Person()
{
delete age; //удаление переменной в динамической памяти
}
void getInfo()
{
cout<<"Name: "<< this->Name <<" age "<< *(this->age) <<endl; //выводим данные на экран
}
void getInfo(string Text) //перегрузка метода
{
cout<<Text<<"Name: "<< this->Name <<" age "<< *(this->age) <<endl; //выводим данные на экран
}
private:
string Name;
unsigned int * age; //указатель на переменную в динамической памяти
};
В функции main создадим 2 объекта с помощью 2 разных конструкторов и вызовем оба варианта метода getInfo();
Person c_Person1("Vasya",30);
c_Person1.getInfo(); //Выведет в консоль Name Vasya age 30
c_Person1.getInfo("Hello "); //Выведет в консоль Hello Name Vasya age 30
cout<<endl<<endl;
Person c_Person2("Petya");
c_Person2.getInfo(); //Выведет в консоль Name Petya age 18
c_Person2.getInfo("Hello "); //Выведет в консоль Hello Name Vasya age 18
cout<<endl<<"c_Person3 created"<<endl;
Person c_Person3(c_Person2);
c_Person3.getInfo(); //Выведет в консоль Name: Petya age 18
c_Person3.getInfo("Hello"); //
Думаю понятно, что компилятор «посмотрел» на типы аргументы функции и вызвал соответствующую реализацию конструктора и метода.
Повторюсь перегрузить деструктор НЕЛЬЗЯ!!! он должен быть только 1.
Конструктор копирования:
Отдельно стоит отметить конструктор копирования Person(const Person &AnotherPerson), дело в том, что в C++ существует конструктор копирования по умолчанию, он полностью копирует все поля (в том числе указатель) т.е. в результате работы конструктора копирования по умолчанию у нас будет 2 переменных-указателя, указывающих на одно и тоже место в памяти, в случае вызова деструктора для 2-х таких классов (основной + копированный) будет предпринята попытка дважды очистить одно и тоже место в памяти и программа завершится с ошибкой.
Для проверки, что адреса разные можно изменить модификатор доступа для переменных с private на public
и дописать следующий код:
//Проверка адресов у переменной age
cout<<"Address \"age\" c_Person2 "<<c_Person2.age<<endl;
cout<<"Address \"age\" c_Person3 "<<c_Person3.age<<endl;
Примечание: const Person &AnotherPerson — константная ссылка на объект класса, за счет нее мы не копируем значение класса, а обращаемся по адресу
Перегрузка операторов:
Классы можно рассматривать как переменные, а в этом случае с ними можно совершать математические операции и операции сравнения, но что такое например операция сравнения для класса Person, рассмотренная выше или операция равенства, или операция сложения/вычитания?
Согласитесь сравнивать указатели у разных объектов нелогично – в текущей реализации они наверняка указывают на разные области в памяти.
Или как можно прибавить/вычесть к/из имени число?
А при операции равенства (по умолчанию) произойдет полное копирование одного класса в другой, в результате мы получим утечку т.к. потеряем указатель на возраст в первоначальном классе.
Поэтому используются перегрузки (считайте что замены) операторов.
Оператор присваивания:
Напишем метод (внутри класса) для перегрузки оператора присваивания — его проблема такая же как у оператора копирования (оператор присваивания по умолчанию скопирует указатель)
//перегрузка оператора присваивания
Person & operator = (const Person &AnotherPerson)
{
this->Name=AnotherPerson.Name;
*(this->age)=*AnotherPerson.age; //копируем ЗНАЧЕНИЕ переменной
return *this;
}
логика идентична конструктору копирования, только переменная типа age уже создана.
возвращать ссылку на объект типа Person нужно для реализации логики: c_Person1=c_Person2=c_Person3; Все эти объекты будет инициализированы значениями c_Person3
После написания оператора присваивания можем его использовать, напишем следующий код:
cout<<endl<<"Creating c_Person4"<<endl;
Person c_Person4("Alex");
c_Person4=c_Person3;
c_Person4.getInfo();
cout<<"Address \"age\" c_Person2 "<<c_Person2.age<<endl;
cout<<"Address \"age\" c_Person3 "<<c_Person3.age<<endl;
cout<<"Address \"age\" c_Person4 "<<c_Person4.age<<endl;
Думаю общий принцип действия понятен.
Оператор присваивания по перемещению:
Напишем метод (внутри класса) для перегрузки оператора присваивания с перемещением
//перегрузка оператора присваивания по перемещению (копируем только указатели без memcpy)
Person & operator = (Person &&AnotherPerson)
{
if(this==&AnotherPerson) return *this;
delete this->age; //очищаем область памяти
this->Name=AnotherPerson.Name;
this->age=AnotherPerson.age; //копируем указатель на переменную
AnotherPerson.age=nullptr;
return *this;
}
Оператор сравнения:
Ради примера также напишем оператор сравнения:
bool operator == (const Person &AnotherPerson)
{
return (this->Name==AnotherPerson.Name) && (*this->age == *AnotherPerson.age);
}
Для проверки напишем код:
if(c_Person3==c_Person4) cout<<">> True"<<endl;
else cout<<">> False"<<endl;
Аналогичным образом можно переопределить и другие операторы (> < >= <= == !=), арифметические [+ — * / %] логические, префиксный и постфиксный инкремент/декремент и даже операторы * & и [].