Полноценной замены вложенным функциям в языке программирования Си нет, но есть несколько способов, как их можно симулировать. Чаще всего в вложенных функциях нам важно то, что код определяется там же, где передаётся в качестве функции обратного вызова. Иногда этот код бывает настолько мал, что выносить его в отдельную функцию в глобальной области видимости смысла нет. Например, для сортировки массива по возрастанию с помощью функции типа 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, а иногда остаются творческими проектами, которые как минимум интересны с образовательной точки зрения, так как именно здесь раскрывается вся гибкость языка и его тонкости.