Введите координаты точки A
Лекция №8
ПЕРЕГРУЗКА ОПЕРАТОРОВ
Понятие перегрузки операторов
В C++ имеется возможность перегрузки большинства встроенных операторов. Операторы могут быть перегружены глобально или в пределах класса. Перегруженные операторы реализуются как функции с помощью ключевого слова operator. Имя перегруженной функции должно быть operatorX, где X – перегруженный оператор. Ключевое слово operator, за которым следует символ оператора, называется именем операторной функции.
Операторы, которые можно перегружать:
Перегружаемые унарные операторы: + – ! * ++ -- ~ &
Перегружаемые бинарные операторы: + – / * % << >> & ^ | = –> || ! = += –= /= *= %= <<= >>= &= ^= |= == –>* &&.
Другие перегружаемые операторы: ( ) [ ] new delete
Неперегружаемые операторы: . .* :: ? :# ##
Необходимо отметить что, существуют две версии унарных операторов инкремента и декремента: префиксная и постфиксная.
Например, чтобы перегрузить оператор сложения, нужно определить функцию с именем operator+, а чтобы перегрузить оператор сложения с присваиванием, нужно определить функцию operator+=. Обычно компилятор вызывает эти функции неявно, когда перегруженные операторы встречаются в коде программы. Тем не менее, их можно вызывать и непосредственно. Например:
Dot A('A'), B('B'), C('C');
C=A+B;// неявный вызов перегруженного оператора сложения
C=A.operator+(B);// явный вызов перегруженного оператора сложения
Правила перегрузки операторов
Операторная функция перегруженного оператора, за исключением new и delete, должна быть:
§ либо нестатической функцией-членом класса;
§ либо принимать аргумент типа класса;
§ либо принимать аргумент перечислимого типа;
§ либо принимать аргумент, который является ссылкой на тип класса;
§ либо принимать аргумент, который является ссылкой на перечислимый тип.
Кроме того, при перегрузке операторной функции выполняются следующие правила:
§ операторная функция не может изменять число аргументов перегружаемого оператора;
§ операторная функция не может изменять приоритет перегружаемого оператора;
§ операторная функция не может иметь параметров по умолчанию;
§ операторные функции = , ( ) , [ ] и –> должны быть нестатическими функциями-членами класса.
Операторная функция может быть объявлена как функция-член класса.В этом случае должны быть выполнены следующие правила:
§ операторная функция унарного оператора не должна иметь параметров;
§ операторная функция бинарного оператора должна иметь один параметр;
§ операторная функция класса наследуются его производными классами (за исключением операторной функции оператора присваивания).
Операторная функция может быть объявлена как глобальная функция, которая имеют дружественный доступ к членам класса. В этом случае должны быть выполнены следующие правила:
§ операторная функция унарного оператора должна иметь один параметр;
§ операторная функция бинарного оператора должна иметь два параметра.
§ операторная функция не наследуются его производными классами (как и любая дружественная функции).
Правила для операторов newи deleteбудут рассмотрены отдельно.
Заметим, что с помощью перегрузки можно совершенно изменить смысл оператора для некоторого класса. Однако рекомендуется не делать этого без веских на то причин, так как это может сильно затруднить понимание кода программы. Перегруженный оператор, как правило, должен следовать семантике его поведения для встроенных типов данных.
Перегрузка унарных операторов
Унарным операторам необходим только один операнд. При перегрузке унарного оператора с помощью функции-члена этот единственный операнд представляет собой объект, для которого и выполняется операция. Поэтому, когда операторная функция унарного оператора объявляется как нестатическая функция-член, она должна быть объявлена в виде:
<FuncType> operatorX();
где <FuncType>– тип возвращаемого функцией значения; X–перегружаемый оператор.
Хотя в эту категорию попадают операторы инкремента и декремента, мы рассмотрим их отдельно. Рассмотрим в качестве примера перегрузку унарных операторов «плюс», «восклицательный знак» и «дополнение до единицы».
В данной задаче мы полагаем, что перегруженные операторы +, !и ~нам не понадобятся по прямому назначению. Поэтому мы приписали им совершенно нехарактерные операции: ввод, вывод и вычисление расстояния. Эти перегруженные унарные операции могут существенно упростить программу.
Файл Dot.h
class Dot// класс точки
{
const char name ;// имя точки
double x , y ;// координаты точки
public:
Dot (char Name ) : name ( Name ) { x = 0 ; y = 0 ;}
Dot (char Name , double X , double Y ) : name ( Name ) { x = X ; y = Y ; }
Dot & operator ~ ( ) ;// перегрузка оператора “дополнение до единицы”
Dot & operator + ( ) ;// перегрузка оператора “плюс”
double & operator ! ( ) ;// перегрузка оператора “восклицательный знак”
};
Файл Dot.cpp
#include ”Dot.h”
// выводит на экран заголовок, имя и координаты текущей точки
Dot & Dot :: operator ~ ( )
{
char S [ ] = "Координаты точки ";
CharToOem ( S , S ) ;
cout<<S<<name<<"\tx = "<<x<<"\ty = "<<y<<'\n' ;
return *this ;// возвращает текущий объект
}
// выводит на экран приглашение, имя и позволяет вводить значения координат с клавиатуры
Dot & Dot :: operator + ( )
{
char S [ ] = "Введите координаты точки " ;
CharToOem ( S , S ) ;
cout<<S<<name<<'\n' ;
cout<<"\tx = "; cin>>x ;
cout<<"\ty = "; cin>>y ;
return *this ;// возвращает текущий объект
}
// возвращает расстояние от текущей точки до начала координат
double Dot :: operator ! ( )
{
return sqrt ( x*x + y*y ) ;
}
Файл Main.cpp
#include ”Dot.h”
Void main ( )
{
Dot A ('A') ;// вызов конструктора Dot ( char Name )
double d = !~+A;// "+" - ввод координат точки с клавиатуры
// "~" - вывод координат точки на экран
// "!" - вычисление расстояния от точки до начала координат
char S [ ] = "Расстояние от точки до начала координат = " ;
CharToOem ( S , S ) ; cout<<S<<d<<'\n' ;
}
При выполнении программа выводит на экран:
Введите координаты точки A
x = 3
y = 4
Координаты точки A x = 3 y = 4
Расстояние от точки до начала координат = 5
Если операторная функция унарного оператора объявляется как глобальная, она должна быть объявлена в виде:
friend <FuncType> operator X ( <Type> <Par> ) ;
где
<FuncType>тип возвращаемого функцией значения;
Xперегружаемый оператор;
<Type>тип параметра;
<Par>параметр.
Те же операторы могут быть перегружены с помощью глобальных функций, используя следующие прототипы функций, объявленных дружественными для класса:
friend Dot & operator ~ ( Dot & D ) ;// перегрузка оператора "дополнение до единицы"
friend Dot & operator + ( Dot & D ) ;// перегрузка оператора "плюс"
friend double operator ! ( Dot & D ) ;// перегрузка оператора "восклицательный знак"
Для рассматриваемого примера реализация этих функций может быть следующей:
Dot & operator ~ ( Dot & D )
{
char S [ ] = "Координаты точки "; CharToOem ( S , S ) ;
cout<<S<<A.name<<"\tx = "<<D.x<<"\ty = "<<D.y<<'\n';
Return D ;
}
Dot & operator + ( Dot & D )
{
char S [ ] = "Введите координаты точки "; CharToOem ( S , S ) ;
cout<<S<<D.name<<'\n';
cout<<"\tx = "; cin>>D.x; cout<<"\ty = "; cin>>D.y;
Return D ;
}
double operator ! ( Dot & D )
{
return sqrt ( D.x*D.x + D.y*D.y ) ;
}
Результат работы программы с перегруженными таким образом операторами будет тем же.
Перегрузка оператора присваивания
Оператор присваивания является бинарным, однако процедура его перегрузки имеет ряд особенностей:
· операторная функция оператора присваивания не может быть объявлена как глобальная функция (функция, не являющаяся членом класса);
· операторная функция оператора присваивания не наследуется производным классом;
· компилятор может сгенерировать операторную функцию оператора присваивания, если она не определена в классе.
Оператор присваивания по умолчанию, сгенерированный компилятором, выполняет почленное присваивание нестатических членов класса. Здесь та же ситуация, что и с генерируемым компилятором конструктором копирования по умолчанию: результат окажется непригодным для использования, если класс содержит указатели.
Хотя в нашем классе Dotуказатели отсутствуют, но класс содержит константный член name, который не может изменяться после инициализации. Поэтому оператор присваивания по умолчанию будет вызывать синтаксическую ошибку.
Необходимо отметить, что левый операнд после выполнения оператора присваивания меняется, так как ему присваивается новое значение. Поэтому функция оператора присваивания должна возвращать ссылку на объект, для которого она вызвана (и которому присваивается значение), если мы хотим сохранить семантику оператора присваивания для встроенных типов данных. Проще всего это сделать, возвратив разыменованный указатель this. Это, в частности, позволяет использовать перегруженный оператор присваивания в выражениях следующего вида:
D1 = D2 = D3 ;
Следующий пример иллюстрирует, как осуществляется перегрузка оператора присваивания:
Файл Dot.h
class Dot// класс точки
{
• • •
public:
Dot& operator = ( Dot & R ) ;// перегрузка оператора присваивания
};
Файл Dot.cpp
#include ”Dot.h”
• • •
// реализация перегруженного оператора присваивания
Dot & Dot :: operator = ( Dot & R )
{
if ( this == &R ) return *this ;
x = R.x ; y = R.y ;
return *this ;
}
Заметим, что при реализации перегруженного оператора необходимо проводить проверку на присваивание самому себе. Если в коде программы встретится строка вида
D = D ;
или, что гораздо более вероятно, нечто вроде следующего:
Coord D , *pD ; D = *рD ;
При этом произойдет присваивание самому себе, связанное с ошибкой в программе.
Если не предусмотреть защиту от такой ситуации, то произойдет следующее: оператор присваивания сначала очистит все ячейки памяти объекта слева, а затем попытается присвоить этому объекту значение объекта справа, который уже не содержит реальных значений.
В приведенном выше примере функция operator = ( )содержит инструкцию
if ( this = &R ) return *this ;
Здесь идентификатор Rиспользуется для обозначения операнда, расположенного в правой части оператора присваивания.
Перегрузка бинарных операторов
Когда операторная функция бинарного оператора объявляется как нестатическая функция-член, она должна быть объявлена в виде:
<FuncType> operator X ( <Type> <Par> ) ;
В этот параметр будет передан тот объект, который стоит справа от оператора. Объект, стоящий слева от оператора, передается неявно с помощью указателя this.
Здесь мы приведем несколько примеров написания операторных функций. Не следует их рассматривать как исчерпывающие все возможные варианты, однако они иллюстрируют несколько наиболее общих приемов.
Вначале рассмотрим случай, когда операторная функция определяется как функция-член класса. В следующей программе перегружаются операторы сложения, вычитания, умножения и деления для класса Dot:
Файл Dot.h
class Dot// класс точки
{
• • •
public:
Dot operator + ( Dot & D ) ;// перегрузка оператора сложения
Dot operator - ( Dot & D ) ;// перегрузка оператора вычитания точек
Dot operator / ( double d ) ;// перегрузка оператора деления точки на число
Dot operator * ( double d ) ;// перегрузка оператора умножения точки на число
} ;
Файл Dot.cpp
#include ”Dot.h”
• • •
// реализация перегруженных операторов
Dot Dot :: operator + ( Dot & D )// оператор сложения точек
{
Dot T ( 'T' , x + D.x , y + D.y ) ;// объявляет и инициализирует временный объект
return T ;// возвращает временный объект
}
Dot Dot :: operator - ( Dot & D )// оператор вычитания точек
{
Dot T ( 'T' , x - D.x , y - D.y ) ;
Return T ;
}
Dot Dot :: operator / ( double d )// оператор деления точки на число
{
Dot T ( 'T' , x / d , y / d ) ;
Return T ;
}
Dot Dot :: operator * ( double d )// оператор умножения точки на число
{
Dot T ( 'T' , x * d , y * d ) ;
Return T ;
}
Файл Main.cpp
#include ”Dot.h”
Void main ( )
{
Dot A ('A') , B ('B') , C ('C') , D ('D') , O ('O') ;// объявляет точки
~ ( D = (+B + +A ) / 2 ) ;// ввод координат точек A и B с клавиатуры,
// вычисление середины отрезка D и
// вывод её координат на экран
~ ( O = (+C + D * 2 ) / 3 ) ;// ввод координат точки С с клавиатуры,
// вычисление точки пересечения медиан и
// вывод её координат на экран
double d = ! ( B – A ) ;// вычисление расстояния между точками
char S [ ] ="Расстояние между точками = "; CharToOem ( S , S ) ;
cout<<S<<d<<'\n' ;
}
В приведённом примере осуществляется ввод вершин треугольника, вычисляется середина основания и точка пересечения медиан. При выполнении программа выводит на экран: