Перегрузка операторов
Перегрузка операторов является одной из разновидностью Полиморфизма.
В С++ есть возможность распространения действия стандартных операций на операнды абстрактных типов данных. Для того, чтобы переопределить одну из стандартных операций для работы с операндами абстрактных типов, программист должен написать функцию с именем operator знак, где знак - обозначение этой операции (например, + - | += и т.д.).
Однако в языке существует несколько ограничений, накладываемых на переопределение операторов:
- Нельзя создавать новые символы операций.
- Нельзя переопределять операции:
:: * (разыменование, а не бинарное умножение) ?: sizeof ## # .
- Cимвол унарной операции не может использоваться для переопределения бинарной операции и наоборот. Например, символ « можно использовать только для бинарной операции, ! - только для унарной, а & - и для унарной, и для бинарной.
- Переопределение операций не меняет ни их приоритетов, ни порядка их выполнения (слева направо или справа налево).
- При перегрузке операции компьютер не делает никаких предположений о ее свойствах. Это означает, что если стандартная операция += может быть выражена через операции + и =, т.е. а + = b эквивалентно а = а + b, то для переопределения операций в общем таких соотношений не существует, хотя, конечно, программист может их обеспечить.
- Никакая операция не может быть переопределена для операндов стандартных типов.
- Как для унарной, так и для бинарной операции число аргументов функции operator () должно точно соответствовать числу операндов этой операции. Причем в перегрузку бинарного оператора принято передавать один аргумент, так как второй - неявный. Его имеет любая функция - член класса, это тот самый Указатель this языка CPP - указатель на объект, для которого вызван метод. Таким образом, в переопределение унарного оператора не следует передавать ничего вовсе.
Пример 1. Перегрузка бинарные операторы +-*%
#include <iostream> using namespace std; class Digit{ private: int dig; // число public: Digit(){ dig=0; } Digit(int iDig){ dig=iDig; } void Show(){ cout<<dig<<"\n"; } // перегружаем четыре оператора // обратите внимания, все операторы // бинарные, поэтому мы передаем в // них один параметр - это операнд, // который будет находиться справа // от оператора в выражении // левый операнд передается с помощью this Digit operator+(const Digit &N) { Digit temp; temp.dig=dig+N.dig; return temp; } Digit operator-(const Digit &N) { Digit temp; temp.dig=dig-N.dig; return temp; } Digit operator*(const Digit &N) { Digit temp; temp.dig=dig*N.dig; return temp; } Digit Digit::operator%(const Digit &N) { Digit temp; temp.dig=dig%N.dig; return temp; } }; void main() { // проверяем работу операторов Digit A(8),B(3); Digit C; cout<<"\Digit A:\n"; A.Show(); cout<<"\Digit B:\n"; B.Show(); cout<<"\noperator+:\n"; C=A+B; C.Show(); cout<<"\noperator-:\n"; C=A-B; C.Show(); cout<<"\noperator*:\n"; C=A*B; C.Show(); cout<<"\noperator%:\n"; C=A%B; C.Show(); }
Пример 2. Класс динамического массива.
Задача: Создайте класс динамического массива, в котором реализована проверка выхода за границы массива. Перегрузите операторы: [ ], =, +, -,++ (добавление элемента в конец массива), – (удаление элемента из конца массива).
/* * File: classdynamicarray.cpp * Author: darkfire * * Created on December 11, 2010, 7:32 PM */ #include <stdlib.h> #include <iostream> /* Создайте класс динамического массива, в котором реализована проверка выхода * за границы массива. * Перегрузите операторы: [ ], =, +, -, * ++ (добавление элемента в конец массива), * -- (удаление элемента из конца массива). */ using namespace std; class A{ int size; int *a; public: A(int size);//Конструктор. Создание, инициализация массива. A(const A &aA);//Конструктор копирования ~A(){//деструктор delete [] a; } int Get () const {// метод выводит размер динамического массива return size; } int &operator[](int j){ //Перегруженный оператор [] return a [j]; } A operator--(); //Перегруженный оператор -- префиксной формы. Размер массива уменьшается на единицу A operator++(); //Перегруженный оператор ++ A operator+(const A &); A operator=(const A &); }; A::A(int size){//Конструктор. Создание, инициализация массива. this->size=size; a = new int [size]; for (int i = 0; i < size; i++) a [i] = i + 1; } A::A(const A &aA){//Конструктор копирования size = aA.size; a = new int [size]; for(int i = 0; i<size; i++) a[i] = aA.a[i]; } A A::operator-- (){ //Перегруженный оператор -- префиксной формы //размер массива уменьшается на единицу size-=1; int *b=new int [size]; for (int i=0;i<size;i++){ b[i]=a[i]; } delete [] a; a=b; return *this; //возвращаем объект генерирующий вызов } A A::operator++(){ //Перегруженный оператор ++ //размер массива увеличивается на единицу size+=1; int *b=new int [size]; for (int i=0;i<size-1;i++){ b[i]=a[i]; } b[size-1]=size;//заполним новый элемент массива delete [] a; a=b; return *this; //возвращаем объект генерирующий вызов } A A::operator+(const A &aA){ size = aA.Get()+this->size; A temp(size); return temp; } A A::operator=(const A &aA){ size = aA.size; //cout<<"aA.size: "<<aA.size<<"\n"; a = new int [size]; for(int i = 0; i<size; i++) a[i] = aA.a[i]; return *this; //возвращаем объект генерирующий вызов } int main() { int i; A object(12); cout<<"\nShow:\n"; for(i=0;i<object.Get();i++){ cout<<object[i]<<" "; } cout<<"\n\n"; --object; --object; --object; --object;//удалить один элемент массива cout<<object.Get()<<"\n"; for(i=0;i<object.Get();i++){ cout<<object[i]<<" "; } cout<<"\n\n"; ++object; ++object;//добавить один элемент массива cout<<object.Get()<<"\n"; for(i=0;i<object.Get();i++){ cout<<object[i]<<" "; } cout<<"\n\n"; A C(0); A B(5); cout<<"object.Get = "<<object.Get()<<" B.Get = "<<B.Get()<<"\n"; C=object+B; for(i=0;i<C.Get();i++){ cout<<C[i]<<" "; } cout<<"\n\n"; return (EXIT_SUCCESS); };
Перегрузка оператора ->
Мы надеемся, что вы помните, что в C++ можно перегрузить почти все операторы, за исключением нескольких. Во всяком случае, оператор → перегружается, и это имеет значение крайне важное. Кстати, этот оператор называется селектором (member selector). Рассмотрим пример:
#include <iostream> using namespace std; // класс, указатель на который // будет инкапсулирован class Temp { int TEMP; public: //конструктор Temp(){TEMP=25;} //функция показа на экран void TempFunction(){ cout<<"TEMP = "<<TEMP<<"\n\n"; } //функция установки значения void TempSet(int T){ TEMP=T; } }; // класс, инкапсулирующий указатель class MyPtr { //указатель на класс Temp Temp*ptr; public: //конструктор MyPtr(Temp*p=NULL){ ptr=p; } // Оператор преобразования типа // от инкапсулированного к инкапсулирующему operator Temp*(){ return ptr; }; // Оператор селектора -> // который позволит обратиться // напрямую к "спрятанному" // указателю Temp* operator->(){ return ptr; } //оператор ++ для смещения указателя вперед MyPtr operator++(){ ptr++; return *this; } }; void main () { //создание нового объекта Temp*main_ptr = new Temp; //простое обращение к членам //объекта через "родной" указатель main_ptr->TempFunction(); //создание объекта класса-указателя MyPtr pTemp(main_ptr); //обращение через класс-указатель pTemp->TempFunction(); //создание массива объектов //инкапсулируемого класса Temp*arr_=new Temp[3]; //заполнение вышеозначенного массива //значениями от 0 до 2 for(int i=0;i<3;i++) arr_[i].TempSet(i); //создание объекта класса указателя //и запись в него адреса массива //(здесь работает преобразование типа) MyPtr arr_temp=arr_; //сдвиг на один элемент вперед arr_temp++; //демонстрация результата arr_temp->TempFunction(); //удаление объектов delete main_ptr; delete[]arr_; } Результат работы программы TEMP = 25 TEMP = 25 TEMP = 1
Итак, обсудим результат: У нас есть класс Temp, который может иметь экземпляры, обладает некоторым членом TEMP и минимальным набором функций для работы с ним. Кроме того, мы создали класс объекта-указателя MyPtr, в котором храним обычный указатель, но доступ к нему ограничиваем, и перегружаем для него операторы:
Оператор приведения типа от Temp к MyPtr Оператор → (selector). Оператор ++, реализующий сдвиг указателя на один шаг вперед. Рассмотрим положительные моменты - мы получили класс объектов-указателей, которые можно смело применять вместо настоящих. Это удобно, т.к. все функции для работы с указателем можно инкапсулировать в этом классе. Однако есть еще одно широко распространенное применение данной конструкции, с которым мы с вами познакомимся в следующем разделе урока. Вперед!
📌 Для тестирования скриптов, установщиков VPN, Python ботов рекомендуем использовать надежные VPS на короткий срок. Если вам нужна помощь с более сложными задачами, вы можете найти фрилансера, который поможет с настройкой. Узнайте больше о быстрой аренде VPS для экспериментов и о фриланс-бирже для настройки VPS, WordPress. 📌
💥 Подпишись в Телеграм 💥 и задай вопрос по сайтам и хостингам бесплатно!7 Самых Популярных Статей
- Как запустить скрипты и веб-приложения на Python
- Что такое страны TIER 1,2,3
- 7 способов сравнения файлов по содержимому в Windows или Linux
- Установка и тестирование веб-панели HestiaCP
- Китайский VPN Shadowsocks простая установка и настройка
- top, htop, atop определение загрузки ОС (Load average, LA)
- Использование rsync в примерах