Клавиша поддерживает состояние функции между вызовами. У вас есть несколько вариантов:
Статическое (или глобальное) состояние. Означает, что последовательность вызовов функции не является реентерабельной, т. Е. Вы не можете иметь рекурсивный вызов самой функции и не можете иметь более одного вызывающего абонента, выполняющего различные последовательности вызовов.
Инициализация (и, возможно, распределение) состояния во время или перед первым вызовом и передача его функции при каждом последующем вызове.
Выполнение умных вещей с помощью setjmp / longjmp, стека или изменяемого кода (где-то есть статья о функциях каррирования в C, которая создает объект с кодом, необходимым для вызова функции каррирования; подобный метод может создать объект с состоянием функции и необходимым кодом для сохранения и восстановления его для каждого вызова). ( Редактировать Нашел - http://asg.unige.ch/site/papers/Dami91a.pdf)
Грэг цитирует интересную статью выше, в которой представлен способ использования статического состояния с синтаксисом, похожим на оператор yield
. Мне понравилось это академически, но, вероятно, я бы не стал его использовать из-за проблемы с повторным входом, и потому что я все еще удивлен, что печально известное Устройство Даффи даже компилируется ;-).
На практике большие программы на Си хотят лениво вычислять, например, сервер базы данных может захотеть удовлетворить запрос SELECT ... LIMIT 10
, заключив простой запрос SELECT
во что-то, что будет возвращать каждую строку до тех пор, пока не будет возвращено 10, вместо того, чтобы вычислять весь результат и затем отбрасывать большинство из них. Наиболее C-подобный метод для этого - явное создание объекта для состояния и вызов функции с ним для каждого вызова. Для вашего примера вы можете увидеть что-то вроде:
/* Definitions in a library somewhere. */
typedef int M2_STATE;
M2_STATE m2_new() { return 0; }
int m2_empty(M2_STATE s) { return s < INT_MAX; }
int m2_next(M2_STATE s) { int orig_s = s; s = s + 2; return orig_s; }
/* Caller. */
M2_STATE s;
s = m2_new();
while (!m2_empty(s))
{
int num = m2_next(s);
printf("%d\n", num);
}
Это кажется громоздким для кратных двух, но становится полезным шаблоном для более сложных генераторов. Вы можете усложнить состояние без необходимости загружать весь код, который использует ваш генератор, деталями. Еще лучшая практика - возвращать непрозрачный указатель в функции new
и (если GC не доступен) предоставлять функцию для очистки генератора.
Большим преимуществом выделения состояния для каждой новой последовательности вызовов являются такие вещи, как рекурсивные генераторы. Например, генератор, который возвращает все файлы в каталоге, вызывая себя в каждом подкаталоге.
char *walk_next(WALK_STATE *s)
{
if (s->subgenerator)
{
if (walk_is_empty(s->subgenerator))
{
walk_finish(s->subgenerator);
s->subgenerator = NULL;
}
else
return walk_next(s->subgenerator);
}
char *name = readdir(s->dir);
if (is_file(name))
return name;
else if (is_dir(name))
{
char subpath[MAX_PATH];
strcpy(subpath, s->path);
strcat(subpath, name);
s->subgenerator = walk_new(subpath);
return walk_next(s->subgenerator);
}
closedir(s->dir);
s->empty = 1;
return NULL;
}
(Вы должны будете извинить мое неправильное использование readdir и др. И мое предлог, что C имеет поддержку строки, защищенную от идиота.)