Б. Страуструп - Язык программирования С++ (1119446), страница 51
Текст из файла (страница 51)
Префикснаяили постфиксная унарная операция может определяться как функция-член без параметров, или какглобальная функция с одними параметром. Для любой префиксной унарной операции @ выражение@aa интерпретируется либо как aa.operator@(), либо как operator@(aa). Если определены обе функции,то выбор интерпретации происходит по правилам сопоставления параметров ($$R.13.2). Для любойпостфиксной унарной операции @ выражение @aa интерпретируется либо как aa.operator@(int), либокак operator@(aa,int). Подробно это объясняется в $$7.10. Если определены обе функции, то выборинтерпретации происходит по правилам сопоставления параметров ($$13.2).
Операцию можноопределить только в соответствии с синтаксическими правилами, имеющимися для нее в грамматикеС++. В частности, нельзя определить % как унарную операцию, а + как тернарную. Проиллюстрируемсказанное примерами:class X {// члены (неявно используется указатель `this'):X* operator&();// префиксная унарная операция &// (взятие адреса)X operator&(X);// бинарная операция & (И поразрядное)X operator++(int);// постфиксный инкрементX operator&(X,X);// ошибка: & не может быть тернарнойX operator/();// ошибка: / не может быть унарной};// глобальные функции (обычно друзья)X operator-(X);// префиксный унарный минус182Бьерн Страуструп.XXXXXЯзык программирования С++operator-(X,X);operator--(X&,int);operator-();operator-(X,X,X);operator%(X);//////////бинарный минуспостфиксный инкрементошибка: нет операндаошибка: тернарная операцияошибка: унарная операция %Операция [] описывается в $$7.7, операция () в $$7.8, операция -> в $$7.9, а операции ++ и -- в $$7.10.7.2.2 Предопределенные свойства операцийИспользуется только несколько предположений о свойствах пользовательских операций.
В частности,operator=, operator[], operator() и operator-> должны быть нестатическими функциями-членами. Этимобеспечивается то, что первый операнд этих операций является адресом.Для некоторых встроенных операций их интерпретация определяется как комбинация других операций,выполняемых над теми же операндами. Так, если a типа int, то ++a означает a+=1, что в свою очередьозначает a=a+1. Такие соотношения не сохраняются для пользовательских операций, если толькопользователь специально не определил их с такой целью. Так, определение operator+=() для типаcomplex нельзя вывести из определений complex::operator+() и complex operator=().По исторической случайности оказалось, что операции = (присваивание), &(взятие адреса) и , (операциязапятая) обладают предопределенными свойствами для объектов классов.
Но можно закрыть отпроизвольного пользователя эти свойства, если описать эти операции как частные:class X {// ...private:void operator=(const X&);void operator&();void operator,(const X&);// ...};void f(X a, X b){a= b;&a;a,b}// ошибка: операция = частная// ошибка: операция & частная// ошибка: операция , частнаяС другой стороны, можно наоборот придать с помощью соответствующих определений этим операцияминое значение.7.2.3 Операторные функции и пользовательские типыОператорная функция должна быть либо членом, либо иметь по крайней мере один параметр,являющийся объектом класса (для функций, переопределяющих операции new и delete, это необязательно).
Это правило гарантирует, что пользователь не сумеет изменить интерпретациювыражений, не содержащих объектов пользовательского типа. В частности, нельзя определитьоператорную функцию, работающую только с указателями. Этим гарантируется, что в С++ возможнырасширения, но не мутации (не считая операций =, &, и , для объектов класса).Операторная функция, имеющая первым параметр основного типа, не может быть функцией-членом.Так, если мы прибавляем комплексную переменную aa к целому 2, то при подходящем описаниифункции-члена aa+2 можно интерпретировать как aa.operator+(2), но 2+aa так интерпретировать нельзя,поскольку не существует класса int, для которого + определяется как 2.operator+(aa). Даже если бы этобыло возможно, для интерпретации aa+2 и 2+aa пришлось иметь дело с двумя разными функциямичленами.
Этот пример тривиально записывается с помощью функций, не являющихся членами.Каждое выражение проверяется для выявления неоднозначностей. Если пользовательские операциизадают возможную интерпретацию выражения, оно проверяется в соответствии с правилами $$R.13.2.183Бьерн Страуструп.Язык программирования С++7.3 Пользовательские операции преобразования типаОписанная во введении реализация комплексного числа является слишком ограниченной, чтобыудовлетворить кого-нибудь, и ее надо расширить.
Делается простым повторением описаний того жевида, что уже были применены:class complex {double re, im;public:complex(double r, double i) { re=r; im=i; }friend complex operator+(complex, complex);friend complex operator+(complex, double);friend complex operator+(double, complex);friend complex operator-(complex, double);friend complex operator-(complex, double);friend complex operator-(double, complex);complex operator-(); // унарный friend complex operator*(complex, complex);friend complex operator*(complex, double);friend complex operator*(double, complex);// ...};Имея такое определение комплексного числа, можно писать:void f(){complex a(1,1), b(2,2), c(3,3), d(4,4), e(5,5);a = -b-c;b = c*2.0*c;c = (d+e)*a;}Все-таки утомительно, как мы это только что делали для operator*() писать для каждой комбинацииcomplex и double свою функцию.
Более того, разумные средства для комплексной арифметики должныпредоставлять десятки таких функций (посмотрите, например, как описан тип complex в <complex.h>).7.3.1 КонструкторыВместо того, чтобы описывать несколько функций, можно описать конструктор, который из параметраdouble создает complex:class complex {// ...complex(double r) { re=r; im=0; }};Этим определяется как получить complex, если задан double.
Это традиционный способ расширениявещественной прямой до комплексной плоскости.Конструктор с единственным параметром не обязательно вызывать явно:complex z1 = complex(23);complex z2 = 23;Обе переменные z1 и z2 будут инициализироваться вызовом complex(23).Конструктор является алгоритмом создания значения заданного типа.
Если требуется значениенекоторого типа и существует строящий его конструктор, параметром которого является это значение,то тогда этот конструктор и будет использоваться. Так, класс complex можно было описать следующимобразом:class complex {double re, im;184Бьерн Страуструп.Язык программирования С++public:complex(double r, double i =0) { re=r; im=i; }friend complex operator+(complex, complex);friend complex operator*(complex, complex);complex operator+=(complex);complex operator*=(complex);// ...};Все операции над комплексными переменными и целыми константами с учетом этого описаниястановятся законными. Целая константа будет интерпретироваться как комплексное число с мнимойчастью, равной нулю.
Так, a=b*2 означаетa = operator*(b, complex( double(2), double(0) ) )Новые версии операций таких, как + , имеет смысл определять только, если практика покажет, чтоповышение эффективности за счет отказа от преобразований типа стоит того. Например, есливыяснится, что операция умножения комплексной переменной на вещественную константу являетсякритичной, то к множеству операций можно добавить operator*=(double):class complex {double re, im;public:complex(double r, double i =0) { re=r; im=i; }friend complex operator+(complex, complex);friend complex operator*(complex, complex);complex& operator+=(complex);complex& operator*=(complex);complex& operator*=(double);// ...};Операции присваивания типа *= и += могут быть очень полезными для работы с пользовательскимитипами, поскольку обычно запись с ними короче, чем с их обычными "двойниками" * и + , а кроме тогоони могут повысить скорость выполнения программы за счет исключения временных переменных:inline complex& complex::operator+=(complex a){re += a.re;im += a.im;return *this;}При использовании этой функции не требуется временной переменной для хранения результата, и онадостаточно проста, чтобы транслятор мог "идеально" произвести подстановку тела.