Анимация
JavaScript


Главная  Библионтека 

0 1 2 3 4 5 6 7 8 9 10 11 [ 12 ] 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82

Вероятно, здесь следует проверить, что строка

представляет собой число, принадлежащее к диапазону длинных целых return atol1(s);

String s("1234");

long x = s; Вызывается функция operator 1ong()

Операторы преобразования должны быть функциями класса. Как видно из показанного фрагмента, операторы преобразования хороши тем, что компилятор обычно сам может определить, когда они должны вызываться. Если ему понадобится длинное целое, он ищет оператор 1ong(). Если ему понадобится объект Foo, он ищет в классе Foo либо конструктор с аргументом String, либо operator Foo(). Возникает интересный вопрос: если оператор преобразования делает фактически то же, что и конструктор, почему бы не обойтись чем-нибудь одним? Преимущество конструкторов состоит в том, что они обеспечивают инкапсуляцию результирующего класса. Чтобы сконструировать объект другого класса, оператор преобразования должен очень много знать о нем. Вот почему для перехода от одного типа к другому обычно используются конструкторы. А если осуществляется переход к базовому типу вроде int? Вряд ли вы будете требовать, чтобы компилятор создавал для int новые конструкторы, которые знают о существовании ваших пользовательских типов. А если и будете, то не рискнете признаться в этом вслух. Только оператор преобразования может автоматически перейти к базовому типу. Даже если результирующий тип не является базовым, он может быть частью готовой библиотеки, которую нежелательно модифицировать. И снова оператор преобразования справляется с задачей.

Операторы преобразования можно объявлять для любых типов данных. Они вызываются без аргументов, а тип возвращаемого значения определяется по имени оператора. Операторы преобразования, как и все остальные операторы, бывают константными или неконстантными. Часто определяется как константная, так и неконстантная версии одного оператора. Как правило, константная версия работает более эффективно, поскольку неконстантная версия обычно выполняет копирование данных.

class String { private:

char* s; public:

operator const char*() const { return s; } operator char*():

String::operator char*()

char* newStr = new char[str1en(s) + 1]; strcpy(newStr, s); return newStr;

Клиентский код, использующий неконстантную версию, должен взять на себя ответственность за удаление дубликата.

Порядок поиска и неоднозначность

Если во время обработки программы компилятор C++ находит оператор, он выполняет описанные ниже действия в указанном порядке, чтобы решить, как его компилировать. Описание относится к бинарному оператору, но та же самая логика используется и для унарных операторов:

1 . Если оба аргумента относятся к базовым типам, используется встроенный оператор.



2. Если слева указан пользовательский тип, компилятор ищет перепруженный оператор в форме функции данного класса, подходящей для всей сигнатуры подвыражения оператора. Если такой оператор будет найден, он используется при компиляции.

3. Если все остальные варианты испробованы, компилятор ищет перегрузку в форме внешней функции.

Неоднозначность может возникнуть лишь в том случае, если она присутствует в левостороннем классе или в глобальном пространстве, и никогда - из-за совпадения перегруженных операторов в форме функции класса и внешней функции. При наличии неоднозначности сообщение об ошибке выдается лишь после вашей попытки реально использовать оператор. Так компилятор внушает ложное чувство безопасности и ждет, пока вы утратите бдительность, чтобы огреть вас дубиной по голове.

Виртуальные операторы

Операторы классов можно объявлять виртуальными, как и все остальные функции классов. Компилятор динамически обрабатывает перегруженный левосторонний оператор, как и любую другую функцию класса. Такая возможность особенно полезна в ситуациях, когда вы пытаетесь создать семейство классов, но открываете внешнему миру лишь их общий базовый класс. С точки зрения синтаксиса все выглядит просто, но логика программы может стать довольно запутанной. Виртуальные операторы являются одной из важнейших тем части 3, поэтому сейчас мы не будем вдаваться в подробности.

Оператор ->

Оператор -> занимает особое место среди операторов. Для начала рассмотрим его базовый синтаксис. class Pointer { private:

Foo* f;

public:

Pointer(Foo* foo) : f(foo) {}

Foo* operator->() const { return f; }

Pointer p(new Foo); p->MemberOfFoo();

В приведенном фрагменте р используется для косвенного вызова функции класса Foo. Компилятор интерпретирует любой указатель на структуру или класс (*-переменная) как базовый тип >, а для всех базовых типов указателей существует встроенный оператор ->. Встретив ->, компилятор смотрит на левостороннее выражение; если оно представляет собой указатель па структуру или указатель на класс, для обращения к членам используется встроенный оператор ->. Если левостороннее выражение представляет собой пользовательский тип, этот тип должен перегрузить оператор ->. Перегруженный вариант должен возвращать либо указатель на структуру/класс, либо какой-нибудь другой пользовательский тип, который также перегружает оператор ->. Если возвращаемое значение относится к пользовательскому типу, компилятор заменяет левостороннее выражение возвращаемым значением оператора -> (в нашем примере Foo*) и продолжает свои попытки до тех пор, пока не доберется до встроенного указателя. Таким образом, следующее двухшаговое косвенное обращение также будет работать.

class Pointer2 { private:

Pointer p; public:

Pointer(Foo* foo) : p(foo) {}

Pointer operator->() const { return p; }

Pointer2 p(new Foo); p->MemberOfFoo();



Здесь оператор -> вызывается трижды:

1. Pointer2::operator-> возвращает Pointer.

2. Затем Pointer::operator-> возвращает Foo.

3. Компилятор интерпретирует Foo* как базовый тип и вызывает его функцию.

В отличие от всех остальных операторов, вы не можете контролировать возвращаемое значение или просто обратиться к нему после выхода из операторной функции. Оно используется исключительно самим компилятором. Концепция объекта, «переодетого» указателем, имеет фундаментальное значение для всей книги.

Оператор []

Оператор [] может быть перегружен, чтобы получать единственный аргумент произвольного типа и возвращать произвольный тип в качестве своего значения.

class String { private:

char* s; public:

String(char*);

char operator[](int n) const; n-й символ

char String::operator[](int n)

Здесь должна выполняться проверка диапазона return s[n];

Поскольку оператор [] может вызываться лишь с одним аргументом, для имитации многомерных массивов часто применяют анонимные экземпляры.

struct Index3 { int X, У, Z;

Index3(int x, int y, int z) : X(x), y(y), Z(z) {}

class Array3D { Трехмерный массив объектов String private:

Настоящая структура данных public:

String& operator[](const Index3&);

String s = anArray[lndex3(17, 29, 31)];

Хотя оператор [] вызывается лишь с одним аргументом, этот умеренно неуклюжий синтаксис позволяет создать произвольное количество псевдоаргументов.

Оператор ()

Наверное, вы и не подозревали, что этот оператор тоже можно перегрузить. Если у вас часто возникает непреодолимое желание превратить объект в функцию, возможно, ваша психика нестабильна и вам стоит серьезно подумать над сменой рода занятий. Тем не менее, C++ с пониманием отнесется к вашим проблемам. Левостороннее выражение оператора () представляет собой объект, который должен интерпретироваться как вызываемая функция. Аргументы оператора представляют собой формальные аргументы, передаваемые объекту-функции при вызове.



0 1 2 3 4 5 6 7 8 9 10 11 [ 12 ] 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82