“Дружні” операторні функції: відмінності, реалізація, особливості застосування

“Дружні” операторні функції: відмінності, реалізація, особливості застосування. Перевантаження операторів +, –, *, / з допомогою “дружніх” операторних функцій

Перед вивченням даної теми рекомендовано ознайомитись з темою:


Зміст


1. Відмінності в параметрах між операторною функцією, яка реалізована всередині класу, та “дружньою” операторною функцією

Як відомо, існує два способи перевантаження будь-якого оператору:

  • з допомогою операторної функції, яка реалізована всередині класу;
  • з допомогою операторної функції, яка реалізована як “дружня” (friend) до класу.

Між способами реалізації операторних функцій існує відмінність у кількості параметрів, які отримує операторна функція:

  • для унарних операторів операторна функція всередині класу не отримує параметрів. А “дружня” до класу операторна функція отримує один параметр;
  • для бінарних операторів операторна функція всередині класу отримує один параметр. А “дружня” функція отримує два параметри. У цьому випадку першим параметром “дружньої” функції є лівий операнд, а другим параметром є правий операнд.

Ці відмінності виникають через те, що “дружня” операторна функція не отримує неявного покажчика this. Тому в ній потрібно явно задавати параметри.

 

2. Загальна форма операторної функції, що реалізована за межами класу (“дружня” функція до класу)

Операторна функція може бути реалізована за межами класу. Якщо операторна функція перевантажує унарний оператор, то вона містить один параметр. Якщо операторна функція перевантажує бінарний оператор, то вона містить два параметри.
Загальна форма операторної функції, що є дружньою до класу має вигляд

return_type operator#(arguments_list)
{
    // деякі операції
    // ...
}

де

  • return_type – тип значення, що повертає операторна функція;
  • operator# – ключове слово, що визначає операторну функцію в класі. Символ # визначає оператор мови C++, який перевантажується. Наприклад, якщо перевантажується оператор +, то потрібно вказати operator+;
  • argument_list – список параметрів, які отримує операторна функція. Якщо у “дружній” функції перевантажується бінарний оператор, то argument_list містить два аргументи. Якщо перевантажується унарний оператор, то argument_list містить один аргумент.

У класі ця функція має бути оголошена як “дружня” з ключовим словом friend.



 

3. Приклад перевантаження бінарного оператора ‘–’ у класі для якого реалізована “дружня” операторна функція

За даним прикладом можна розробляти власні операторні функції, які є “дружніми” до заданого класу.
Задано клас Complex, який реалізує комплексне число. У класі оголошуються внутрішні змінні, конструктори, методи доступу та “дружня” функція operator-(). “Дружня” функція operator-(), що реалізована за межами класу, здійснює віднімання комплексних чисел.

// клас Complex
class Complex
{
private:
    float real; // дійсна частина
    float imag; // уявна частина

public:
    // конструктори
    Complex(void)
    {
        real = imag = 0;
    }

    Complex(float _real, float _imag)
    {
        real = _real;
        imag = _imag;
    }

    // методи доступу
    float GetR(void) { return real; }
    float GetI(void) { return imag; }

    void SetRI(float _real, float _imag)
    {
        real = _real;
        imag = _imag;
    }

    // оголошення "дружньої" до класу Complex операторної функції
    friend Complex operator-(Complex c1, Complex c2);
};

// "дружня" до класу Complex операторна функція,
// реалізована за межами класу,
// здійснює віднімання комплексних чисел
Complex operator-(Complex c1, Complex c2)
{
    Complex c; // створити об'єкт класу Complex

    // віднімання комплексних чисел
    c.real = c1.real - c2.real;
    c.imag = c1.imag - c2.imag;

    return c;
}

Використання класу Complex в іншому методі

// використання "дружньої" операторної функції
Complex c1(5,6);
Complex c2(3,-2);
Complex c3; // результат
float a, b;

// перевірка
a = c1.GetR(); // a = 5
b = c1.GetI(); // b = 6

// виклик "дружньої" до класу Complex операторної функції
c3 = c1 - c2;

