Модуль может хранить некоторое глобальное состояние - некоторый набор общих данных, над которыми функции модуля могут производить различные операции. Внешний код может вызывать эти функции и таким образом выполнять различные действия над состоянием модуля. Если состоние модуля не зависит от внешнего кода, то это состояние определяется в виде глобальных переменных и констант, к которым применяется ключевое слово static.
Таким образом, доступ к этому состоянию ограничивается текущим модулем. А функции, которые выполняют операции с этим состоянием, будет определять API модуля и объявляются в заголовочном файле. Благодаря этому внешний код посредством функций может выполнять действия над состоянием модуля. А модуль внутри функции может проконтролировать эти действия. Формально это выглядело бы следующим образом:
/* API (заголовочный файл) */ // условные операции с состоянием int addNext(int value); /* Реализация в модуле */ // условное состояние static int sum = 0; // реализация API int addNext(int value) { sum = sum + value; return sum; } /* Внешний код, который обращается к API модуля */ int result; result = addNext(10); result = addNext(20);
Таким образом, состояние модуля полностью отделено от внешнего кода, который производит с ним операции. Если для инициализации состояния нужны некоторые действия, например выделение памяти и т.д., а после работы с состоянием освобождение ресурсов, то все эти действия ложаться на модуль.
Рассмотрим небольшой пример. Сначала определим API. Для этого создадим следующий файл database.h:
// вывод одного объекта по индексу extern void printUser(int); // вывод всех объектов extern void printUsers();
Здесь определены объявления функций printUser и printUsers для вывода одного объекта по индексу и всех объектов из условной базы данных соответственно.
Далее определим реализацию этих функций в файле database.c:
#include <stdio.h> #include "database.h" #define DATA_MAX_SIZE 5 // условные данные static char* data[DATA_MAX_SIZE] = {"Tom", "Bob", "Sam", "Alice", "Kate"}; void printUser(int index) { if(index > -1 && index < DATA_MAX_SIZE) printf("%s\n",data[index]); else printf("User not found\n"); } void printUsers() { printf("Users List\n"); for(size_t i =0; i < DATA_MAX_SIZE; i++) { printf("%s\n",data[i]); } }
В данном файле/модуле определено общее, разделяемое состояние в виде массива указателей char*, то есть строк. Это состояние не зависит от внешнего кода и скрыто от него применением оператора static. То есть массив data доступен только в текущем модуле. А внешний код может обращаться к нему только опосредственно - через функции.
Для тестирования определим файл app.c со следующим кодом:
#include <stdio.h> #include "database.h" int main(void) { printUser(1); printUsers(); }
Для файла "app.c" состояние модуля database.c - массив data доступно только через функции API модуля, а напрямую недоступно.
Сокмпилируем и запустим программу:
c:\C>gcc -Wall -pedantic app.c database.c -o app & app Bob Users List Tom Bob Sam Alice Kate
Инкапсуляция состояния в модуле и опосредованный доступа к нему через функции - очень простой прием, который прекрасно работает, когда есть только один клиент, который обращается к модулю. Однако в многопоточных программах могут возникнуть проблемы, поскольку несколько потоков могут одновременно обращаться к состоянию, и в этом случае может потребоваться использование мьютексов или иных инструментов для разграничения доступа потоков к общему ресурсу.