Cтатичні члени класів і статичні класні функції
Доповнимо клас Employee визначенням вільного ідентифікаційного коду службовця.
Самий простий спосіб — оголосити його статичною змінною:
static unsigned int currentId;
інкапсулювавши її клас, для чого її оголошення розміщується в його інтерфейсі.
Як ми пам'ятаємо, атрибути ініціалізуються особливо. Звичайні атрибути — при
створенні об'єкта. Статичний атрибут спільний для всіх об'єктів класу, а тому
ідентифікується без посилання на об'єкт і створюється компонувальником за його
визначенням, яке розміщується у файлі реалізації
unsigned int Employee :: currentId = 0;
Відповідних змін може зазнати конструктор, в якому тепер стане на параметр
менше. Залежно від обставин ним може виявитися додатково довизначений конструктор
class EmployeeP
{
friend void Payroll :: printCheck(EmployeeP *);
// також:
friend class Employer;
// крім них:
friend bool operator>
(const EmployeeP&, const EmployeeP&);
// Атрибути
private:
static unsigned int currentId;
char * name;
unsigned int id;
float salary;
// Методи
EmployeeP(char*, unsigned int, float);
EmployeeP(char*, float);
float getSalary();
public:
~EmployeeP();
void printEmployee();
char* getName();
};
Тепер займемося вдосконаленням класу Date , включивши в нього додатковий статичний
атрибут стандартної дати defaultDate .
static Date defaultDate;
З його ініціалізацією трошки складніше: статичний атрибут сам є об'єктом типу
Date . Для його ініціалізації доведеться застосувати конструктор (правильно
читайте наведений нижче текст: об'єкт defaultDate з області видимості (класу)
Date має тип Date і замовчувані значення параметрів конструктора)
<
p>Date Date :: defaultDate;
або, взявши за стандартну дату початку відрахування часу системним годинником
Date Date :: defaultDate (1, 1, 1970);
Маємо на увазі, що два написаних вище рядки є альтернативними визначеннями
об'єкту, а тому в програмі може бути присутнім лише одне з них. При необхідності
зміни стандарної дати слід визначити функцію її встановлення
void Date::setDefault (int d, int m, int y)
{
defaultDate = Date(d, m, y);
}
за значеннями параметрів або за системним годинником
void Date :: setDefault ()
{
struct tm * today =new tm;
time_t timer;
time( &timer );
today = gmtime(&timer);
defaultDate._day = today->tm_mday;
defaultDate._month = ++(today->tm_mon);
defaultDate._year = today->tm_year+=1900;
}
Функція одержує setDefault особливий статус. Вона не потребує для свого виклику
жодного об'єкту, оскільки працює не з атрибутами певного об'єкту, а статичним
атрибутом класу. Ось як зміниться інтерфейс
class Date
{
static Date defaultDate;
int _day, _month, _year;
public:
Date (int d, int m, int y);
Date (int d, int m);
Date (int d);
Date ();
Date (const char *cdate);
Date (const Date&);
void showDate();
static void setDefault(int,int,int);
static void setDefault();
static void showDefault();
}
Попутно ми визначимо також функцію виводу стандартної дати. Вона теж статична
void Date::showDefault()
{
cout<<defaultDate._day<<':'
<<defaultDate._month<<':'
<<defaultDate._year<<endl;
}
Наявність стандартної дати дозволяє визначити конструктор більш елегантно
Date::Date ( int d, int m, int y)
{
_day = d? d: defaultDate._day;
_month=m? m: defaultDate._month;
_year= y? y: defaultDate._year;
};
перенісши інші варіанти виклику конструктора в замовчування
class Date
{
static Date defaultDate;
int _day, _month, _year;
public:
Date (int d=0, int m=0, int y=0);
Date (const char *cdate);
Date (const Date&);
void showDate();
static void setDefault(int,int,int);
static void setDefault();
static void showDefault();
}
Ось як може використовуватися клас Date
int main()
{
Date::showDefault();
Date::setDefault();
Date::showDefault();
Date::setDefault(11, Date::feb, 2003);
Date::showDefault();
Date copyDefault = Date();
copyDefault.showDate();
return 0;
}
Звернемо увагу на те, що Date() і Date::defaultDate стали тепер синонімами,
але з різними областями видимості і правами доступу.
5.5 Константні об'єкти, константні функції, змінювані члени константних об'єктів (mutable)
Константні об'єкти, константні функції, змінювані члени константних об'єктів (mutable)
Як і будь-які інші об'єкти, екземпляри класів можуть бути сталими. Застосування
методів до сталих об'єктів має певні обмеження: методи не мають права змінювати
ці сталі об'єкти. Так з'являється поняття константної класної функції як функції,
що не має права змінювати об'єкт, який її активізує. Якщо ми спробуємо відкомпілювати
код
const Date myDate = Date();
myDate.showDate();
то одержимо повідомлення про помилку, викликану спробою застосування неконстантної
функції до сталого об'єкту. При виклику функції компілятор обмежується формальною
перевіркою наявності у неї кваліфікатора const . Компілятор не додасть цього
кваліфікатора сам, але разом з тим, якщо вже функція позначена константною,
то він не дозволить їй модифікацію свого об'єкта.
void Date::showDate() const
{
cout<<_day<<':'<<_month<<':'<<_year<<endl;
}
Константну функцію може активізувати і неконстантний об'єкт, але не навпаки.
Доповнимо інтерфейс іншими константними функціями
class Date
{
static Date defaultDate;
int _day, _month, _year;
public:
Date (int d=0, int m=0, int y=0);
Date (const char *cdate);
Date (const Date&);
// Константні методи
void showDate() const;
int day() const;
int month() const;
int year() const;
// Статичні методи
static void setDefault(int,int,int);
static void setDefault();
static void showDefault();
}
Звернемо увагу на те, що кваліфікатор const стає частиною сигнатури функції.
Ось невеликий приклад. В класі Point одночасно існують дві пари функцій. сигнатури
яких розрізняються лише кваліфікатором const . Константна функція координати
x() або y() повертає значення відповідної координати, в той час як неконстантна
функція повертає лівостороннє значення. Тому її можна вживати як зліва, так
і справа від знаку присвоєння.
class Point
{
// Атрибути
private:
double _x;
double _y;
// Методи
public:
double& x();
double x() const;
double& y();
double y() const;
Point (double a=0, double b=0);
Point operator+(Point);
bool operator==(Point);
double modulus ();
double phi ();
}
Відповідні реалізації
double& Point::x()
{
cout<<"non-const ";
return _x;
};
double Point::x() const
{
cout<<"const ";
return _x;
};
double& Point::y()
{
cout<<"non-const ";
return _y;
};
double Point::y() const
{
cout<<"const ";
return _y;
};
і виклики
int main()
{
Point a(1,2);
cout<<a.x()<<' '<<a.y(); //не сталі
a.x()=10; a.y()=20; //не сталі
const Point c(3,4);
cout<<c.x()<<' '<<c.y(); //сталі
return 0;
}
Розглянемо ще один простий приклад
class Screen
{
public:
Screen(int, int, char*);
Screen(const Screen&);
~Screen();
void home();
void move(int, int);
void move();
char get() const;
void set(char);
void show();
void clear();
private:
static const int maxHeight;
static const int maxWidth;
int _height;
int _width;
char *_wContent;
int _cursor;
char _filler;
};
Клас призначено для виведення на екран текстових вікон. Ось визначення констант
і реалізація його методів
const int Screen::maxHeight=24;
const int Screen::maxWidth=80;
Screen::Screen(int m, int n, char* s)
{
_filler = ’.’;
_width = m>maxWidth? maxWidth: m;
_height = n>maxHeight? maxHeight: n;
int len = strlen(s)>_height*_width?
height*_width: strlen(s);
_wContent = new char [_height*_width+1];
_wContent[_height*_width]='\0';
for(int k=0;k<len;k++) *(_wContent+k) = *s++;
for(;k<_height*_width;k++) *(_wContent+k) = ' ';
_cursor = 0;
};
Screen::Screen(const Screen& v)
{
_filler = v._filler;
_height = v._height;
_width = v._width;
_wContent = new char [_height*_width+1];
strcpy(_wContent,v._wContent);
_cursor=v._cursor;
}
Screen::~Screen()
{
delete []_wContent;
}
void Screen::home()
{
_cursor=0;
};
void Screen::move(int i, int j)
{
if ((i>=_height) || (j>=_width))
_cursor=0;
else
_cursor = _width*i + j;
};
void Screen::move()
{
if ((++_cursor)>=_width*_height) _cursor=0;
};
char Screen::get() const
{
return *(_wContent + _cursor);
};
void Screen::set(char a)
{
*(_wContent + _cursor)=a;
}
void Screen::clear()
{
for (int i=0; i<_height*_width; i++)
*(_wContent+i)= _filler;
_cursor=0;
}
void Screen::show()
{
home();
for(int i=0;i<_height;i++)
{
for (int j=0; j<_width; j++)
{
cout<<get();
move();
}
cout<<endl;
}
cout<<_cursor<<endl;
};
Тепер розглянемо визначення
const Screen w(4,3,"aaaabbbbcccc");
спробуємо застосувати до w метод show . З цього нічого не вийде, оскільки
метод show не константний. Стати константним йому заважають два методи home
і move , які змінюють значення курсору.
Введемо до поняття сталого об'єкту одне послаблення. Скажемо, що в ньому можна
виділити другорядні атрибути, значення яких не впливають на значення об'єкту
з точки зору його предметної семантики. Вважатимемо, наприклад, що до таких
відноситься позиція курсору. Курсор не впливає на вміст вікна, а служить лише
для посимвольної його обробки. Позначимо через mutable атрибути, які дозволяється
змінювати константним функціям. Тоді всі три згадані функції можна буде вважати
константними.
class Screen
{
public:
Screen(int, int, char*);
Screen(const Screen&);
~Screen();
void home() const;
void move(int, int) const;
void move() const;
char get() const;
void set(char);
void show() const;
void clear();
private:
static const int maxHeight;
static const int maxWidth;
int _height;
int _width;
char *_wContent;
mutable int _cursor;
char _filler;
};
Кваліфікаторами const необхідно також доповнити реалізації методів. Зробіть
це самостійно.