Смекни!
smekni.com

Правила правой руки 17 Замечания для программистов на c 17 Глава 1 (стр. 32 из 43)

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

|| ++ -- [] () 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 обычные целые операции, определяется