Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
C++ для начинающих.pdf
Скачиваний:
199
Добавлен:
01.05.2014
Размер:
3.97 Mб
Скачать

Упражнение 15.1

Почему при выполнении следующего сравнения не вызывается перегруженный оператор operator==(const String&, const String&):

"cobble" == "stone"

Упражнение 15.2 Напишите перегруженные операторы неравенства, которые могут быть использованы в

String != String

String != С-

строка

таких сравнениях:

C-строка != String

Объясните, почему вы решили реализовать один или несколько операторов. Упражнение 15.3

Выявите те функции-члены класса Screen, реализованного в главе 13 (разделы 13.3, 13.4 и 13.6), которые можно перегружать.

Упражнение 15.4

Объясните, почему перегруженные операторы ввода и вывода, определенные для класса String из раздела 3.15, объявлены как глобальные функции, а не функции-члены.

Упражнение 15.5

Реализуйте перегруженные операторы ввода и вывода для класса Screen из главы 13.

15.2. Друзья

Рассмотрим еще раз перегруженные операторы равенства для класса String, определенные в области видимости пространства имен. Оператор равенства для двух

bool operator==( const String &str1, const String &str2 )

{

if ( str1.size() != str2.size() ) return false;

return strcmp( str1.c_str(), str2.c_str() ) ? false : true;

объектов String выглядит следующим образом:

}

Сравните это определение с определением того же оператора как функции-члена:

bool String::operator==( const String &rhs ) const

{

if ( _size != rhs._size ) return false;

return strcmp( _string, rhs._string ) ? false : true;

}

Нам пришлось модифицировать способ обращения к закрытым членам класса String. Поскольку новый оператор равенства – это глобальная функция, а не функция-член, у него нет доступа к закрытым членам класса String. Для получения размера объекта String и лежащей в его основе C-строки символов используются функции-члены size()

и c_str().

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

Объявление друга (оно начинается с ключевого слова friend) встречается только внутри определения класса. Поскольку друзья не являются членами класса, объявляющего дружественные отношения, то безразлично, в какой из секций – public, private или protected – они объявлены. В примере ниже мы решили поместить все подобные

class String {

friend bool operator==( const String &, const String & );

friend bool operator==( const char *, const String & ); friend bool operator==( const String &, const char * );

public:

// ... остальная часть класса String

объявления сразу после заголовка класса:

};

В этих трех строчках три перегруженных оператора сравнения, принадлежащие глобальной области видимости, объявляются друзьями класса String, а следовательно, в

//дружественные операторы напрямую обращаются к закрытым членам

//класса String

bool operator==( const String &str1, const String &str2 )

{

if ( str1._size != str2._size ) return false;

return strcmp( str1._string, str2._string ) ? false : true;

их определениях можно напрямую обращаться к закрытым членам данного класса:

inline bool operator==( const String &str, const char *s )

{

return strcmp( str._string, s ) ? false : true;

}

}

// и т.д.

Можно возразить, что в данном случае прямой доступ к членам _size и _string необязателен, так как встроенные функции c_str() и size() столь же эффективны и при этом сохраняют инкапсуляцию, а значит, нет особой нужды объявлять операторы равенства для класса String его друзьями.

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

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

Хотя объявления друзей обычно употребляются по отношению к операторам, бывают случаи, когда функцию в пространстве имен, функцию-член другого класса или даже целый класс приходится объявлять таким образом. Если один класс объявлен другом второго, то все функции-члены первого класса получают доступ к неоткрытым членам другого. Рассмотрим это на примере функций, не являющихся операторами.

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

extern ostream& storeOn( ostream &, Screen & ); extern BitMap& storeOn( BitMap &, Screen & ); // ...

class Screen

{

friend ostream& storeOn( ostream &, Screen & );

friend BitMap& storeOn( BitMap &, Screen & ); // ...

он хочет дать неограниченные права доступа:

};

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

Объявление функции другом двух классов должно выглядеть так:

class

Window; // это всего лишь объявление

class

Screen {

friend bool is_equal( Screen &, Window & );

// ...

};

class Window {

friend bool is_equal( Screen &, Window & );

// ...

};

Если же мы решили сделать функцию членом одного класса и другом второго, то

class Window; class Screen {

//copy() - член класса Screen Screen& copy( Window & );

//...

};

class Window {

//Screen::copy() - друг класса Window friend Screen& Screen::copy( Window

&);

//...

};

объявления будут построены следующим образом:

Screen& Screen::copy( Window & ) { /* ... */ }

Функция-член одного класса не может быть объявлена другом второго, пока компилятор не увидел определения ее собственного класса. Это не всегда возможно. Предположим, что Screen должен объявить некоторые функции-члены Window своими друзьями, а Window – объявить таким же образом некоторые функции-члена Screen. В таком случае

class Window; class Screen { friend class

Window;

// ...

весь класс Window объявляется другом Screen:

};

К закрытым членам класса Screen теперь можно обращаться из любой функции-члена

Window. Упражнение 15.6

Реализуйте операторы ввода и вывода, определенные для класса Screen в упражнении 15.5, в виде друзей и модифицируйте их определения так, чтобы они напрямую обращались к закрытым членам. Какая реализация лучше? Объясните почему.