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

Содержание статьи:

Вступление.
Перегрузка унарных операторов.
Перегрузка бинарных операторов.
N-арный оператор.
Перегрузка операторов new и delete.
Перегрузка в форме внешних функций.

Вступление.

к началу статьи
Вот мы с вами и дожили до перегрузки операторов. Наверное, это моя самая любимая тема. Не скрою, я просто обожаю перегружать операторы, но стараюсь не злоупотреблять (хотя иногда на меня находит), к чему и Вас призываю. Перегрузка операторов позволит Вам делать ваш код более абстрактным (но одновременно более сложным). Если вы пишете класс массива, однонаправленного, двунаправленного списков, других фундаментальных структур данных, то оператор индексирования необходим Вам как воздух. Если Вы пишете математическую библиотеку, то для работы с векторами неплохо бы перегрузить оператор умножения на число для записи линейных комбинаций векторов, так же неплохо бы выделить операторы для сложения, скалярного и векторного умножения. Вы может захотеть, чтобы объекты ваших классов могли выводиться через стандартный поток cout. Ещё много примеров можно привести, но уже этих достаточно, чтобы заняться полиморфизмом вплотную.

Перегрузка унарных операторов.

к началу статьи
Начнем с унарных операторов, как наиболее простых. Например, оператор постинкрементации:
class	apple{
public:
apple( void ){x=0;}
int x;
apple operator++( void );
friend apple operator++( apple );
};

apple operator ++( apple a ){
a++;
return( a );
}

apple apple::operator ++( void ){
x++;
return( *this );
}

Первое что бросается в глаза так это возврат объекта (мы уже сталкивались с подобной ситуацией в одной из предыдущих глав, только там у нас был оператор присваивания), это сделано для того, что бы данный объект мог после инкрементации участвовать в выражениях, например: B = A++; Механика вызова такова: компилятор видит объект, затем оператор ++, и вызывает метод apple::operator++( void ). Заметьте, что объект неявно передался в этот метод, поэтому повторная передача в качестве параметра не требуется. Теперь разберемся с другом и преинкрементированием. Компилятор сначала видит ++ и пытается вызвать метод, но какой? Про то, что стоит справа он не знает, да и не хочет знать, т.к. операция инкрементации вызывается для пользовательского типа, а, следовательно, должен быть специальный метод, но слева то пусто (при вызове метода класса, сначала должен идти объект, а затем метод, который мы вызываем, но не наоборот)! Тут на сцену выходит друг operator++. Он, как Вы помните не является членом класса, поэтому такой синтаксис для него правомочен. *this друзьям не передается, поэтому нужно передавать объект как параметр. Вот и все(стоит отметь, что MSVC не делает различия между преинкрементированием и постиинкрементированием, более при попытке дописать ещё и друга, компилятор выдаст ошибки, о том что мол неоднозначность, извольте поправить). А вот при перегрузке унарного минуса (так же как и !, ~) без друга не обойтись.
class	apple{
public:
apple( void ){x=0;}
int x;
friend apple operator-( apple a ){a.x=-a.x;return(a);}
};

Вы забыли оператор ->, а ведь он то же унарный:
class	A{
public:
float x;
};

class cSomeclass{
public:
A array[1024];
A *operator->( void ){return(array);}
};

Оператор преобразования:
class	A{
public:
float x;
operator long(){return(12);}
};

Тип возвращаемого значения не указывается, потому что однозначно определяется по типу, к которому происходит преобразование. Перегрузка этого оператора приводит к интересным эффектам. Так следующий код вполне правомерен:
int _tmain(int argc, _TCHAR* argv[]){
A a;
//здесь А автоматически преобразовывается к long!
if( a ){}
return 0;
}

Перегрузка бинарных операторов.

к началу статьи
Ну, про перегрузку оператора присваивания мы уже говорили, поэтому долго останавливаться здесь не будем. Собственно как и обещал оператор скалярного произведения:
class	cVector{
public:
float x,y,z;
float operator*( cVector v ){return( x * v.x + y * v.y + z * v.z );}
};

и оператор индексирования:
class	cSomeclass{
public:
float array[1024];
float operator[]( int i ){ return(array[i]); }
float operator[]( char * c ){ return( array[ c[ 0 ] ] ); }
}

