Пока смерть не разлучит нас или всё о static в C++

Всем привет. На одном из код-ревью я столкнулся с мыслью, что многие, а чего скрывать и я сам, не то чтобы хорошо понимаем когда нужно использовать ключевое слова static. В данной статье я хотел бы поделиться своими знаниями и информацией по поводу ключевого слова static. Статья будет полезна как начинающим программистам, так и людям, работающим с языком С++. Для понимания статьи у вас должны быть знания о процессе сборки проектов и владение языком С/С++ на базовом уровне. Кстати, static используется не только в С++, но и в С. В этой статье я буду говорить о С++, но имейте в виду, что всё то, что не связано с объектами и классами, в основном применимо и к языку С.
Что такое static?
Static — это ключевое слово в C++, используемое для придания элементу особых характеристик. Для статических элементов выделение памяти происходит только один раз и существуют эти элементы до завершения программы. Хранятся все эти элементы не в heap и не на stack, а в специальных сегментах памяти, которые называются .data и .bss (зависит от того инициализированы статические данные или нет). На картинке ниже показан типичный макет программной памяти.

Где используется?
Ниже приведена схема, как и где используется static в программе.

А теперь я постараюсь детально описать все то, что изображено на схеме. Поехали!
Статические переменные внутри функции
Статические переменные при использовании внутри функции инициализируются только один раз, а затем они сохраняют свое значение. Эти статические переменные хранятся в статической области памяти (.data или .bss), а не в стеке, что позволяет хранить и использовать значение переменной на протяжении всей жизни программы. Давайте рассмотрим две почти одинаковые программы и их поведение. Отличие в них только в том, что одна использует статическую переменную, а вторая нет.
#include void counter() < static int count = 0; // строка 4 std::cout int main() < for (int i = 0; i < 10; ++i) < counter(); >return 0; >
Вывод программы:
0123456789
#include void counter() < int count = 0; // строка 4 std::cout int main() < for (int i = 0; i < 10; ++i) < counter(); >return 0; >
Вывод программы:
Если не использовать static в строке 4, выделение памяти и инициализация переменной count происходит при каждом вызове функции counter(), и уничтожается каждый раз, когда функция завершается. Но если мы сделаем переменную статической, после инициализации (при первом вызове функции counter()) область видимости count будет до конца функции main(), и переменная будет хранить свое значение между вызовами функции counter().
Статические объекты класса
Статический объект класса имеет такие же свойства как и обычная статическая переменная, описанная выше, т.е. хранится в .data или .bss сегменте памяти, создается на старте и уничтожается при завершении программы, и инициализируется только один раз. Инициализация объекта происходит, как и обычно — через конструктор класса. Рассмотрим пример со статическим объектом класса.
#include class Base < // строка 3 public: Base() < // строка 5 std::cout ~Base() < // строка 8 std::cout >; void foo() < static Base obj; // строка 14 >// строка 15 int main() < foo(); // строка 18 std::cout
Вывод программы:
Constructor
End of main()
Destructor
В строке 3 мы создаем класс Base с конструктором (строка 5) и деструктором (строка 8). При вызове конструктора либо деструктора мы выводим название метода класса в консоль. В строке 14 мы создаем статический объект obj класса Base. Создание этого статического объекта будет происходить только при первом вызове функции foo() в строке 18.
Из-за того, что объект статический, деструктор вызывается не при выходе из функции foo() в строке 15, а только при завершении программы, т.к. статический объект разрушается при завершении программы. Ниже приведен пример той же программы, за исключением того, что наш объект нестатический.
#include class Base < public: Base() < std::cout ~Base() < std::cout >; void foo() < Base obj; >// строка 15 int main()
Если мы уберем static при создании переменной в функции foo(), то разрушение объекта будет происходить в строке 15 при каждом вызове функции. В таком случае вывод программы будет вполне ожидаемый для локальной переменной с выделенной памятью на стеке:
Constructor
Destructor
End of main()
Статические члены класса
В сравнении с предыдущими вариантами использования, статические члены класса немного сложнее для понимания. Давайте разберемся, почему. Предположим, у нас есть следующая программа:
#include class A < // строка 3 public: A() < std::cout ~A() < std::cout >; class B < // строка 9 public: B() < std::cout ~B() < std::cout private: static A a; // строка 15 (объявление) >; int main() < B b; // строка 19 return 0; >
В нашем примере мы создали класс А (строка 3) и класс В (строка 9) со статическими членами класса (строка 15). Мы предполагаем, что при создании объекта b в строке 19 будет создан объект a в строке 15. Так бы и произошло, если бы мы использовали нестатические члены класса. Но вывод программы будет следующим:
Constructor B
Destructor B
Причиной такого поведения является то, что статические члены класса не инициализируются с помощью конструктора, поскольку они не зависят от инициализации объекта. Т.е. в строке 15 мы только объявляем объект, а не определяем его, так как определение должно происходить вне класса с помощью оператора разрешения области видимости (::). Давайте определим члены класса B.
#include class A < public: A() < std::cout ~A() < std::cout >; class B < public: B() < std::cout ~B() < std::cout private: static A a; // строка 15 (объявление) >; A B::a; // строка 18 (определение) int main()
Теперь, после того как мы определили наш статический член класса в строке 18, мы можем увидеть следующий результат программы:
Constructor A
Constructor B
Destructor B
Destructor A
Нужно помнить, что член класса будет один для всех экземпляров класса B, т.е. если мы создали три объекта класса B, то конструктор статического члена класса будет вызван только один раз. Вот пример того, о чем я говорю:
#include class A < public: A() < std::cout ~A() < std::cout >; class B < public: B() < std::cout ~B() < std::cout private: static A a; // объявление static int count; // объявление >; A B::a; // определение int B::count = 1; // определение int main()
Вывод программы:
Constructor A
Constructor B1
Constructor B2
Constructor B3
Destructor B3
Destructor B2
Destructor B1
Destructor A
Статические функции
Статические функции пришли в С++ из С. По умолчанию все функции в С глобальные и, если вы захотите создать две функции с одинаковым именем в двух разных .c(.cpp) файлах одного проекта, то получите ошибку о том, что данная функция уже определена (fatal error LNK1169: one or more multiply defined symbols found). Ниже приведен листинг трех файлов одной программы.
// extend_math.cpp int sum(int a, int b)
// math.cpp int sum(int a, int b)
// main.cpp int sum(int, int); // declaration int main()
Для того чтобы исправить данную проблему, одну из функций мы объявим статической. Например эту:
// extend_math.cpp static int sum(int a, int b)
В этом случае вы говорите компилятору, что доступ к статическим функциям ограничен файлом, в котором они объявлены. И он имеет доступ только к функции sum() из math.cpp файла. Таким образом, используя static для функции, мы можем ограничить область видимости этой функции, и данная функция не будет видна в других файлах, если, конечно, это не заголовочный файл (.h).
Как известно, мы не можем определить функцию в заголовочном файле не сделав ее inline или static, потому что при повторном включении этого заголовочного файла мы получим такую же ошибку, как и при использовании двух функций с одинаковым именем. При определении статической функции в заголовочном файле мы даем возможность каждому файлу (.cpp), который сделает #include нашего заголовочного файла, иметь свое собственное определение этой функции. Это решает проблему, но влечет за собой увеличение размера выполняемого файла, т.к. директива include просто копирует содержимое заголовочного файла в .cpp файл.
Статические функции-члены класса (методы)
Статическую функцию-член вы можете использовать без создания объекта класса. Доступ к статическим функциям осуществляется с использованием имени класса и оператора разрешения области видимости (::). При использовании статической функции-члена есть ограничения, такие как:
- Внутри функции обращаться можно только к статическим членам данных, другим статическим функциям-членам и любым другим функциям извне класса.
- Статические функции-члены имеют область видимости класса, в котором они находятся.
- Вы не имеете доступа к указателю this класса, потому что мы не создаем никакого объекта для вызова этой функции.
#include class A < public: A() < std::cout ~A() < std::cout static void foo() < // строка 8 std::cout >; int main() < A::foo(); // строка 14 return 0; >
В классе A в строке 8 у нас есть статическая функция-член foo(). В строке 14, мы вызываем функцию используя имя класса и оператор разрешения области видимости и получаем следующий результат программы:
static foo()
Из вывода видно, что никакого создания объекта нет и конструктор/деструктор не вызывается.
Если бы метод foo() был бы нестатическим, то компилятор выдал бы ошибку на выражение в строке 14, т.к. нужно создать объект для того, чтобы получить доступ к его нестатическим методам.
Заключение
В одной статье в интернете я нашел совет от автора – «Используйте static везде, где только можно». Я хотел бы написать, почему так делать не стоит, а стоит использовать только в случае необходимости.
- Статические переменные медленнее, чем нестатические переменные. Для того, чтобы обратиться к статической переменной, нам нужно сделать несколько дополнительных действий, таких как переход в другой сегмент памяти и проверка инициализации переменной. Чаще всего, быстрее выделить локальную переменную на стеке, чем делать дополнительные действия по использованию статической переменной.
- Если вы используете многопоточность, то здесь вы должны быть крайне осторожными, т.к. возможна ситуация, когда два и более потока захотят писать в одну статическую переменную. Если вы будете использовать нестатические переменные в функциях, то избежите подобного, т.к. для каждого потока будет создана собственная нестатическая переменная.
- Ключевое слово static является неотъемлемой частью порождающего шаблона проектирования Singleton, который гарантирует, что будет создан только один экземпляр этого класса. В реализации этого паттерна используется и статический объект, и статическая функция-член. На практике вы можете использовать Singleton для создания объекта трейсера, логгера или любого другого объекта, который должен быть один на всё ваше приложение.
- Иногда для того, чтобы функция отработала только один раз без хранения предыдущего состояния где-то в объекте, используют статические переменные. Пример вы можете посмотреть в разделе «Статические переменные внутри функции». Но это не очень хороший подход, и может привести к долгим часам поиска ошибки, если вы используете многопоточность.
- На практике, программисты C++ часто используют статические функции-члены как альтернативу обычным функциям, которые не требуют создания объекта для выполнения ее.
Статические функции в Си
По идеи область видимости funct должна ограничиваться файлом, но программа компилируется и выводит правильный результат, почему?
Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
Статические функции.
Как вытащить все необходимые мне поля из таблицы с использованием стат. ф-ии типа: SELECT field1.
Статические функции
Добрый день. Пример из Лафорте, ООП: // statfunc.cpp // Статические функции и ID объектов.
статические функции
Всем здрасьте. У меня тут впоросик на счёт static функций. я знаю как работает static с.
Статические функции
Начинаю изучать С++, написал код, но не хочет работать, подскажите, в чем может быть ошибка. class.
Шаровик затейник
696 / 445 / 78
Регистрация: 06.05.2010
Сообщений: 1,109
потому что вы инклюдите файл #include "global.h" в main.c, т.е. соединяете файлы грубо говоря
4866 / 3288 / 468
Регистрация: 10.12.2008
Сообщений: 10,570
#include
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562
Не поясните смысла оператора
static
в объявлении функции? Для локальных переменных и полей в классах понятно: ровно один экземпляр в программе. Для методов тоже понятно: вызов не через объект. А для функций что означает этот оператор?
ниначмуроФ
851 / 535 / 110
Регистрация: 12.10.2009
Сообщений: 1,913
Сообщение от taras atavin 
А для функций что означает этот оператор?
чтобы не привязывать функцию к какому нибудь объекту.
напрмер
без static:
classA obj; obj.show();
а если стоит static show() то записывается так
classA obj; classA::show();
Регистрация: 31.10.2010
Сообщений: 103
Сообщение от norge_goth 
По идеи область видимости funct должна ограничиваться файлом, но программа компилируется и выводит правильный результат, почему?
Область видимости static ограничена единицей трансляции, когда ты делаешь include, то файл .h присоединяется к .c и получается одна единица трансляции.
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562
Сообщение от PointsEqual 
чтобы не привязывать функцию к какому нибудь объекту.
Это если бы был метод, а функция и так не привязана ни к одному объекту.
Регистрация: 31.10.2010
Сообщений: 103

