Лекция 13. Статические члены класса. Друзья класса

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

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

Объявление статических данных-членов внутри определения класса не является его определением. Определение должно быть дано где-нибудь ещё.

Статический член mem класса cl можно обозначать как cl::mem, т.е. независимо от объекта. На него можно также ссылаться при помощи операций доступа к членам класса . и ->. При обращении к статическому члену класса через операцию доступа к членам класса выражение слева от этой операции не вычисляется.

Функция-член класса также может быть статической. Такая функция не получает указатель this и может ссылаться на нестатические члены класса только посредством операций доступа к членам класса (. и ->).

Статическая функция-член класса не может быть виртуальной. Не могут существовать одновременно статическая и нестатическая функция-член класса с одинаковым именем и одинаковым набором параметров.

Статические члены глобального класса имеют внешнее связывание, т.е. могут быть доступны из других файлов программы. Локальные (т.е. объявленные внутри некоторой функции) классы не могут иметь статических членов.

Статические члены класса подчиняются обычным правилам доступа к членам класса.

Константные статические члены класса могут быть инициализированы внутри определения класса.
class BitSet { private: typedef _int64 elem; static const int cBits = sizeof(elem) * 8; static const int size = 256 / cBits; elem set[size]; ... };

Пример использования статических членов класса см. в примере в конце лекции.

2. Друзья класса

Обычное объявление функции-члена класса гарантирует три логически разные вещи:

  1. функция имеет право доступа к приватным членам класса;
  2. функция находится в области видимости класса;
  3. функция должна вызываться для объекта класса (имеется указатель this).

Объявив функцию статической, мы придаём ей только первые два свойства. Объявив функцию дружественной, мы наделяем её только первым свойством.

Друг класса – это функция, которая не является членом этого класса, но которой разрешается использовать его приватные и защищённые члены. Имя друга класса не лежит в области действия этого класса, и друг класса не вызывается при помощи операции доступа к члену класса.

Друг класса, как и его члены, является частью интерфейса класса. Дружба, как и другие права доступа, предоставляются классом, а не захватываются.

Спецификаторы доступа не затрагивают объявлений дружественных функций. «Дружба» не обладает ни наследуемостью, ни транзитивностью.

class X { private: int n; friend void friend_function(X* p, int i); public: void member_function(int i); }; // Спецификатор доступа private не работает
void friend_function(X* p, int i) { p->n = i; } // Функция не является членом класса, поэтому X:: не пишем. // Поскольку дружественная функция не является членом класса, она не получает указатель this // и ей необходимо явно передавать объект, с которым она будет работать.
void X::member_function(int i) { n = i; } // Имеется в виду this->n = i
void f() { X obj; friend_function(&obj, 10); obj.member_function(10); } // Функции вызываются по-разному, // но приводят к одному и тому же результату

Когда дружественной объявляется совместно используемая функция, другом становится лишь функция с заданными типами параметров. Это сделано, чтобы избежать «захвата» прав доступа путём объявления ещё одной совместно используемой функции с новым набором параметров.

Функция-член класса Х может быть дружественной классу Y.
class Y { friend void X::f(); ... };

Сразу все функции-члены класса Х могут быть объявлены дружественными классу Y:
class Y { friend class X; ... };

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

Во-вторых, дружественная функция допускает применение пользовательских преобразований к своему первому параметру, в то время как функции-члены класса – нет*. Это позволяет программисту выразить требование к первому параметру быть l-значением**, описав соответствующую функцию как член класса, и напротив, выразить отсутствие такого требования, описав такую функцию как дружественную.

