25. C++ Перегрузка конструкторов, методов и операторов

Перегрузка конструкторов, методов и операторов

Как было сказано ранее метод и конструктор по сути своей являются функциями, а функции как мы уже рассматривали можно перегружать, т.е. создавать несколько функций с одинаковыми именами, но разными аргументами или типом функции (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;

Аналогичным образом можно переопределить и другие операторы (> < >= <= == !=), арифметические [+ — * / %] логические, префиксный и постфиксный инкремент/декремент и даже операторы * & и [].

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *