операторные функции (1079630)
Текст из файла
Объявление и определение операторных функций
При объявлении и определении операторных функций (в том числе и operator=() ), используется синтаксическая конструкция, обозначаемая в терминах формальной грамматики нетерминальным символом ИмяФункцииОперации. Несколько форм Бэкуса-Наура позволяют однозначно определить это понятие:
Имя ::= ИмяФункцииОперации ::= ***** ИмяФункцииОперации ::= operator СимволОперации СимволОперации ::= +|-|*|?|%|^|&|~|!|,|=|<|>|<=|>=|++|--|<<|>>|==|!=|&&| |||+=|-=|*=|<<=|>>=|[]|()|->|->*|new|delete|
Как следует из приведённых БНФ, большинство символов операций языка C++ могут участвовать в создании так называемых имён функций операций или операторных функций. То есть на основе этих символов можно объявлять операторные функции, сокращённая форма вызова которых позволяет создавать видимость применения операций к объектам производных типов.
C++ не накладывает никаких ограничений на семантику этих самых операторных функций. Наша операторная функция operator=() могла бы вообще не заниматься присвоением значений данных-членов. Она могла бы не возвращать никаких значений. Само собой, что тогда выражение вызова этой функции не могло бы быть коммутативным. А единственный параметр можно было бы передавать по значению. Но тогда всякий раз при вызове функции неизбежно должен был бы вызываться конструктор копирования, который бы создавал в области активации функции копию объекта, которую впоследствии должен был бы разрушать деструктор.
Операторная функция operator=(), как и любая другая функция, может быть перегружена. Например, объявление параметра типа int, позволило бы присваивать комплексным числам целочисленные значения. Здесь нет пределов совершенствования. В принципе, механизм операторных функций регламентирует лишь внешний вид заголовка функции (его "операторное" имя, количество параметров, в ряде случаев - возвращаемое значение). Информация о заголовке принципиальна, поскольку от этого зависит форма сокращённого вызова операторной функции.
Ещё несколько замечаний по поводу спецификации возвращаемого значения операторной функции.
Операторная функция operator=() может вообще не возвращать никаких значений. Сокращённая форма вызова
ctVal2 = ctVal1;
с точки зрения транслятора абсолютно корректна и полностью соответствует следующим прототипам:
void ComplexType::operator = (const ComplexType& ctKey); void ComplexType::operator = (ComplexType& ctKey); void ComplexType::operator = (ComplexType ctKey);
Правда, в таком случае ни о какой коммутативности, "безопасности" и эффективности вновь определяемой операторной функции нет и быть не может.
С другой стороны, уже существующий вариант нашей операторной функции также может быть оптимизирован. Функция может возвращать не ОБЪЕКТ (ЗНАЧЕНИЕ), а ССЫЛКУ на объект.
В этом случае при возвращении значения не будет создано временного объекта. Также не будет вызываться деструктор для его уничтожения. Модификация операторной функции operator=() минимальна - всего лишь дополнительная ptrОперация & в спецификации возвращаемого значения (мы приводим здесь только прототип новой версии функции):
ComplexType& operator = (const ComplexType &);
Всё остальное транслятор исправит самостоятельно, так что никаких дополнительных модификаций в тексте программы производить не придётся. Эта функция будет эффективней, правда, семантика выражения её вызова будет отличаться от семантики соответствующего выражения присвоения с базовыми типами. В первом случае результатом выполнения выражения оказывается присваиваемое значение, во втором - ссылка на объект.
Следующий пример является подтверждением того факта, что при объявлении операторных функций полностью отсутствуют чёткие правила. Это подтверждает следующий пример, посвящённый объявлению и вызову различных вариантов операторных функций operator():
:::::
class ComplexType
{
:::::
};
:::::
class rrr // Объявляется новый класс.
{
public:
ComplexType* pComplexVal;
// Собственные версии конструкторов и деструкторов.
rrr ()
{
pComplexVal = new ComplexType;
// Порождение собственного экземпляра объекта ComplexType.
}
~rrr ()
{
if (pComplexVal) = delete pComplexVal;
}
// Наконец, встроенная операторная функция.
ComplexType* operator -> ()
{
cout << "This is operator -> ()..." << endl;
return pComplexVal;
}
};
:::::
// А это уже собственно фрагмент программы…
rrr rrrVal; // Определяем объект - представитель класса rrr.
cout << rrrVal ->real << " real." << endl;
:::::
Сокращённая форма вызова операторной функции operator->() имеет вид rrrVal->real и
интерпретируется транслятором как (rrrVal.operator->())->real, о чём и свидетельствует оператор, содержащий полную
форму вызова этой операторной функции.
:::::
cout << (rrrVal.operator->())->imag << " imag." << endl;
:::::
Представление операций для классов. Операторные функции
Классы вводят в программу производные типы. Такие типы могут входить в списки параметров функций и определять тип возвращаемого значения. В вызовах функций при передаче параметров и возвращении значений данные производных типов используются в программе наравне с данными базовых типов. В условиях фактического равноправия производных и основных типов данных должна существовать возможность сохранения привычной структуры выражений при работе с данными производных типов.
Это означает, что выражение для вычисления суммы двух слагаемых уже известного нам типа ComplexType по своей структуре не должно отличаться от соответствующих выражений для слагаемых типа int или float. Но большинство операций языка C++ определены лишь для основных типов данных. Использование в качестве операндов операций выражений производных типов вызывает ошибки трансляции. Поэтому в классе ComplexType и были определены специальные функции-члены, реализующие арифметические операции над множеством комплексных чисел.
И всё же возможность сохранения привычной структуры выражений для производных типов в C++ существует.
Вернёмся к известному классу ComplexType. Мы определим два объекта класса ComplexType, после чего воспользуемся операцией присвоения.
ComplexType ctVal1(3.14, 0.712); ComplexType ctVal2, ctVal3; /* Комплексные числа со случайными значениями данных-членов.*/ ::::: ctVal2 = ctVal1; ctVal3 = ctVal2 = ctVal1; /* Операция присвоения коммутативна.*/
Если теперь вывести значения данных-членов объектов ctVal2 и ctVal3, то окажется, что они полностью совпадают со значениями данных-членов объекта ctVal1. Операция присваивания изначально определена для объектов класса ComplexType. Её можно рассматривать как предопределённую операцию, которая обеспечивает фактически побитовое копирование объекта, стоящего справа от символа = в объект, расположенный слева от этого знака.
Подобно любому другому выражению, выражение присваивания имеет собственное значение. Это значение равно выражению, стоящему справа от операции =. В ходе выполнения программы изменяется значение l-выражения в выражении присваивания, после чего значение этого выражения оказывается равным изменённому значению этого самого l-выражения.
Строго говоря, операцию присваивания для объектов производных типов нельзя называть операцией. Как и ранее рассмотренные нами "операции" приведения, она является операторной функцией. Это значит, что при её объявлении используется специальное имя, состоящее из ключевого слова operator с символом операции, а для её вызова можно использовать полную и сокращённую форму.
Мы приступаем к очередной модификации объявления класса ComplexType с целью переопределения новой операторной функции, реализующей то, что можно называть "операцией присваивания".
Работу по объявлению этой функции мы начнём с того, что попытаемся представить её общий вид:
-
функция объявляется как нестатический член класса и вызывается для объекта, которому надо присвоить соответствующее значение;
-
её имя состоит из ключевого слова operator, за которым, очевидно, следует символ = ;
-
основное назначение функции состоит в присваивании значения (множества значений) одного объекта другому. Следовательно, операторная функция operator=() должна иметь, по крайней мере, один параметр, который должен представлять присваиваемое значение;
-
и последнее, очень важное обстоятельство. Операторная функция operator=() должна возвращать новое значение объекта и по форме вызова должна создавать видимость коммутативности, поскольку этим свойством обладает операция присвоения.
class ComplexType { ::::: ComplexType operator = (const ComplexType &); /* Ссылка на константу при объявлении параметра не является обязательным условием для объявления операторной функции. Но это гарантия того, что присваиваемое значение не будет изменено в результате обращения к данным-членам. Операторная функция operator=() возвращает значение (именно значение!) объект класса ComplexType. Это не самый оптимальный способ обеспечения коммутативности операторной функции. Но при этом обеспечивается подобие операторной функции операции присваивания. */ ::::: } ::::: ComplexType ComplexType::operator = (const ComplexType& ctKey) { cout << "This is operator = (ComplexType& ctKey)..." << endl; /* Подтверждение о том, что выполняется именно эта функция. */ this->real = ctKey.real; this->imag = ctKey.imag; this->CTcharVal = ctKey.CTcharVal; this->x = ctKey.x; /* Теперь вся ответственность за корректность процесса копирования целиком и полностью возлагается на программиста. */ return *this; /* Мы возвращаем значение объекта, представленного this указателем. */ } ::::: /* Будем считать, что объекты ctVal1 и ctVal2 уже определены. Осталось рассмотреть варианты вызовов этой функции. */ ctVal2.operator = (ctVal1); /* Вариант полной формы вызова функции.*/ ctVal2 = ctVal1; /* Вариант сокращённой формы вызова функции. Операция обращения, ключевое слово operator в составном имени операторной функции и скобки, заключающие выражение, представляющее значение параметра опускаются. Создаётся иллюзия использования обычной операции присваивания. */ /* Демонстрация коммутативности операторной функции присваивания. */ ctVal3.operator = (ctVal2.operator = (ctVal1)); /* Операторная функция operator=() вызывается непосредственно из объекта ctVal3 со значением атрибута (ссылкой на объект), который сам в свою очередь является результатом применения операторной функции operator=() к объекту ctVal2 с параметром-ссылкой на объект ctVal1. Всё очень просто и красиво! */ ctVal3 = ctVal2 = ctVal1; /* Сокращённая форма коммутативного вызова операторной функции присваивания. */
Операторная функция operator() с несколькими параметрами
И это ещё не всё! Ещё не рассматривались варианты операторной функции operator() с несколькими параметрами. И здесь следует вспомнить о функциях с переменным количеством параметров. Это не единственный, но наиболее оптимальный подход к объявлению операторной функции operator() с несколькими параметрами. Здесь мы не будем вдаваться в детали алгоритма извлечения информации из списка параметров (мы их уже обсуждали раньше), а ограничимся лишь общей схемой объявления и вариантами выражения вызова. В нашей версии (всего лишь одной из возможных!), первым параметром функции всегда будет целое число:
ComplexType& operator () (int, ...);// Прототип. ::::: ComplexType& ComplexType::operator () (int iKey, ...) { cout << "This is operator ( " << iKey << ", ...)" << endl; return *this; } ::::: CDw2(50); CDw2(50, 100); CDw2(50, "Это тоже вызов операторной функции", 3.14, 0,123456789);
В C++ может быть объявлено более трёх десятков различных вариантов операторных функций. К этому выводу приводит анализ списка символов операций, которые потенциально могут входить в качестве элемента имени операции.
Здесь не имеет смысла описывать все возможные операторные функции по отдельности. В этом разделе мы рассмотрим ещё несколько интересных "нетипичных" случаев объявления, в следующих разделах будут описаны типичные общие схемы объявлений операторных функций.
Как известно, операция косвенного обращения -> является бинарной операцией. Её первым операндом является указатель на объект, вторым - имя члена класса.
Характеристики
Тип файла документ
Документы такого типа открываются такими программами, как Microsoft Office Word на компьютерах Windows, Apple Pages на компьютерах Mac, Open Office - бесплатная альтернатива на различных платформах, в том числе Linux. Наиболее простым и современным решением будут Google документы, так как открываются онлайн без скачивания прямо в браузере на любой платформе. Существуют российские качественные аналоги, например от Яндекса.
Будьте внимательны на мобильных устройствах, так как там используются упрощённый функционал даже в официальном приложении от Microsoft, поэтому для просмотра скачивайте PDF-версию. А если нужно редактировать файл, то используйте оригинальный файл.
Файлы такого типа обычно разбиты на страницы, а текст может быть форматированным (жирный, курсив, выбор шрифта, таблицы и т.п.), а также в него можно добавлять изображения. Формат идеально подходит для рефератов, докладов и РПЗ курсовых проектов, которые необходимо распечатать. Кстати перед печатью также сохраняйте файл в PDF, так как принтер может начудить со шрифтами.