Этот код абсолютно корректен – оператор индексирования может принимать в качестве параметра не только целые типы, а все что Вашей душе будет угодно. Я обратил на это внимание, потому что в силу своей специфики, этот оператор редко удостаивается чего-то кроме int, хотя если Вы задумаете делать какой-нибудь словарь то operator[]( char * c ) будет очень кстати.

N-арный оператор.

к началу статьи
Есть один экзотический оператор – (). Да, да. Скобки! Вряд ли у Вас когда-нибудь возникнет дьявольский план использовать объект как функцию, но все же:
class	B{
public:
float operator()( float a , float b , float c , float d , float e , float f )
{return( a + b + c + d + e + f );}
};

int main( void ){
B ob;
ob( 0 , 1 , 2 , 3 , 4 , 5 );
getch();
return 0;
}

Перегрузка операторов new и delete.

к началу статьи
Оказывается new и delete то же операторы! Зачем может понадобиться перегружать эти операторы? Возможно, Вы захотите реализовать какую-то изощренную стратегию выделения памяти, или сборщик мусора, или просто захотите проверить свою программу на наличие/отсутствие утечек памяти. Начнем с оператора new – он имеет стандартный вид void* operator new( size_t bytes ) Параметр size_t должен быть в каждой перегруженной версии этого оператора, причем стоять должен на первом месте. Через этот параметр передается размер памяти в байтах, которую нужно выделить. Если есть желание, то можно передавать что-то помимо этого, как я это сделал в следующем примере:
class	cMemory{
public:
void *operator new( size_t bytes , char * );
void *operator new( size_t bytes ){return(::new char[ bytes ]);}
void operator delete( void * address );
void operator delete( void * address , size_t bytes ){::delete [] address;}
};

void cMemory::operator delete( void * address ){
cout<<str<<endl;
cout<<"delete"<<endl;
::delete [] address;
}

void *cMemory::operator new( size_t bytes , char *str ){
cout<<str<<endl;
cout<<"new"<<endl;
return( ::new char[ 100 ] );
}

Оператор delete имеет две стандартные формы:
void	operator delete( void *address )
void operator delete( void *address , size_t bytes)

Насколько мне известно, перегрузить этот оператор с другими списками параметров не получится.
Во второй версии оператора delete через второй параметр ( size_t bytes ) передается размер блока памяти, который предназначен к удалению.

Перегрузка в форме внешних функций.

к началу статьи
Собственно мы это уже проходили, просто хотел подчеркнуть возможность такой перегрузки и для бинарных операторов.
class	B{
public:
float x;
};

B operator+( B &b1 , B &b2 ){
B ret;
ret.x = b1.x + b2.x;
return( ret );
}

Осталось только подвести несколько итогов: операторы наследуются, оператор можно перегружать произвольное число раз (условие, что списки формальных параметров должны отличаться в силе), Вы не можете изменить приоритет оператора. Так селектор члена (->) всегда будет иметь высший приоритет.

Как следует впитайте эту статью, а затем, когда решите, что Вам все понятно, обмозгуйте следующий кусочек кода:

#include "stdafx.h"
#include <stdio.h>
#include <conio.h>
#include <iostream.h>

class MAIN{};
class B;
class A{
MAIN *PTR;
public:
A( MAIN * ){cout<<"A( MAIN * )"<<endl;}
operator B();
~A(){cout<<"~A"<<endl;}
};

class B{
friend A;
B( MAIN * ){cout<<"B( MAIN * )"<<endl;};
public:
operator A();
~B(){cout<<"~B"<<endl;}
};

A::operator B(){cout<<"A::operator B()"<<endl;return(NULL);}
B::operator A(){cout<<"B::operator A()"<<endl;return(NULL);}

A a( NULL );
B b = a;

int _tmain(int argc, _TCHAR* argv[]){
getch();
return 0;
}

к началу статьи

Советы, вопросы, замечания, пожелания как всегда шлите на dodonov_a_a (___AT) inbox.ru


Смежные вопросы:
Урок 1. Основы классов.
Урок 2. Конструкторы копий, оператор присваивания.
Урок 3. Перегрузка функций.
Урок 4. Друзья.
© 2004-2005 Savardge.ru
Hosted by uCoz