Глобальные переменные
Объявление глобальной переменной (в заголовочном файле): extern int global;
Определение глобальной переменной (в любой единице трансляции): int global = 10;
- Доступ к глобальной переменной можно получить из любой точки программы.
- Cтоит помнить, что если не проинициализировать глобальную переменную, то она будет проинициализированна нулём (при загрузке исполняемого файла в память, ОС заполняет нулями всю область .bss: второй абзац)
- Время жизни глобальной переменной ограничиваеться запуском программы и выходом из программы.
У глобальных переменных достаточно проблем и их использования стоит избегать.
Ключевое слово static
Static имеет пять различных применений.
Статические глобальные переменные
Статическая глобальная переменная - глобальная переменная, доступная только в пределах модуля.
static int global = 10;
Статические локальные переменные
Статическая локальная переменная - глобальная переменная, доступная только в пределах функции (в которой определена).
По сути мы увеличиваем время жизни локальной переменной, её время жизни: от первого вызова функции и до конца программы.
Такая переменная будет храниться не на стеке, а в той же области где храняться другие глобальные переменные (.bss, .data).
int next(int start = 0)
{
static int k = start;
return k++;
}
next(10); // k инициализируется числом 10
next(20); // ничего не произойдёт, k уже инициализирована
Статические функции
Статическая функция - функция, доступная только в пределах модуля.
Две разные функции, но с одинаковыми сигнатурами:
// 1.cpp
static void test()
{
cout << "A\n";
}
// 2.cpp
static void test()
{
cout << "B\n";
}
Подстановка адресов этих функций происходит локально - только в модулях где они определены, поэтому не происходит коллизии. Говорят, что статические глобальные переменные и стаические функции проходят внутренюю линковку. Обычные функции и глобальные переменные проходят внешнюю линковку.
Отсюда и взялось слово extern (при объявлении глобальных переменных), его можно использовать и для объявления функций, однако определение и объявление функции и так очень сильно синтаксически отличаются, поэтому его всегда опускают.
Статические поля класса
Статические поля класса - глобальные переменные, определённые внутри класса.
Объявление:
struct User
{
...
private:
static size_t instances_;
};
Определение:
size_t User::instances_ = 0;
Модификаторы доступа это единственное что отличает статические поля класса от глобальных переменных.
Статические поля класса храняться в той же области памяти, где храняться глобальные переменные.
Обычные поля класса храняться в экземпляре, а статические поля класса храняться в области глобальных переменных, грубо говоря принадлежат самому классу и изменяются одновременно у всех экземпляров. Поэтому для доступа к статическим полям класса не нужен экземпляр (если бы модификатор был public: User::instances_)
Это может быть полезно, например, если нам нужно посчитать количество экземпляров класса, мы можем завести статическое поле класса, которое будем инкрементировать в конструкторе и декрементировать в деструкторе, при этом оно должно быть приватным, что бы нельзя было его накрутить.
Статические методы
Статические методы - методы имеющие доступ к закрытым полям и методам. Обычные методы вроде бы тоже имеют доступ к static полям, однако для обычных методов обязательно наличие экземпляра!
struct User
{
...
static size_t count() { return instances_; }
private:
static size_t instances_;
};
Для вызова статического метода не нужен объект (у статического метода нет this):
cout << User::count();
Ключевое слово inline
inline говорит компилятору что функция достаточно проста и советует её встроить.
Встроить функцию означает заменить вызов функции на само тело функции.
Дешевле будет встроить следующую функцию чем вызвать (работа со стеком с кучей операций против одного умножения):
inline double square(double x) { return x * x; }
Однако inline накладывает некоторые ограничения:
- Не получиться разделить inline функцию на объявление и определение.
- inline функции можно и нужно определять в заголовочных файлах.
- Все методы, определённые внутри (именно определены и именно внутри, не снаружи!) класса, являются inline. Это объясняет почему класс описанный в заголовке можно подключать к множеству модулей.
- Все определения одной и той же inline функции (в разных модулях) должны быть идентичными, поскольку при линковке из всех скомпилированных inline функций компилятор выберет только одну.
- inline - это совет компилятору, а не указ!
А вообще современные компиляторы достаточно умные и могут сами решить встраивать функцию или нет даже если не указывать inline, так что inline по большей части используют для того, что бы сразу описать функцию в заголовочном файле и не разбивать на определение и объявление (таким образом можно делать библиотеки состоящие только из заголовочных файлов). Однако не все так красочно, мы получаем удобную конструкцию, однако платим временем компиляции, ведь заголовочный файл будет полностью вставляться в каждую единицу трансляции и каждую функцию из заголовочного файла приходиться компилировать по много раз!
Правило одного определения - One Definition Rule (ODR)
- В пределах любой единицы трансляции сущность не может иметь более одного определения.
- В пределах программы не-inline функции и глобальные переменные не могут меть больше одного определения.
- Классы и inline функции могут определяться в более чем одном модуле, однако их определения обязаны совпадать.