Сообщение было отмечено как решение
Решение
Сообщение от taras atavin 
в объявлении функции? Для локальных переменных и полей в классах понятно: ровно один экземпляр в программе. Для методов тоже понятно: вызов не через объект. А для функций что означает этот оператор?
static часто используется для того, чтобы "скрыть" метод в единице трансляции, т.е. в начале .c файла объявляются static функции и переменные и они будут доступны только в этом файле. Даже объявив где-то точно такой же прототип ты из другого .c файла не получишь доступ к этим функциям. А если не использовать static то по-умолчанию область видимости у функций и переменных глобальная, т.е. объявив в другом .c файле прототип функции можно будет использовать реализацию этой функции из другого файла, где она не объявлена static (либо объявлена extern).
4226 / 1795 / 211
Регистрация: 24.11.2009
Сообщений: 27,562
То есть
functions.hpp
static void f();
functions.cpp
static void f() .
1 2 3 4
#include "functions.hpp" . f(); .
нельзя, а
functions.hpp
void f();
functions.cpp
void f() .
1 2 3 4
#include "functions.hpp" . f(); .
и
functions.cpp
static void f() .
1 2 3 4
#include "functions.сpp" . f(); .
Регистрация: 31.10.2010
Сообщений: 103
Сообщение от taras atavin 
Да, именно так.
62 / 62 / 13
Регистрация: 27.01.2009
Сообщений: 279
Сообщение от accept 
#include
это было сделано умышленно(влом printf писать )
Добавлено через 18 минут
Сообщение от KpeHDeJIb 
Область видимости static ограничена единицей трансляции, когда ты делаешь include, то файл .h присоединяется к .c и получается одна единица трансляции.
тоесть это всеравно что я бы написал все в одном файле?
Добавлено через 18 минут
Сообщение от KpeHDeJIb 
static часто используется для того, чтобы "скрыть" метод в единице трансляции, т.е. в начале .c файла объявляются static функции и переменные и они будут доступны только в этом файле. Даже объявив где-то точно такой же прототип ты из другого .c файла не получишь доступ к этим функциям. А если не использовать static то по-умолчанию область видимости у функций и переменных глобальная, т.е. объявив в другом .c файле прототип функции можно будет использовать реализацию этой функции из другого файла, где она не объявлена static (либо объявлена extern).
Все понятно тут, но напрашивается другой вопрос где это нужно?
если у нас допустим 3 файла: main.c, global.h, global.c
В ф-ции main.c - находится наша основная ф-ция которая по определению может быть только одной,
допустим в global.h мы объявляем ф-цию статическую, а в global.c - ее определяем. Вопрос - так зачем нам вообще нужна эта ф-ция если мы не можем ее негде использовать?
Если я в чем-то неправ поправьте пожалуйста
Добавлено через 1 минуту
Сообщение от taras atavin 
functions.hpp
Расширение .hpp это формат для срр - хедеров?
Регистрация: 31.10.2010
Сообщений: 103
Сообщение от norge_goth 
Все понятно тут, но напрашивается другой вопрос где это нужно?
если у нас допустим 3 файла: main.c, global.h, global.c
В ф-ции main.c - находится наша основная ф-ция которая по определению может быть только одной,
допустим в global.h мы объявляем ф-цию статическую, а в global.c - ее определяем. Вопрос - так зачем нам вообще нужна эта ф-ция если мы не можем ее негде использовать?
Если я в чем-то неправ поправьте пожалуйста
Это используется довольно часто в языке Си, например если твоя программа состоит из множества компонентов, много .c файлов, то внутри каждого .c файла какие-то локальные функции (либо вспомогательные или закрытые для других компонентов функции) принято объявлять как static, таким образом ограничивая их область видимости только этим .c файлом. То же самое и с переменными.
Сообщение от norge_goth 
Расширение .hpp это формат для срр - хедеров?
Вроде того, т.е. это явным образом говорит что заголовочный файл языка C++, но этим не все пользуются, например в Boost используется .hpp, а во многих проектах все тот же .h, это скорее дело вкуса и поддержки общего мнения, строго стандарта на эту тему нету, хоть .inc называй
Добавлено через 2 минуты
Сообщение от norge_goth 
тоесть это всеравно что я бы написал все в одном файле?
Да. Поэтому в крупных проектах стараются ограничивать число включаемых файлов, потому что при большом количестве кода в несколько десятков тысяч строк компилятору нужно довольно много времени чтобы все это проперчить. Тем более если такие заголовочные файлы включаются везде и всюду без особой надобности. Поэтому хорошей практикой является как минимум включение только необходимых заголовочных файлов.
62 / 62 / 13
Регистрация: 27.01.2009
Сообщений: 279
Сообщение от KpeHDeJIb 
то внутри каждого .c файла какие-то локальные функции (либо вспомогательные или закрытые для других компонентов функции) принято объявлять как static, таким образом ограничивая их область видимости только этим .c файлом.
это я понял, но не понимаю одного:
в С++ было так - есть проект в котором один файл содержит ф-цию main, этот файл есть главным с него начинается работа программы и есть остальные файлы .сpp .h (в хэдерах декларации классов, в .сpp - их описание), и все объекты создаются и используются в главном файле у которого main. По аналогии с С++ вопрос состоит в том зачем нам локальные ф-ции если их не сможет использовать ф-ция main?
Или может в Си по другому идея, прошу прощения за глупые вопросы
Регистрация: 31.10.2010
Сообщений: 103
Сообщение от norge_goth 
это я понял, но не понимаю одного:
в С++ было так - есть проект в котором один файл содержит ф-цию main, этот файл есть главным с него начинается работа программы и есть остальные файлы .сpp .h (в хэдерах декларации классов, в .сpp - их описание), и все объекты создаются и используются в главном файле у которого main. По аналогии с С++ вопрос состоит в том зачем нам локальные ф-ции если их не сможет использовать ф-ция main?
Или может в Си по другому идея, прошу прощения за глупые вопросы
Есть функции которые используются внутри компонетов, они не вызываются напрямую пользователем, вот представь такую ситуацию:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// file foobar.h int DoBigJob(); // file foobar.c static int DoPart1() { . } static int DoPart2() { . } static int DoPart3() { . } int DoBigJob() { return DoPart1() + DoPart2() + DoPart3(); } // file main.c #include "foobar.h" int main() { return DoBigJob(); }
Как видишь работа компонента foobar из которого используется функция DoBigJob разбита на три части, каждая из которых выделена в отдельную процедуру, как в отдельную подпрограмму, для придания правильной логической структуры всему компоненту. Однако пользователю незачем знать ни о каких вспомогательных функциях, он хочет только запустить процедуру DoBigJob. А возможно пользователь захочет создать свою функцию с названием DoPart1, или такая функция будет в другом модуле, если ее не объявить как static, то на этапе линковки будет ругань, что одна и та же функция реализована в разных модулях трансляции.
Кстати обращаю твое внимание что функция "main" это не "главная функция" это точка входа в твою программу, при желании ее можно сделать в любом месте и назвать как угодно, хоть mYsUpErPuPeRmAiN, главное потом компилятору сказать где у тебя точка входа в программу
Функции объявленные как static внутри
В этом примере невозможно инициализировать объект List всеми начальными значениями вместе с объявлением, поэтому используется статический блок.
Код ниже демонстрирует особенность статических блоков — они выполняются раньше конструкторов и при создании нескольких объектов класса, статический блок выполняется только один раз.
public class Test < static < System.out.println("Вызов статического блока"); >Test() < System.out.println("Вызов конструктора"); >> class Main < public static void main(String[] args) < Test t1 = new Test(); Test t2 = new Test(); >>
На консоль будет выведен следующий результат