class Complex { private: double r, m; public: Complex(double r = 0, double m = 0) : r(r), m(m) { } //operator double () { return r; }; Complex operator =(const Complex& c); friend Complex operator ++(Complex& c); friend Complex operator ++(Complex& c, int); friend Complex operator + (const Complex& c1, const Complex& c2);
Complex operator +=(const Complex& c); }; // Функцию operator += лучше реализовывать как функцию-член класса
Complex Complex::operator =(const Complex& c) { if (&c == this) return *this; r = c.r; m = c.m; return *this; } Complex operator ++(Complex& c) { ++c.r; return c; } Complex operator ++(Complex& c, int) { Complex x = c; c.r++; return x; } Complex operator +(const Complex& c1, const Complex& c2) { return Complex(c1.r + c2.r, c1.m + c2.m); } Complex Complex::operator +=(const Complex& c) { r += c.r; m += c.m; return *this; }
void main() { Complex a(0, 0), b(2, 2), c; double d = 2; c = ++a; c = a++; c = a + ++b; c = a + d; c = 3 + b; c += 4; 2 += c; } // Неявные вызовы преобразующего конструктора. // При наличии функции преобразования operator double() возникала бы неоднозначность. // Ошибка, даже при наличии преобразования из числа типа double к классу Complex, // т.к. 2 не является l-значением

3. Пример

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

В классе Circle существуют три статических члена – переменные, хранящие общее количество кружочков и размеры окна программы. Один экземпляр каждой из этих переменных разделяется всем объектами Circle. Также в классе Circle объявлены функции SetSize и GetCount, которые работают со статическими членами класса. Поскольку эти функции должны вызываться безотносительно некоторого объекта класса, и даже когда не существует ни одного объекта класса Circle, они должны быть объявлены как дружественные функции или как статические функции-члены класса. Если функции объявлены как статические функции-члены класса, при вызове они должны быть квалифицированы именем класса с помощью операции разрешения области действия.

Файл Circle.h

class Circle { private: static int count; // Общее количество кружочков static long int maxX, maxY; // Размеры экрана int x, y, sx, sy; // Центр кружочка и его скорость int radius; // Радиус int colour; // Цвет public: Circle(); ~Circle(); // Выбираем один из двух вариантов (но можно и сразу оба – функции принадлежат разным областям видимости) friend void SetSize(int x, int y); // Дружественные функции friend int GetCount(); static void SetSize(int x, int y); // Статические функции-члены класса static int GetCount(); void Paint(HDC hdc); void Move(); int Is_Inside(int px, int py); int GetColour(); void ChangeSpeed(); }; #define WM_END (WM_USER + 1)

Файл Circle.cpp

#include "StdAfx.h" #include "Circle.h" extern HWND hWnd; int Circle::count = 0; // Определение и инициализация статических членов класса Circle long int Circle::maxX = 800; // (в дополнение к объявлению внутри класса) long int Circle::maxY = 600; Circle::Circle() { ++count; // При создании кружочка увеличиваем общее количество на 1 x = rand() % maxX; y = rand() % maxY; radius = rand() % 7 + 6; colour = rand() % 16; sx = rand() % 15 - 7; sy = rand() % 15 - 7; } Circle::~Circle() { if (--count == 0) // При уничтожении – уменьшаем на 1 PostMessage(hWnd, WM_END, 0, 0); } void Circle::Paint(HDC hdc) // Рисуем { Ellipse(hdc, x - radius, y + radius, x + radius, y - radius); } void Circle::Move() // Перемещаем { x += sx; if (x < 0) x = 0; if (x > maxX) x = maxX; y += sy; if (y < 0) y = 0; if (y > maxY) y = maxY; } int Circle::Is_Inside(int px, int py) // Попали? { return (px - x) * (px - x) + (py - y) * (py - y) <= radius * radius; } inline int Circle::GetColour() { return colour; } void Circle::ChangeSpeed() { sx = rand() % 15 - 7; sy = rand() % 15 - 7; } // Функции SetSize и GetCount должны быть объявлены как дружественные функции или статические функции-члены класса, // т.к. они должны вызываться безотносительно некоторого объекта класса, и даже когда нет ни одного объекта класса void SetSize(int x, int y) // Дружественные функции { Circle::maxX = x; Circle::maxY = y; } int GetCount() { return Circle::count; } void Circle::SetSize(int x, int y) // Статические функции-члены класса { maxX = x; maxY = y; } int Circle::GetCount() { return count; }

Вы можете скачать полную версию этой программы