Полноценной замены вложенным функциям в языке программирования Си нет, но есть несколько способов, как их можно симулировать. Чаще всего в вложенных функциях нам важно то, что код определяется там же, где передаётся в качестве функции обратного вызова. Иногда этот код бывает настолько мал, что выносить его в отдельную функцию в глобальной области видимости смысла нет. Например, для сортировки массива по возрастанию с помощью функции типа qsort чаще всего достаточно такого кода: return e1 - e2;. Вынести его в отдельную функцию в глобальной области видимости, а затем ещё придумывать корректное название — так себе удовольствие. Вложенные функции, добавленные в GCC как расширение, могли бы решить эту проблему, но такой код не будет работать на других компиляторах языка Си.
Способ, который описан в этой статье, основан на возврате из функции вместо обратного вызова и работает примерно так же, как функции-генераторы или цикл событий. Для начала взглянем на код и сравним его с вложенными функциями из GCC.
int array[] = {1, 4, 2, 5, 2, 2, 4, 6}; int n = 8; //GCC расширение int removePred(int e){ return e % 2; } n = remove_if(array, n, removePred); {func(remove_if, array, n){ ths.ret = ths.e % 2; } n = ths.res; }
Здесь мы использовали макрос func, который сокращает нашу запись, а вот во что он превратиться после обработки препроцессором.
{ struct remove_if ths = {0, array, n}; while(remove_if(&ths)){ ths.ret = ths.e % 2 == 1; } n = ths.res; }
Логика для выявления нечётных чисел расположена внутри вызывающей функции, а чтобы получить к ней доступ, наша функция remove_if возвращает управление, запоминая состояние внутри структуры ths, а возвращаемым значением сообщает, что её работа не окончена. Фигурные скобки используются для ограничения области видимости, чтобы предотвратить конфликт имен, но можно избавиться от них — тогда наш код примет вид.
func(remove_if, obj, array, n){ obj.ret = obj.e % 2 == 1; } n = obj.res;
Весь код будет выглядеть так
#include <stdio.h> struct remove_if{ int st; int *arr; int n; int e; int res; int ret; int i; int ofs; }; /* Суть алгоритма ниже в этом int remove_if(Callback callback, int* array, int n){ int ofs = 0, i = 0; for(; i < n; i++){ if(callback(array[i]){ ofs++; } else { arr[i - ofs] = arr[i]; } } return i - ofs; } */ int remove_if(struct remove_if *r){ if(r->st == 0){ r->st = 1; r->i = 0; r->ofs = 0; }else{ if(r->ret){ r->ofs++; }else{ r->arr[r->i - r->ofs] = r->arr[r->i]; } r->i++; } if(r->i == r->n){ r->res = r->i - r->ofs; return 0; } r->e = r->arr[r->i]; return 1; } #define func(fn, ...)\ struct fn ths = {0, __VA_ARGS__};\ while(fn(&ths)) void printArray(int *array, int n){ for(int i = 0; i < n; i++){ printf("%d, ", array[i]); } printf("\n"); } int main(){ int array[10] = [5, 2, 8, 6, 5, 2, 1, 1, 2, 3]; int n = 10; {func(remove_if, array, n){ ths.ret = ths.e % 2 == 1; } n = ths.res; } printArray(array, n); return 0; }
Функция для удаления элементов выглядит немного необычно, но производительность будет примерно такой же, как при использовании вложенных функций, так как операций происходит примерно одинаковое количество, вызовов и возвращений из функций происходит тоже равное количество. Возможно наш код лишится некоторых оптимизаций, но не критических, в случае необходимости мы можем даже использовать inline. Но у этого способа есть другой недостаток — у нас нет указателя на функцию, следовательно мы не можем запомнить её, чтобы вызвать позже. Также способ немного необычный, но в этой необычности есть своя логика и чтобы разобраться в ней, взглянем на следующий код.
typedef void (*func)(); int template(func f, int a1, int a2){ int b, c, d; //код до цикла while(/*условие выхода из цикла*/0){ //код до вызова обратной функции внутри цикла int ret = f(); //код после вызова обратной функции внутри цикла } //код после цикла return 5; //возвращаемое значение } struct template{ int st; //состояние первый вызов = 0, повторный вызов = 1 int a1, a2; //аргументы int a, b, c; //переменные int ret; //возвращаемое обратной функцией значение int res; //возвращаемое значение }; int template(struct template *t){ if(t->st == 0){ //инициализация //код до цикла }else{ //код после вызова обратной функции внутри цикла } if(/*условие выхода из цикла*/0){ //код после цикла t->res = 5;//возвращаемое значение return 0; } //код до вызова обратной функции внутри цикла return 1; }
Здесь сравнивается простой шаблон, по которому мы можем создать функцию. Комментарии показывают, какая часть соответствует которой. Все переменные мы храним в структуре, так как они нам нужный при повторном вызове. state указывает, это первый вызов или повторный, если простыми словами. Это простой шаблон с одним циклом и вызовом внутри цикла. Его можно расширить под нужды. В следующем коде собраны несколько алгоритмов: remove_if, map и сортировка пузырьком bsort.
#include <stdio.h> #include <stdlib.h> struct remove_if{ int st; int *arr; int n; int e; int res; int ret; int i; int ofs; }; int remove_if(struct remove_if *r){ if(r->st == 0){ r->st = 1; r->i = 0; r->ofs = 0; }else{ if(r->ret){ r->ofs++; }else{ r->arr[r->i - r->ofs] = r->arr[r->i]; } r->i++; } if(r->i == r->n){ r->res = r->i - r->ofs; return 0; } r->e = r->arr[r->i]; return 1; } struct map{ int st; int *arr; int n; int ret; int *res; int i, e; }; int map(struct map* m){ if(m->st == 0){ m->i = 0; m->st = 1; m->res = malloc(sizeof(int) * m->n); }else{ m->res[m->i] = m->ret; m->i++; } if(m->i == m->n){ return 0; } m->e = m->arr[m->i]; return 1; } struct bsort{ int st; int *arr; int n; int i, j; int e1, e2; int ret; }; int bsort(struct bsort *s){ if(s->st == 0){ s->st = 1; s->i = 1; s->j = 0; }else{ if(s->ret > 0){ int tmp = s->arr[s->j]; s->arr[s->j] = s->arr[s->j + 1]; s->arr[s->j + 1] = tmp; } s->j++; if(s->j >= s->n - s->i){ s->j = 0; s->i++; } } if(s->i >= s->n){ return 0; } s->e1 = s->arr[s->j]; s->e2 = s->arr[s->j + 1]; return 1; } #define func(fn, ...)\ struct fn ths = {0, __VA_ARGS__};\ while(fn(&ths)) void printArray(int *array, int n){ for(int i = 0; i < n; i++){ printf("%d, ", array[i]); } printf("\n"); } int main(){ int array[10] = {5, 2, 8, 6, 5, 2, 1, 1, 2, 3}; int n = 10; printArray(array, n); {func(remove_if, array, n){ ths.ret = ths.e % 2 == 1; } n = ths.res; } printArray(array, n); {func(bsort, array, n){ ths.ret = ths.e1 - ths.e2; }} printArray(array, n); int *arr2; {func(map, array, n){ ths.ret = ths.e * 2; } arr2 = ths.res; } printArray(arr2, n); free(arr2); return 0; }
Многие вещи в языке программирования Си создаются на уровне кода: сопрограммы, исключения и даже ООП. Такие конструкции почти всегда уступают встроенным средствам как по гибкости, так и по производительности. Иногда они используются в крупных проектах, как например GObject, а иногда остаются творческими проектами, которые как минимум интересны с образовательной точки зрения, так как именно здесь раскрывается вся гибкость языка и его тонкости.
ссылка на оригинал статьи https://habr.com/ru/articles/944600/
Добавить комментарий