Введите координаты точки A. Введите координаты точки B

x = 0

y = 0

Введите координаты точки B

x = 6

y = 0

Координаты точки D x = 3 y = 0

Введите координаты точки C

x = 0

y = 6

Координаты точки O (x = 2 y = 2 )

Расстояние между точками = 6

Несколько замечаний по поводу этого примера. При передаче аргумента в операторные функции здесь использована передача по ссылке. Это повышает эффективность работы программы, поскольку такой аргумент не копируется при передаче; в функцию фактически передается указатель на объект. Другой довод в пользу использования ссылки в качестве параметра – она позволяет избежать вызова деструктора для копии объекта, созданной при передаче объекта-параметра. Конечно, с таким же успехом можно было бы передавать в функцию сам объект, однако это менее эффективно. Хотя нет никаких требований к возвращаемому операторной функцией значению, здесь обе операторные функции возвращают объект, который имеет тип класса. Смысл этого состоит в том, что это позволяет использовать результат сложения двух объектов в сложном выражении. Например, становятся вполне допустимыми следующие выражения:

D = A + B + C ;

С другой стороны, допустимы выражения вида:

~ ( A + B ) ;

Здесь при вызове перегруженного оператора ~используется временный объект, возвращаемый оператором сложения.

Обратите внимание также на то обстоятельство, что в приведённых выше реализациях перегруженных операторов создаётся, инициализируется и возвращается временный объект, который уничтожается после завершения работы. Возврат ссылки на несуществующий объект приводит к ошибке.

Пример также демонстрирует возможность использования в качестве параметра операторной функции-члена перечислимого типа данных. Надо только не забывать, что этот параметр представляет правый операнд оператора.

Благодаря перегруженному оператору умножения Dot Dot :: operator * ( double d ), компилятор поймет, что означает инструкция:

D = A * 2 ;

Однако для него останется совершенно-непонятной инструкция

D = 2 * A ;

Более того, нет никакого способа дать разумное определение этой инструкции, ограничиваясь средствами операторной функции-члена. Путь для решения такой задачи открывает использование дружественной операторной функции.

Если операторная функция бинарного оператора объявляется как глобальная, она должна быть объявлена в виде:

friend <FuncType> operator X ( <Type1> <Par1> , <Type2> <Par2>) ;

По крайней мере, один из этих параметров должен иметь тип класса, для которого перегружается оператор.

Хотя нет никаких ограничений на тип возвращаемого значения бинарных операторов, большинство определяемых пользователем операторов возвращают либо тип класса, либо ссылку на тип класса.

Рассмотрим пример использования дружественной операторной функции для перегрузки оператора умножения класса Dot. Дружественной функции не передается скрытый указатель this. Поэтому в случае перегрузки бинарного оператора ей приходится передавать два аргумента, первый из которых соответст-вует левому операнду, а второй – правому.

 

Файл Dot.h

class Dot// класс точки

{

• • •

public:

friend Dot operator * ( double m , Dot & D ) ;// перегрузка оператора умножения числа на точку

} ;

Файл Dot.cpp

#include ”Dot.h”

// реализация перегруженного оператора умножения числа на точку

Dot operator * ( double m , Dot & D)

{

Dot T ( 'T' , D.x*m , D.y*m ) ;

Return T ;

}

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

В C++ существует несколько операторов, которые выполняют некоторое действие и присваивание одновременно:

+= -= /= *= %= <<= >>= &= ^= |=

Например,

int A = 5 ;// объявление и инициализация переменной A

A += 2 ;// увеличивает значение переменной A на два

Эти операторы трактуются компилятором как бинарные. Если операторные функции являются членами класса, то первый операнд является текущим, а второй – параметром. При выполнении указанных операторов состояние текущего объекта, как правило, изменяется.

В классе задач, для которых мы создаём класс Vec, требуется операция поворота вектора на заданный угол, для которой удобно определить операции сдвига и сдвига с присваиванием. Для поворота вектора против часовой стрелки (положительный угол поворота) определим сдвиг влево, по часовой стрелке – сдвиг вправо. Если угол поворота измеряется в градусах, то второй операнд задаётся целым числом, если в радианах – числом с плавающей точкой.

Следующий пример показывает, как определить операторы сдвига и сдвига с присваиванием для класса Vec.

Файл Vec.h

class Vec// класс вектора

{

• • •

public:

Vec operator << ( double f ) ;// перегрузка оператора сдвига влево с вещественным параметром

Vec operator << ( int f ) ;// перегрузка оператора сдвига влево с целым параметром

Vec& operator <<= ( int f ) ;// перегрузка оператора сдвига влево c присваиванием

} ;

Файл Vec.h

#include "Vec.h"

// возвращает вектор, повёрнутый на f радиан против часовой стрелки

Vec Vec :: operator << ( double f )

