§ 3. Перегрузка операций
Алгоритмические конструкции
Как вам уже известно из курса информатики, любой алгоритм может быть записан с использованием трех базовых алгоритмических конструкций: следование, цикл и ветвление (пример 3.1). Команды, составляющие алгоритмическую конструкцию следование, выполняются последовательно, друг за другом, в том порядке, в котором они записаны. Команды цикла и ветвления управляют порядком выполнения других команд в программе и относятся к командам управления (управляющим конструкциями). Алгоритмическая конструкция ветвления обеспечивает выполнение одной или другой последовательности команд в зависимости от истинности или ложности некоторого условия. Оператор ветвления — команда, реализующая алгоритмическую конструкцию ветвления на языке программирования. Алгоритмическая конструкция повторение (цикл) представляет собой последовательность действий, выполняемых многократно. Саму последовательность называют телом цикла. Оператор цикла — это команда, реализующая алгоритмическую конструкцию повторения на языке программирования. Существуют разные возможности управлять тем, сколько раз будет повторяться тело цикла. Может быть задано условие продолжения или окончания работы цикла, а также число повторений тела цикла. В зависимости от этого выделяют цикл с предусловием, цикл с постусловием и цикл с параметром. Существуют разные возможности управлять тем, сколько раз будет повторяться тело цикла. Может быть задано условие продолжения или окончания работы цикла, а также число повторений тела цикла. Выделяют следующие циклы: цикл с предусловием, цикл с постусловием и цикл с параметром. Выбор цикла зависит от задачи. Во многих случаях циклы взаимозаменяемы. При выборе цикла можно ориентироваться на следующее:
|
Пример 3.1 Блок-схемы алгоритмических конструкций. Следование Ветвление Цикл
Кроме блок-схем, для графического представления алгоритмов используют структурограммы (N — S-диаграммы, диаграммы Насси — Шнейдермана). Примеры структурограмм Команда ветвления: Команда цикла с предусловием: |
Сайт: | Профильное обучение |
Курс: | Информатика. 11 класс (Повышенный уровень) |
Книга: | § 3. Перегрузка операций |
Напечатано:: | Гость |
Дата: | Четверг, 2 Май 2024, 18:10 |
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. Реализовать перегрузку операций.
Любой перегруженный оператор можно вызвать с использованием функциональной формы записи (функции-операции): Vect v3 = operator + (v1, v2); Использование операции — это всего лишь сокращенная запись явного вызова функции операции. Поскольку функция-операция описывается так же, как любая другая функция, то и вызываться она может аналогично. Прямоугольную таблицу с числами в математике называют матрицей. Размер матрицы определяется количеством строк и столбцов в ней. Для матриц определены арифметические операции. Матрицу можно умножить на число — для этого каждый элемент матрицы умножается на него. Две матрицы одинакового размера можно сложить или отнять. Действия выполняются над элементами, стоящими на соответствующих местах. Пример 3.6. Описать класс Matrix. Перегрузить операции сложения, вычитания и умножения матрицы на число. Этапы выполнения задания 1. Полями класса будут целые числа m, n — количество строк и столбцов в матрице и двумерный вектор для хранения самой матрицы. 2. Реализовать метод rand_data, который будет заполнять матрицу случайными числами. 3. Перегрузка операций аналогична перегрузке операций, которые реализованы в примере 3.4.
[1] Более подробно о перегрузке операторов можно почитать, например здесь: https://habr.com/ru/post/489666/ [2] Операция является бинарной, если для ее выполнения необходимы два операнда. Например, операция сложения (a + b). Операция с одним операндом является унарной. Например, операция смены знака у числа (-a). |
Необходимость в перегрузке операций обусловлена потребностью описывать и применять к созданным программистом типам данных операции, по смыслу эквивалентные уже имеющимся в языке. Для реализации перегрузки операций в языке программирования необходимо ввести в нем соответствующие синтаксические конструкции. Вариантов реализации может быть много, однако они мало чем отличаются друг от друга. Достаточно помнить, что запись вида <операнд1> <знакОперации> <операнд2> аналогична вызову функции <знакОперации>(<операнд1>,<операнд2>). Если разрешить программисту описывать поведение операторов в виде функций, то проблема перегрузки операторов будет решена. Пример 3.1. Операции, которые можно перегрузить в С++:
Пример 3.2. Операции, которые нельзя перегрузить в С++:
Дружественная функция — это функция, не являющаяся членом класса, но имеющая доступ к его закрытым членам. Ею может быть как обычная функция, так и метод другого класса. Для объявления дружественной функции используется ключевое слово friend перед объявлением функции, которая станет дружественной классу. Функция может быть объявлена как в разделе public, так и разделе private. Пример 3.3. Перегрузка операции «+» с применением метода класса:
Пример 3.4. Перегрузка операции «+» с применением внешней функции, дружественной классу:
Пример 3.5. Программа.
Результат работы: Роб Мюррей, в своей книге «C++ Strategies and Tactics» рекомендовал перегружать унарные операции и операции, совмещенные с присваиванием (+=, *= и др.), как члены класса. Бинарные операции он рекомендовал перегружать с использованием дружественных функций.
Пример 3.6. Описание класса Matrix.
|
3.2. Логический тип данных
Для стандартных типов данных таких как int или double ввод и вывод реализован через стандартные операторы >> и <<. Эти же операторы перегружены для ввода-вывода строк. Для пользовательских типов данных, имеющих множество полей, также можно перегрузить операторы ввода-вывода. Перегрузка операторов << и >> намного упрощает процесс вывода объекта на экран и получение пользовательского ввода с записью данных в объект класса. Рассмотрим перегрузку оператора <<. Оператор является бинарным оператором, поэтому его перегрузка будет аналогична перегрузке оператора «+», рассмотренной в примерах 3.5 и 3.6. Левым операндом у оператора << является объект cout, а правым — объект класса, который нужно вывести. Переопределяемый оператор должен возвращать значение типа ostream, объектом которого является cout. Параметры, описываемой функции, должны быть ссылками (примеры 3.7 и 3.8). Также ссылкой должен быть и возвращаемый результат, поскольку ostream запрещает свое копирование. Перегрузка оператора ввода происходит аналогично. Отличие в том, что cin является объектом типа istream (примеры 3.9 и 3.10). Ссылка на объект класса, являющийся вторым параметром, не может быть константой, поскольку объект изменяется при вводе. После перезагрузки операторов ввода и вывода их можно использовать также для чтения и записи файлов. |
Пример 3.7. Перегрузка оператора вывода << для класса Vect, определенного в примере 3.5. Объявление:
Описание:
Пример 3.8. Перегрузка оператора вывода << для класса Matrix, определенного в примере 3.6. Объявление:
Описание:
Пример 3.9. Перегрузка оператора вывода >> для класса Vect, определенного в примере 3.5. Объявление:
Описание:
Пример 3.10. Перегрузка оператора вывода >> для класса Matrix, определенного в примере 3.6. Объявление:
Описание:
|
Вопросы к параграфу
1. Для чего используется перегрузка операций? 2. Какие операции можно перегрузить в С++? 3. Какие операции нельзя перегрузить в С++? 4. Какими способами можно определить функцию для перегрузки операторов?
|
Упражнения
1. Для программы из примера 3.5 выполните следующее:
- Дополните описание класса перегрузкой операторов ввода и вывода, приведенное в примерах 3.7 и 3.9.
- Измените функции для перегрузки операторов сравнения так, чтобы они были дружественными.
- Как будет реагировать программа, если использовать знак «<» для сравнения длин векторов? Почему?
- Перегрузите операции «<» и «!=».
- Перегрузите оператор «*» еще раз, считая, что он используется и для нахождения скалярного произведения двух векторов [1].
[1] Скалярным произведением двух векторов и является число .
2. Для программы из примера 3.6 выполните следующее:
- Реализуйте многофайловый проект, разделите объявление и определение класса. Создайте файлы Matrix.h и Matrix.cpp.
- Дополните описание класса перегрузкой операторов ввода и вывода, приведенное в примерах 3.8 и 3.10.
- Измените функции для перегрузки операторов так, чтобы они были дружественными.
- Перегрузите операцию «==», которая будет возвращать true, в том случае, если матрицы имеют одинаковые размеры и false в противном случае.
- * Перегрузите оператор «*» еще раз, считая, что он используется и для нахождения произведения матриц[1]
[1] О том, как умножать матрицы, можно почитать здесь: https://www.webmath.ru/poleznoe/formules_6_6.php.
- Продемонстрируйте работу методов и операторов описанного класса.
3. Измените описание класса Parallelepiped (задание 4, §2), добавив перегрузку операторов сравнения параллелепипедов по объему.
4. Добавьте в класс Rect (задание 6, §2) перегрузку операций «+» для получения наименьшего прямоугольника, содержащего два заданных прямоугольника, и «*» — для получения прямоугольника, являющегося общей частью (пересечением) двух прямоугольников.
5. Создать класс Drob. Конструктор принимает два числа: числитель и знаменатель дроби. Перегрузить операции: сложение, умножение, деление, вычитание. Реализовать методы: сокращение (использовать private функцию для нахождения НОД по алгоритму Евклида), перевод обыкновенной дроби в десятичную. Продемонстрировать работу всех функций класса и перегруженных операций на примерах.
6. Создать базовый класс Progressii и ее наследников — арифметическую и геометрическую прогрессии. Реализовать методы: n-ый член прогрессии, проверка прогрессии на убывание. Перегрузить операции: «+» (первый операнд — переменная типа прогрессии, второй — число (количество элементов)) для вычисления суммы арифметической прогрессии; «*» — аналогично для вычисления суммы геометрической прогрессии. Продемонстрировать работу всех функций класса и перегруженных операций на примерах.
7. Описать класс Polinom для работы с многочленами. Полями класса является степень многочлена и массив коэффициентов. Перегрузить операции «+», «-», «*», а также ввод и вывод многочлена. Реализовать метод вычисления значения многочлена для заданного значения переменной[1]. *Используя метод двоичного деления, найти корень многочлена на заданном промежутке (корень должен быть на этом промежутке единственным).
[1] Рекомендуется использовать для вычисления значения многочлена схему Горнера — https://intuit.ru/studies/professional_retraining/941/courses/67/lecture/1966?page=2
8*. Описать класс BigNumber для работы с «большими» числами (числа, которые не помещаются в стандартные типы). Перегрузите операции «+», «-», «*», сравнения, ввода и вывода.
Пример описания некоторых членов класса:
///Класс "большое число", описывает способ хранения большого числа и сложение
class BigNumber
{
private:
vector < int > cifr;
///нормализация числа моделирует перенос в следующий разряд, если цифра > 10
void norm();
public:
///Конструктор по умолчанию ("пустое" число)
BigNumber() {};
///Конструктор, конвертирует строку в большое число
BigNumber(string str)
BigNumber operator + (const BigNumber &);
friend ostream & operator << (ostream &, const BigNumber &);
};
BigNumber::BigNumber(string str)
{
///Записываем цифры с конца строки
for (int i = str.size() - 1; i >= 0; i --)
cifr.push_back(str[i] - '0');
}
///Оператор +, выполняет сложение больших чисел
BigNumber BigNumber::operator + (const BigNumber &num)
{
BigNumber res;
int r1 = min(cifr.size(), num.cifr.size());
res.cifr.resize(r1);
/// сложение цифр
for (int i = 0; i < r1; i++)
res.cifr[i] = cifr[i] + num.cifr[i];
///если в одном из чисел цифры закончились
for (int i = r1; i < cifr.size(); i++)
res.cifr.push_back(cifr[i]);
for (int i = r1; i < num.cifr.size(); i++)
res.cifr.push_back(num.cifr[i]);
res.norm();
return res;
}
void BigNumber::norm()
{
if (cifr.size() > 1) {
for (int i = 0; i < cifr.size()-1; i++) {
cifr[i + 1] += cifr[i] / 10;
cifr[i] = cifr[i] % 10;
}
if (cifr[cifr.size() - 1] >= 10) {
int t = cifr[cifr.size() - 1] / 10;
cifr[cifr.size() - 1] = cifr[cifr.size() - 1] % 10;
cifr.resize(cifr.size() + 1);
cifr[cifr.size()-1] = t;
}
}
}
///Перегрузка оператора << для вывода
ostream & operator << (ostream &cout_bn, const BigNumber &num)
{
for (int i = num.cifr.size() - 1; i >= 0; i--)
cout_bn << num.cifr[i];
}
Пример использования:
int main() {
BigNumber n1("9999999999999999");
BigNumber n2("1");
cout << n1 << endl;
cout << n2 << endl;
BigNumber n3 = n1 + n2;
cout << n3 << endl;
return 0;