При объявлении класса данные-члены и функции-члены класса можно объявлять статическими с помощью ключевого слова 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];
...
};
Пример использования статических членов класса см. в примере в конце лекции.
Обычное объявление функции-члена класса гарантирует три логически разные вещи:
Объявив функцию статической, мы придаём ей только первые два свойства. Объявив функцию дружественной, мы наделяем её только первым свойством.
Друг класса – это функция, которая не является членом этого класса, но которой разрешается использовать его приватные и защищённые члены. Имя друга класса не лежит в области действия этого класса, и друг класса не вызывается при помощи операции доступа к члену класса.
Друг класса, как и его члены, является частью интерфейса класса. Дружба, как и другие права доступа, предоставляются классом, а не захватываются.
Спецификаторы доступа не затрагивают объявлений дружественных функций. «Дружба» не обладает ни наследуемостью, ни транзитивностью.
class X
{ private:
int n;
friend void friend_function(X* p, int i); // Спецификатор доступа private не работает
public:
void member_function(int i);
};
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;
}
int 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; // При наличии функции преобразования operator double() возникала бы неоднозначность.
c += 4;
2 += c; // Ошибка, даже при наличии преобразования из числа типа double к классу Complex,
} // т.к. 2 не является l-значением
Использование дружественных функций для реализации таких операторов, как +, –, *, / представляется логичным, так как операнды этих операций равнозначны. Однако в некоторых случаях использование дружественных функций также может приводить к определённым проблемам.
class Vector
{ private:
int size;
double *v;
public:
Vector(int n = 0);
...
friend Vector operator + (const Vector& vector1, const Vector& vector2);
...
};
int main()
{ Vector v1(3), v2;
v2 = 3.5 + v1; // conversion from 'double' to 'int', possible loss of data
}
В данном примере возможность преобразования первого операнда и наличие конструктора с одним параметром, который может быть вызван неявно, приводит к тому, что вещественный литерал 3.5 преобразуется к целому типу, создаётся вектор из 3 элементов, который складывается с вектором, являющимся вторым операндом операции +, что приводит к неопределённому результату. Для того чтобы избежать этого, желательно объявить конструктор класса с модификатором explicit.
class Vector
{ private:
int size;
double *v;
public:
explicit Vector(int n = 0);
...
friend Vector operator + (const Vector& vector1, const Vector& vector2);
...
};
Приведённый в примере класс является частью программы, в которой на экране существуют несколько движущихся кружочков. При попадании мышкой в кружочек он уничтожается. Программа завершается после уничтожения всех кружочков.
В классе Circle объявлен статический член класса – переменная, хранящая общее количество кружочков. Один экземпляр этой переменной разделяется всем объектами класса Circle. Также в классе Circle должна быть функция Count, которые работала бы со статическим членом класса. Поскольку эта функция должна вызываться безотносительно некоторого объекта класса, и даже когда не существует ни одного объекта класса Circle, она должна быть объявлена как дружественная или как статическая. Если функция объявлена как статическая функция-член класса, при вызове она должна быть квалифицирована именем класса с помощью операции разрешения области видимости.
class Circle
{ private:
static int count; // Общее количество кружочков
int x, y, sx, sy; // Центр кружочка и его скорость
int radius; // Радиус
int colour; // Цвет
public:
Circle();
~Circle();
// Выбираем один из двух вариантов (но можно и сразу оба – функции принадлежат разным областям видимости)
friend int Count(); // Дружественная функция
static int Count(); // Статическая функция-член класса
void Paint(HDC hdc);
void Move();
int Is_Inside(int px, int py);
int Colour() { return colour; }
void ChangeSpeed();
};
#define WM_END (WM_USER + 1)
#include "StdAfx.h"
#include "Circle.h"
extern HWND hWnd;
int Circle::count = 0; // Определение и инициализация статического члена класса Circle
// (в дополнение к объявлению внутри класса)
Circle::Circle()
{ ++count; // При создании кружочка увеличиваем общее количество на 1
...
}
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() // Перемещаем
{
...
}
int Circle::Is_Inside(int px, int py) // Попали?
{ return (px - x) * (px - x) + (py - y) * (py - y) <= radius * radius; }
void Circle::ChangeSpeed()
{ ... }
int Count() // Дружественная функция
{ return Circle::count; }
int Circle::Count() // Статическая функция-член класса
{ return count; }