§ 3. Перагрузка аперацый

3.1. Перагрузка арыфметычных аперацый

Перагрузка аператараў у праграмiраванні — адзін з спосабаў рэалізацыі палімарфізму. Яна дазваляе выкарыстоўваць некалькі розных варыянтаў прымянення аператара. Аператары маюць адно і тое ж імя, але адрозніваюцца тыпамі параметраў, да якіх яны ўжываюцца.

У мове C++ аператары рэалізаваны ў выглядзе функцый. Выкарыстоўваючы перагрузку функцыі аператара, вы можаце вызначыць свае ўласныя версіі аператараў, якія будуць працаваць з рознымі тыпамі даных, уключаючы класы.

З перагружанымі аператарамі вы ўжо працавалі. У класе string перагружаныя аператары параўнання і аператар «+». Для параўнання радкоў выкарыстоўваюцца тыя ж знакі, што і для параўнання лікаў, аднак параўнанне радкоў выконваецца не так, як параўноўваюцца лікі. Складанне радкоў таксама мае сэнс, які адрозніваецца ад складання лікаў.

Большасць аперацый у С++ можна перазагрузіць (прыклад 3.1)[1].

Важна памятаць, што перагрузка пашырае магчымасці мовы, а не змяняе мову. Таму нельга перагружаць аператары для ўбудаваных тыпаў даных. Нельга мяняць прыярытэт выканання аператараў і іх асацыятыўнасць (злева направа ці справа налева). Нельга ствараць уласныя аператары і перагружаць некаторыя ўбудаваныя (прыклад 3.2).

Сінтаксіс перагрузкі аперацый:

тып operator @ (спiс_параметраў-аперанд)
{
// цела функцыі
}

дзе @ — знак перагружанай аперацыі,

тып — тып значэння, якое вяртаецца.

Перагружаць можна толькі аперацыі, для якіх хоць бы адзін аргумент уяўляе тып даных, вызначаны карыстальнікам (напрыклад, клас). Функцыя для перагрузкі аперацыі павінна быць вызначана або як функцыя-член класа (прыклад 3.3), або як знешняя функцыя, але дружалюбная класу (пример 3.4). Адзін і той жа аператар можна перагрузіць некалькі разоў.

Калі бінарная[2] аперацыя перагружаецца з выкарыстаннем метаду класа, то першым аперандам яна атрымлівае пераменную класа, якая няяўна перадаецца праз паказальнік this на аб'ект. Другім аперандам з'яўляецца аргумент функцыі. Такім чынам, бінарная аперацыя, перагружаная метадам класа, мае фактычна адзін параметр (правы аперанд), а левы перадаецца няяўна праз паказальнік this.

Калі перагрузка бінарнай аперацыі рэалізавана з выкарыстаннем дружалюбнай функцыі, то ў спісе параметраў павінна быць два аргумента.

Прыклад 3.5. Вектар на плоскасці задаецца парай каардынат. Апісаць клас Vect для прадстаўлення вектара на плоскасці. Рэалізаваць метад, які вылічае даўжыню вектара, перагрузіць аператары «+» (складанне двух вектараў), «*» (множання вектара на лік), «==» (параўнанне вектараў), «>» (параўнанне вектараў па даўжыні).

Этапы выканання задання

1. Палямі класа будуць каардынаты (х, y) вектара на плоскасці.

2. Для вылічэння даўжыні вектара ў метадзе класа dl выкарыстоўваецца формула вылічэння даўжыні адрэзка.

3. Рэалізаваць перагрузку аперацый.

    1. Для перагрузкі аперацый ««==» і «>» трэба апісаць адпаведныя функцыі. Функцыі павінны мець тып bool. Функцыі рэалізаваць як метады класа.
    2. Для перагрузкі аперацыі «*» трэба апісаць адпаведную функцыю. Функцыя павінна мець тып Vect. Функцыю рэалізаваць як метад класа.
    3. Для перагрузкі аперацыі «+» рэалізаваць дружалюбную функцыю, якая вяртае тып Vect.

Любы перагружаны аператар можна выклікаць з выкарыстаннем функцыянальнай формы запісу (функцыі-аперацыі):

Vect v3 = operator + (v1, v2);

Выкарыстанне аперацыі — гэта ўсяго толькі скарочаны запіс яўнага выкліку функцыі аперацыі. Паколькі функцыя-аперацыя апісваецца так жа, як любая іншая функцыя, то і яна можа выклікацца аналагічна.