{

Vec T ("T") ;// объявляет временный объект

T.x = x*cos ( f ) - y*sin ( f ) ;// вычисляет проекции

T.y = x*sin ( f ) + y*cos ( f ) ;// повёрнутого вектора

return T ;// возвращает временный объект

}

// возвращает вектор, повёрнутый на f градусов против часовой стрелки

Vec Vec :: operator << ( int f )

{

Vec T ("T") ;// объявляет временный объект

const double pi = 4 * atan ( 1 ) ;// объявляет и инициализирует константу π

double F = f * pi / 180.0 ;// пересчитывает градусы в радианы

T = *this << F ;// вызывает операторную функцию Vec :: operator << ( double )

return T ;// возвращает временный объект

}

// поворачивает текущий вектор на f градусов против часовой стрелки

Vec& Vec :: operator <<= ( int f )

{

Vec T ("T") ;// объявляет временный вектор

T = *this << f ;// вызывает операторную функцию Vec :: operator << ( int )

*this = T ;// передаёт повёрнутый временный вектор в текущий объект

return *this ;// возвращает текущий объект

}

Файл Main.cpp

#include "Vec.h"

Void main ( )

{

Vec V ("V", 1 , 0 ) ,W ("W") ;// объявляет вектора

~ ( W = V << 45 ) ;// вычисляет и выводит на экран повёрнутый вектор

~ ( W <<= 45 ) ;// поворачивает вектор и выводит его на экран

}

При выполнении программа выводит на экран:

Проекции вектора W x = 0.707107 y = 0.707107

Проекции вектора W x = 5.55112e-017 y = 1

Перегрузка бинарных операторов, использующих объекты двух классов

Перегрузка бинарных операторов, использующих объекты двух классов, имеет некоторые особенности. Мы хотим, чтобы операцию вычисления координат конца вектора можно было бы записать следующим образом:

B = A + AB ;

Сначала рассмотрим реализацию перегрузки сложения точки и вектора с помощью операторной функции-члена класса.

Поскольку тип первого параметра – точка, то операторная функция должна быть членом класса Dot. Операторная функция содержит параметр типа Vec. Но класс вектора объявлен позже класса точки. Поэтому нам необходимо сделать неполное объявление класса вектора. Программа, решающая поставленную задачу будет иметь вид:

Файл DotVec.h

class Vec ;// неполное объявление класса вектора

class Dot// класс точки

{

• • •

public:

Dot operator + ( Vec & V ) ;// перегрузка оператора сложения точки и вектора

} ;

class Vec// класс вектора

{

char name [ 3 ] ;// имя вектора

double x , y ;// проекции вектора

public:

Vec ( char * pName ) { strncpy ( name , pName , 3 ) ; name [ 2 ] = '\0' ; x = 0 ; y = 0 ; }

Vec ( char * pName , double X , double Y )

{ strncpy ( name , pName , 3 ) ; name [ 2 ] = '\0' ; x = X ; y = Y ; }

Vec ( char * pName , Dot A , Dot B ) ;

inline double GetX ( ) const { return x ; }

inline double GetY ( ) const { return y ; }

} ;

Файл DotVec.cpp

#include ”DotVec.h”

• • •

// реализация перегруженного оператора сложения точки и вектора с помощью функции-члена класса

Dot Dot :: operator + (Vec &V)

{

Dot T ( 'T' , x + V.GetX() , y + V.GetY() ) ;

Return T ;

}

Поскольку функциям членам класса Dotнедоступны закрытые члены класса Vec, то для получения значений проекций вектора мы используем открытые функции GetX ( )и GetY ( ).

Рассмотрим реализацию перегрузки сложения точки и вектора с помощью операторной глобальной функции. В приведённом ниже примере мы объявили операторную функцию дружественную классам Dotи Vec. Поэтому в теле функции мы можем обращаться к закрытым членам с помощью оператора “точка”.

Файл DotVec.h

class Vec ;// неполное объявление класса вектора

class Dot// класс точки

{

• • •

public:

friend Dot operator + ( Dot & D , Vec & V ) ;// перегрузка оператора сложения точки и вектора

} ;

class Vec// класс вектора

{

• • •

public:

friend Dot operator + ( Dot & D , Vec & V ) ;// перегрузка оператора сложения точки и вектора

} ;

Файл DotVec.cpp

#include ”DotVec.h”

• • •

// реализация перегруженного оператора сложения точки и вектора с помощью глобальной функции

Dot operator + ( Dot & D , Vec & V )

{

Dot T ( 'T' , D.x + V.x , D.y + V.y ) ;

Return T ;

}

Перегрузка операторов инкремента и декремента

Операторы инкремента и декремента попадают в отдельную категорию, поскольку каждый из них имеет два варианта: префиксный и постфиксный. Чтобы различить префиксный и постфиксный варианты этих операторов, используется следующее правило:

