И.А. Волкова, А.В. Иванов, Л.Е. Карпов - Основы объектно-ориентированного программирования. Язык программирования С++ (ЧБ) (1114895), страница 9
Текст из файла (страница 9)
Если первый операнд имеет встроенный илибиблиотечный тип, в описание которого невозможно вставить описание дружественной функции, то такую операцию можно перегружать только функцией-другом класса, к которому относится второй операнд.Пример: Перегрузка операции вывода.В файле внешней стандартной библиотеки iostream стандартная операцияязыка ‘<<’, осуществляющая побитный сдвиг, перегружена в классе ostreamкак операция вывода. Операция ‘<<’ перегружена для вывода объектов стандартных типов: int, char, double, char* и других встроенных типов.
Форматиспользования данной операции:cout << переменная_стандартного_типа;Таким образом, первый операнд операции ‘<<’ должен иметь типostream. Если необходимо перегрузить данную операцию для структурированного вывода объекта пользовательского типа, то, как было пояснено, этоможно сделать только функцией-другом разработанного класса. Например, длякласса комплексных чисел операция ‘<<’ может быть перегружена так:class complex {double re,im;public:complex(double re2, double im2):re(re2),im(im2){}friend ostream& operator<<(ostream & out,const complex par);. .
.};ostream& operator<<(ostream& out, const complex par){out << par.re << "+" << par.im << "i";return out;}44Здесь операция ‘<<’ получает в качестве первого параметра ссылку на существующий объект типа ostream. Данный объект дополняется необходимойдля вывода информацией и ссылка на него возвращается во внешнюю среду.Благодаря этому можно в одном операторе программы осуществить вывод рядазначений:cout << c1 << "" << c2;Примечание.
Операцию вывода можно перегружать и методом соответствующегокласса. Но это будет выглядеть не совсем естественно. Действительно, в этом случаепервый операнд будет тип текущего класса. Поэтому, например, для класса комплексныхчисел перегрузка операции вывода методом класса будет выглядеть следующим образом:ostream& operator<<(ostream& out){out << re << "+" << im << "i";return out;}a вызов операции вывода будет выглядеть так:c1 << cout;либо так:c1.operator << (cout);Конечно, и в том, и в другом случае операция вывода выглядит не совсем привычно. Для того, чтобы вид операции вывода при перегрузке методом остался привычным, данную операцию необходимо перегрузить непосредственно в классе ostream(если, это целесообразно и возможно, поскольку этот тип определен в библиотеке, произвольно менять которую обычно не рекомендуется).8.2.Перегрузка унарных операцийЕсли для унарной операции имеется только одна форма, то ее перегрузкареализуется по общим описанным выше правилам.
При этом, как уже было описано, для оптимизации использования результата операции в других операциях,совмещенных в одном операторе с данной операцией, рекомендуется объявлятьвыходной параметр в виде ссылки на текущий объект.Специфика перегрузки операций инкремента и декремента, операциииндексацииПри перегрузке унарной операции в том случае, если для нее в языке определены две формы – префиксная и постфиксная, имеются особенности.Для того, чтобы отличать постфиксную форму от префиксной, при перегрузке операции в постфиксной форме в списке формальных параметров указывается дополнительный, неиспользуемый в алгоритме операции, параметр (точнее, тип параметра).Примечание.
Компилятор корректно обрабатывает перегруженную операцию и вслучае явного указания дополнительного, неиспользуемого в алгоритме операции, параметра.Пример: Для класса complex перегрузим операцию ‘++’ в префиксной ипостфиксной формах со следующей семантикой:45complex c1, c2;.
. .c1 = ++c2; // c2 = c2 + 1; c1 = c2;c1 = c2++; // c1 = c2; c2 = c2 + 1;complex & operator++(){++re;return *this;}// Префиксная формаcomplex operator++(int){ // Постфиксная формаcomplex tmp(*this);// Во временном объекте// запоминается состояние// текущего объекта.re++;// Изменяется текущий объект.return tmp;// Во внешнюю среду выдается// запомненное состояние в// виде объекта, а не ссылка.// Во внешнюю среду не может// быть выдана ссылка на// текущий объект, т.к.
он// изменил свое состояние.}Примечание. Приведенная форма перегрузки префиксной операции ‘++’ позволяет корректно выполнять следующую операцию:c1 = ++ ++c2;с семантикой:c2 = c2 + 2; c1 = c2;Однако, приведенная форма перегрузки постфиксной операции ‘++’ не позволяеткорректно выполнить операциюc1 = c2++ ++;с семантикойc1 = c2; c2 = c2 + 2;Дело в том, что первое исполнение операции ‘++’ передает во внешнюю средунеизмененный объект c2, а второе исполнение операции ‘++’, принимая на входе неизмененное значение объекта, передает его объекту c1, параллельно изменяя его на 1, ане на 2.Операция индексирования является бинарной: ее операнды – объект с нумерованными элементами (массив, вектор и т.
д.) и целое число – индекс элемента.Примечание. В некоторых пособиях операция индексирования ошибочно рассматривается как унарная, хотя явно имеются два вышеуказанных операнда.При перегрузке операции индексирования объявление в качестве выходногопараметра ссылки на элемент объекта позволяет присваивать ее результату новыезначения.46Пример:class vector {int* p;int size;public:.
. .int & operator[](int i){return p[i];}};int main(){vector v1(10);v1[1] = 5;}Здесь оператор return возвращает значение выбранного элемента вектора,который инициализирует выходной объект, как было описано, по реализацииадресом этого элемента.Примечание. Перегрузка операций (как бинарных, так и унарных) позволяет нетолько описывать для стандартных операций необходимую семантику, но и блокироватьисполнение нежелательных операций над объектами описываемого типа. Для этого необходимо перегрузку операции описывать в открытой области.
Естественно, что такаяперегрузка описывается методом класса, а не функцией-другом.8.3.Перегрузка функцийИмеется возможность описывать разные алгоритмы для одного и того жеидентификатора функции при разных количествах и наборах типов входных параметров. Такое описание разных алгоритмов в одной зоне описания (класс,пространство имен) называется перегрузкой функций (если описание разныхалгоритмов для одного и того же имени осуществляется в разных зонах, то говорят о перекрытии).При вызове функции для выбора подходящей перегруженной функции выполняется следующий алгоритм:Алгоритм поиска оптимально отождествляемой функции1) Отбираются функции с необходимым количеством формальных параметров.Примечание. Возможно описание функции с переменным числом параметров.
Дляэтого используются символы ‘. . .’ в конце списка формальных параметров, обозначающих произвольное количество дополнительных неименованных параметров,типы которых будут определяться непосредственно при вызове функции. Пример прототипа функции с переменным числом параметров:void f1(int i1, . .
.);В этом случае внутри функции необходимо иметь специальные средства получениязначений таких дополнительных параметров, не имеющих собственных имен. При обработке списка формальных параметров компилятор не имеет информации, необходи47мой для выполнения стандартной проверки и преобразования типов неименованныхпараметров.
Поэтому средства получения таких параметров могут использовать толькоинформацию, недоступную компилятору. Для облегчения работы с этими параметрами вфайле stdarg.h стандартной библиотеки имеются описания структуры va_list ифункций: va_start(), va_arg(), va_end(). Описания, содержащиеся в библиотечном файле <cstdarg>, становятся доступными после его подключения директивойпрепроцессора #include <cstdarg>.Функции с переменным числом параметров рекомендуется использовать в исключительных случаях, когда типы параметров действительно неизвестны.
В большинстве случаев можно использовать функции с аргументами по умолчанию или функцией сдвумя параметрами следующего вида: первый параметр – целое число, равное количеству содержательных параметров, второй аргумент – указатель на массив указателей нафактические параметры. Такой метод используется при передаче списка строковых параметров из командной строки вызова программы на исполнение:int main(int argc, char* argv[])Здесь args – количество строковых параметров в командной строке вызова программы на исполнение, включая идентификатор программы.2) Для каждого фактического параметра вызова функции строится множество функций, оптимально отождествляемых по этому параметру (bestmatching)3) Находится пересечение этих множеств4) Если полученное множество состоит из одной функции, то вызов разрешим.
Если множество пусто или содержит более одной функции, тогенерируется сообщение об ошибке.Пример:class x{. . .public:x(int i1){. . . }. . .};class y{. . .};voidvoidvoidvoidf(x,f(x x1, int i1){. . .}f(x x1, double d1){. . .}f(y y1, double d1){. . .}g(){. . . f(1,1) . . .} // вызов первой реализацииint)Пример:class x{. . .public:48x(int i1){. . . }. . .};void f(x x1, int i1){. . .}void f(int i1, x x1){. . .}void g(){. . . f(1,1) .
. .}// ошибка: пересечение// множеств – пусто.8.4.Алгоритм поиска оптимально отождествляемой функции дляодного параметраЕсли функция имеет один параметр, то выполняется следующая последовательность шагов для поиска оптимально отождествляемой функции.Такая же последовательность шагов выполняется для каждого параметрафункции с несколькими параметрами на втором этапе ранее описанного алгоритма поиска оптимально отождествляемой функции с несколькими параметрами.1). Точное отождествление.2). Отождествление с помощью расширений.3).
Отождествление с помощью стандартных преобразований.4). Отождествление с помощью преобразований пользователя.5). Отождествление по ‘. . .’.Данные шаги определяют приоритет метода отождествления параметра. Врамках каждого шага разные виды преобразований являются равно приоритетными.Развернутое описание пунктов 1). – 5).1). Точное отождествление1.1). Точное совпадение1.2). Совпадение с точностью до typedef1.3). Тривиальные преобразования:T[] <-> T*T<-> T&T-> const TПример:voidvoidvoidvoidf(float);f(double);f(int);g(){ . .