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

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

Объектная модель С++ дает широкие возможности программисту в приспособлении средств языка к новым потребностям. Одной из таких возможностей является перегрузка (overloading) стандартных операций. Если при разработке приложения оказывается, что некоторая операция не применима к операндам пользовательского типа (класса) или ее поведение отличается от требуемого, то ее действие можно расширить (скорректировать) на такие операнды. Расширение действия сводится к определению для операции нового алгоритма, который будет использоваться при наличии операндов пользовательского класса. Это и составляет сущность перегрузки операций в С++.

6.1. Назначение перегрузки операций и ее реализация в С++

Необходимость перегрузки операций возникает весьма часто. Пусть, например, в приложении определен класс матриц (см. задачи предыдущих глав) и необходимо сделать так, чтобы отдельные матрицы можно было перемножать, используя традиционный синтаксис A*B, характерный для встроенных типов. Для этого достаточно лишь расширить действие бинарной операции * на класс матриц путем ее перегрузки. В результате выражения типа A*B, где оба операнда – матрицы станут обрабатываться по алгоритму, предусмотренному перегрузкой24.

Для перегрузки некоторой стандартной операции, обозначаемой символом @25, необходимо определить соответствующую функцию-операцию (operator function). Эта функция-операция будет содержать запись нового алгоритма операции @; он будет применяться при обработке операндов (или операнда) пользовательского класса. Для того, чтобы обеспечить связь функции-операции с указанным классом, необходимо либо использовать этот класс в декларации ее параметров, либо сделать ее компонентом этого класса.

Форматы прототипа и определения функции-операции для перегрузки операции @ в общем виде выглядят следующим образом:

Value_type operator @ (parameter_list);

Value_type operator @ (parameter_list) { statements }

где operator – ключевое слово, предназначенное для обозначения функций-операций; value_type, parameter_list, statements – соответственно тип значения, список параметров и операторы тела функции-операции. В списке параметров функции-операции, в зависимости от характера, «арности» (местности) операции и способа определения функции, может быть от нуля до произвольного конечного числа параметров26 (в большинстве случаев, однако, число параметров не превышает двух).

Функцию-операцию всегда можно интерпретировать и вызвать как обычную функцию (в «функциональной» форме). Форматы «функционального» вызова функции-операции таковы:

// operator @ – компонентная функция

object_id.operator @(argument_list);

pointer_to_object_id –> operator @(argument_list)

(*pointer_to_object_id).operator @(argument_list)

// operator @ – глобальная функция

operator @(argument_list);

где object_id, pointer_to_object_id – имена соответственно объекта и указателя на объект, для которого вызывается функция-операция. Однако гораздо более удобна неявная, «операционная» форма вызова, при которой применяется привычный синтаксис работы с операциями:

@ argument // для унарной префиксной операции

argument @ // для унарной постфиксной операции

argument1 @ argument2 // для бинарной операции

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

Пример

class CIntMatrix { // класс целочисленных матриц

public:

...

// сумма матриц

const CIntMatrix operator + (const CIntMatrix & m2) const;

// произведение матриц

const CIntMatrix operator * (const CIntMatrix & m2) const;

// присваивание

CIntMatrix & operator = (const CIntMatrix & m2);

// умножение элементов на -1

const CIntMatrix & operator - ();

...

};

...

// использование функций-операций в операционной форме

{

CIntMatrix m1(/*...*/),m2(/*...*/),m3(/*...*/);

...

m3 = m1 + (-m2) * m3;

...

}

// использование функций-операций в функциональной форме

{

CIntMatrix m1(/*...*/),m2(/*...*/),m3(/*...*/);

...

// эквивалентная функциональная форма оператора m3 = m1 + (-m2) * m3;

m3.operator=(m1.operator+((m2.operator-()).operator*(m3)));

...

}

...

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

Во-первых, перегружать можно только стандартные операции; вводить новые символы операций С++ не позволяет. Во-вторых, перегрузке в С++ подлежат не все стандартные операции. К числу операций, не подлежащих перегрузке, относятся следующие:

. (уточнение имени);

.* (прямой доступ через указатель на компонент);

:: (уточнение контекста);

?: (условная операция);

# (запрет обработки аргументов макросов);

## (конкатенация лексем);

sizeof (вычисление размера операнда);

typeid (идентификация типа);

new, new[], delete, delete[] (операции управления памятью);

static_cast, dynamic_cast, const_cast, reinterpret_cast (операции преобразования типов). Остальные операции могут быть перегружены, причем такие операции, как &, *, +, –, допускают перегрузку как унарной, так и бинарной форм. В-третьих, то, что операция подлежит перегрузке, не означает, что она может быть всегда перегружена грамотно и безопасно; имеется ряд операций, безопасная перегрузка которых труднореализуема (их перечень будет рассмотрен далее). В-четвертых, при перегрузке нельзя изменить местность операции; нельзя также сменить уровень ее приоритета, ассоциативность и смысл содержащих ее выражений.

Теперь перейдем к детальному изучению способов перегрузки операций и затронем также некоторые «подводные камни», появляющиеся при перегрузке.

Практически любую из функций-операций можно ввести тремя разными способами: 1) как нестатическую компонентную функцию класса, на который выполняется перегрузка; 2) как глобальную (свободную) функцию, хотя бы один параметр которой имеет тип, связанный с требуемым классом; 3) как глобальную дружественную функцию к указанному классу. Поскольку 2-ой способ в большинстве случаев сводится к 3-му способу, фактически имеется два способа перегрузки: либо глобальной, либо нестатической компонентной функцией-операцией. Ниже отдельно рассматриваются варианты и правила перегрузки унарных, а затем бинарных операций (те операции, при перегрузке которых есть специальные ограничения, выделяются отдельно).

6.2. Перегрузка унарных операций

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

value_type operator @ (); // компонентная функция

value_type operator @ (parameter); // глобальная функция

friend value_type operator @ (parameter); // дружественная функция

где parameter – декларация параметра функции-операции (параметр должен быть связан с классом C, на который выполняется перегрузка операции, например, const C &); value_type – в общем случае произвольный тип возвращаемого значения (часто он также связан с классом C).

Если функция-операция определена как компонентная функция, то запись @x (x – объект класса C) будет интерпретирована как x.operator@() (в роли операнда операции выступает *this). Если же используется глобальная функция-операция, то имеет место интерпретация operator@(x) (операнд поступает в качестве единственного аргумента).

Ниже, для примера, дано определение функции-операции, перегружающей унарную операцию ! для стандартного шаблонного класса basic_ios (эта операция используется для проверки корректности работы с потоком ввода-вывода):

template<class charT, class traits>

inline bool basic_ios<charT, traits>::operator!() const

{

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]