Объектно-ориентированное программирование, страница 7

Напомним, что private-данные и методы какого-либо класса прямо доступны только внутри методов класса. Эта ограниченная доступность с одной стороны предлагает выгоды, которые предоставляет инкапсуляция и скрытие данных, с другой стороны она накладывает значительную ответственность, так как необходимо обеспечить каждый класс достаточным количеством методов для манипуляции данными во всех реально встречаемых ситуациях. C++ имеет способ обойти это жесткое ограничение и позволить избранным внешним классам, отдельным методам класса или обычным внешним функциям прямой доступ ко всем данным класса, в том числе и к private. Самое простое — это объявить обычную функцию (не являющуюся членом какого-либо класса) friend-функцией класса. Теперь она имеет доступ ко всем данным и методам класса и может модифицировать private-данные класса. Например,

class A

{

private:  int i;              // private-данное

friend void Set (A*, int);   // friend-функция

public:

void Set (int j) { i=j; }    // public-метод

void p() { printf("\n i=%d",i); }

};

void Set (A* p, int i)        // Тело friend-функции

{

p->i =i;

}

void main()

{

A obj;          // Объект obj класса A

Set (&obj, 10); // Доступ с помощью friend-функции

obj.p();        // Вывод поля

obj.Set (11);   // То же, но с помощью public-метода

obj.p();

}

В примере объявлена внешняя функция Set. Она не принадлежит никакому классу. Но она, будучи объявленной friend-функцией класса A, имеет прямой доступ к private-переменной класса А, так же, как и метод Set собственного класса. Указатель (или ссылку) на объект класса A следует передать в качестве параметра, так как friend-функция является посторонней функцией и не знает объекта obj класса A (в отличие от метода void A::Set(int); класса, которому неявно передается указатель this). При вызове внешней функции мы передаем ей &obj — адрес объекта. Этот адрес присваивается локальному указателю p, после чего выражение p->i позволяет обратиться к private-переменной объекта, на который ссылается указатель p. Поле объекта obj прямо доступно внутри friend-функции Set. Отметим, что эти же действия можно было осуществить, используя передачу ссылкой. При этом вызов функции должен быть: Set (obj, 10);, ее прототип — friend void Set (A&, int);, а сама функция:

void Set (A& a, int i) { a.i=i; }

Friend-функция может принадлежать какому-либо другому классу. Тогда ее можно назвать friend-методом. Чтобы сократить время доступа метода класса B к методу класса A, целесообразно объявить в классе А конкретный метод класса В как friend-метод класса А. Кроме того, в классе А можно объявить весь класс В как friend-класс класса А. Теперь все методы класса В являются friend-методами класса А, то есть внутренние данные класса А теперь прямо доступны в методах класса В. Объявление friend-конструкций может быть размещено в любой секции (private, protected, public) описания класса. Это не влияет на смысл объявления. Следующая схема иллюстрируют рассмотренные возможности.

class A

{

friend int Strange (char*);  // Friend-функция

friend class B;              // Friend-класс

friend char* C::Method();    // Friend-метод класса C

};

Свойство friend не транзитивно, то есть если класс B объявлен friend-классом в классе A, а класс С — в классе B, то это не означает, что класс C является friend-классом для класса A (друг моего друга — не мой друг). При создании производных классов (что будет рассмотрено ниже) следует помнить, что свойство friend не наследуется.

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

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

#define P printf("\n%d objects of %s",A::GetCount(),A::Name)

class A

{

private:

static int num;   // Число объектов класса A — static-переменная

public:

int i;                  // Доступная переменная

static char* name;      // Статическая переменная

static int GetCount()   // Статические функции класса

{

  return num;

}

static void Set(A& obj, int j) { obj.i=j; }

A()  { num++; }   // Конструктор увеличивает количество объектов

~A() { num--; P; } // Деструктор — уменьшает

};

int A::num = 0;      // Глобальная инициализация

char* A::name;

void main ()

{

                 // Доступ без помощи объекта класса

A::name = "Class A";

P;

A a;          P;  // Создание в стеке

A *pa = new A; P;  // Создание в heap

delete pa;        // Освобождение heap

A arr[5];     P;  // Массив объектов

char buf[] = "New class name"; // Доступ с помощью объекта класса

a.name = strcpy (new char[strlen(buf) + 1], buf);

A::Set(a,7);    printf("\n i=%d",a.i);

arr[3].Set(a,8);    printf("\n i=%d",a.i);

}

Объявление: static int num; внутри класса A объявляет (declares), но не определяет (defines) переменную. Определение static-переменных необходимо выполнять глобально. Оператор int A::num = 0; делает это вне функции main и явно инициализирует переменную num. Вторая переменная, будучи глобальной, инициализируется нулем неявно. Так как класс декларирован глобально, то невозможны локальное определение и инициализация переменных. Компилятор даст сообщение об ошибке, если перенести определение int A::num = 0; внутрь main. Так как статические переменные не привязаны ни к какому объекту класса, то объекты a, *pa и arr[] класса A пользуются общими экземплярами переменных name, num. Заметьте, что конструктор вызывается при создании всех объектов и увеличивает счетчик созданных объектов на единицу. Так как переменная num является скрытой (private), то пользователь не в состоянии ошибочными действиями исказить текущее реальное количество объектов класса. Но он может повлиять на значение другой (public) static-переменной name. Сделать это можно как в применении к объекту класса (a.name =...), так и без какой-либо привязки к объекту (A::name =...). В обоих случаях будет изменена одна и та же общая переменная name.