6.5. Приятели на клас
В някои случаи правилата за достъп до скриваната информация са доста ограничаващи. Механизмът за приятелство дава права за достъп до членове на класа на нечленове. Преди да разгледаме правилата за деклариране на приятели, нека да разгледаме един пример, в който е необходим приятел. Операторите за вход и изход от iostream (“>>”,”<<”) могат да бъдат презареждани за да обработват типа клас. След като операторите веднъж са били дефинирани за типа клас, обектите на класа могат да бъдат извeждани по същия начин, както и вградените типове. Например,
Screen myScreen;
cout << myScreen;
cout << "myScreen: " << myScreen << "n";
Както операторът за вход, така и операторът за изход изискват обект от iostream като техен ляв операнд и връщат обекта от iostream, с който са оперирали. Това позволява успешно завършилите оператори за вход и изход да бъдат конкатенирани. Например,
((( (cout << "myScreen: ") << myScreen ) << "n")
Всеки подизраз, заграден в скоби, връща обекта cout от iostream, който става ляв операнд на следващия израз.
cout << myScreen
трябва да бъде реализиран като ostream
&operator<<( ostream&, Screen& );
Тази реализация, обаче, предотвратява дефинирането на тези оператор функции като член функции на Screen.
Ето декларацията на оператора за изход като член функция на Screen:
class Screen
{ public: ostream &operator<<( ostream& );
// ... };
Левият операнд на всяка член функция е обект или указател към обект от класа й. Поради това представителят на член функцията на оператора за изход декларира само един ostream аргумент. Обръщението към този представител има следния вид:
myScreen << cout;
Би било объркващо както за програмиста, така и за читателя на програмата да добави този представител. Ако бъде добавен представител, който не е член функция, обаче, операторът за изход няма да има права за достъп до непубличните членове на класа Screen - много от които се предвижда да бъдат показани. Ето къде е необходим механизма на приятелството.
Приятел се нарича всеки нечлен на класа, на който е даден достъп до непубличните членове на класа. Приятел може да бъде една нечлен функция, член функция на предварително дефиниран клас или цял един клас. Ако един клас бъде дефиниран като приятел на друг, на член функциите на класа приятел се дават права на достъп до непубличните членове на другия клас. Една декларация за приятелство започва с ключовата дума friend. Тя може да се появява само в дефиницията на клас. Тъй като приятелите не са членове на класа, на тях не може да се въздейства чрез публичните, личните или защитените раздели, в които те са декларирани в тялото на класа. Според едно съглашение относно стила на записване всички декларации за приятелство се групират веднага след заглавието на класа.
class Screen {
friend iostream&
operator>>( iostream&, Screen& );
friend iostream&
operator<<( iostream&, Screen& );
public:
// ... rest of the Screen class };
Как може за бъде написан оператора за изход на Screen? Необходими са три от член данните на Screen - heigth, width и адреса на фактическият масив от символи screen. За oпростяване курсорът се връща в позиция home когато се чете обектът Screen. Форматът на изхода за обект Screen има вида:
<heigth,width> linear Screen Dump
Операторът за изход може да бъде реализиран по следния начин:
ostream& operator<<( ostream& os, Screen& s )
{ os << "n<" << s.heigth << "," << s.width << ">";
char *p = s.screen;
while ( *p ) os.put( *p++ );
return os; }
Ето пример за използуване на оператора за изход. main() се реализира така:
#include <stream.h>
#include "Screen.h"
main() {
Screen x(4,4,’%’);
cout << x;
return 0; }
Когато компилираме и изпълним тази програма получаваме:
<4,4>%%%%%%%%%%%%%%%%
Операторът за вход ще чете като входни данни резултата от изпълнението на оператора за изход на Screen. Нека запазим изходния резултат във файл с име output. Реализацията на операторът за вход е представена по нататък; проверката на формата на входния текст е пропусната за да се спести място.
istream& operator>>( istream& is, Screen& s )
{ // read Screen object output by operator <<
int wid, hi;
char ch; // format verification not shown
// <hi,wid>screenDump
is >> ch; // ‘<’
is >> hi; // get heigth
is >> ch; // ‘,’
is >> wid; // get width
is >> ch; // // ‘>’
delete s.screen;
int sz = hi * wid; s.height = hi; s.width = wid;
s.cursor = s.screen = new char[ sz + 1 ];
char *endptr = s.screen + sz;
char *ptr = s.screen;
while ( ptr != endptr ) is.get( *ptr++ );
*ptr = ‘�’;
return is; }
Следната малка програма дава пример за използване както на оператора за вход, така и на оператора за изход за класа Screen.
#include <stream.h>
#include "Screen.h"
main() {
Screen x(5,5,’?’);
cout << "Initial Screen: t" << x;
cin >> x;
cout << "nInput Screen: t" << x;
return 0; }
Входните данни са като изхода на предходната програма. Когато тази програма бъде компилирана и изпълнена се получава следния резултат:
Initial Screen: <5,5>??????????????????????????
Input Screen: <4,4>%%%%%%%%%%%%%%%%
Упражнение 6-8. Операторът за вход трябва да проверява правилността на форматираните входни данни. Изменете го така, че това да бъде изпълнено.
Упражнение 6-9. Реализирането на оператора за изход така, че да запазва позицията на курсора не е трудно. За това могат да послужат член функциите col() и row(). Напишете отново оператора за изход, така че да запазва текущата позиция на курсора.
Упражнение 6-10. Напишете отново оператора за вход, така че да може да обработва формата на описания в предходното упражнение оператор за изход.
Един клас трябва да определи всеки представител на дадена презаредима функция, която желае да бъде приятел на класа. Например,
iostream& storeOn( iostream&, Screen& );
BitMap& storeOn( BitMap&, Screen& );
class Screen {
friend iostream& storeOn( iostream&, Screen& );
friend BitMap& storeOn( BitMap&, Screen& );
public: // ... rest of the Screen class };
Ако дадена функция обработва обекти от два различни класа, тя може да бъде направена приятелска на двата класа или да стане член функция на единия клас и приятел на другия. Нека да разгледаме как това може да бъде направено.
В първия случай функцията логически трябва да бъде член на двата класа. Понеже това е невъзможно вместо това функцията става приятел на и двата класа. Например,
// forward declarations
class Screen;
class Window;
Screen& isEqual( Screen&, Window& );
class Screen {
friend Screen& isEqual( Screen&, Window& );
// ... rest of Screen goes here };
class Window {
friend Screen& isEqual( Screen&, Window& );
// ... rest of Window goes here };
Във втория случай функцията логически принадлежи като член на единия клас. Освен това, обаче, й е необходим достъп до втория клас. Следователно, тя може да бъде направена приятел на втория клас:
class Window;
class Screen
{ public:
Screen& copy( Window& );
// ... remaining Screen members };
class Window {
friend Screen& Screen::copy( Window& )
// ... remaining Window members };
Screen& Screen::copy( Window& ) { /* ... */ }
Цял един клас може да бъде деклариран като приятелски на някой клас. Например,
class Window;
class Screen {
friend class Window;
public: // ... rest of the Screen class };
Сега непубличните членове на класа Screen са достъпни за всички член функции на Window.