5.4.1 Друзья | |
5.4.2 Уточнение Имени Члена | |
5.4.3 Вложенные Классы | |
5.4.4 Статические Члены | |
5.4.5 Указатели на Члены | |
5.4.6 Структуры и Объединения |
В это разделе описываются еще некоторые особенности, касающиеся классов. Показано, как предоставить функции не члену доступ к закрытым членам. Описывается, как разрешать конфликты имен членов, как можно делать вложенные описания классов, и как избежать нежелательной вложенности. Обсуждается также, как объекты класса могут совместно использовать члены данные, и как использовать указатели на члены. Наконец, приводится пример, показывающий, как построить дискриминирующее (экономное) объединение.
Друзья
Предположим, вы определили два класса, vector и matrix (вектор и матрица). Каждый скрывает свое представление и предоставляет полный набор действий для манипуляции объектами его типа. Теперь определим функцию, умножающую матрицу на вектор. Для простоты допустим, что в векторе четыре элемента, которые индексируются 0...3, и что матрица состоит из четырех векторов, индексированных 0...3. Допустим также, что доступ к элементам вектора осуществляется через функцию elem(), которая осуществляет проверку индекса, и что в matrix имеется аналогичная функция. Один подход состоит в определении глобальной функции multiply() (перемножить) примерно следующим образом:
|
|
Это своего рода "естественный" способ, но он очень неэффективен. При каждом обращении к multiply() elem() будет вызываться 4*(1+4*3) раза.
Теперь, если мы сделаем multiply() членом класса vector, мы сможем обойтись без проверки индексов при обращении к элементу вектора, а если мы сделаем multiply() членом класса matrix, то мы сможем обойтись без проверки индексов при обращении к элементу матрицы. Однако членом двух классов функция быть не может. Нам нужно средство языка, предоставляющее функции право доступа к закрытой части класса. Функция не член, получившая право доступа к закрытой части класса, называется другом класса (friend). Функция становится другом класса после описания как friend. Например:
class matrix; class vector { float v[4]; //... friend vector multiply(matrix&, vector&); }; class matrix { vector v[4]; //... friend vector multiply(matrix&, vector&); };Функция друг не имеет никаких особенностей, помимо права доступа к закрытой части класса. В частности, friend функция не имеет указателя this (если только она не является полноправным членом функцией). Описание friend - настоящее описание. Оно вводит имя функции в самой внешней области видимости программы и сопоставляется с другими описаниями этого имени. Описание друга может располагаться или в закрытой, или в открытой части описания класса; где именно, значения не имеет.
|
|
Теперь можно написать функцию умножения, которая использует элементы векторов и матрицы непосредственно:
vector multiply(matrix& m, vector& 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 Уточнение*1 Имени Члена
Иногда полезно делать явное различие между именами членов класса и прочими именами. Для этого используется операция:: разрешения области видимости:
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) //... } //... }