Да, я знаю, что "не приятно" в Си делать функции высших порядков (когда функции используют другие функции в качестве аргументов)... И на самом деле это частое заблуждение! Частый случай использования функций – навешивание колбеков – вызовов, которые должны случиться, когда произойдёт другое действие. Например, приход нового сетевого пакета. Да множество функций API сетевогои системного программирования просто эксплуатирует колбеки и тут и там! А это как раз частный случай функций высшего порядка...
Опять же, в Си нет разграничения данных и кода – код также данные, на которые мы также можем получить указатель и передать переменной.
Функции как аргументы функций
Пожалуй, самый простой пример, когда полезно использовать функцию как агрумент другой функции – сортировка. Например, когда мы в Python сортируем итерабельные объекты:
>>> lst = [122, 43, 1, 452, 87, 5]
>>> sorted(lst, key=lambda x: len(str(x)))
[1, 5, 43, 87, 122, 452]
– мы передаём параметром функцию, которая "оценивает" каждый элемент на "больше-меньше". Другой подход (более каноничный) мы можем увидеть в языке Perl:
my @lst = (122, 43, 1, 452, 87, 5);
@result = sort { length($a) <=> length($b) } @lst;
print join(" ", @result);
^D
1 5 43 87 122 452
– здесь мы передаём функции sort
функцию сравнения от двух сравниваемых
значений $a
и $b
, которая вернёт -1
- если $b > $a
, 0
- если $b == $a
,
1
- если $a > $b
.
На Си это будет выглядеть так:
#include <stdio.h>
#include <stdlib.h> // Подключаем для qsort
#include <string.h>
int cmpfunc (const void *a, const void *b) {
char a_str[10];
char b_str[10];
sprintf(a_str, "%d", *(int *)a);
sprintf(b_str, "%d", *(int *)b);
return strlen(a_str) - strlen(b_str);
}
int main () {
int values[] = { 122, 43, 1, 452, 87, 5 };
qsort(values, 6, sizeof(int), cmpfunc);
for(int n = 0; n < 6; n++) {
printf("%d ", values[n]);
}
return(0);
}
Прототип функции qsort
(man qsort
):
void qsort( // Функция ничего не вернёт
void *base, // Указатель на что-либо
size_t nitems, // Беззнаковое целое для "размеров"
size_t size,
int (*compar)(const void *, const void *)
);
Параметры:
- base − указатель на первый элемент массива, который мы сортируем.
- nitems − количество элементов массива.
- size − размер в байтах каждого элемента массива.
- compar − функция сравнения 2-х элементов.
Напишем свою функцию высшего порядка
map
– применить функцию к каждому элементу "коллекции".
#include <stdio.h>
#include <stdlib.h>
void map(
void *from,
void *to,
size_t count,
size_t size,
void (*modify)(void *, void *)
) {
/**
* Адресной арифметики на void-указателях нет.
* Поэтому преобразуем в байт-указатели.
*/
char *from_bytes = (char *)from;
char *to_bytes = (char *)to;
for (int i = 0; i < count; ++i) {
modify(from_bytes + i * size, to_bytes + i * size);
}
}
/**
* Можем описать любую функцию, реализующую прототип,
* которая всё также будет изменять каждый элемент
* в исходном массиве и кладёт в результирующий.
*/
void modify_int(void *from, void *to) {
int *a = (int *)from;
int *b = (int *)to;
*b = (*a) * (*a);
}
int main() {
int a[] = {1, 2, 3, 4, 5};
int b[5];
map(a, b, 5, sizeof(int), modify_int);
for (int i = 0; i < 5; ++i) {
printf("%d\n", b[i]);
}
return 0;
}
Callback
Часто бывает нужно выполнить какое-то действие после другого (что очевидно :)). Что не очевидно – не всегда это можно сделать последовательными вызовами 2-х функций. Подробнее это мы рассмотрим на практике в следующих темах – "обратные вызовы" – частый инструмент API в Си библиотеках.
Чисто технически это не сильно отличается от функций высшего порядка.
ДЗ
Написать функции filter, reduce - аналоги функций python.