· префиксная форма объявляется в точности так же, как любой другой унарный оператор;

· постфиксная форма принимает дополнительный аргумент типа int.

На самом деле этот аргумент обычно не используется и при передаче в функцию равен нулю (однако при желании он может быть использован). Этот параметр просто указывает компилятору, что речь идет о постфиксной форме оператора.

В C++ для оператора ++ определено увеличение операнда на единицу. В классе задач, для которых мы создаём класс Vec, такие действия, скорее всего, не потребуются. Поэтому в данном случае удобно для операции декремента и инкремента определить операции поворота вектора на 90° и –90° соответственно.

Следующий пример показывает, как определить префиксную и постфиксную формы операторов инкремента для класса Vec.

Файл Vec.h

class Vec// класс вектора

{

• • •

public:

Vec & operator ++ ( ) ;// префиксная форма оператора инкремента

Vec operator ++ ( int ) ;// постфиксная форма оператора инкремента

} ;

Файл Vec.cpp

#include ”Vec.h”

• • •

// реализация перегруженных операторов инкремента с помощью функций-членов класса

Vec & Vec :: operator ++ ( )// префиксная форма

{

double buff = x ;// x и y обмениваются значениями

x = -y ; y = buffer ;// через промежуточную переменную buffer

return *this ;// возвращает ссылку на текущий объект

}

Vec Vec :: operator ++ ( int )// постфиксная форма

{

Vec T = *this ;// объявляет временный объект и

// инициализирует его значением текущего объекта

++ *this ;// вызывает префиксную форму оператора инкремента

return T ;// возвращает временный объект

}

Файл Main.cpp

#include ”Vec.h”

Void main ( )

{

char S [ 30 ] ;

Vec A ("A" , 3 , 4 ) , B ( "B" ) ;// объявляет вектора

CharToOem ("\t\tПрефиксная форма\n" , S ) ; cout<<S ;

~ ( B = ++ A ) ; ~ A ;// вызывает префиксную форму оператора инкремента

CharToOem ("\t\tПостфиксная форма\n" , S ) ; cout<<S ;

~ ( B = A ++ ) ; ~ A ;// вызывает постфиксную форму оператора инкремента

}

При выполнении программа выводит на экран:

Префиксная форма

Проекции вектора B x = -4 y = 3

Проекции вектора A x = -4 y = 3

Постфиксная форма

Проекции вектора B x = -4 y = 3

Проекции вектора A x = -3 y = -4

Как видно из результата работы программы префиксная форма вызывается раньше оператора присвоения, постфиксная как бы – после. Достигается этот результат тем, что постфиксная форма создает временный объект и инициализирует его значением текущего, изменяет текущий и возвращает временный.

Обратите внимание, что постфиксная форма операторной функции возвращает не ссылку, а сам объект, поскольку из функции нельзя возвращать ссылку на временный объект.

Рассмотрим теперь перегрузку операторов инкремента с помощью дружественных функций. Заметим, что при использовании дружественной функции для перегрузки этих операторов в операторную функцию нужно передавать параметр в виде ссылки, поскольку он должен быть изменен в функции, и это измененное значение объекта должно быть возвращено.

Файл Vec.h

class Vec// класс вектора

{

• • •

public:

friend Vec & operator ++ ( Vec & D ) ;// префиксная форма оператора декремента

friend Vec operator ++ ( Vec & D , int ) ;// постфиксная форма оператора декремента

} ;

Файл Vec.cpp

#include ”Vec.h”

• • •

// реализация перегруженных операторов инкремента с помощью дружественных функций

Vec & operator ++ ( Vec & V)// префиксная форма

{

double buff = V.x ;// V.x и V.y обмениваются значениями

V.x = -V.y ; V.y = buffer ;// через промежуточную переменную buffer

return V ;// возвращает ссылку на параметр функции

}

Vec operator ++ ( Vec & V , int )// постфиксная форма

{

Vec T = V ;// объявляет временный объект и

// инициализирует его значением текущего объекта

++ V ;// вызывает префиксную форму оператора инкремента

return T ;// возвращает временный объект

}

Файл Main.cpp

#include ”Vec.h”

Void main ( )

{

Dot A ('A') , B ('B') , C ('C') , D ('D') ;// объявляет вершины квадрата

Vec AB ("AB", +A , +B ) ;// вводит координаты двух вершин квадрата с клавиатуры

// объявляет и вычисляет вектор AB

~ ( D = A + ++AB ) ;// поворачивает вектор на 90°,

// складывает точку A с повёрнутым вектором,

// результат сложения передаёт в точку D,

// выводит на экран координаты точки D

~ ( C = B + AB ) ;// складывает точку B с повёрнутым вектором,

// результат сложения передаёт в точку C,

// выводит на экран координаты точки C

}

При выполнении программа выводит на экран: