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

7.1 Введение

Обычно в программах используются объекты, являющиеся конкретным

представлением абстрактных понятий. Например, в С++ тип данных int

вместе с операциями +, -, *, / и т.д. реализует (хотя и ограниченно)

математическое понятие целого. Обычно с понятием связывается набор

действий, которые реализуются в языке в виде основных операций над

объектами, задаваемых в сжатом, удобном и привычном виде.

К сожалению, в языках программирования непосредственно представляется

только малое число понятий. Так, понятия комплексных чисел, алгебры

матриц, логических сигналов и строк в С++ не имеют непосредственного

выражения. Возможность задать представление сложных объектов вместе

с набором операций, выполняемых над такими объектами,

реализуют в С++ классы. Позволяя программисту определять операции

над объектами классов, мы получаем более удобную и традиционную

систему обозначений для работы с этими объектами по сравнению с той,

в которой все операции задаются как обычные функции. Приведем пример:

class complex {

double re, im;

public:

complex(double r, double i) { re=r; im=i; }

friend complex operator+(complex, complex);

friend complex operator*(complex, complex);

};

Здесь приведена простая реализация понятия комплексного числа, когда

оно представлено парой чисел с плавающей точкой двойной точности,

с которыми можно оперировать только с помощью операций + и *.

Интерпретацию этих операций задает программист в определениях функций

с именами operator+ и operator*. Так, если b и c имеют тип complex,

то b+c означает (по определению) operator+(b,c). Теперь можно

приблизиться к привычной записи комплексных выражений:

void f()

{

complex a = complex(1,3.1);

complex b = complex(1.2,2);

complex c = b;

a = b+c;

b = b+c*a;

c = a*b+complex(1,2);

}

Сохраняются обычные приоритеты операций, поэтому второе выражение

выполняется как b=b+(c*a), а не как b=(b+c)*a.

7.2 Операторные функции

Можно описать функции, определяющие интерпретацию следующих операций:

+ - * / % ^ & | ~ !

= < > += -= *= /= %= ^= &=

|= << >> >>= <<= == != <= >= &&

|| ++ -- ->* , -> [] () new delete

Последние пять операций означают: косвенное обращение ($$7.9),

индексацию ($$7.7), вызов функции ($$7.8), размещение в свободной

памяти и освобождение ($$3.2.6). Нельзя изменить приоритеты этих

операций, равно как и синтаксические правила для выражений. Так,

нельзя определить унарную операцию % , также как и бинарную операцию

!. Нельзя ввести новые лексемы для обозначения операций, но если

набор операций вас не устраивает, можно воспользоваться привычным

обозначением вызова функции. Поэтому используйте pow(), а не ** .

Эти ограничения можно счесть драконовскими, но более свободные

правила легко приводят к неоднозначности. Допустим, мы определим

операцию ** как возведение в степень, что на первый взгляд кажется

очевидной и простой задачей. Но если как следует подумать, то

возникают вопросы: должны ли операции ** выполняться слева направо

(как в Фортране) или справа налево (как в Алголе)? Как

интерпретировать выражение a**p как a*(*p) или как (a)**(p)?

Именем операторной функции является служебное слово operator, за

которым идет сама операция, например, operator<<. Операторная

функция описывается и вызывается как обычная функция. Использование

символа операции является просто краткой формой записи вызова

операторной функции:

void f(complex a, complex b)

{

complex c = a + b; // краткая форма

complex d = operator+(a,b); // явный вызов

}

С учетом приведенного описания типа complex инициализаторы в этом

примере являются эквивалентными.

7.2.1 Бинарные и унарные операции

Бинарную операцию можно определить как функцию-член с одним

параметром, или как глобальную функцию с двумя параметрами. Значит,

для любой бинарной операции @ выражение aa @ bb интерпретируется

либо как aa.operator(bb), либо как operator@(aa,bb). Если определены обе

функции, то выбор интерпретации происходит по правилам сопоставления

параметров ($$R.13.2). Префиксная или постфиксная унарная операция

может определяться как функция-член без параметров, или как глобальная

функция с одними параметром. Для любой префиксной унарной операции

@ выражение @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); // префиксный унарный минус

X operator-(X,X); // бинарный минус

X operator--(X&,int); // постфиксный инкремент

X operator-(); // ошибка: нет операнда

X operator-(X,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.