C++ відомий своєю заплутаністю. Ключових слів ніби й небагато, але кожне може бути використане у багатьх контекстах. В цьому дописі я спробую звести воєдино всі відомі мені способи використання слова static. Хто знає, може буде щось таке, чого ви не знали раніше
Всередині функції
Найпростіший випадок - коли слово static
використовується всередині функції. Це просто означає, що з того моменту, коли змінну було ініціалізовано, вона буде лишатися в пам’яті, доки програма не завершиться. Тобто, значення, записане в таку змінну, зберігатиметься до кінця програми. Наприклад, можна використати статичну змінну для того, щоб підрахувати, скільки разів було викликано функцію. Для цього оголосимо змінну static int count =0
, а потім count++
(все всередині функції). Оскільки змінна статична , команда “static int count =0
” виконається лише раз. Водночас, значення count буде збільшуватися з кожним викликом функції.
У такий спосіб можна використовувати слово static
для того, щоб запобігти повторній ініціалізації змінної всередині циклу. Наприклад, у наведеному коді змінна number_of_times
набуватиме значення 100. Фокус в тому, що static
забезпечує, що ініціалізація нулем відбудеться рівно один раз, і не більше.
for(int x=0; x<10; x++)
{
for(int y=0; y<10; y++)
{
static int number_of_times = 0;
number_of_times++;
}
}
Завдяки цьому фокус перед циклом відсутні оператоти ініціалізації. Це і добре (більше уваги до власне до циклу), і не дуже, бо сторонній людині буде важче зрозуміти, що робить цей код.
Статичні змінні можна використовувати також для того, щоб запам’ятати останнє повернене функцією значення. Наприклад, щоб знайти мінімальне з усіх значень, які видала функція.
Всередині визначення класу
Звичайні змінні, оголошені для класу, набувають окремого значення для кожного конкретного об’єкту класу. На відміну від них, статичні члени класу мають одне значення у всіх його об’єктах. Для використання статичних змінних навіть не потрібно мати об’єкт класу, бо значення змінної можна отримати, записавши .
До речі, за правилами гарного тону в програмуванні рекомендується використовувати саме таку форму (а не ). Це підкреслює статичний характер змінної.
Важливо знати про те, що ви не можете ініціалізувати статичну змінну всередині класу. Насправді, якщо клас оголошено у *.h-файлі, ви взагалі не зможете виконати ініціалізацію, тільки в *.cpp. Нижче буде показано, як це робити. синтаксис досить кривий: :: =
Окрім статичних змінних, існують ще статичні функції-члени класу. Це функції, які можна викликати, не маючи об’єкту класу. Викликаються вони так само, як і статичні змінні: з використанням назви класу та “::”. Статичні функції можуть працювати лише з статичними змінними (не дивно, адже їх викликають без прив’язки до певного об’єкту).
Розглянемо наступний приклад. Уявіть, що нам потрібно пронумерувати всі створені обїєкти класу. Це легко зробити, використовуючи в якості лічильника якусь статичну змінну. А для отримання власне номеру створимо статичну функцію. Код буде якимось таким:
class user
{
private:
int id;
static int next_id;
public:
static int next_user_id()
{
next_id++;
return next_id;
}
/* тут оголошення ще чогось */
user()
{
id = user::next_id++; //or, id = user.next_user_id();
}
};
int user::next_id = 0;//оце воно, ініціалізація статичної змінної
При оголошенні глобальної змінної чи функції
Слово static
поряд з функцією чи глобальною змінною означає, що видимість цієї змінної обмежена файлом, в якому та знаходиться. Розглянемо приклад, стане ясніше.
Ось хедер:
#ifndef _P1H_
#define _P1H_
void init_p1();
void show_p1();
#endif
Уявіть собі, що це був заголовочний файл до якоїсь бібліотеки. У поганому, але цілком реальному випадку може так статися, що ви матимете лише хедер і статично скомпільований файл, без решти вихідних кодів. І нехай в цій бібліотеці є якась внутрішня змінна, що зберігає налаштування конфігурації. Зовні ця змінна, звісно, експортуватися не повинна. Тобто, бібліотека може мати десь таку реалізацію:
#include
#include "p1.h"
struct {
char tt[35];
unsigned char v1;
} config;
void
common_func()
{
printf(”hi, this is p1.cppn”);
}
void
init_p1()
{
sprintf(config.tt,”%s”,”the p1 struct”);
config.v1=43;
}
void
show_p1()
{
printf(”some_p1_func::startedn”);
common_func();
printf(”p1:::config.tt=’%s’, config.v1=%un”,
config.tt, config.v1);
printf(”some_p1_func::finishedn”);
}
В даному випадку маємо структуру з типовою назвою config. Крім того, зверніть увагу, в реалізації бібліотеки наявна ще функція common_func(),
не оголошена в заголовочному файлі.
Ось як могла б виглядати основна програма:
#include
#include "p1.h"
struct {
unsigned char var1;
char text[45];
} config; /// !!! структура з тою ж самою назвою
void
common_func() ///!!! функція з тою ж самою назвою
{
printf(”hi, this from mainn”);
}
int main()
{
printf(”main::startedn”);
sprintf(config.text,”%s”,”main”);
config.var1=33;
init_p1();
show_p1();
common_func();
printf(”main:::config.text=’%s’, config.var1=%un”,
config.text, config.var1);
printf(”main::finishedn”);
return 0;
}
Припустимо, ми не бачили реалізації бібліотеки, а тому теж в себе в програмі оголосили структуру config. Геть іншого змісту, розміру і призначення. Ну, і функція common_func()
в нас теж є, подобається нам так.
Компілюємо, запускаємо, отримуємо результат:
main::started
some_p1_func::started
hi, this from main
p1:::config.tt='the p1 struct', config.v1=43
some_p1_func::finished
hi, this from main
main:::config.text='he p1 struct', config.var1=116
main::finished
Зовсім не те, що мало б бути. Лінкер усе переплутав. Не дивлячись на те, поодинці і програма, і бібліотека компілюються нормально, поєднавши їх, отримуємо непередбачувану поведінку.
Вихід з даної ситуації — при реалізації бібліотеки слід було позначити всі імена, що не експортуються, модифікатором static. Якось так:
static void
common_func()
{
....
В такому випадку символ common_func()
не буде експортуватися, і вся програма скомпілюється нормально. Втім, цей фокус порівняно рідко використовується. Справа в тому, що він не спрацьовує, коли бібліотека складається з кількох файлів. Тоді вживається ряд інших методів, котрим, можливо, я і присвячу наступний допис.
Delete