Как объяснить обратные вызовы на простом английском языке? Чем они отличаются от вызова одной функции из другой функции? - PullRequest
330 голосов
/ 07 марта 2012

Как объяснить обратные вызовы на простом английском языке? Чем они отличаются от вызова одной функции из другой, принимая некоторый контекст из вызывающей функции? Как объяснить их силу начинающему программисту?

Ответы [ 32 ]

6 голосов
/ 11 марта 2012

У вас есть код, который вы хотите запустить.Обычно, когда вы вызываете его, вы ждете его завершения, прежде чем продолжить (что может привести к тому, что ваше приложение станет серым / создаст время вращения курсора).

Альтернативный метод - запустить этот код параллельно и продолжить свою работу.Но что, если ваш исходный код должен делать разные вещи в зависимости от ответа вызываемого кода?Ну, в этом случае вы можете передать имя / местоположение кода, который вы хотите, чтобы он вызвал, когда это будет сделано.Это «обратный вызов».

Нормальный код: Запрос информации-> Информация о процессе-> Работа с результатами обработки-> Продолжение других действий.

С обратными вызовами: ЗапроситьИнформация-> Информация о процессе-> Продолжать делать другие вещи.А в более поздний момент -> дело с результатами обработки.

6 голосов
/ 14 марта 2012

Без обратного вызова и других специальных программных ресурсов (таких как многопоточность и т. Д.), программа представляет собой последовательность инструкций, которые выполняются последовательно одна за другой , и даже с неким «динамическим поведением» «определяется определенными условиями, все возможные сценарии должны быть предварительно запрограммированы .

Итак, если нам нужно обеспечить реальное динамическое поведение программы, мы можем использовать обратный вызов. С помощью функции обратного вызова вы можете указывать по параметрам, что программа вызывает другую программу, предоставляющую некоторые ранее определенные параметры, и может ожидать некоторые результаты ( это подпись контракта или операции ), поэтому эти результаты могут быть получены / обработаны сторонняя программа, которая ранее не была известна.

Эта методика является основой полиморфизма, применяемого к программам, функциям, объектам и всем остальным элементам кода, управляемым компьютерами.

Человеческий мир, используемый в качестве примера для обратного вызова, хорошо объясняется, когда вы выполняете какую-то работу, давайте предположим, что вы художник ( здесь вы - главная программа, которая рисует ) и иногда звоните своему клиенту попросите его утвердить результат вашей работы, и он решит, хороша ли картинка ( ваш клиент - сторонняя программа ).

В приведенном выше примере вы являетесь художником и «поручаете» другим работу по утверждению результата, картинка является параметром, и каждый новый клиент (вызываемая «функция») меняет результат вашей работы, решая что он хочет от картинки ( решение, принятое клиентами, является возвращенным результатом "функции обратного вызова" ).

Надеюсь, это объяснение может быть полезным.

6 голосов
/ 14 марта 2012

Давайте представим, что вы дали мне потенциально долгосрочное задание: запишите имена первых пяти уникальных людей, с которыми вы столкнетесь.Это может занять несколько дней, если я нахожусь в малонаселенном районе.Вам не очень интересно сидеть на руках, пока я бегаю, и вы говорите: «Когда у вас есть список, позвоните мне в мою камеру и прочитайте его мне. Вот номер».

Вы дали мне ссылку на обратный вызов - функцию, которую я должен выполнить для дальнейшей обработки.

В JavaScript это может выглядеть примерно так:

var lottoNumbers = [];
var callback = function(theNames) {
  for (var i=0; i<theNames.length; i++) {
    lottoNumbers.push(theNames[i].length);
  }
};

db.executeQuery("SELECT name " +
                "FROM tblEveryOneInTheWholeWorld " +
                "ORDER BY proximity DESC " +
                "LIMIT 5", callback);

while (lottoNumbers.length < 5) {
  playGolf();
}
playLotto(lottoNumbers);

Возможно, это можно улучшить многими способами.Например, вы могли бы предоставить второй обратный вызов: если это займет больше часа, позвоните по красному телефону и скажите человеку, который отвечает, что у вас истекло время ожидания.

6 голосов
/ 11 марта 2012

Представьте, что ваш друг покидает ваш дом, и вы говорите ей: «Позвони мне, когда вернешься домой, чтобы я знал, что ты благополучно прибыл»;это (буквально) обратный вызов .Вот что такое функция обратного вызова, независимо от языка.Вы хотите, чтобы какая-то процедура возвращала управление вам, когда она выполнила какое-то задание, поэтому вы даете ей функцию, которую можно использовать для обратного вызова вам.