// результат
a = c3.GetR(); // a = 5-3 = 2
b = c3.GetI(); // b = 6-(-2) = 8

Як видно з вищенаведеного прикладу, “дружня” функція operator-() отримує два параметри. Перший параметр відповідає лівому операнду в бінарному операторі віднімання ‘–’. Другий параметр відповідає правому операнду.

 

4. Приклад перевантаження оператора ‘/’, що обробляє клас, який містить динамічний масив дійсних чисел. Операторна функція реалізована як “дружня” функція

У прикладі продемонстровано, як правильно реалізовувати перевантаження “дружніх” операторних функцій для класів, в яких пам’ять виділяється динамічно. Клас містить динамічний масив елементів типу float.

Оголошується клас ArrayFloat, який реалізує динамічний масив дійсних чисел. У класі реалізовані такі елементи:

  • внутрішня змінна size – розмір масиву;
  • покажчик A на тип float. Це є масив, пам’ять для якого виділяється динамічно;
  • конструктор класу ArrayFloat() без параметрів;
  • конструктор з двома параметрами ArrayFloat(int, float*);
  • конструктор копіювання ArrayFloat(const ArrayFloat&). Цей конструктор необхідний для уникнення недоліків побітового копіювання. Без конструктора копіювання робота об’єктів класу буде мати помилки;
  • деструктор ~ArrayFloat(). У деструкторі звільняється пам’ять, виділена для динамічного масиву A;
  • методи GetSize(), SetSize() для доступу до змінної size;
  • методи GetAi(), SetAi() для доступу до конкретного елементу масиву з заданим індексом;
  • метод FormArray(), який формує елементи масиву за деякою формулою;
  • метод Print(), який виводить масив;
  • операторна функція operator=(const ArrayFloat&). Ця функція перевантажує оператор присвоєння =, який реалізує копіювання об’єктів. Функція необхідна для уникнення недоліків побітового копіювання у випадку присвоювання об’єктів obj1=obj2;
  • “дружня” до класу ArrayFloat операторна функція operator/(const ArrayFloat&, const ArrayFloat&), яка перевантажує оператор ділення / для даного класу. Функція реалізує поелементне ділення масивів. Якщо кількість елементів масивів не співпадає, то результуючий масив встановлюється в розмір найменшого масиву.

Реалізація класу для додатку типу Console Application наступна:

#include <iostream>
using namespace std;

// клас - масив типу float
class ArrayFloat
{
private:
  int size;
  float * A; // динамічний розмір масиву

public:
  // конструктори класу
  // конструктор без параметрів
  ArrayFloat()
  {
    size = 0;
    A = nullptr;
  }

  // конструктор з двома параметрами
  ArrayFloat(int nsize, float * nA)
  {
    size = nsize;
    A = new float[size];

    for (int i = 0; i < nsize; i++)
      A[i] = nA[i];
  }

  // конструктор копіювання,
  // потрібний для уникнення недоліків побітового копіювання
  ArrayFloat(const ArrayFloat& nA)
  {
    if (size > 0)
    {
      delete[] A; // звільнити попередньо виділену пам'ять
    }

    // виділити пам'ять для A по іншому
    A = new float[nA.size];

    // виконати копіювання *this <= nA
    size = nA.size;
    for (int i = 0; i < size; i++)
      A[i] = nA.A[i];
  }

  // деструктор
  ~ArrayFloat()
  {
    // звільнити пам'ять, виділену для масиву A
    if (size > 0)
      delete[] A;
  }

  // методи доступу
  int GetSize(void) { return size; }

  void SetSize(int nsize)
  {
    if (size > 0)
      delete[] A;

    size = nsize;
    A = new float[size]; // виділити новий фрагмент пам'яті

    // заповнити масив нулями
    for (int i = 0; i < size; i++)
      A[i] = 0.0f;
  }

  // зчитати значення за індексом index
  float GetAi(int index)
  {
    if ((index >= 0) && (index < size))
      return A[index];
    else
      return 0;
  }

