Анимация
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

class String { Как раньше public:

String& operator=(const String&); Нормальный вариант String& operator=(char*); Перегруженный вариант

String& operator=(int); Вызывает atoi()

В показанном фрагменте создается несколько перегруженных вариантов оператора = для различных типов данных в правой части выражения. Второй вариант позволяет избежать конструирования временного объекта String из char* лишь для того, чтобы присвоить его объекту в левой части. Третий вариант выполняет преобразование другого рода. Тем не менее, лишь первый вариант перегружает (то есть заменяет) версию оператора по умолчанию.

Перегрузка операторов

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

Функциональная форма операторов

Операторы (например, +) используются в двух вариантах: как особая синтаксическая форма или как функция. В C++ функциональная форма всегда представляет собой ключевое слово operator, за которым следует символ оператора.

class Foo {...} Foo x, y, z;

z = x + y; Инфиксная (нормальная) форма

z = operator+(x, y); Функциональная форма (внешняя функция)

z = x.operator+(y); Функциональная форма (функция класса)

С концептуальной точки зрения три последние строки эквивалентны, хотя на практике, вероятно, оператор будет определен либо в виде внешней функции, либо в виде функции класса, но не в обоих вариантах сразу. Для бинарных операторов знак оператора указывается между двух аргументов в инфиксной форме. В форме внешней функции оба аргумента передаются глобальной функции. В форме функции класса объект, которому принадлежит вызываемый оператор, указывается слева, а аргумент - справа от знака оператора. Унарные операторы (такие как ! и ~) тоже могут перегружаться. Форма внешней функции вызывается с одним аргументом, а форма функции класса вызывается без аргументов (операция выполняется с объектом, находящимся слева от оператора . или

->).

Не разрешается перегружать встроенные операторы (например, оператор целочисленного сложения). Чтобы обеспечить выполнение этого условия, компилятор требует, чтобы хотя бы один аргумент каждого перегруженного оператора относился к пользовательскому типу (обычно к классу). Выбор ограничен операторами, уже определенными в C++. Во время долгих ночных отладок мне часто хотелось создать оператор с именем #$%а&, но C++ на этот счет неумолим.

Перегруженные операторы наследуют приоритеты и атрибуты группировки от встроенных операторов, поэтому вы не можете, например, изменить стандартный порядок группировки «слева направо» для оператора +. Не существует ограничений на тип значения, возвращаемого перегруженным оператором, и один оператор можно перегружать произвольное число раз при условии, что сигнатуры остаются уникальными.



Перегрузка операторов в форме внешних функций

Чтобы перегрузить оператор в форме внешней функции, необходимо определить глобальную функцию.

class String {

friend String& operator+(const String&, const String&); private:

char* s; public:

Конструкторы и т.д.

String& operator+(const String& s1, const String& s2)

char* s = new char[str1en(s1.s) + str1en(s2.s) + 1]; strcat(s, s1.s, s2.s); String newStr(s); delete s; return newStr;

String s1 = "Hello"; String s2 = "Goodbye"; String s3 = s1 + s2;

Перегруженная функция выглядит так же, как и любая глобальная функция (если не считать странного имени). Именно для таких случаев и были придуманы друзья. Если бы мы не объявили функцию operator+ другом, то она не имела бы доступа к переменной s, и мы оказались бы перед выбором: то ли разрешить всем на свете доступ к char*, то ли перейти к менее эффективной реализации, при которой строка копируется при каждом обращении к ней. С концептуальной точки зрения operator+ является частью библиотеки String, поэтому нет ничего страшного в том, чтобы объявить эту функцию другом и вручить ей ключи к внутреннему устройству String.

Внешними функциями могут перегружаться любые операторы, кроме операторов преобразования, =, [], () и -> - все эти операторы должны перегружаться только функциями класса.

Перегрузка операторов в форме функций класса

Синтаксис напоминает обычную перегрузку функций класса, разве что количество аргументов уменьшается на 1 по сравнению с формой внешней функции.

class String { private:

char* s; public:

Конструкторы и т.д.

String& operator+(const String&) const;

String& String::operator+(const String& s1) const

char* s2 = new char[str1en(s1.s) + strlen(s) + 1]; strcat(s2, s1, s); String newStr(s2); delete s2; return newStr;



String s1 = "Hello"; String s2 = "Goodbye"; String s3 = s1 + s2;

Любой оператор может быть перегружен в форме функции класса. Если оператор может перегружаться как внешней функцией, так и функцией класса, какую из двух форм выбрать? Ответ: используйте перегрузку в форме функции класса, если только у вас не найдется веских причин для перегрузки внешней функцией. Из этих причин наиболее распространены следующие:

1. Первый аргумент относится к базовому типу (например, int или double).

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

Компилятор ищет перегрузку в форме функций класса, просматривая левую часть бинарных операторов и единственный аргумент унарных. Если ваш тип указывается справа и вы хотите воспользоваться перегрузкой в форме функции класса, вам не повезло. Самый распространенный пример перегрузки в форме внешней функции - оператор << в библиотеке ostream.

ostream& operator«(ostream& os, const String& s)

os << str.s; Предполагается, что данная функция является другом

return os;

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

Примечание: предыдущий пример может не работать в вашем компиляторе, если функции strlen и strcat, как это часто бывает, по недосмотру разработчиков получают char* вместо const char*. Вы можете решить, что игра не стоит свеч, и объявить функцию неконстантной, но это выглядит слишком жестоко. Лучше избавиться от константности посредством преобразования типов, если вы абсолютно уверены, что библиотечная функция не модифицирует свои аргументы, и готовы смириться с предупреждениями компилятора.

String& String::operator+(const String& s1) const

char* s2 = new char[str1en((char*)s1.s) + strlen(s) + 1]; strcat(s2, (char*)s1.s, s); String newStr(s2); delete s2; return newStr;

Видите, что происходит, если кто-то забывает о константности? Операторы преобразования

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

class String { private:

char* s; public:

operator 1ong(); Использует atol для преобразования к типу long

String::operator 1ong()



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