Прамавугольную табліцу з лікамі ў матэматыцы называюць матрыцай. Памер матрыцы вызначаецца колькасцю радкоў і слупкоў ў ёй. Для матрыц вызначаны арыфметычныя аперацыі. Матрыцу можна памножыць на лік — для гэтага кожны элемент матрыцы памнажаецца на яго. Дзве матрыцы аднолькавага памеру можна скласці ці адняць. Дзеянні выконваюцца над элементамі, якія стаяць на адпаведных месцах.

Прыклад 3.6. Апісаць клас Matrix. Перагрузіць аперацыі складання, аднімання і множання матрыцы на лік.

Этапы выканання задання

1.  Палямі класа будуць цэлыя лікі m, n — колькасць радкоў і слупкоў у матрыцы і двухмерны вектар для захоўвання самой матрыцы.

2. Рэалізаваць метад rand_data, які будзе запаўняць матрыцу выпадковымі лікамі.

3. Перагрузка аперацый аналагічная перагрузцы аперацый, якія рэалізаваны ў прыкладзе 3.4.

    1. Пры перагрузцы аперацыі множання на лік памножым на гэты лік кожны элемент матрыцы.
    2. Пры перагрузцы аперацыі «+» прасумуем элементы з аднолькавымі нумарамі радка і слупка.
    3. Пры перагрузцы аперацыі «-» скарыстаемся тым, што
      AB = A + B * (–1).

[1] Больш падрабязна аб перагрузцы аператараў можна пачытаць, напрыклад тут: https://habr.com/ru/post/489666/

[2] Аперацыя з'яўляецца бінарнай, калі для яе выканання неабходны два аперанда. Напрыклад, аперацыя складання (a + b). Аперацыя з адным аперандам з'яўляецца ўнарнай. Напрыклад, аперацыя змены знака ў ліку (-a).

Неабходнасць у перагрузкi аперацый абумоўлена патрэбай апісваць і ўжываць да створаных праграмістам тыпам даных аперацыі, па сэнсе эквівалентныя ўжо іcнуючым у мове.

Для рэалізацыі перагрузкі аперацый у мове праграмiравання неабходна ўвесці ў ім адпаведныя сінтаксічныя канструкцыі. Варыянтаў рэалізацыі можа быць шмат, аднак яны мала чым адрозніваюцца адзін ад аднаго. Дастаткова памятаць, што запіс выгляду

<аперанд1> <знакАперацыi> <аперанд2>

аналагічны выкліку функцыі <знакАперацыi>(<аперанд1>,<аперанд2>).

Калі дазволіць праграмісту апісваць паводзіны аператараў у выглядзе функцый, то праблема перагрузкі аператараў будзе вырашана.

Прыклад 3.1. Аперацыі, якія можна перагрузіць у С++:

+   -   *  /    %  ^   & |     ~  !    =  <  >  

+=     -=     *=      /=      %=    ^=     &=     |=

<<     >>    >>=    <<=   ==     !=     <=      >=

&&    ||       ++      --       []       ()

Прыклад 3.2. Аперацыі, якія нельга перагрузіць у С++:

  • аператар выбару члена класа «.»;
  • аператар разнаймення паказальніка на член класа «.*»;
  • тэрнарны аператар «? :»;
  • аперацыя ўказання вобласці бачнасці «::»;
  • аперацыя вылічэння памеру ў байтах sizeof.

Дружалюбная функцыя — гэта функцыя, якая не з'яўляецца членам класа, але якая мае доступ да яго закрытых членаў. Ёю можа быць як звычайная функцыя, так і метад іншага класа. Для аб'явы дружалюбнай функцыі выкарыстоўваецца ключавое слова friend перад аб'явай функцыі, якая стане дружалюбнай класу. Функцыя можа быць аб'яўлена як у раздзеле public, так і раздзеле private.

Прыклад 3.3. Перагрузка аперацыі «+» з прымяненнем метаду класа:

class Vect

{

  ...

public:

  Vect operator + (const Vect &);

  ...

};

Прыклад 3.4. Перагрузка аперацыі «+» з ужываннем знешняй функцыі, дружалюбнай класу:

class Vect

{

  ...

public:

  friend Vect operator + (Vect &, Vect &);

  ...

};

Прыклад 3.5. Праграма.

#include <iostream>

