Теперь, если мы сделаем multiply() членом класса vector, мы сможем обойтись без проверки индексов при обращении к элменту вектора, а если мы сделаем multiply() членом класса matrix, то мы сможем обойтись без проверки индексов при обрщении к элементу матрицы. Однако членом двух классов функция быть не может. Нам нужно средство языка, предоставляющее функции право доступа к закрытой части класса. Функция не член, получившая право доступа к закрытой части класса, назвается другом класса (friend). Функция становится другом класса после описания как friend. Например:
class matrix;
class vector (* float v[4]; // ... friend vector multiply(matrix amp;, vector amp;); *);
class matrix (* vector v[4]; // ... friend vector multiply(matrix amp;, vector amp;); *);
Функция друг не имеет никаких особенностей, помимо права доступа к закрытой части класса. В частности, friend функция не имеет указателя this (если только она не является полноравным членом функцией). Описание friend – настоящее описние. Оно вводит имя функции в самой внешней области видимости
программы и сопоставляется с другими описаниями этого имени. Описание друга может располагаться или в закрытой, или в отрытой части описания класса. Где именно, значения не имеет.
Теперь можно написать функцию умножения, которая исползует элементы векторов и матрицы непосредственно:
vector multiply(matrix amp; m, vector amp; v); (* vector r; for (int i = 0; i«3; i++) (* // r[i] = m[i] * v; r.v[i] = 0; for (int j = 0; j«3; j++) r.v[i] += m.v[i][j] * v.v[j]; *) return r; *)
Есть способы преодолеть эту конкретную проблему эффетивности не используя аппарат friend (можно было бы определить операцию векторного умножения и определить multiply() с ее помощью). Однако существует много задач, кторые проще всего решаются, если есть возможность предоствить доступ к закрытой части класса функции, которая не явлется членом этого класса. В Главе 6 есть много примеров применения friend. Достоинства функций друзей и членов будут обсуждаться позже.
Функция член одного класса может быть другом другого. Например:
class x (* // ... void f(); *);
class y (* // ... friend void x::f(); *);
Нет ничего необычного в том, что все функции члены однго класса являются друзьями другого. Для этого есть даже блее краткая запись:
class x (* friend class y; // ... *);
Такое описание friend делает все функции члены класса y друзьями x.
5.4.2 Уточнение* Имени Члена
– * Иногда называется также квалификацией. (прим. перев.)
Иногда полезно делать явное различие между именами члнов класса и прочими именами. Для этого используется операция ::, «разрешения области видимости»:
class x (* int m; public: int readm() (* return x::m; *) void setm(int m) (* x::m = m; *)
*);
В x::setm() имя параметра m прячет член m, поэтому единственный способ сослаться на член – это использовать его уточненное имя x::m. Операнд в левой части :: должен быть именем класса.
Имя с префиксом :: (просто) должно быть глобальным имнем. Это особенно полезно для того, чтобы можно было исползовать часто употребимые имена вроде read, put и open как имена функций членов, не теряя при этом возможности обращатся к той версии функции, которая не является членом. Например:
class my_file (* // ... public: int open(char*, char*); *);
int my_file::open(char* name, char* spec) (* // ... if (::open(name,flag))(*//использовать open() из UNIX(2) // ... *) // ... *)
5.4.3 Вложенные Классы
Описание класса может быть вложенным. Например:
class set (* struct setmem (* int mem; setmem* next; setmem(int m, setmem* n) (* mem=m; next=n; *) *); setmem* first; public: set() (* first=0; *) insert(int m) (* first = new setmem(m,first);*) // ... *);
Если только вложенный класс не является очень простым, в таком описании трудно разобраться. Кроме того, вложение класов – это не более чем соглашение о записи, поскольку вложеный класс не является скрытым в области видимости лексически охватывающего класса:
class set (* struct setmem (* int mem; setmem* next; setmem(int m, setmem* n) *); // ... *);
setmem::setmem(int m, setmem* n) (* mem=m, next=n*) setmem m1(1,0);
Такая запись, как set::setmem::setmem(), не является ни необходимой, ни допустимой. Единственный способ скрыть имя класса – это сделать это с помощью метода файлы-как-модули (#
4.4). Большую часть нетривиальных классов лучше описывать раздельно:
class setmem (* friend class set; // доступ только с помощью членов set int mem; setmem* next; setmem(int m, setmem* n) (* mem=m; next=n; *) *);
class set (* setmem* first; public: set() (* first=0; *) insert(int m) (* first = new setmem(m,first);*) // ... *);
5.4.4 Статические Члены