  // встановити нове значення
  void SetAi(int index, float value)
  {
    if ((index >= 0) && (index < size))
      A[index] = value;
  }

  // метод, що формує масив
  void FormArray()
  {
    for (int i = 0; i < size; i++)
      A[i] = (float)i;
  }

  // метод, що відображає масив
  void Print(const char* objName)
  {
    cout << "Object: " << objName << endl;
    for (int i = 0; i < size; i++)
      cout << A[i] << " ";
    cout << endl << endl;
  }

  // Перевантаження операторів
  // Перевантаження оператору копіювання operator=(const ArrayFloat&)
  ArrayFloat operator=(const ArrayFloat& nA)
  {
    if (size > 0)
      delete[] A; // звільнити попередньо виділену пам'ять

    // виділити пам'ять для A по іншому
    A = new float[nA.size];

    // виконати копіювання *this <= nA
    size = nA.size;
    for (int i = 0; i < size; i++)
      A[i] = nA.A[i];
    return *this;
  }

  // "дружня" операторна функція '/',
  // тільки оголошення, реалізація за межами класу
  friend ArrayFloat operator/(const ArrayFloat& A1, const ArrayFloat& A2);
};

// "дружня" до класу ArrayFloat операторна функція operator/()
// функція отримує два параметри A1, A2
ArrayFloat operator/(const ArrayFloat& A1, const ArrayFloat& A2)
{
  // створити об'єкт класу ArrayFloat
  ArrayFloat A; // викликається конструктор без параметрів
  int n;

  // взяти мінімальне значення з розміру двох масивів A1, A2
  n = A1.size;
  if (n > A2.size) n = A2.size;

  // встановити новий розмір масиву A, перерозподіляється пам'ять
  A.SetSize(n);

  // поелементне ділення
  for (int i = 0; i < n; i++)
  {
    if (A2.A[i] != 0) // обійти ділення на 0
      A.A[i] = A1.A[i] / A2.A[i];
    else
      A.A[i] = 0.0f;
  }
  return A; // повернути новий об'єкт
}

void main()
{
  // Демонстрація роботи класу ArrayFloat
  ArrayFloat AF1; // конструктор без параметрів
  AF1.FormArray();

  int size; // додаткові змінні
  float x;

  size = AF1.GetSize(); // size = 0
  x = AF1.GetAi(3); // x = 0
  cout << "size = " << size << endl;
  cout << "x = " << x << endl;

  ArrayFloat AF2 = AF1; // виклик конструктора копіювання
  cout << AF2.GetAi(3) << endl;

  ArrayFloat AF3;
  AF3.SetSize(10);
  AF3.FormArray();
  AF2 = AF3; // виклик оператора копіювання operator=()
  cout << "AF2[3] = " << AF2.GetAi(3) << endl;

  ArrayFloat AF4 = AF2; // виклик конструктора копіювання
  cout << "AF4[5] = " << AF2.GetAi(5) << endl;

  // оператор копіювання у вигляді "ланцюжка"
  AF1 = AF2 = AF4 = AF3; // виклик оператора копіювання operator=()
  cout << "AF1[8] = " << AF1.GetAi(8) << endl;

  // Демонстрація "дружньої" операторної функції operator/
  AF3 = AF2 / AF1; // виклик "дружньої" функції operator/()
  cout << endl << endl;
  AF3.Print("AF3");

  // "Дружня" функція працює у вигляді ланцюжка
  ArrayFloat AF5;
  AF5 = AF1 / AF2 / AF3;
  cout << endl;
  AF5.Print("AF5");

  //
  float F[] = { 1.1, 2.3, 4.5 };
  ArrayFloat AF6(3, F); // конструктор з двома параметрами
  AF6.Print("AF6");
}

Результат роботи програми:

size = 0
x = 0
0
AF2[3] = 3
AF4[5] = 5
AF1[8] = 8

Object: AF3
0 1 1 1 1 1 1 1 1 1

Object: AF5
0 1 1 1 1 1 1 1 1 1

Object: AF6
1.1 2.3 4.5

 


Зв’язані теми