【问题标题】:callback function vs ordinary function in C回调函数与 C 中的普通函数
【发布时间】:2015-05-22 18:37:10
【问题描述】:

我正在学习 C 中的回调函数,发现自己太难理解了 回调的概念。

据我所知,回调函数是使用 c 中的函数指针实现的,这意味着 我们可以通过使用指针来引用函数,就像我们使用指针一样 引用一个变量。

我这里有两个函数实现:

1.首先是使​​用回调函数

#include <stdio.h>

int add_two_number(int a, int b);
int call_func(int (*ptr_func)(int, int), int a, int b);

int main (int *argc, char *argv[])
{
     printf("%d", call_func(add_two_number, 5, 9));

     return 0;
}

int add_two_number(int a, int b)
{
     return a + b;
}

int call_func(int (*ptr_func)(int, int), int a, int b)
{
    return ptr_func(a, b);
}

2.二是使用普通函数调用:

#include <stdio.h>

int add_two_number(int a, int b);
int call_two_number(int a, int b);

int main (int *argc, char *argv[])
{
    printf("%d", call_two_number(5, 9));

    return 0;
}

int add_two_number(int a, int b)
{
    return a + b;
}

int call_two_number(int a, int b)
{
    return add_two_number(a, b);
}

这两个函数在两个数字和这两个数字之间进行简单的数学加法 功能也可以正常工作。

我的问题是这两者有什么区别?当我们使用回调而不是普通函数时?

【问题讨论】:

    标签: c function callback


    【解决方案1】:

    当您不知道要在调用的地方调用什么函数时,使用回调。例如,如果您还想减去两个数字怎么办:

    #include <stdio.h>
    
    int add_two_number(int a, int b);
    int sub_two_number(int a, int b);
    int call_func(int (*ptr_func)(int, int), int a, int b);
    
    int main (int *argc, char *argv[])
    {
         // Here is where we decide what the function should do for the first call
         printf("%d", call_func(add_two_number, 5, 9)); 
         // Here is where we decide what the function should do for the second call
         printf("%d", call_func(sub_two_number, 5, 9));
         return 0;
    }
    
    int add_two_number(int a, int b)
    {
         return a + b;
    }
    
    int sub_two_number(int a, int b)
    {
         return a - b;
    }
    
    int call_func(int (*ptr_func)(int, int), int a, int b)
    {
        return ptr_func(a, b); // Here is the place the call is made
    }
    

    回调对于解耦代码也很有用。考虑一个有几个按钮的应用程序。如果没有回调,您将不得不在按钮代码中使用一些逻辑来确定单击每个按钮时会发生什么。通过使用回调,按钮代码可以保持通用,但每个按钮仍然可以在单击时调用不同的操作。

    【讨论】:

      【解决方案2】:

      回调为您提供了一个抽象层,允许您将调用函数的行为与必须知道需要调用它的函数的名称分离。例如他们让你编写“通用”函数

      function do_something(pointer foo) {
      }
      

      假设这个“do_something”函数在您的问题中被称为很多,它需要“回调”到您的程序以获取更多数据。

      do_something(&callback_func_number_1);
      do_something(&callback_func_number_2);
      

      等等。

      一个函数,做它必须做的任何事情,使用回调来请求它需要的任何东西。

      相比之下,通过直接调用,您需要一个特定的do_something 来处理您拥有的每个变体:

      do_something_and_call_data_from_func1() { ... }
      do_something_and_call_data_from_func2() { ... }
      

      【讨论】:

        【解决方案3】:

        回调函数的一种用法是Inversion of Control。 假设您没有调用其函数的库,而是一个可以完成大部分工作的框架,您只需添加函数来“填补漏洞”。 您将回调函数传递给框架,以便它知道在哪里回调:)

        回调函数可以稍后交换,您可以从框架功能和您自己的功能中很好地抽象出来。

        【讨论】:

        • 感谢您指出我们的术语反转控制,我经常觉得有必要表达这个想法,找不到比模块化更好的表达方式,但这是正确的表达方式。
        【解决方案4】:

        我的问题是这两者有什么区别?

        前一个版本比后者更模块化,即: 假设您要实现一个简单的计算器。第一个解决方案可以轻松编写几个“运算符”,您将根据需要将它们传递给 call_func。

        示例

        #include <stdio.h>
        
        int add_two_number(int a, int b);
        int sub_two_number(int a, int b);
        int call_func(int (*ptr_func)(int, int), int a, int b);
        
        int main (int *argc, char *argv[])
        {
             if (argv[1][0] == '+')
                 printf("%d\n", call_func(add_two_number, 5, 9));
             else if (argv[1][1] == '-')
                 printf("%d\n", call_func(sub_two_number, 5, 9));
        
             return 0;
        }
        
        int add_two_number(int a, int b)
        {
             return a + b;
        }
        
        int sub_two_number(int a, int b)
        {
             return a - b;
        }
        
        int call_func(int (*ptr_func)(int, int), int a, int b)
        {
            return ptr_func(a, b);
        }
        

        在这种特殊情况下,使用函数指针代替普通函数调用并没有太多好处,但它通常可以帮助您编写更简洁的代码(而不是多个 if)。

        当我们使用回调而不是普通函数时?

        在大多数情况下,您会在需要模块化时使用它们。如果您发现自己编写了 4 个具有相同循环或/和条件的函数,并且只有一个函数彼此不同,那么您应该使用函数指针。 它们还与qsort 函数一起使用来定义您自己的排序规则。您还可以在处理外部库时找到函数指针,例如 GLFW,它使用它们(除其他外)在您按下键时定义您自己的函数:参见 example

        【讨论】:

          【解决方案5】:

          在编译时不知道要调用的函数时使用回调函数。一个示例是在某个库中完成的通用审计例程,但实际的清理例程是您程序中的一个函数。

          回调函数(或函数指针,在下一个例子中)的另一个用途是拥有一个函数指针数组,或一个包含函数指针的结构数组。只需在需要调用时将索引更改为数组,就可以调用不同的函数。这方面的一个例子是设备驱动程序。内核接口对大多数设备和大多数相同的函数调用使用相同的结构。使用函数指针,对文件的打开调用通过 vnode 系统路由,而对设备的打开调用通过设备驱动程序路由。这允许一种简单的方法来映射函数调用,而无需使用 switch 语句或大型 if-then-else 链。它还允许在加载和卸载内核模块时动态添加和删除函数调用(在设备驱动程序的情况下)。

          【讨论】:

            【解决方案6】:

            很多答案都源于常见的实现。我将尝试给出回调何时首次出现的观点。

            这两者有什么区别?
            只是某种程度的间接性。

            当我们使用回调而不是普通函数时?
            当事件驱动系统(如窗口中的小部件)变得流行时,就会出现回调。您将获得一个事件(触发器),并且回调(对函数的引用)将嵌入事件中断代码中,或者 对于早期的系统,其他一些代码块将决定调用哪个函数。在这两种情况下,“回调”一词都是用来表示这是响应事件的回调函数。

            如果回调是指针,则它们很有用,因为您可以方便地更改它们(更改为另一组函数),但您不必以这种方式使用它们。如果您的主程序是某种响应事件的开关,那么您可以将任一版本称为“回调”函数。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2012-01-08
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2011-09-22
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多