Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ТП лекции Раздел 4.doc
Скачиваний:
16
Добавлен:
28.09.2019
Размер:
2.56 Mб
Скачать

4.11.1. Перегрузка операций.

Суть этой уникальной возможности, предоставляемой язы­ком C++, состоит в том, что программист может переопределить смысл любой из более 40 разрешенных операций для объектов данного класса. Сделав это од­нажды, мы как бы наделяем язык свойствами, которыми он раньше не обладал. Например, переопределив операции +,-,*,/ в классе комплексных чисел, мы как бы наделяем язык C++ способностью оперировать комплексными числами по соответствующим правилам. Конечно, этого же можно добиться и традицион­ным способом, например, создав четыре функции: cadd, csubtr, cmult, cdiv. Однако вызов этих функций будет выглядеть намного менее естественным, чем про­сто запись вида z=zl+z2; или z=zl/z2; в предположении, что z, zl и z2 были объявлены объектами класса комплексных чисел. Чтобы переопределить опера­цию, необходимо особым образом определить ее либо как метод класса, либо как friend-функцию. Существует возможность переопределить в классе любую опе­рацию за исключением следующих шести: . . * :: ?: # ##. Функция, переопре­деляющая операцию, не может изменить количество аргументов операции. На­пример, унарная операция не может стать бинарной, и наоборот. Невозможно также изменить существующий приоритет и правила ассоциации, действующие при нормальном использовании операции. Рассмотрим выражение х + у, где х, у являются объектами какого-либо класса. Предположим, что в этом классе одним из двух возможных способов переопределена операция +. Тогда выражение х + у может быть интерпретировано компилятором также двумя способами:

х.operator + (у); //либо как

operator + (х.у);

Первая интерпретация означает, что объекту х посылается сообщение operator* (у). То есть вызывается функция (член класса) с именем operator+(), при этом в качестве единственного параметра передается объект у. Вторая означает вызов внешней функции с именем operator+(), которой передаются два параметра х и у. В соответствии с этим существуют два способа переопределения бинарных операций: public-методом с одним аргументом и friend-функцией с двумя аргу­ментами. Сходные рассуждения приводят к выводу, что унарную операцию можно переопределить либо public-методом класса без параметров, либо friend-функ­цией с одним параметром.

Следует обратить внимание на следующие особенности:

• следует отдельно переопределять префиксную и постфиксную модифика­ции унарных операций ++ и - -, чтобы иметь возможность их различать;

• если типы операндов бинарной операции различаются, и первым идет объект другого типа, то переопределить операцию можно с помощью friend-функ­ции с двумя параметрами.

Например, в выражении вида c+z, где с — число типа double, a z — объект класса комплексных чисел, операция + может быть переопределена только friend-функцией с двумя параметрами. В этом можно убедиться, анализируя способ интерпретации компилятором выражения z+48. Если операция + реализована как метод класса, то он будет z.operator+(48);, что вполне осмысленно, но при этом выражение 48+z; должно интерпретироваться как 48.operator+(z);. Однако это не имеет смысла, так как 48 не является и не может быть (в C++) объектом класса, определенного пользователем.

Необходимо также иметь в виду и следу­ющие ограничения:

• операции =,(),[],-> могут быть переопределены только с помощью метода класса;

• описатель static для метода класса, переопределяющего операцию, недо­пустим.

Рассмотрим, как используется возможность переопределения операции на при­мере реализации абстрактного типа данных вектор на плоскости. Каждый век­тор (объект класса Vector) имеет две координаты, задающие положение вектора на плоскости. Будем хранить эти координаты в двух private-компонентах класса xl, х2. Совместим с этим типом данных следующие операции: присвоения, сложе­ния, вычитания, скалярного произведения, проверки равенства, отношения «боль­ше», а также вычисления нормы. С этой целью осуществим переопределение опе­раций (соответственно: =, +, -, *, ==, >, !) в классе Vector. Ясно, что только одна операция взятия нормы является унарной. Реализуем ее в виде метода класса без параметров. Операции =, ==, >, + , - тоже можно реализовать в виде мето­дов класса с одним параметром. С целью иллюстрации технологии выполним переопределение операции умножения на скаляр (слева) с помощью внешней friend-функции:

class Vector {

double xl, x2; // Координаты вектора

public:

// Три конструктора

Vector (double cl, double c2) // С параметрами

{ xl=cl; x2=c2;}

Vector () // Default

{ xl = x2 = 0.; }

Vector (const Vector& v) // Сору

(xl = v.xl; x2 = v.x2;}

//= = = = = = Переопределение операций =====//

// Присвоение

Vector operator=(Vector& v)

{

if (this==&v) return *this; xl=v.xl;

x2=v.x2; return *this;

}

// Операция >

bool operator>(Vectors v)

{return !*this > !v;}

// Проверка равенства

bool operator==(Vector& g)

{ return xl==g.xl && x2==g.x2; }

// Сложение

Vector operator+(Vector& g)

{ return Vector (xl+g.xl, x2+g.x2); }

Vector operator-(Vectors g) //Вычитание {return Vector(xl-g.xl, x2-g.x2);}

Vector operator*(double f) //Умножение на скаляр {return Vector(f*xl, f*x2);}

double operator*(Vector& g) // Скалярное произведение return (xl*g.xl + x2*g.x2):

double operator! () // Вычисление нормы {return sqrt (xl*xl+x2*x2);}

//====== Внешняя friend-функция ======//

friend Vector operator*(double, Vector&);

Vector operator*(double f, Vectors g) { // Умножение на скаляр return Vector(f*g.xl, f*g.x2);

}

void main()

{

Vector a(3,4), b(0,4), c=a, vv[5];

puts ("\n Class Vector Demonstration");

c=vv[0]=b; //Цепочка присвоений

printf ("\n Norm of c=vv[0]=b:\t %6.2f",!c);

c=5.*a;

printf ("\n Norm of 5.*a:\t\t %6.2f",!c):

c=b*!a*5.;

printf ("\n Norm of c=b*!a*5.:\t %6.2f",!c);

c=a+b;

printf ("\n Norm of a+b:\t\t %6.2f",!c);

printf ("\n Norm of a-b:\t\t %6.2f",!(a-b));

printf ("\n\n Scalar product (a.b) = %3.0f",a*b);

if (b>a)

puts("\n\n Norm of b is greater than that of a");

if (a==b) ;

else

puts("\n\n Vector a is not equal to vector b");

a=b;

if (a==b)

puts("\n After assignment a=b; we have a==b");

vv[3]=a;

printf ("\n Norm of vv[3]=a =%6.2f",!vv[3]);

}

При анализе этой программы следует обратить внимание на следующие мо­менты. Класс Vector имеет три конструктора. Конструктор с двумя параметрами позволяет создать вектор с желаемыми компонентами, конструктор без парамет­ров позволяет определить массив объектов класса, конструктор копирования по­зволяет корректно производить инициализацию объектов класса Vector другими объектами своего же класса.

Реализация public-метода, переопределяющего би­нарную операцию присвоения, использует указатель this. Так как this содер­жит адрес объекта, пославшего сообщение (operator=), то значением, возвращае­мым оператором return *this;, будет объект, стоящий слева от знака присвоения. Поэтому становится возможной цепочка присвоений c=vv[0]=a;. Метод класса, переопределяющий операцию !, возвращает вещественное число, равное сумме квадратов компонентов вектора, то есть выбранную нами норму вектора. Здесь унарная операция переопределяется с помощью public-метода без аргументов. Результат проверки равенства двух векторов (операция ==) имеет тип boo!. Па­раметры всех friend-функций есть ссылки на объекты класса Vector. Это позво­ляет сэкономить память стека, так как при передаче ссылкой копии параметров в системном стеке не делаются. Методы, переопределяющие операции + и -, возвращают объекты класса Vector. Операция * переопределена трижды:

• методом, возвращающим double (скалярное произведение двух векторов);

• методом, возвращающим Vector (произведение вектора на скаляр);

• внешней функцией, возвращающей Vector (произведение скаляра на вектор).

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

xl=(|x|+l)*xl/|x|. х2=(|х|+1)*х2/|х|.

где | х | — модуль вектора. Любую унарную операцию можно переопределить либо с помощью метода класса без параметров, либо с помощью friend-функции с од­ним параметром. В Visual C++ принято отличать постфиксную модификацию от префиксной путем описания лишнего фиктивного, неиспользуемого формального параметра типа int в заголовке метода или функции. Например, объявления:

Vector Vector:: operator++(int);

friend Vector operator++(Vector& v, int);

определяют постфиксную модификацию при переопределении операции ++ мето­дом класса или внешней функцией. Если реализация тела префиксной операции ++ очевидна, то постфиксная модификация требует пояснений. Возвращаемое функ­цией значение вектора должно быть старым, а содержимое объекта, пославшего сообщение, следует изменить на новое, вычисленное в соответствии с приведен­ными формулами. Таким образом, описание класса Vector можно дополнить двумя методами:

Vector operator++() //Префиксная + +

{

dn=(d+l.)/d;

x2 *= dn;

double d=!*this, xl *= dn;

return *this;

}

Vector operator++(int) //Постфиксная ++

{

Vector temp=*this; // Запоминаем старый вектор double d=!*this, dn=(d+l. )/d;

xl *= dn; x2 *= dn: // Новые значения компонентов return temp ; // Результат выражения — старый }

Обратите внимание: d=!*this; вычисляет норму вектора, пославшего сообще­ние. Здесь * означает разадресацию, ! означает уже переопределенную опера­цию взятия нормы. Для проверки функционирования вновь введенных операций в конец функции main можно добавить строки:

а=++b ;

printf ("\n Norm of a=++b:\t\t %6.2f",!a);

b=a++ ;:

printf ("\n Norm of b=a++:\t\t %6.2f",!b);

printf ("\n After a++ the norm of a:%6.2f", !a) ;

Если постфиксная модификация работает корректно, то последнее выведен­ное число должно быть на единицу больше, чем предпоследнее. Операция -- может быть переопределена аналогичным образом. Класс Vector выглядит дале­ко не полным. Разумно было бы дополнить его методами вычисления угла меж­ду двумя векторами, вычисления векторного произведения и другими. Не хвата­ет также методов для ввода и вывода компонентов вектора.