В Python, например,

grabDBValue( (lambda x: passValueToGUIWindow(x) ))

grabDBValue можно записать, чтобы получить только значение из базы данных, а затем указать, что на самом деле делать со значением, чтобы оно принимало функцию.Вы не знаете, когда или если grabDBValue вернется, но если / когда это произойдет, вы знаете, что хотите, чтобы он сделал.Здесь я передаю анонимную функцию (или lambda ), которая отправляет значение в окно графического интерфейса.Я мог бы легко изменить поведение программы, выполнив это:

grabDBValue( (lambda x: passToLogger(x) ))

Обратные вызовы хорошо работают в языках, где функции имеют значения первого класса , как обычные целые числа, строки символов, логические значенияи т. д. В C вы можете «передавать» функцию, передавая указатель на нее, и вызывающая сторона может использовать это;в Java вызывающая сторона будет запрашивать статический класс определенного типа с определенным именем метода, поскольку за пределами классов нет никаких функций («методов»);а в большинстве других динамических языков вы можете просто передать функцию с простым синтаксисом.

Protip:

В языках с лексическим ограничением (например, Scheme или Perl) вы можете использоватьхитрость, подобная этой:

my $var = 2;
my $val = someCallerBackFunction(sub callback { return $var * 3; });
# Perlistas note: I know the sub doesn't need a name, this is for illustration

$val в этом случае будет 6, потому что обратный вызов имеет доступ к переменным, объявленным в среде lexical , где он был определен.Лексическая область действия и анонимные обратные вызовы - мощная комбинация, требующая дальнейшего изучения для начинающего программиста.

6 голосов
/ 11 января 2014

Обратные вызовы проще всего описать в терминах телефонной системы. Функциональный вызов аналогичен вызову кого-либо по телефону, задает ей вопрос, получает ответ и вешает трубку; добавление обратного вызова изменяет аналогию, так что после того, как вы зададите ей вопрос, вы также дадите ей свое имя и номер, чтобы она могла перезвонить вам с ответом. - Пол Якубик , "Реализации обратного вызова в C ++"

5 голосов
/ 07 марта 2012

Обычно мы отправляем переменные в функции.Предположим, у вас есть задача, где переменная должна быть обработана перед передачей в качестве аргумента - вы можете использовать обратный вызов.

function1(var1, var2) - это обычный способ.

Что, если я хочу var2обрабатываться и затем отправляться в качестве аргумента?function1(var1, function2(var2))

Это один тип обратного вызова - где function2 выполняет некоторый код и возвращает переменную обратно в исходную функцию.

5 голосов
/ 14 марта 2012

Обратный вызов - это функция, которая будет вызываться второй функцией. Эта вторая функция не знает заранее, какую функцию она будет вызывать. Таким образом, идентичность функции обратного вызова сохраняется где-то или передается второй функции в качестве параметра. Эта «идентичность», в зависимости от языка программирования, может быть адресом обратного вызова или каким-либо другим видом указателя, или это может быть имя функции. Принцип один и тот же, мы храним или передаем некоторую информацию, которая однозначно идентифицирует функцию.

Когда придет время, вторая функция может вызвать обратный вызов, предоставив параметры в зависимости от обстоятельств на данный момент. Он может даже выбрать обратный вызов из набора возможных обратных вызовов. Язык программирования должен обеспечивать некоторый синтаксис, позволяющий второй функции вызывать обратный вызов, зная свою «идентичность».

Этот механизм имеет множество возможных применений. С помощью обратных вызовов конструктор функции может позволить ей настраивать ее, вызывая любые предоставляемые обратные вызовы. Например, функция сортировки может принимать обратный вызов в качестве параметра, и этот обратный вызов может быть функцией для сравнения двух элементов, чтобы определить, какой из них стоит первым.

Кстати, в зависимости от языка программирования слово «функция» в приведенном выше обсуждении может быть заменено на «блок», «закрытие», «лямбда» и т. Д.

4 голосов
/ 11 марта 2012

Метафорическое объяснение:

У меня есть посылка, которую я хочу доставить другу, и я также хочу знать, когда мой друг получит ее.

Поэтому я беру посылку на почтуофис и попросить их доставить его.Если я хочу узнать, когда мой друг получит посылку, у меня есть два варианта:

(а) Я могу подождать в почтовом отделении, пока она не будет доставлена.

(б) Я получуэлектронное письмо при его доставке.

Опция (b) аналогична обратному вызову.

4 голосов
/ 13 марта 2012

