Немного функционального программирования в Си
Да, я знаю, что "не приятно" в Си делать функции высших порядков (когда функции используют другие функции в качестве аргументов)... И на самом деле это частое заблуждение! Частый случай использования функций – навешивание колбеков – вызовов, которые должны случиться, когда произойдёт другое действие. Например, приход нового сетевого пакета. Да множество функций 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 *) );
Параметры:
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; }
Часто бывает нужно выполнить какое-то действие после другого (что очевидно :)). Что не очевидно – не всегда это можно сделать последовательными вызовами 2-х функций. Подробнее это мы рассмотрим на практике в следующих темах – "обратные вызовы" – частый инструмент API в Си библиотеках.
Чисто технически это не сильно отличается от функций высшего порядка.
Написать функции filter, reduce - аналоги функций python.