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

Алгоритмические конструкции

Как вам уже известно из курса информатики, любой алгоритм может быть записан с использованием трех базовых алгоритмических конструкций: следование, цикл и ветвление (пример 3.1).

Команды, составляющие алгоритмическую конструкцию следование, выполняются последовательно, друг за другом, в том порядке, в котором они записаны. Команды цикла и ветвления управляют порядком выполнения других команд в программе и относятся к командам управления (управляющим конструкциями).

Алгоритмическая конструкция ветвления обеспечивает выполнение одной или другой последовательности команд в зависимости от истинности или ложности некоторого условия. Оператор ветвления — команда, реализующая алгоритмическую конструкцию ветвления на языке программирования.

Алгоритмическая конструкция повторение (цикл) представляет собой последовательность действий, выполняемых многократно. Саму последовательность называют телом цикла. Оператор цикла — это команда, реализующая алгоритмическую конструкцию повторения на языке программирования.

Существуют разные возможности управлять тем, сколько раз будет повторяться тело цикла. Может быть задано условие продолжения или окончания работы цикла, а также число повторений тела цикла. В зависимости от этого выделяют цикл с предусловием, цикл с постусловием и цикл с параметром.

Существуют разные возможности управлять тем, сколько раз будет повторяться тело цикла. Может быть задано условие продолжения или окончания работы цикла, а также число повторений тела цикла. Выделяют следующие циклы: цикл с предусловием, цикл с постусловием и цикл с параметром. Выбор цикла зависит от задачи. Во многих случаях циклы взаимозаменяемы. При выборе цикла можно ориентироваться на следующее:

  • цикл с параметром используется тогда, когда известно количество повторений;
  • цикл с предусловием используется в том случае, когда известно условие продолжения работы;
  • цикл с постусловием используется тогда, когда тело цикла должно быть выполнено хотя бы один раз.

Пример 3.1 Блок-схемы алгоритмических конструкций.

Следование

Ветвление

Цикл

  • Цикл с параметром (значение параметра изменяется от 1 до N):

  • Цикл с предусловием:

  • Цикл с постусловием:

Кроме блок-схем, для графического представления алгоритмов используют структурограммы (NS-диаграммы, диаграммы Насси — Шнейдермана).

Примеры структурограмм

Команда ветвления:

Команда цикла с предусловием:

3.1. Перегрузка арифметических операций

Перегрузка операторов в программировании — один из способов реализации полиморфизма. Она позволяет использовать нескольких различных вариантов применения оператора. Операторы имеют одно и то же имя, но различаются типами параметров, к которым они применяются.

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

С перегруженными операторами вы уже работали. В классе string перегружены операторы сравнения и оператор «+». Для сравнения строк используются те же знаки, что и для сравнения чисел, однако сравнение строк выполняется не так, как сравниваются числа. Сложение строк также имеет смысл, отличный от сложения чисел.

Большинство операций в С++ можно перезагрузить (пример 3.1)[1].

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

Синтаксис перегрузки операций:

тип operator @ (список_параметров-операндов)
{
// тело функции
}

где @ — знак перегружаемой операции,

тип — тип возвращаемого значения.

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

Если бинарная[2] операция перегружается с использованием метода класса, то первым операндом она получает переменную класса, которая неявно передается через указатель this на объект. Вторым операндом является аргумент функции. Таким образом, бинарная операция, перегружаемая методом класса, имеет фактически один параметр (правый операнд), а левый передается неявно через указатель this.

Если перегрузка бинарной операции реализована с использованием дружественной функции, то в списке параметров должно быть два аргумента.

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

Этапы выполнения задания

1. Полями класса будут координаты (x, 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).

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

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

<операнд1> <знакОперации> <операнд2>

аналогична вызову функции <знакОперации>(<операнд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;

}