Примечание:От начинающих программистов C++ часто можно услышать замечание, что объявление классов-друзей противоречит принципу инкапсуляции, лежащему в основе объектно-ориентированного программирования. Это, честно говоря, довольно широко распространенная бессмыслица. Объявление класса-друга просто расширяет интерфейс другого класса, что влияет на инкапсуляцию не больше, чем открытое наследование классов.
Дружественный класс
Объявление одного класса другом какого-либо иного с помощью ключевого слова friend в объявлении второго класса открывает первому классу доступ к членам второго класса. Иными словами, я могу объявить вас своим другом, но вы не можете объявить себя моим другом. Пример:
class PartNode{
public:
friend class PartsList: // обьявление класса PartsList другом PartNode
Функции друзья
Иногда бывает необходимо предоставить права доступа не всему классу, а только одной или нескольким функциям-членам. Это реализуется посредством объявления друзьями функций-членов другого класса. Причем объявлять другом весь класс вовсе не обязательно. Фактически другом можно объявить любую функцию, независимо от того, является ли она функцией-членом другого класса или нет.
Функции друзья и перегрузка оператора
В листинге 15.1 представлен класс String, в котором перегружается operator+. В нем также объявляется конструктор, принимающий указатель на константную строку, поэтому объект класса String можно создавать из строки с концевым нулевым символом.
Примечание:Строки в С и C++ представляют собой массивы символов, заканчивающиеся концевым нулевым символом. Такая строка получается, например,в следующем выражении присвоения: myString[] = "Hello World".
Но чего невозможно сделать в классе String, так это получить новую строку в результате сложения объекта этого класса с массивом символов:
char cString[] = { "Hello"} ; String sString(" Worid");
String sStringTwo = cString + sString; //ошибка!
Строки нельзя использовать с перегруженной функции operator+. Как объяснялось на занятии 10, выражение cString + sString на самом деле вызывает функцию cString.operator+(sString). Поскольку функция operator+() не может вызываться для символьной строки, данная попытка приведет к ошибке компиляции.
Эту проблему можно решить, объявив функцию-друга в классе String, которая перегружает operator+ таким образом, чтобы суммировать два объекта String. Соответствующий конструктор класса String преобразует строки в объекты String, после чего вызывается функция-друг operator+, выполняющая конкатенацию двух объектов.
Листинг 15.8. Функция-друг operator+
1: // Листинг 15.8. Операторы друзья
2:
3: #include <iostream.h>
4: #include <string.h>
5:
6: // Рудиментарный класс string
7: class String
8: {
9: public:
10: // constructors
11: String();
12: String(const char *const);
13: String(const String &);
14: ~String();
15:
16: // перегруженные операторы
17: char & operator[](int offset);
18: char operator[](int offset) const;
19: String operator+(const String&);
20: friend String operator+(const String&, const String&);
21: void operator+=(const String&);
22: String & operator= (const String &);
23:
24: // методы общего доступа
25: int GetLen()const { return itsLen; }
26: const char * GetString() const { return itsString; }
27:
28: private:
29: String (int); // закрытый конструктор
30: char * itsString;
31: unsigned short itsLen;
32: };
33:
34: // конструктор, заданный по умолчанию, создает строку длиной 0 байт
35: String::String()
36: {
37: itsString = new char[1];
38: itsString[0] = ' ';
39: itsLen=0;
40: // cout << "tDefault string constructorn";
41: // ConstructorCount++:
42: }
43:
44: // закрытый конструктор, используемый только
45: // методами класса для создания новой строки
46: // указанного размера, заполненной нулями.
47: String::String(int len)
48: {
49: itsString = new char[len+1];
50: for (int i = 0; i<=len; i++)
51: itsString[i] = ' ';
52: itsLen=len;
53: // cout << "tString(int) constructorn";
54: // ConstructorCount++;
55: }
56:
57: // Преобразует массив символов в строку
58: String::String(const char * const cString)
59: {
60: itsLen = strlen(cString);
61: itsString = new char[itsLen+1];
62: for (int i = 0; i<itsLen; i++)
63: itsString[i] = cString[i];
64: itsString[itsLen]=' ';
65: // cout << "tString(char*) constructorn";
66: // ConstructorCount++;
67: }
68:
69: // конструктор-копировщик
70: String::String (const String & rhs)
71: {
72: itsLen=rhs.GetLen();
73: itsString = new char[itsLen+1];
74: for (int i = 0; i<itsLen;i++)
75: itsString[i] = rhs[i];
76: itsString[itsLen] = ' ';
77: // cout << "tString(String&) constructorn";
78: // ConstructorCount++;
79: }
80:
81: // деструктор, освобождает занятую память
82: String::~String ()
83: {
84: delete [] itsString;
85: itsLen = 0;
86: // cout << "tString destructorn";
87: }
88:
89: // этот оператор освобождает память, а затем
90: // копирует строку и размер
91: String& String::operator=(const String & rhs)
92: {
93: if (this == &rhs)
94: return <<this;
95: delete [] itsString;
96: itsLen=rhs.GetLen();
97: itsString = new char[itsLen+1];
98: for (int i = 0; i<itsLen;i++)
99: itsString[i] = rhs[i];
100: itsString[itsLen] = 1 ';
101: return *this;
102: // cout << "tString operator=n";
103: }
104:
105: // неконстантный оператор индексирования,
106: // возвращает ссылку на символ, который можно
107: // изменить!
108: char & String::operator[](int offset)
109: {
110: if (offset > itsLen)
111: return itsString[itsLen-1];
112: else
113: return itsString[offset];
114: }
115:
116: // константный оператор индексирования,
117: // используется для константных объектов (см. конструктор-копировщик!)
118: char String::operator[](int offset) const
119: {
120: if (offset > itsLen)
121: return itsString[itsLen-1];
122: else
123: return itsString[offset];
124: }
125: // создает новый объект String, добавляя
126: // текущий обьект к rhs
127: String String::operator+(const String& rhs)
128: {
129: int totalLen = itsLen + rhs.GetLen();
130: String temp(totalLen);
131: int i, j;
132: for (i = 0; i<itsLen; i++)
133: temp[i] = itsString[i];
134: for (j = 0, i = itsLen; j<rhs.GetLen(); j++, i++)
135: temp[i] = rhs[j];
136: temp[totalLen]=' ';
137: return temp;
138: }
139:
140: // создает новый объект String
141: // из двух объектов класса String
142: String operator+(const String& lhs, const String& rhs)
143: {
144: int totalLen = lhs.GetLen() + rhs.GetLen();
145: String temp(totalLen);
146: int i, j;
147: for (i = 0; i<lhs.GetLen(); i++)
148: temp[i] = lhs[i];
149: for (j = 0, i = lhs.GetLen();; j<rhs.GetLen(); j++, i++)
150: temp[i] = rhs[j];
151: temp[totalLen]=' ';
152: return temp;
153: }
154:
155: int main()
156: {
157: String s1("String 0ne ");
158: String s2("String Two ");
159: char *c1 = { "C-String 0ne " } ;
160: String s3;
161: Stnng s4;
162: String s5;
163:
164: cout << "s1: " << s1.GetString() << endl;
165: cout << "s2: " << s2.GetString() << endl;
166: cout << "c1: " << c1 << endl;
167: s3 = s1 + s2;
168: cout << "s3: " << s3.GetString() << endl;
169: s4 = s1 + cl;
170: cout << "s4: " << s4.GetStnng() << endl;
171: s5 = c1 + s2;
172: cout << "s5: " << s5.GetString() << endl;
173: return 0;
174: }
Результат:
s1: String 0ne
s2: String Two
c1: C-String One
s3: String One String Two
s4: String One C-String One
s5: C-String One String Two
Анализ: Объявления всех методов класса String, за исключением operator+, остались такими же, как в листинге 15.1. В строке 20 листинга 15.8 перегружается новый operator+, который принимает две ссылки на константные строки и возвращает строку, полученную в результате конкатенации исходных строк. Эта функция объявлена как друг класса String.
Обратите внимание, что функция operator+ не является функцией-членом этого или любого другого класса. Она объявляется среди функций-членов класса string как друг, но не как член класса. Тем не менее это все же полноценное объявление функции, и нет необходимости еще раз объявлять в программе прототип этой функции.
Выполнение функции operator+ определяется в строках 142—153. Определение выполнения функции аналогично приведенному в версии программы, представленной в листинге 15.1, за тем исключением что функция принимает в качестве аргументов две строки, обращаясь к ними с помощью открытых методов доступа класса.
Перегруженный оператор применяется в строке 171, где выполняется конкатенация двух строк.
Функции-друзья
Для объявления функции как друга класса используется ключевое слово friend, за которым следует объявление функции Это не предоставляет функции доступ к указателю this, но обеспечивает доступ ко всем закрытым и защищенным данным и функциям-членам.