А.В. Столяров - Введение в язык Си++ (1114949), страница 11
Текст из файла (страница 11)
Инициализаторы полей должныследовать в списке (после двоеточия) в том же порядке, в котором соответстующие поля описаны в классе. Иное, формально говоря, не являетсяошибкой, но хороший компилятор обязательно выдаст предупреждение.§2.17. О писание сим волов операций внек л ассаВ классе Complex мы перегружали символы арифметических операций в виде функций-методов. Благодаря наличию в классе конструкторапреобразования возможно, например, прибавить к комплексному числудействительное:Complex z , t ;И ...z = t + 0 .5 ;(при этом константа 0.5 будет преобразована к объекту Complex с помощью конструктора преобразования). В то же время, следующая операцияокажется ошибочной:z=0.5+t;/ / ошибка!Дело тут в том, что метод operator+ может быть вызван только дляобъекта класса Complex, а константа 0.5 таковым не является.
В отличие от аргументов функций, объекты, для которых вызываются методы,компилятор не преобразует.Эту проблему также можно решить. Дело в том, что операцию сложения двух комплексных чисел можно представить не только как методсамого комплексного числа (с одним параметром, по принципу «прибавьк себе этот параметр и скажи, что получится»), но и как стороннююфункцию, которая по двум заданным комплексным числам выдаёт другое комплексное число.
Итак, уберём operator+ из класса Complex, а внекласса напишем следующее:Complex operator+(const Complex& a , const Complex& b)48{return Com plex(a.get_re() + b .g e t_ r e ( ),a.get_im () + b .g e t_ im ());}Теперь мы можем написать:Complex z , t ;u ...z = t + 0 .5 ;z = 0.5 + t ;и никаких ошибок это не вызовет.Перегрузка символов стандартных операций в виде отдельных функций (а не методов в классах) может оказаться полезной также и в томслучае, если нам необходимо ввести операцию для объектов некоторогокласса, который мы по тем или иным причинам не можем изменить; например, этот класс может быть частью библиотеки, созданной другимпрограммистским коллективом, от которой у нас нет исходных текстов(к сожалению, несмотря на успехи OpenSource, такие ситуации пока чтоне редкость).§2.18.
Д р у ж еств ен н ы е ф у н к ц и и и кл ассыВ некоторых случаях бывает полезно сделать исключения из запретов, налагаемых механизмом защиты. В языке С и + + класс или структура, имеющие защищенную часть, могут объявить ту или иную функциюсвоим «другом» (friend); в этом случае из тела такой д р у ж е ст в е н н о йфункции все детали реализации класса или структуры будут доступны.Можно объявить «дружественным» также целый класс или структуру;эффект от этого будет такой же, как если бы мы объявили дружественными все методы дружественного класса.
Иначе говоря, если в классеА будет заявлено, что класс В является для него дружественным, то вовсех методах класса В будут доступны все детали реализации класса А.Применять механизм дружественных функций и классов следует состорожностью. Как мы видели в § 2.1.4, защита деталей реализациикласса — механизм очень полезный; как можно догадаться, необдуманные исключения из него могут нанести существенный вред.Одна из ситуаций, в которых применение механизма дружественных функций можно считать практически безопасным — это выносперекрытых символов стандартных операций за пределы класса, какэто обсуждалось в предыдущем параграфе.
Как можно заметить, в теле функции operator+ мы были вынуждены пользоваться методами49Complex: :g e t_ re () и Complex: :get_im (), поскольку из функции, не являющейся формально методом класса Complex, прямого доступа к полямге и im нет. Однако использование методов доступа (так называемых аксессоров) не всегда удобно, к тому же их может попросту не быть; частобывает так, что некоторые детали реализации не следует делать доступными даже через метод доступа.Ситуацию можно исправить с помощью механизма дружественныхфункций. Для начала в заголовок класса Complex вставим директивуfrien d , объявив функцию operator+ дружественной.
Для этого нужнозаписать прототип дружественной функции, предварив его директивойfriend:c la s s Complex {frie n d Complex operator+(const Complex&, const Complex&);//...Теперь мы можем переписать функцию operatort, используя напрямуюполя складываемых объектов (то есть без обращения к методам g e t_ re()и get_im ()):Complex operator+(const Complex& a , const Complex& b){return Complex(a.re + b .r e , a.im + b .im );}Сравните это описание с тем, которое мы приводили на стр.
49.Конечно, дружественной может быть и обычная функция:c la s s C ls l {frie n d void f ( i n t , const char * ) ;//...>;void f ( i n t , const char *){/ / здесь можно использовать закрытые поля C lsl}Чтобы сделать дружественным сразу целый класс, следует после слова frie n d написать « c la s s имя»:c la s s А {frie n d c la s s В;I I ...>;50Использовать такой вариант «дружбы» следует с особенной осторожностью, только в ситуации, когда понятия, описываемые обоими классами, тесно связаны между собой; желательно сначала задать себе вопрос, нельзя ли обойтись без friend. Большим злом, чем использование«дружественных» отношений, является разве что введение публичныхметодов доступа к деталям, которые по смыслу должны быть скрыты,либо снятие защиты с внутренних полей: ясно, что «дружественность»всё-таки ограничивает объём кода, который придётся просмотреть приизменении реализации класса, так что использование frien d заведомопредпочтительнее полного отказа от защиты.
С примерами ситуаций, когда применение «дружественных» классов оказывается оправданно, мыстолкнёмся в нашем курсе позже.§2.19. О собенности п ереоп ределен и янекоторы х операций§2.19.1. Переопределение операций присваиванияНапомним, что присваивание является в языках Си и С и + + операцией, а не оператором, как в Паскале и большинстве других языков.Это выражается в том, что запись вида «а = выражение» сама по себе является выражением, имеющим значение, и, таким образом, можетвходить в состав других выражений.
То же самое можно сказать и прооперации присваивания, совмещённые с арифметическими действиями,такие как +=, -=, <<=, | = и др. Как мы уже отмечали, все эти операциимогут быть переопределены для классов и структур, введённых пользователем. Здесь действует, однако, некоторое ограничение: все операцииприсваивания могут переопределяться только как м етоды к л асса илиструктуры , а определять их в виде обычных функций нельзя.Как правило, при переопределении операций присваивания учитывают, что обычное присваивание возвращает значение, которое только чтобыло присвоено. Из этих соображений из операции присваивания возвращают либо копию объекта, либо (что предпочтительнее) константнуюссылку на объект, для которого операция была вызвана. Это, однако, необязательно: язык С и + + не требует именно такого оформления операций присваивания.
Можно, в частности, сделать операцию присваиванияфункцией типа void, то есть не возвращающей никакого значения. Например, для нашего класса Complex мы могли бы написать такие операции:c la s s Complex {II . . .const Complex& operator=(const Complex& c)51{ re = c .r e ; im = c.im ; return * t h i s ; }const Complex& operator+=(const Complex& c){ re += c .r e ; im += c.im ; return * t h i s ; }// ...>;или, если нас не слишком волнуют семантические традиции, можно былобы написать и так:c la s s Complex {// ...void operator=(const Complex& c){ re = c .r e ; im = c.im ; }void operator+=(const Complex& c){ re += c .r e ; im += c.im ; }// ...>;Здесь стоит ответить на один вопрос, часто возникающий у студентов.Операции +=, -= и другие подобные им являются с точки зрения языкаС и + + полностью самостоятельными, то есть, например, операция += никак не связана ни с операцией =, ни с операцией +.
Если мы опишем внекотором классе операции = и +, это само по себе не даст нам возможности применять к объектам этого класса операцию +=, она должна бытьописана отдельно.Аргумент операции присваивания не обязан иметь тот же тип, чтои описываемый класс. Так, для комплексных чисел мы могли бы определить и присваивание комплексной переменной действительного числа,например, так:c la s s Complex {// ...void operator=(double x) { re = x; im = 0; }// ...>;Однако операция присваивания, аргумент которой представляет объекттого же класса (или ссылку на такой объект, как в предыдущем примере),имеет одну важную особенность: как и некоторые конструкторы, такаяоперация генерируется неявно, если её не описать.
Это вполне понятно, ведь в языке Си можно было присваивать между собой переменные,имеющие один структурный тип, и эта возможность была унаследована в С и ++. В случае, если по каким-либо причинам присваивание объектов описываемого класса желательно запретить, достаточно описатьоперацию присваивания с аргументом «константная ссылка на объектописываемого класса» в приватной части класса:52c la s s A {// ...p r iv a te :void operator=(const A& r e f) -Q / / no assignments>;§2.19.2.
Переопределение операции индексированияОперация извлечения элемента из массива, обозначаемая квадратными скобками, как известно, является арифметической операцией надуказателем и целым числом; в языке Си выражение «а[Ъ]>> полностьюэквивалентно выражению « * (а+Ъ) », что лишний раз подчёркивает арифметическую сущность индексирования. В языке С и + + операцию индексирования можно переопределить для объектов класса или структуры,заставив, таким образом, объект в некоторых случаях выглядеть синтаксически похожим на обычный массив или даже просто исполнять рольмассива.
Отметим, что, подобно операциями присваивания, операция индексирования может быть переопределена только как метод класса илиструктуры.Для примера опишем класс, объект которого представляет собой массив целых чисел с заранее неизвестным размером, который при обращении к несуществующим (пока) элементам автоматически увеличивается вразмерах. Хранить элементы массива будем в динамически создаваемоммассиве, скрытом в приватной части класса. Исходно создадим массивразмером 16 элементов, а при возникновении такой необходимости будемудваивать его размеры до тех пор, пока нужный нам индекс не станетдопустимым.Заметим, что при попытке копирования объекта такого класса возникнет проблема с разделением одного и того же массива в динамической памяти между двумя объектами класса (подробно проблема описанав § 2.11).