Бьерн Страуструп. Язык программирования С++



бет42/124
Дата16.07.2016
өлшемі3.27 Mb.
#204081
түріКнига
1   ...   38   39   40   41   42   43   44   45   ...   124

5.4.1 Друзья


Пусть определены два класса: vector (вектор) и matrix (матрица).

Каждый из них скрывает свое представление, но дает полный набор операций

для работы с объектами его типа. Допустим, надо определить функцию,

умножающую матрицу на вектор. Для простоты предположим, что

вектор имеет четыре элемента с индексами от 0 до 3, а в матрице

четыре вектора тоже с индексами от 0 до 3. Доступ к элементам

вектора обеспечивается функцией elem(), и аналогичная функция есть

для матрицы. Можно определить глобальную функцию multiply

(умножить) следующим образом:


vector multiply(const matrix& m, const vector& v);

{

vector r;



for (int i = 0; i<3; i++) { // r[i] = m[i] * v;

r.elem(i) = 0;

for (int j = 0; j<3; j++)

r.elem(i) +=m.elem(i,j) * v.elem(j);

}

return r;



}
Это вполне естественное решение, но оно может оказаться очень

неэффективным. При каждом вызове multiply() функция elem() будет

вызываться 4*(1+4*3) раз. Если в elem() проводится настоящий

контроль границ массива, то на такой контроль будет потрачено

значительно больше времени, чем на выполнение самой функции, и в

результате она окажется непригодной для пользователей. С другой

стороны, если elem() есть некий специальный вариант доступа без

контроля, то тем самым мы засоряем интерфейс с вектором и матрицей

особой функцией доступа, которая нужна только для обхода контроля.

Если можно было бы сделать multiply членом обоих классов

vector и matrix, мы могли бы обойтись без контроля индекса при

обращении к элементу матрицы, но в то же время не вводить специальной

функции elem(). Однако, функция не может быть членом двух классов.

Надо иметь в языке возможность предоставлять функции, не являющейся

членом, право доступа к частным членам класса. Функция - не член

класса, - имеющая доступ к его закрытой части, называется другом

этого класса. Функция может стать другом класса, если в его

описании она описана как friend (друг). Например:


class matrix;
class vector {

float v[4];

// ...

friend vector multiply(const matrix&, const vector&);



};
class matrix {

vector v[4];

// ...

friend vector multiply(const matrix&, const vector&);



};
Функция-друг не имеет никаких особенностей, за исключением права

доступа к закрытой части класса. В частности, в такой функции

нельзя использовать указатель this, если только она действительно

не является членом класса. Описание friend является настоящим

описанием. Оно вводит имя функции в область видимости класса,

в котором она была описана, и при этом происходят обычные проверки

на наличие других описаний такого же имени в этой области

видимости. Описание friend может находится как в общей, так и в

частной частях класса, это не имеет значения.

Теперь можно написать функцию multiply, используя элементы

вектора и матрицы непосредственно:
vector multiply(const matrix& m, const 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;



}
Отметим, что подобно функции-члену дружественная функция

явно описывается в описании класса, с которым дружит. Поэтому она

является неотъемлемой частью интерфейса класса наравне с

функцией-членом.

Функция-член одного класса может быть другом другого класса:
class x {

// ...


void f();

};
class y {

// ...

friend void x::f();



};
Вполне возможно, что все функции одного класса являются друзьями

другого класса. Для этого есть краткая форма записи:


class x {

friend class y;

// ...

};
В результате такого описания все функции-члены y становятся друзьями



класса x.

5.4.2 Уточнение имени члена


Иногда полезно делать явное различие между именами членов классов

и прочими именами. Для этого используется операция :: (разрешения

области видимости):


class X {

int m;


public:

int readm() const { return m; }

void setm(int m) { X::m = m; }

};
В функции X::setm() параметр m скрывает член m, поэтому к члену

можно обращаться, только используя уточненное имя X::m. Правый

операнд операции :: должен быть именем класса.

Начинающееся с :: имя должно быть глобальным именем. Это особенно

полезно при использовании таких распространенных имен как read, put,

open, которыми можно обозначать функции-члены, не теряя возможности

обозначать ими же функции, не являющиеся членами.

Например:
class my_file {

// ...


public:

int open(const char*, const char*);

};
int my_file::jpen(const char* name, const 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); }

// ...


};
Доступность вложенного класса ограничивается областью видимости

лексически объемлющего класса:


setmem m1(1,0); // ошибка: setmem не находится

// в глобальной области видимости


Если только описание вложенного класса не является совсем простым,

то лучше описывать этот класс отдельно, поскольку вложенные описания

могут стать очень запутанными:
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); }

// ...

};
Полезное свойство вложенности - это сокращение числа глобальных имен,



а недостаток его в том, что оно нарушает свободу использования

вложенных типов (см. $$12.3).

Имя класса-члена (вложенного класса) можно использовать вне

описания объемлющего его класса так же, как имя любого другого

члена:
class X {

struct M1 { int m; };

public:

struct M2 { int m; };


M1 f(M2);

};
void f()

{ M1 a; // ошибка: имя `M1' вне области видимости

M2 b; // ошибка: имя `M1' вне области видимости

X::M1 c; // ошибка: X::M1 частный член

X::M2 d; // нормально

}
Отметим, что контроль доступа происходит и для имен вложенных

классов.


В функции-члене область видимости класса начинается после

уточнения X:: и простирается до конца описания функции. Например:


M1 X::f(M2 a) // ошибка: имя `M1' вне области видимости

{ /* ... */ }


X::M1 X::f(M2 a) // нормально

{ /* ... */ }


X::M1 X::f(X::M2 a) // нормально, но третье уточнение X:: излишне

{ /* ... */ }





Достарыңызбен бөлісу:
1   ...   38   39   40   41   42   43   44   45   ...   124




©dereksiz.org 2024
әкімшілігінің қараңыз

    Басты бет