5.2 Причины использовать статические блоки
- Если для инициализации статических переменных требуется дополнительная логика, за исключением операции присваивания
- Если инициализация статических переменных подвержена ошибкам и требует обработки исключений
5.3 Ключевые моменты
- У класса может быть несколько статических блоков
- Статические поля и статические блоки выполняются в том же порядке, в котором они присутствуют в классе
- Из статического блока нельзя получить доступ к не статическим членам класса
- Статический блок не может пробросить дальше перехваченные исключения, но может их выбросить. При этом всегда будет выкидываться только java.lang.ExceptionInInitializerError
- Статические поля или переменные инициализируются после загрузки класса в память в том же порядке, в каком они описаны в классе
6. Статический вложенный класс (nested class)
Язык программирования Java позволяет создавать классы внутри другого класса. Такой класс называется вложенным (nested). Вложенный класс группирует элементы, которые будут использоваться в одном месте, сделав тем сам код более организованным и читабельным.
Вложенные классы бывают двух видов:
- вложенные классы, объявленные статическими, называются статическими вложенными классами (static nested classes)
- вложенные классы, объявленные без static, называются внутренними классами (inner classes)
Основное различие между этими понятиями состоит в том, что внутренние классы имеют доступ ко всем членам включающего их класса (включая приватные) верхнего уровня, тогда как статические вложенные классы имеют доступ только к статическим членам внешнего класса.
6.1 Пример статического класса
Наиболее широко используемый подход для создания объектов «одиночка» (singleton) — это статический вложенный класс, поскольку он не требует никакой синхронизации, его легко изучить и реализовать:
public class Singleton < private Singleton() <>private static class SingletonHolder < private static final Singleton INSTANCE = new Singleton(); >public static Singleton getInstance() < return SingletonHolder.instance; >>
6.2 Причины использовать статический внутренний класс
- Если какой-то класс используются только в одном другом классе, то их можно сгруппировать, поместив в один общий класс. Это усиливает инкапсуляцию
- Если вложенный класс не требует какого-либо доступа к членам экземпляра его класса, то лучше объявить его как статический, потому, что таким образом он не будет связан с внешним классом и, следовательно, будет более оптимальным, поскольку ему не потребуется память в куче или в стеке
6.3 Ключевые моменты
- Статические вложенные классы не имеют доступа к какому-либо члену экземпляра внешнего класса — он может получить к ним доступ только через ссылку на объект
- Статические вложенные классы могут получить доступ ко всем статическим членам внешнего класса, включая приватные
- Спецификация Java не позволяет объявлять класс верхнего уровня статическим. Только классы внутри других классов могут быть статическими
- Опять же, этот класс привязан к внешнему классу и если внешний наследуется другим классом, то этот не будет унаследован. При этом данный класс можно наследовать, как и он может наследоваться от любого другого класса и имплементировать интерфейс
- По сути статический вложенный класс ничем не отличается от любого другого внутреннего класса за исключением того, что его объект не содержит ссылку на создавший его объект внешнего класса
- Для использования статических методов/переменных/классов нам не нужно создавать объект данного класса
- Яркий пример вложенного статического класса — HashMap.Entry, который предоставляет структуру данных внутри HashMap. Стоит заметить, также как и любой другой внутренний класс, вложенные классы находятся в отдельном файле .class. Таким образом, если вы объявили пять вложенных классов в вашем главном классе, у вас будет 6 файлов с расширением .class
7. Константы
Говоря о ключевом слове static, нельзя не упомянуть о его применении в определении констант — переменных, которые никогда не изменяются.
В языке Java существует зарезервированное слово «const», но оно не используется, и Java не поддерживает константы на уровне языка. Выход из ситуации имеется: для определения константы необходимо добавить модификаторы «static final» к полю класса.
Константы — это статические финальные поля, содержимое которых неизменно. Это относится к примитивам, String, неизменяемым типам и неизменяемым коллекциям неизменяемых типов. Если состояние объекта может измениться, он не является константой.
Модификатор static делает переменную доступной без создания экземпляра класса, а final делает ее неизменяемой. При этом нужно помнить, что если мы сделаем переменную только static, то ее легко можно будет изменить, обратившись к ней через имя класса. Если переменная будет иметь только модификатор final, то при создании каждого экземпляра класса она может быть проинициализирована своим значением. Соответственно, используя совместно модификаторы static и final, переменная остается статической и может быть проинициализирована только один раз. В Java константой считается не та переменная, которую нельзя изменить в рамках одного объекта, а та, которую не могут изменить ни один экземпляр класса в котором она находится (такая переменная создается и инициализируется один раз для всех экземпляров, сколько бы их не было).
Static: Многоцелевое ключевое слово
Большинство ключевых слов C++ позволяют сделать одну вещь. Вы используете int для объявления целочисленной переменной, или тогда, когда функция возвращает целое значение, или принимает целое число в качестве аргумента. Вы используете оператор new для выделения памяти, а оператор delete — для ее освобождения. Вы можете использовать const для указания, что значение переменной не может быть изменено. По иронии судьбы, ключевое слово static , хотя и означает «неизменный», имеет несколько (и, видимо, не связанных между собой) способов использования. Ключевое слово static может быть использовано в трех основных контекстах:
- внутри функции;
- внутри определения класса;
- перед глобальной переменной внутри файла, составляющего многофайловую программу.
Использование static внутри функции является самым простым. Это просто означает, что после того, как переменная была инициализирована, она остается в памяти до конца программы. Вы можете думать об этом, как о переменной, которая хранит свое значение до полного завершения программы. Например, вы можете использовать статическую переменную для записи количества раз, когда функция была вызвана, просто добавив строки static int count = 0; и count++; в функцию. Так как count является статической переменной, строка static int count = 0; будет выполняться только один раз. Всякий раз, когда функция вызывается, count будет иметь последнее значение, данное ему.
Вы также можете использовать static таким образом, чтобы предотвратить переинициализацию переменной внутри цикла. Например, в следующем коде переменная number_of_times будет равна 100, несмотря на то что строка static int number_of_times = 0; находится внутри цикла, где она, по-видимому, должна исполнятся каждый раз, когда программа доходит до цикла. Хитрость заключается в том, что ключевое слово static препятствует повторной инициализации переменной. Одной из особенностей использования ключевого слова static является то, что оно автоматически устанавливает переменную в ноль для вас — но не полагайтесь на это (это делает ваши намерения неясными).
for(int ix=0; ix < 10; ix++) < for(int iy = 0; iy < 10; iy++) < static int number_of_times = 0; number_of_times++; >>
Вы можете использовать статические переменные для сохранения информации о последнем значении возвращенной функции, например, если вы хотите сохранить максимальное значение, рассчитанное по функции. Если вы делаете разбор строки, вы можете также хранить последний знак, возвращенный функцией, для того, чтобы иметь возможность вызвать ее с аргументом, означающим, что она должна вернуть последний знак.
Второе использование static — внутри определения класса. Хотя большинство переменных, объявленных внутри класса могут иметь разное значение в каждом экземпляре класса, статические поля класса будут иметь то же значение для всех экземпляров данного класса и даже не обязательно создавать экземпляр этого класса. Полезно представить себе, что статические переменные класса содержат информацию, необходимую для создания новых объектов (например в фабрике классов). Например, если вы хотите пронумеровать экземпляры класса, можно использовать статическую переменную для отслеживания последнего используемого номера. Важно отметить, что хорошим тоном при использовании статических переменных класса является использование class_name::х; , а не instance_of_class.x; . Это помогает напомнить программисту, что статические переменные не принадлежат к одному экземпляру класса, и что вам не обязательно создавать экземпляр этого класса. Как вы уже, наверное, заметили, для доступа к static можно использовать оператор области видимости, :: , когда вы обращаетесь к нему через имя класса.
Важно иметь в виду, при отладке или реализации программы с использованием static , что вы не можете инициализировать его внутри класса. В самом деле, если вы решите написать весь код класса в файл заголовка, вы даже не сможете инициализировать статическую переменную внутри файла заголовка; сделайте это в файле .cpp . Кроме того, вам необходимо инициализировать статические члены класса, или их не будет в области видимости. (Синтаксис немного странный: type class_name::static_variable = value .)
У вас также могут быть статические функции класса. Статические функции — это функции, которые не требуют экземпляра класса и вызываются так же, по аналогии со статическими переменным, с именем класса, а не с именем объекта. Например, a_class::static_function(); , а не an_instance.function(); . Статические функции могут работать только со статическими членами класса, так как они не относятся к конкретным экземплярам класса. Статические функции могут быть использованы для изменения статических переменных, отслеживать их значения — например, вы можете использовать статическую функцию, если вы решили использовать счетчик, чтобы дать каждому экземпляру класса уникальный идентификатор.
Например, вы можете использовать следующий код:
class user < private: int id; static int next_id; public: static int next_user_id() < next_id++; return next_id; >// остальные методы для класса user user() // конструктор класса < // или вызов метода, >>; int user::next_id = 0;
Обратите внимание, что вы должны включать тип статической переменной, когда вы устанавливаете его!
user a_user;
установит идентификатор на следующий идентификационный номер, не используемый любым другим объектом user . Обратите внимание, что это хороший стиль объявления идентификатора как константы.
Последнее использование static — глобальная переменная в файле кода. В этом случае использование static указывает, что исходный код в других файлах, которые являются частью проекта, не может получить доступ к переменной. Только код внутри того же файла может увидеть переменную (её область видимости ограничена файлом). Эта техника может быть использована для моделирования объектно-ориентированного кода, потому что она ограничивает видимость переменных и таким образом помогает избежать конфликта имен. Этот способ использования static является пережитком Cи.
К сожалению, для данной темы пока нет подходящих задач. Если у вас есть таковые на примете, отправте их по адресу: admin@cppstudio.com. Мы их опубликуем!