Для обучения обратных вызовов, вы должны сначала научить указатель. Как только студенты поймут идею указателя на переменную, идея обратных вызовов станет легче. Предполагая, что вы используете C / C ++, эти шаги можно выполнить.

  • Сначала покажите своим студентам, как использовать и манипулировать переменными, используя указатели вместе с обычными идентификаторами переменных.
  • Тогда научите их, что есть вещи, которые можно сделать только с помощью указателей (например, передача переменной по ссылке).
  • Затем скажите им, как исполняемый код или функции похожи на некоторые другие данные (или переменные) в памяти. Итак, функции также имеют адреса или указатели.
  • Затем покажите им, как функции можно вызывать с помощью указателей функций, и скажите, что они называются обратными вызовами.
  • Теперь вопрос в том, почему все эти хлопоты вызывают некоторые функции? В чем выгода? Как и указатели данных, указатель на функцию, называемый обратными вызовами, имеет некоторые преимущества по сравнению с использованием обычных идентификаторов.
  • Во-первых, идентификаторы функций или имена функций не могут использоваться в качестве обычных данных. Я имею в виду, вы не можете создать структуру данных с функциями (например, массив или связанный список функций). Но с помощью обратных вызовов вы можете создать массив, связанный список или использовать их с другими данными, как в словаре пар ключ-значение или деревьев, или для любых других целей. Это мощное преимущество. И другие преимущества на самом деле являются детищем этого.
  • Наиболее распространенное использование обратных вызовов наблюдается в программировании драйвера событий. Где одна или несколько функций выполняются на основе некоторого входящего сигнала. С помощью обратных вызовов можно поддерживать словарь для отображения сигналов с обратными вызовами. Тогда разрешение входного сигнала и выполнение соответствующего кода станут намного проще.
  • Второе использование обратных вызовов, приходящих мне на ум, - это функции более высокого порядка. Функции, которые принимают другие функции в качестве входных аргументов. И для отправки функций в качестве аргументов нам нужны обратные вызовы. Примером может служить функция, которая принимает массив и обратный вызов. Затем он выполняет обратный вызов для каждого элемента массива и возвращает результаты в другом массиве. Если мы передадим функцию двойного обратного вызова, мы получим массив с двойным значением. Если мы передадим квадратный обратный вызов, мы получим квадраты. Для квадратных корней просто отправьте соответствующий обратный вызов. Это невозможно сделать с помощью обычных функций.

Там может быть еще много вещей. Вовлеките студентов, и они обнаружат. Надеюсь, это поможет.

3 голосов
/ 26 марта 2012

Что такое функция обратного вызова?

Простой ответ на этот первый вопрос заключается в том, что функция обратного вызова - это функция, которая вызывается через указатель на функцию. Если вы передаете указатель (адрес) функции в качестве аргумента другому, когда этот указатель используется для вызова функции, на которую он указывает, говорят, что обратный вызов сделан.

Функция обратного вызова трудно отследить, но иногда она очень полезна. Особенно, когда вы проектируете библиотеки. Функция обратного вызова аналогична просьбе вашего пользователя дать вам имя функции, и вы вызовете эту функцию при определенных условиях.

Например, вы пишете таймер обратного вызова. Это позволяет вам указать продолжительность и функцию для вызова, и функция будет соответственно вызываться. «Запускать myfunction () каждые 10 секунд 5 раз»

Или вы можете создать каталог функций, передав список имен функций и попросив библиотеку перезвонить соответственно. «Обратный вызов success () в случае успеха, обратный вызов fail () в случае ошибки».

Давайте рассмотрим пример простого указателя на функцию

void cbfunc()
{
     printf("called");
}

 int main ()
 {
                   /* function pointer */ 
      void (*callback)(void); 
                   /* point to your callback function */ 
      callback=(void *)cbfunc; 
                   /* perform callback */
      callback();
      return 0; 
}

Как передать аргумент в функцию обратного вызова?

Заметил, что указатель функции для реализации обратного вызова принимает значение void *, что указывает на то, что он может принимать переменные любого типа, включая структуру. Поэтому вы можете передать несколько аргументов по структуре.

typedef struct myst
{
     int a;
     char b[10];
}myst;

void cbfunc(myst *mt) 
{
     fprintf(stdout,"called %d %s.",mt->a,mt->b); 
}

int main() 
{
       /* func pointer */
    void (*callback)(void *);       //param
     myst m;
     m.a=10;
     strcpy(m.b,"123");       
     callback = (void*)cbfunc;    /* point to callback function */
     callback(&m);                /* perform callback and pass in the param */
     return 0;   
}
...