Перегрузка операторов

Перегрузка операторов является одной из разновидностью Полиморфизма.

В С++ есть возможность распространения действия стандартных операций на операнды абстрактных типов данных. Для того, чтобы переопределить одну из стандартных операций для работы с операндами абстрактных типов, программист должен написать функцию с именем operator знак, где знак - обозначение этой операции (например, + - | += и т.д.).

Однако в языке существует несколько ограничений, накладываемых на переопределение операторов:

  1. Нельзя создавать новые символы операций.
  2. Нельзя переопределять операции:
    ::
    * (разыменование, а не бинарное умножение)
    ?: 
    sizeof
    ## 
    #
    .
  3. Cимвол унарной операции не может использоваться для переопределения бинарной операции и наоборот. Например, символ « можно использовать только для бинарной операции, ! - только для унарной, а & - и для унарной, и для бинарной.
  4. Переопределение операций не меняет ни их приоритетов, ни порядка их выполнения (слева направо или справа налево).
  5. При перегрузке операции компьютер не делает никаких предположений о ее свойствах. Это означает, что если стандартная операция += может быть выражена через операции + и =, т.е. а + = b эквивалентно а = а + b, то для переопределения операций в общем таких соотношений не существует, хотя, конечно, программист может их обеспечить.
  6. Никакая операция не может быть переопределена для операндов стандартных типов.
  7. Как для унарной, так и для бинарной операции число аргументов функции operator () должно точно соответствовать числу операндов этой операции. Причем в перегрузку бинарного оператора принято передавать один аргумент, так как второй - неявный. Его имеет любая функция - член класса, это тот самый Указатель this языка CPP - указатель на объект, для которого вызван метод. Таким образом, в переопределение унарного оператора не следует передавать ничего вовсе.
Удобно передавать значения параметров в функцию operator() не по значению, а по ссылке.

Пример 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). Оператор ++, реализующий сдвиг указателя на один шаг вперед. Рассмотрим положительные моменты - мы получили класс объектов-указателей, которые можно смело применять вместо настоящих. Это удобно, т.к. все функции для работы с указателем можно инкапсулировать в этом классе. Однако есть еще одно широко распространенное применение данной конструкции, с которым мы с вами познакомимся в следующем разделе урока. Вперед!

PQ VPS сервера в 28+ странах.