Что такое обратные вызовы?
Я знаю только что это функция которая передается как аргумент в другую функцию.
В принципе, можно и так сказать. Если быть более точным - это вызов функции переданной в качестве аргумента.
Почему они так называются?
Это игра слов. На английском callback - это не только обратный вызов, но и обратный звонок (по телефону). Данная абстракция позволяет вызываемому коду вызвать вызывающий код, подобно тому как собеседник может перезвонить Вам позднее, если Вы сообщите ему куда.
В чем их смысл и зачем нужны?
В принципе я уже ответил, они нужны для возможности вызываемому коду вызвать вызывающий код. Это позволяет строить высокоуровневые абстракции, вроде обобщенных функций или асинхронных функций.
Обобщенные функции позволяют не писать однотипный код, снижая тем самым вероятность ошибок, а с помощью обратных вызовов они могут принимать в себя фрагменты кода, которые могут меняться от использования к использованию. Для примера, абстрагируем цикл от 0 до n на C:
// абстракция цикла
void each(int n, void (*callback)(int, void*), void* closure_data) {
if(n <= 0) { return; }
for(int i = 0; i < n; i++) {
(*callback)(i, closure_data);
}
}
// колбэк - тело цикла, вариант 1
void cb_body1(int i, void*) {
printf("%d", i);
}
// колбэк - тело цикла, вариант 2
void cb_body2(int i, void* acc) {
int* normalized_acc = (int*)acc;
*normalized_acc += i;
}
int main() {
each(10, cb_body1, null); // напечатает строки 0, 1, ...9
int result = 0;
each(10, cb_body2, &result); // посчитает в result сумму чисел от 0 до 9
printf("%d", result);
return 0;
}
Асинхронные функции позволяют выносить долгие вычисления в фоновые потоки, тем самым не блокируя основной поток. А свой результат, когда он готов, они передают в обратный вызов.
Так же стоит заметить, что во многих высокоуровневых языках наряду с обратными вызовами используется механизм замыканий, который позволяет объявлять функции внутри других функций и захватывать окружающие переменные. Но нужно понимать, что это лишь компиляторный сахар, и на самом деле в функцию просто передаются указатели на захваченные переменные в качестве аргументов, подобно тому, как я сделал это руками в примере выше, с помощью аргумента closure_data в функции each. Обычно компилятор создает для этого анонимные структуры (C++, Rust) или анонимные классы (C#), которые хранят указатель на функцию и указатели на окружение. А в некоторых языках, например в js, замыкания возведены в абсолют, и каждая функция является замыканием.