Перегрузка операторов в C++. Операторная функция. Ключевое слово operator. Перегрузка базовых арифметических операторов +, –, *, /. Примеры реализации встроенных операторных функций
Данная тема отображает возможности языка C++ по реализации «перегрузки» операторов. Не все современные языки программирования поддерживают перегрузку операторов. Хорошое понимание процесса программирования перегруженных операторов есть показателем профессиональности и высокого мастерства современного программиста.
Перед рассмотрением данной темы рекомендуется ознакомиться со следующей темой:
Содержание
- 1. Что такое унарные и бинарные операторы?
- 2. В чем состоит суть перегрузки операторов? Что такое операторная функция?
- 3. Какими способами можно реализовать операторную функцию для заданного класса? Какие существуют разновидности операторных функций?
- 4. Общая форма операторной функции, которая реализована в классе. Ключевое слово operator
- 5. Пример перегрузки унарных и бинарных операторов для класса, который содержит одиночные данные. Операторная функция реализована внутри класса
- 6. Пример перегрузки оператора ‘*‘, обрабатывающего класс, который содержит массив вещественных чисел. Операторная функция реализована внутри класса
- 7. Пример суммирования двух массивов. Операторная функция operator+() размещается внутри класса
- 8. Какие ограничения накладываются на перегруженные операторы?
- 9. Какие операторы нельзя перегружать?
- 10. Объекты каких типов может возвращать операторная функция? Примеры операторных функций, которые возвращают объекты разных типов
- 11. Можно ли изменять значения операндов в операторной функции?
- 12. Можно ли реализовать операторные функции в классе, которые перегружают одинаковый оператор, получают одинаковые параметры но возвращают разные значения?
- 13. Можно ли реализовать две и более операторных функции в классе, которые перегружают одинаковый оператор и получают разные (отличные между собой) параметры?
- Связанные темы
Поиск на других ресурсах:
1. Что такое унарные и бинарные операторы?
Различают три основных вида операторов: унарные, бинарные и n-арные (n>2).
Унарные операторы – это операторы, которые для вычислений требуют одного операнда, который может размещаться справа или слева от самого оператора.
Примеры унарных операторов:
i++ --a -8
Бинарные операторы – это операторы, которые для вычисления требуют двух операндов.
Пример. Ниже отображены фрагменты выражений с бинарными операторами +, –, %, *
a+b f1-f2 c%d x1*x2
n-арные операторы для вычислений требуют более двух операндов. В языке C++ есть тернарная операция ?:, которая для своей работы требует три операнда. Более подробно об этой тернарной операции ?: описывается здесь.
⇑
2. В чем состоит суть перегрузки операторов? Что такое операторная функция?
Язык C++ имеет широкие возможности для перегрузки большинства операторов. Перегрузка оператора означает использование оператора для оперирования объектами классов. Перегрузка оператора – способ объявления и реализации оператора таким образом, что он обрабатывает объекты конкретных классов или выполняет некоторые другие действия. При перегрузке оператора в классе вызывается соответствующая операторная функция (operator function), которая выполняет действия, которые касаются данного класса.
Если оператор «перегружен», то его можно использовать в других методах в обычном для него виде. Например, команды поэлементного суммирования двух массивов a1 и a2
a1.add(a2); a3 = add(a1, a2);
лучше вызвать более естественном способом:
a1 = a1 + a2; a3 = a1 + a2;
В данном примере оператор ‘+’ считается перегруженным.
⇑
3. Какими способами можно реализовать операторную функцию для заданного класса? Какие существуют разновидности операторных функций?
Для заданного класса операторную функцию в классе можно реализовать:
- внутри класса. В этом случае, операторная функция есть методом класса;
- за пределами класса. В этом случае операторная функция объявляется за пределами класса как «дружественная» (с ключевым словом friend). Более подробно о реализации «дружественных» функций за пределами класса описывается здесь.
⇑
4. Общая форма операторной функции, которая реализована в классе. Ключевое слово operator
Общая форма операторной функции, реализованной в классе, имеет следующий вид:
return_type ClassName::operator#(arguments_list) { // некоторые операции // ... }
где
- return_type – тип значения, которое возвращается операторной функцией;
- ClassName – имя класса, в котором реализована операторная функция;
- operator# – ключевое слово, определяющее операторную функцию в классе. Символ # заменяется оператором языка C++, который перегружается. Например, если перегружается оператор +, то нужно указать operator+;
- argument_list – список параметров, которые получает операторная функция. Если перегружается бинарный оператор, то argument_list содержит один аргумент. Если перегружается унарный оператор, то список аргументов пустой.
⇑
5. Пример перегрузки унарных и бинарных операторов для класса, который содержит одиночные данные. Операторная функция реализована внутри класса
Объявляется класс Point, реализующий точку на координатной плоскости. В классе реализованы:
- две внутренние переменные x, y, которые есть координатами точки;
- два конструктора класса;
- методы доступа к внутренним переменным класса GetX(), GetY(), SetX(), SetY();
- две операторные функции operator+() и operator-().
// Класс, который реализует точку на координатной плоскости // класс содержит две операторные функции class Point { private: int x, y; // координаты точки public: // конструкторы класса Point() { x = y = 0; } Point(int nx, int ny) { x = nx; y = ny; } // методы доступа к членам класса int GetX(void) { return x; } int GetY(void) { return y; } void SetX(int nx) { x = nx; } void Set(int ny) { y = ny; } // перегруженный бинарный оператор '+' Point operator+(Point pt) { // p - временный объект, который создается с помощью конструктора без параметров Point p; p.x = x + pt.x; p.y = y + pt.y; return p; } // перегруженный унарный оператор '-' Point operator-(void) { Point p; p.x = -x; p.y = -y; return p; } };
Как видно из вышеприведенного кода, операторная функция operator+() получает один параметр. Это значит, что эта функция реализует бинарный оператор ‘+’. Этот параметр соответствует операнду, который размещается в правой части бинарного оператора ‘+’. Операнд, который размещается в левой части оператора ‘+’ передается операторной функции неявно с помощью указателя this данного класса.
Вызов операторной функции осуществляет объект, который размещается в левой части оператора присваивания.
Демонстрация использования перегруженных операторов класса Point в другом методе:
// объявление переменных - объектов класса CPoint Point P1(3,4); Point P2(5,7); Point P3; int x, y; // дополнительные переменные // 1. Использование перегруженного бинарного оператора '+' P3 = P1 + P2; // объект P1 вызывает операторную функцию // проверка x = P3.GetX(); // x = 8 y = P3.GetY(); // y = 11 // 2. Использование перегруженного унарного оператора '-' P3 = -P2; x = P3.GetX(); // x = -5 y = P3.GetY(); // y = -7
В вышеприведенном коде, в операции суммирования ‘+’ объект P1 вызывает операторную функцию. То есть, фрагмент строки
P1 + P2
заменяется вызовом
P1.operator+(P2)
Реализовать операторную функцию operator+() в классе можно и по другому
Point operator+(Point pt) { // вызов конструктора с двумя параметрами return Point(x+pt.x, y+pt.y); // создается временный объект, который затем копируется }
В вышеприведенной функции в операторе return создается временный объект путем вызова конструктора с двумя параметрами, реализованного в классе. Если (в данном случае) из тела класса убрать конструктор с двумя параметрами
// конструктор с двумя параметрами Point(int nx, int ny) { // ... }
то вышеприведенный вариант функции operator+() работать не будет, так как для создания объекта типа Point эта функция использует конструктор с двумя параметрами. В этом случае компилятор выдаст сообщение
Point::Point : no overloaded function takes 2 arguments
что значит: нет метода (конструктора) Point::Point() принимающего 2 аргумента.
⇑
6. Пример перегрузки оператора ‘*’, обрабатывающего класс, который содержит массив вещественных чисел. Операторная функция реализована внутри класса
В примере реализуется операторная функция operator*(), которая умножает поэлементно значения внутренних массивов объектов класса ArrayFloat. Если размер массивов неодинаков, то умножается только то число элементов, которое есть минимальным между двумя размерами массивов.
// массив вещественных чисел class ArrayFloat { private: float A[10]; // массив вещественных чисел, фиксированный размер массива int size; public: ArrayFloat() { size = 0; } ArrayFloat(int nsize, float nA[]) { size = nsize; for (int i=0; i<nsize; i++) A[i] = nA[i]; } // методы доступа float GetAi(int i) { if ((i>=0) && (i<=size-1)) return A[i]; else return 0; } void SetAi(int i, float value) { if ((i>=0) && (i<=size-1)) A[i] = value; } // перегруженный оператор '*' ArrayFloat operator*(ArrayFloat AF) { ArrayFloat tmp; int n; if (size<AF.size) n = AF.size; else n = size; for (int i=0; i<n; i++) tmp.A[i] = A[i] * AF.A[i]; tmp.size = n; return tmp; } };
Использование класса ArrayFloat в другом методе
// дополнительные переменные и массивы float x, y; float AF1[] = { 2, 5, 7, 9, 12 }; float AF2[] = { 3, 4, 9, 8, 10, 13 }; // создать объекты класса ArrayFloat ArrayFloat A1(5, AF1); ArrayFloat A2(6, AF2); ArrayFloat A3; // вызов операторной функции operator* A3 = A1 * A2; // осуществляется поэлементное умножение // проверка x = A3.GetAi(0); // x = 6 y = A3.GetAi(1); // y = 20 x = A3.GetAi(2); // x = 63 y = A3.GetAi(4); // y = 120
⇑
7. Пример суммирования двух массивов. Операторная функция operator+() размещается внутри класса
Задан класс ArrayFloat, реализующий динамический массив чисел типа float. В классе реализованы:
- внутренние переменные size, A, описывающие размер массива и сам массив;
- два конструктора, инициализирующие начальными значениями элементы массива;
- конструктор копирования;
- методы доступа GetSize(), SetSize(), GetAi(), SetAi(), которые реализуют доступ к внутренним переменным массива с соответствующими операциями (выделение памяти, проверка на допустимые границы);
- операторная функция operator=(), которая реализует копирование объектов;
- операторная функция operator+(), которая реализует перегрузку оператора ‘+’ для массивов типа ArrayFloat. Операторная функция добавляет поэлементно значения массивов, которые являются операндами операции ‘+’.
- деструктор.
// класс - массив типа float class ArrayFloat { private: int size; // размер массива float * A; // динамический размер массива public: // конструкторы класса // конструктор без параметров ArrayFloat() { size = 0; A = NULL; } // конструктор с двумя параметрами ArrayFloat(int nsize, float * nA) { if (size>0) delete A; size = nsize; A = new float[size]; for (int i=0; i<nsize; i++) A[i] = nA[i]; } // конструктор копирования ArrayFloat(const ArrayFloat& _A) { size = _A.size; A = new float[size]; for (int i = 0; i < size; i++) A[i] = _A.A[i]; } // методы доступа 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; } 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; } // оператор копирования operator=() ArrayFloat operator=(const ArrayFloat& _A) { if (size > 0) delete[] A; size = _A.size; A = new float[size]; // выделить память for (int i = 0; i < size; i++) A[i] = _A.A[i]; return *this; } // перегрузка оператора '+', // операторная функция ArrayFloat operator+(ArrayFloat AF2) { int n; // взять минимальный размер из двух массивов if (size<AF2.size) n = size; else n = AF2.size; ArrayFloat tmpA; // объект класса tmpA.SetSize(n); // новый размер массива // поэлементное суммирование for (int i=0; i<n; i++) tmpA.A[i] = A[i] + AF2.A[i]; return tmpA; } // деструктор ~ArrayFloat() { if (size > 0) delete[] A; } };
Ниже продемонстрировано использование класса ArrayFloat и операторной функции operator+() этого класса.
// дополнительные переменные, массивы float x, y; float F1[] = { 3.8f, 2.9f, 1.5f }; float F2[] = { 4.3f, 1.5f, 7.0f, 3.3f }; // объявление объектов класса ArrayFloat ArrayFloat AF1(3, F1); ArrayFloat AF2(4, F2); // проверка x = AF1.GetAi(0); // x = 3.8 y = AF2.GetAi(3); // y = 3.3 // объявление дополнительного объекта - результата ArrayFloat AF3; AF3 = AF1 + AF2; // вызов операторной функции operator+ // проверка x = AF3.GetAi(0); // x = 3.8 + 4.3 = 8.1 y = AF3.GetAi(1); // y = 2.9 + 1.5 = 4.4 // еще один вызов AF3 = AF1 + AF1 + AF2; x = AF3.GetAi(1); // x = 2.9 + 2.9 + 1.5 = 7.3 y = AF3.GetAi(2); // y = 1.5 + 1.5 + 7.0 = 10.0
⇑
8. Какие ограничения накладываются на перегруженные операторы?
На использование перегруженных операторов накладываются следующие ограничения:
- при перегрузке оператора нельзя изменить приоритет этого оператора;
- нельзя изменить количество операндов оператора. Однако, в коде операторной функции можно один из параметров (операндов) не использовать;
- нельзя перегружать операторы ::, ., *, ?:;
- нельзя вызвать операторную функцию с аргументами по умолчанию. Исключение – операторная функция вызова функции operator()().
⇑
9. Какие операторы нельзя перегружать?
Нельзя перегружать следующие операторы:
- :: – расширение области видимости;
- . (точка) – доступ к члену структуры или класса;
- * – доступ по указателю;
- ?: – тернарная операция.
⇑
10. Объекты каких типов может возвращать операторная функция? Примеры операторных функций, которые возвращают объекты разных типов
Операторная функция может возвращать объекты любых типов. Наиболее часто операторная функция возвращает объект типа класса, в котором она реализованная или с которыми она работает.
Пример. Задан класс Complex, в котором перегружаются два оператора:
- унарный оператор ‘+’, возвращающий модуль комплексного числа (тип double);
- бинарный оператор ‘+’, возвращающий сумму комплексных чисел. Операторная функция возвращает объект типа Complex;
- бинарный оператор ‘+’, который добавляет к комплексному числу некоторое вещественное число. В этом случае операторная функция получает входным параметром вещественное число и возвращает объект типа Complex.
Текст класса следующий:
// класс 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 operator+(Complex c) { Complex c2; // временный объект // суммирование комплексных чисел c2.real = real + c.real; c2.imag = imag + c.imag; return c2; } // объявление операторной функции, перегружающей унарный '+' // функция возвращает модуль комплексного числа float operator+(void) { float res; res = std::sqrt(real*real+imag*imag); return res; } // объявление операторной функции operator+() // функция добавляет к комплексному числу некоторое число, которое есть входящим параметром Complex operator+(float real) { Complex c2; // результирующий объект c2.real = this->real + real; c2.imag = this->imag; return c2; } };
Далее демонстрируется использование класса Complex и перегруженных операторных функций в некотором другому методе
Complex c1(1,5); Complex c2(3,-8); Complex c3; // результирующий объект double d; // проверка c3 = c1 + c2; d = c3.GetR(); // d = 1 + 3 = 4 d = c3.GetI(); // d = 5 + (-8) = -3 // перегруженный унарный оператор '+' d = +c1; // d = |1 + 5j| = 5.09902 - модуль числа d = +c2; // d = |3 + (-8)j| = 8.544 // вызов перегруженного бинарного '+', // добавить к комплексному числу число c3 = c1 + 5.0; d = c3.GetR(); // d = 1 + 5 = 6
⇑
11. Можно ли изменять значения операндов в операторной функции?
Да, можно. Однако такие действия не являются полезными с точки зрения здравого смысла. Так, например, операция умножения
6 * 9
не изменяет значения своих операндов 6 и 9. Результат равен 54. Если операторная функция operator*() будет изменять значения своих операндов, то это может привести к невидимым ошибкам в программах, поскольку программист по привычке, будет считать, что значения операндов есть неизменными.
⇑
12. Можно ли реализовать операторные функции в классе, которые перегружают одинаковый оператор, получают одинаковые параметры но возвращают разные значения?
Нет, нельзя. Операторная функция не может иметь несколько реализаций в классе с одинаковой сигнатурой параметров (когда типы и количество параметров совпадают). В случае нарушения этого правила компилятор выдает ошибку:
cannot overload functions distinguished by return type alone
что значит
нельзя перегружать функции, которые отличаются только возвращаемым типом
Например. Нельзя в классе перегружать оператор ‘+’ так как показано ниже
class SomeClass { // ... SomeClass operator+(SomeClass c1) { // ... } double operator+(SomeClass c1) // это есть ошибка! { // ... } // ... }
Это правило касается любых функций класса.
⇑
13. Можно ли реализовать две и более операторных функции в классе, которые перегружают одинаковый оператор и получают разные (отличные между собой) параметры?
Да, можно. В п. 10 реализован класс Complex, в котором реализованы две операторные функции, которые перегружают оператор ‘+’ разными способами. Эти функции отличаются входящими параметрами.
⇑
Связанные темы
- «Дружественные» операторные функции: отличия, реализации, особенности применения. Перегрузка операторов +, –, *, / с помощью «дружественных» операторных функций
- Перегрузка функций. Перегрузка функций в классах. Перегрузка конструкторов класса. Доступ к перегруженной функции по указателю. Примеры