|= << >> >>= <<= == != <= >= &&
|| ++ -- [] () new delete
Последние четыре - это индексирование (#6.7), вызов функции
(#6.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 оба инициализатора
являются синонимиами.
- стр 178 -
6.2.1 Бинарные и Унарные Онерации
Бинарная операция может быть определена или как функция член,
получающая один параметр, или как функция друг, получающая два
параметра. Таким образом, для любой бинарной операции @ aa@bb может
интерпретироваться или как aa.operator@(bb), или как
operator@(aa,bb). Если определены обе, то aa@bb является ошибкой.
Унарная операция, префиксная или постфиксная, может быть определена
или как функция член, не получающая параметров, или как функция
друг, получающая один параметр. Таким образом, для любой унарной
операции @ aa@ или @aa может интерпретироваться или как
aa.operator@(), или как operator@(aa). Если определена и то, и
другое, то и aa@ и @aa являются ошибками. Рассмотрим следующие
примеры:
class X {
// друзья
friend X operator-(X); // унарный минус
friend X operator-(X,X); // бинарный минус
friend X operator-(); // ошибка: нет операндов
friend X operator-(X,X,X); // ошибка: тернарная
// члены (с неявным первым параметром: this)
X* operator&(); // унарное & (взятие адреса)
X operator&(X); // бинарное & (операция И)
X operator&(X,X); // ошибка: тернарное
};
Когда операции ++ и -- перегружены, префиксное использование и
постфиксное различить невозможно.
6.2.2 Предопределенные Значения Операций
Относительно смысла операций, определяемых пользователем, не
делается никаких предположений. В частности, поскольку не
предполагается, что перегруженное = реализует присваивание ее
первому операнду, не делается никакой проверки, чтобы
удостовериться, является ли этот операнд lvalue (#с.6).
Значения некоторых встроенный операций определены как
равносильные определенным комбинациям другий операций над теми же
аргументами. Например, если a является int, то ++a означает a+=1,
что в свою очередь означает a=a+1. Такие соотношения для
определенных пользователем операций не выполняются, если только не
случилось так, что пользователь сам определил их таким образом.
Например, определение operator+=() для типа complex не может быть
выведено из определений complex::operator+() и
complex::operator=().
По историческому совпадению операции = и & имеют предопределенный
смысл для объектов классов. Никакого элегантного сполоба
"неопределить" эти две операции не существует. Их можно, однако,
сделать недееспособными для класса X. Можно, например, описать
X::operator&(), не задав ее определения. Если где-либо будет
- стр 179 -
браться адрес объекта класса X, то компоновщик обнаружит отсутствие
определения*. Или, другой способ, можно определить X::operator&()
так, чтобы вызывала ошибку во время выполнения.
6.2.3 Операции и Определяемые Пользователем Типы
Функция операция должна или быть членом, или получать в качестве
параметра по меньшей мере один объект класса (функциям, которые
переопределяют операции new и delete, это делать необязательно).
Это правило гарантирует, что пользователь не может изменить смысл
никакого выражения, не включающего в себя определенного
пользователем типа. В частности, невозможно определить функцию,
которая действует исключительно на указтели.
Функция операция, первым параметром которой предполагается
осповной тип, не может быть функцией членом. Рассмотрим, например,
сложение комплексной переменной aa с целым 2: aa+2, при подходящим
образом описанной функции члене, может быть проинтерпретировано как
aa.operator+(2), но с 2+aa это не может быть сделано, потому что
нет такого класса int, для которого можно было бы определить + так,
чтобы это означало 2.operator+(aa). Даже если бы такой тип был, то
для того, чтобы обработать и 2+aa и aa+2, понадобилось бы две
различных функции члена. Так как компилятор не знает смысла +,
определенного пользователем, то не может предполагать, что он
коммутативен, и интерпретировать 2+aa как aa+2. С этим примером
могут легко справиться функции друзья.
Все функции операции по определению перегружены. Функция операция
задает новый смысл операции в дополнение к встроенному определению,
и может существовать несколько функций операций с одним и тем же
именем, если в типах их параметров имеются отличия, различимые для
компилятора, чтобы он мог различать их при обращении (см. #4.6.7).
6.3 Определяемое Преобразование Типа
Приведенная во введении реализация комплексных чисел слишком
ограничена, чтобы она могла устроить кого-либо, поэтому ее нужно
расширить. Это будет в основном повторением описанных выше методов.
Например:
____________________
* В некоторых системах компоновщик настолько "умен", что
ругается, даже если неопределена неиспользуемая функция. В таких
системах этим методом воспользоваться нельзя. (прим автора)
- стр 180 -
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, complex);
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);
// ...
};
Теперь, имея описание 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;
}
Но писать функцию для каждого сочетания complex и double, как это
делалось выше для operator+(), невыносимо нудно. Кроме того,
близкие к реальности средства комплексной арифметики должны
предоставлять по меньшей мере дюжину таких функций; посмотрите,
например, на тип complex, описаннчй в .
6.3.1 Конструкторы
Альтенативу использованию нескольких функций (перегруженных)
составлет описание конструктора, который по заданному double
создает complex. Например:
class complex {
// ...
complex(double r) { re=r; im=0; }
};
Конструктор, требующий только один параметр, необязательно вызывать
явно:
complex z1 = complex(23);
complex z2 = 23;
И z1, и z2 будут инициализированы вызовом complex(23).
- стр 181 -
Конструктор - это предписание, как создавать значение данного
типа. Когда требуется значение типа, и когда такое значение может
быть создано конструктором, тогда, если такое значение дается для
присваивания, вызывается конструктор. Например, класс complex можно
было бы описать так:
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 и целые
константы, стали бы допустимы. Целая константа будет
интерпретироваться как complex с нулевой мнимой частью. Например,
a=b*2 означает:
a=operator*( b, complex( double(2), double(0) ) )
Определенное пользователем преобразование типа применяется неявно
только тогда, когда оно является едиственным.
Объект, сконструированный с помощью явного или неявного вызова
конструктора, является автоматическим и будет уничтожен при первой
возможности, обычно сразу же после оператора, в котором он был
создан.
6.3.2 Операции Преобразования
Использование конструктора для задания преобразования типа
является удобным, но имеет следствия, которые могут оказаться
нежелательными:
[1] Не может быть неявного преобразования из определенного
пользователем типа в основной тип (поскольку основные типы не
являются классами);
[2] Невозможно задать преобразование из нового типа в старый, не
изменяя описание старого; и
[3] Невозможно иметь конструктор с одним параметром, не имея при
этом преобразования.
Последнее не является серьезной проблемой, а с первыми двумя
можно справиться, определив для исходного типа операцию
преобразования. Функция член X::operator T(), где T - имя типа,
определяет преобразование из X в T. Например, можно определить тип
tiny (крошечный), который может иметь значение только в диапазоне
0...63, но все равно может свободно сочетаться в целыми в
арифметических операциях:
- стр 182 -
class tiny {
char v;
int assign(int i)
{ return v = (i&~63) ? (error("ошибка диапазона"),0) : i; }
public:
tiny(int i) { assign(i); }
tiny(tiny& i) { v = t.v; }
int operator=(tiny& i) { return v = t.v; }
int operator=(int i) { return assign(i); }
operator int() { return v; }
}
Диапазон значения проверяется всегда, когда tiny инициализируется
int, и всегда, когда ему присваивается int. Одно tiny может
присваиваться другому без проверки диапазона. Чтобы разрешить
выполнять над переменными tiny обычные целые операции, определяется