Библиотека классов

3. ДРУЗЬЯ КЛАССОВ

Пусть определены два класса: 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 {

friend class y;

// .

};

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

3.1. Вложенные классы

Описание класса может быть вложенным. Например:

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); }

// .

};

Полезное свойство вложенности - это сокращение числа глобальных имен, а недостаток его в том, что оно нарушает свободу использования вложенных типов.

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

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

{ /* . */ }

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

{ /* . */ }

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

{ /* . */ }

3.2. Статические члены

Класс - это тип, а не некоторое данное, и для каждого объекта класса создается своя копия членов, представляющих данные. Однако, наиболее удачная реализация некоторых типов требует, чтобы все объекты этого типа имели некоторые общие данные. Лучше, если эти данные можно описать как часть класса. Например, в операционных системах или при моделировании управления задачами часто нужен список задач:

class task {

// .

static task* chain;

// .

};

Описав член chain как статический, мы получаем гарантию, что он будет создан в единственном числе, т.е. не будет создаваться для каждого объекта task. Но он находится в области видимости класса task, и может быть доступен вне этой области, если только описан в общей части. В этом случае имя члена должно уточняться именем класса:

if (task::chain == 0) // какие-то операторы

В функции-члене его можно обозначать просто chain. Использование статических членов класса может заметно сократить потребность в глобальных переменных.

Описывая член как статический, мы ограничиваем его область видимости и делаем его независимым от отдельных объектов его класса.

Описание статического члена - это только описание, и где-то в программе должно быть единственное определение для описываемого объекта или функции, например, такое:

task* task::task_chain = 0;

void task::shedule(int p) { /* . */ }

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

Слово static одно из самых перегруженных служебных слов в С и С++. К статическому члену, представляющему данные, относятся оба основных его значения: "статически размещаемый", т.е. противоположный объектам, размещаемым в стеке или свободной памяти, и "статический" в смысле с ограниченной областью видимости, т.е. противоположный объектам, подлежащим внешнему связыванию. К функциям-членам относится только второе значение static.

4. КОНСТРУКТОРЫ И ДЕСТРУКТОРЫ

Если у класса есть конструктор, он вызывается всякий раз при создании объекта этого класса. Если у класса есть деструктор, он вызывается всякий раз, когда уничтожается объект этого класса. Объект может создаваться как:

[1] автоматический, который создается каждый раз, когда его описание встречается при выполнении программы, и уничтожается по выходе из блока, в котором он описан;


Страница: