6.6. Статични членове на класа
Понякога е необходимо всички обекти на даден клас да имат достъп до определена променлива. Това може да бъде някакъв флаг за условие или брояч, свързан с класа, който се променя динамично от програмата - може един брояч да отразява колко обекти от един клас съществуват във всяка една точка на програмата. Понякога просто е по-ефективно да се зададе една променлива за всички обекти от даден клас вместо всеки обект да обработва свое собствено копие. Тази променлива може да бъде указател към процедура, която обработва грешки, отнасящи се до класа или указател към свободната памет на класа. Едно решение за тези случаи се предлага от статичните членове на класа.
Всеки статичен член данни работи като глобална променлива за своя клас. Статичните член данни, за разлика от глобалните променливи имат две допълнителни характеристики:
1. Може да бъде предизвикано скриване на информация. Статичните член данни може да бъдат дефиниран като непублични; глобалните променливи не могат.
2. Статичните членове не влизат в глобалното “пространство на имената” на програмата и поради това се отстранява възможността за случаен конфликт между имената.
За даден клас съществува само един представител на статичния член данни. Той е дефиниран в обхвата на класа; т.е. винаги, когато дефиницията на класа е достъпна. Статичният член данни е единичен обект, достъпен за всички обекти на класа си.
Един член данни може да бъде направен статичен като към декларацията му бъде добавена ключовата дума static. Статичните член данни се подчиняват на правилата за достъп public/ private/ protеcted.
Например, в дефинираният по-нататък клас CoOp costPerShare е декларирана като личен статичен член от тип double.
class CoOp
{
friend compareCost( CoOp&, CoOp* );
public: CoOp( int, char* );
inline double monthlyMaint();
void raiseCost( double incr )
{ cosrPerShare += incr ); }
double getCost() { return costPerShare; }
private:
static double costPerShare;
int shares;
char *owner;
};
С решението да бъде направена costPerShare static се целят две неща: да се запазва стойността й и да се ограничат възможностите за грешки. Всеки CoOp обект трябва да има достъп до costPerShare. Въпреки, че нейната текуща стойност е една и съща за всички обекти, тя се променя във времето. Следователно тя не може да бъде дефинирана като константа. Но понеже не ефикасно всеки обект от този клас да обработва копие на тази стойност ние я декларираме като static. След като costPerShare е static, с еднократното й изменение ние сме уверени, че всеки обект от класа има достъп до една и съща стойност. Ако всеки обект на класа имаше собствено копие, то /копието/ трябваше да бъде изменено, което би довело до неефективност и грешки. Всеки статичен член данни се инициализира вън от дефиницията на класа по същия начин, както и променливите нечленове. Единствената разлика е, че трябва да бъде използуван синтаксиса на оператора за обхват на класа. Например, ето как бихме могли да инициализираме costPerScope:
#include "CoOp.h"
double CoOp::costPerShare = 23.99;
Както променливите нечленове, така и статичните член данни могат да бъдат инициализирани в програмата еднократно. Това означава, че инициализирането на статичните член данни трябва да бъде поставяно във файл заедно с дефинициите на член функциите, които не са inline, а не в заглавния файл на класа. Нивото на достъп до един статичен член данни се отнася само до достъпа за четене и запис на този член, а не до инициализацията му. Ето защо costPerShare може да бъде инициализиран само във файлов обхват.
Достъпът до един статичен член на клас е същия, както и достъпът да статичен член. Например,
inline double CoOp::monthlyMaint()
{ return( costPerShare * shares ); }
// Pointer and Reference arguments in order to
// illustrate object and pointer access
compareCost( CoOp& unit1, CoOp* unit2 )
{ double maint1, maint2;
maint1 = unit1.costPerShare * unit1.shares;
maint2 = unit2->costPerShare * unit2->shares; // ... }
Както unit1.costPerShare, така и unit2->costPerShare се отнасят за статичния член CoOP::costPerShare.
Понеже съществува само едно копие на статичните член данни на класа, те са достъпни и директно. Това изглежда така:
if ( CoOp::costPerShare < 100.00 )
Операторът за обхват на класа (“CoOp::”) трябва да бъде използуван понеже статичните член данни имат обхвата на класа, а не глобален обхват за програмата.
Следващата дефиниция на приятелската функция compareCost е еквивалентна на представената току що:
compareCost( CoOp& until1, CoOp *unit2 );
{ double maint1, maint2;
maint1 = CoOp::costPerShare * unit1.shares;
maint2 = CoOp::costPerShare * unit2.shares;
// ... }
Двете член функции за достъп raiseCost() и getCost() работят само със статичния член на класа costPerShare. Проблемът тук е, че всяка от тях трябва да бъде викана за определен обект на класа. Въпросът е за кой обект? Обект тук е необходим само за да бъде спазен синтаксиса за обръщение, понеже съществува само един представител на costPerShare, разделен между всички обекти на класа. Това може да доведе до неясноти в кода на програмата.
Поради това член функции, които работят само със статични членове на класа могат също да бъдат декларирани като статични. Това може да бъде направено така:
class CoOp
{ friend compareCost( CoOp&, CoOp* );
public:
CoOp( int, char* );
inline double monthlyMaint();
static void raiseCost( double incr );
static double getCost() {return costPerShare;}
private: static double costPerShare;
int shares;
char *owner; };
void CoOp::raiseCost( double incr )
{ costPerShare += incr; }
Една статична член функция не съдържа указателя this; следователно произволно явно или неявно използуване на this ще предизвика грешка по време на компилация. Опитът за достъп до един нестатичен член на клас представлява неявно обръщение към указателя this. Например, monthlyMaint() не може да бъде декларирана като статична член функция, защото й е необходим достъп до член данните shares. Дефиницията на една статична член функция е същата както тази на една нестатична член функция.
Една статична член функция може да бъде извикана през обект на класа или през указател към обект на класа по същия начин, както може да бъде извикана и една нестатична член функция. С нея може да се работи и директно като се използува синтаксиса за членове на класа. Например,
compareCost( CoOp& unit1, CoOp * unit2 )
{ // equivalent calls of static member
getCost() double maint1, maint2;
if ( CoOp::getCost() == 0 ) return 0;
maint1 = unit1.getCost() * unit1.shares;
maint2 = unit2->getCost() * unit2->shares;
// ... }
Един статичен член на клас е достъпен и може да бъде викан директно дори ако няма декларирани никакви обекти на класа.
Уппражнение 6-11. Даден е класа Y, съдъжащ два статични члена данни и две статични член функции:
class X { public:
X( int i ) { val = i };
getVal() { return val; }
private: int val; };
class Y { public:
Y( int i );
static getXval();
static getCallsXval();
private:
static X Xval;
static callsXval; };
Инициализирайте Xval с 20 и callsXval с 0.
Упражнение 6-12. Реализирайте двете статични член фуннкции за достъп. CallsXval просто брои колко пъти е извикана getXval().