Объектно-ориентированное программирование, страница 9

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

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

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

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

class Vector

{

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

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

Vector () { x1 = x2 = 0.; } // Default

Vector (double c1, double c2) { x1=c1; x2=c2; } // С параметрами

Vector (const Vector& v){ x1 = v.x1; x2 = v.x2; }// Copy

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

Vector operator=(Vector& v) // Присвоение

{

  if (this==&v)

    return *this;

  x1=v.x1;     x2=v.x2;

  return *this;

}

bool operator>(Vector& v)  // Операция >

{

  return !*this > !v;

}

bool operator==(Vector& g) // Проверка равенства

{

  return x1==g.x1 && x2==g.x2;

}

Vector operator+(Vector& g) // Сложение

{

  return Vector (x1+g.x1, x2+g.x2);

}

Vector operator-(Vector& g) // Вычитание

{

  return Vector(x1-g.x1, x2-g.x2);

}

Vector operator*(double f) // Умножение на скаляр

{

  return Vector(f*x1, f*x2);

}

double operator*(Vector& g) // Скалярное произведение

{

  return (x1*g.x1 + x2*g.x2);

}

double operator!()         // Вычисление нормы

{

  return sqrt (x1*x1+x2*x2);

}

friend Vector operator*(double, Vector&);

};

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

Vector operator*(double f, Vector& g) // Умножение. на скаляр

{  

return Vector(f*g.x1, 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-метода без аргументов. Результат проверки равенства двух векторов (операция ==) имеет тип bool. Параметры всех friend-функций — есть ссылки на объекты класса Vector. Это позволяет сэкономить память стека, так как при передаче ссылкой копии параметров в системном стеке не делаются. Методы, переопределяющие операции + и - возвращают объекты класса Vector. Операция * переопределена трижды:

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

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

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

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

x1=(|x|+1)*x1/|x|,        x2=(|x|+1)*x2/|x|,

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