Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP.doc
Скачиваний:
13
Добавлен:
25.04.2019
Размер:
1 Мб
Скачать
☆

/* Присваиваем собственные данные класса d */

...

}

Особенность приведенной реализации в том, что в ней путем приведения статического типа текущего объекта к B& «провоцируется» неявный вызов функции B::operator= вместо D::operator=. Особое внимание необходимо уделить использованию B&, а не B, так как во втором случае вызов будет происходить для временного объекта, создаваемого в результате преобразования типа.

Еще одной операцией, перегрузка которой имеет определенные особенности, является индексация (operator[]). Для перегрузки операции [] необходимо определить нестатическую компонентную функцию-операцию operator[] с единственным параметром целого типа. Тогда запись X[i] для объекта X некоторого класса C, содержащего operator[], будет транслироваться в формат X.operator[](i).

Главный вопрос при перегрузке operator[] связан с тем, что функций-операций должно быть две. Первая для неконстантного объекта, а вторая для константного. Функция для неконстантного объекта должна возвращать ссылку на тип элемента без модификаторов, что делает результат ее вызова леводопустимым выражением. Функция для константного объекта должна возвращать ссылку на тип элемента с квалификатором const (или даже просто значение элемента). В результате ее вызов будет праводопустимым выражением.

Ниже приведен пример определения класса, моделирующего вещественные массивы с контролем диапазона индексов; в этом классе определяется операция operator[]:

class CArray {

public:

double & operator[] (int i)

{ // для неконстантного объекта

if (i < 0 || i >= __MaxLen)

throw std::range_error(“invalid index”);

return __Array[i];

}

double operator[] (int i) const

{ // для константного объекта

if (i < 0 || i >= __MaxLen) return MAXDOUBLE;

return __Array[i];

}

...

private:

static const int __MaxLen;

double __Array[__MaxLen];

};

В примере функция operator[] для константного объекта при неправильном индексе просто возвращает значение «бесконечность» (MAXDOUBLE), а функция для константного объекта возбуждает исключение range_error. Это обусловлено тем, что запись по неверному индексу является более серьезной проблемой нежели чтение и продолжать программу при ее наличии вряд ли разумно.

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

Операции << и >> перегружаются глобальными (как правило, дружественными) функциями-операциями. Перегрузить их компонентными функциями класса можно, но это приведет к неестественному синтаксису, поскольку объект будет левым операндом, а поток правым:

class X {

public:

...

// плохой вариант перегрузки operator <<

ostream & operator << (ostream & os) {

os << __m1 << “ “ << __m2 << endl;

return os;

}

// плохой вариант перегрузки operator >>

istream & operator >> (istream & is) {

is >> __m1 >> __m2;

return is;

}

private:

int __m1;

double __m2;

};

// вызов функций operator << и operator >>

X x;

...

x.operator<<(cout); // функциональная форма вызова: нормально

x.operator>>(cin); // функциональная форма вызова: нормально

x << cout; // операционная форма: странный синтаксис

x >> cin; // операционная форма: странный синтаксис

Дружественность функции operator << (или operator >>) необходима из-за того, что в ходе ввода-вывода выполняется работа с данными объекта, а они, как правило, входят в закрытую (защищенную) часть класса. Если для чтения данных класса есть отдельные функции, то operator << (operator >>) можно не делать дружественной функцией.

Ниже дан пример корректного определения функций operator << и operator >>.

Пример

class X {

public:

...

// верный вариант перегрузки operator <<

friend ostream & operator << (ostream &, const X &);

// верный вариант перегрузки operator >>

friend istream & operator >> (istream &, X &);

private:

int __m1;

double __m2;

};

// определение функций operator << и operator >>

ostream & operator << (ostream & os, const X & x) {

os << x.__m1 << “ “ << x.__m2 << endl;

return os;

}

istream & operator >> (istream & is, X & x) {

is >> x.__m1 >> x.__m2;

return is;

}

// вызов функций operator << и operator >>

X x;

...

operator<<(cout,x); // функциональная форма вызова: нормально

operator>>(cin,x); // функциональная форма вызова: нормально

cout << x; // операционная форма: удобный синтаксис

cin >> x; // операционная форма: удобный синтаксис

Еще один аспект корректности определения функций operator << и operator >> связан с правильной спецификацией возвращаемого значения. Выше в примерах его тип определялся как неконстантная ссылка на поток. Это разумный вариант, поскольку благодаря ему становятся возможными серии операций ввода-вывода в одном операторе:

X x,y,z;

...

cin >> x >> y >> z;

cout << x << y << z;

...

Если использовать иной вариант спецификации, то такие цепочки станут невозможны. Например, ошибочно определять функцию operator << с заголовком вида:

void operator << (ostream & os, const X & x);

То же самое справедливо и для перегрузки операции >>.

И последний момент, на который надо обратить внимание: потоки os и is передаются в operator << (>>) как неконстантная ссылка, поскольку имеют значительный размер и, кроме того, изменяют свое состояние при выполнении операций ввода-вывода.

В число операций, для которых разрешается перегрузка, входит операция функционального вызова operator(). Использование этой операции создает удобный синтаксис, при котором имя объекта класса можно рассматривать как имя функции. Класс, который содержит operator(), часто называют классом функциональных объектов, или классом функторов (а сами объекты именуют функторами)34.

Операцию operator() можно перегрузить нестатической компонентной функцией-операцией. Эта функция может иметь список из произвольного числа параметров (параметры могут вообще отсутствовать)35. Вызов ее в операционной и функциональной формах будет иметь вид соответственно:

object_id(arguments);

object_id.operator()(arguments);

где object_id – имя объекта, класс которого определяет функцию operator()36; arguments – список аргументов.

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

Пример

class Function {

public:

...

double operator()(double,double); // прототип операции

private:

...

};

// внешнее определение операции

double Function::operator()(double a, double b) {

...

}

// использование класса Function

Function f;

double z = f(2.5,5.6); // операционная форма вызова

double w = f.operator()(2.5,5.6); // функциональная форма вызова

Функция operator() может иметь параметры со значениями по умолчанию. Также допускается наличие в одном и том же классе нескольких перегруженных вариантов функции operator(). Их выбор осуществляется по порядку и типам параметров, как и в случае обычной перегрузки функций.