#include <math.h>

#include <windows.h>

 

using namespace std;

 

class Vect ///

клас, які апісвае вектар на плоскасці

{

    double x,y;

  public:

    ///канструктары класа

    Vect() {= 0; y = 0;}

    Vect (double z1, double z2):

             x(z1), y(z2) {}

 

    ///вяртанне каардынат

    double get_x(){return x;}

    double get_y(){return y;}

    ///даўжыня вектара

    double dl();

    /// аператар параўнання двух вектараў

    bool operator == (Vect &);

    /// аператар параўнання двух вектараў па даўжыні

    bool operator > (Vect &);

    /// аператар множання вектара на лік

    Vect operator *(int);

    ///аператар складання двух вектараў

    friend Vect operator +(Vect &, Vect &);

};

 

double Vect::dl()

{

  return sqrt(* x + y * y);

}

 

bool Vect::operator == (Vect &p)

{

  return  (== p.&& y == p.y);

}

 

bool Vect::operator > (Vect &p)

{

  return (dl() > p.dl()) ? true : false;

}

 

Vect Vect::operator *(int k)

{

  return Vect(* k, y * k);

}

 

Vect operator +(Vect &p, Vect &q)

{

  return Vect(p.+ q.x, p.+ q.y);

}

 

int main()

{

  SetConsoleCP(1251);

  SetConsoleOutputCP(1251);

  double a,b,c,d;

  cout << "каардынаты:" << endl;

  cin >> a >> b >> c >> d;

  Vect v1(a, b), v2(c, d);

  cout << "даўжыня 1-га = " ;

  cout << v1.dl() << endl;

  cout << "даўжыня 2-га = " ;

  cout << v2.dl() << endl;

  if (v1 == v2)

    cout << "вектары супадаюць" << endl;

  else

    cout << "вектары не  супадаюць" << endl;

  if (v1 > v2)

    cout << "даўжыня 1-га больш" << endl;

  else

    cout << "даўжыня 1-га не больш" << endl;

  Vect v3 = v1 + v2;

  cout << "каардынаты вектара v3: " ;

  cout << v3.get_x() << " ";

  cout << v3.get_y() << endl;

  cout << "даўжыня вектара v3 = ";

  cout << v3.dl() << endl;

  Vect v4 = v1 * 5;

  cout << "каардынаты вектара v4: ";

  cout << v4.get_x() << " ";

  cout << v4.get_y() << endl;

  cout << "даўжыня вектара v4 = " ;

  cout << v4.dl() << endl;

  return 0;

}

Вынік працы:

Роб Мюрэй, у сваёй кнізе «C++ Strategies and Tactics» рэкамендаваў перагружаць унарныя аперацыі і аперацыі, сумешчаныя з прысвойваннем (+=, *= і інш.), як члены класа. Бінарныя аперацыі ён рэкамендаваў перагружаць з выкарыстаннем дружалюбных функцый.

Прыклад 3.6. Апісанне класа Matrix.

class Matrix

{

    int n, m;

    vector < vector <int> > data;

  public:

    Matrix();

    Matrix(int n_, int m_);

    void rand_data ();

    Matrix operator + (Matrix &);

    Matrix operator * (int);

    Matrix operator - (Matrix &);

};

 

Matrix::Matrix()

{

    n = 1;  m = 1;

    data.resize(n);

    data[0].resize(m, 0);

}

 

Matrix::Matrix(int n_, int m_)

{

    n = n_;

    m = m_;

    data.resize(n);

    for (int i = 0; i < n; i++)

        data[i].resize(m, 0);

}

 

void Matrix::rand_data()

{

    for (int i = 0; i < n; i++)

      for (int j = 0; j < m; j++)

        data[i][j] = rand() % 100;

}

 

Matrix Matrix::operator + (Matrix &d)

{

    Matrix t(n, m);

    for (int i = 0; i < n; i++)

      for (int j = 0; j < m; j++)

         t.data[i][j] = data[i][j] +

                       d.data[i][j];

    return t;

}

 

Matrix Matrix::operator * (int k)

{

    Matrix t(n, m);

    for (int i = 0; i < n; i++)

      for (int j = 0; j < m; j++)

        t.data[i][j] = data[i][j] * k;

    return t;

}

 

Matrix Matrix::operator - (Matrix &d)

{

    Matrix t(n, m);

    t = d * (-1);

    t = *this + t;

    return t;

}