【问题标题】:Call back programming w.r.t 'c' only, why we should use?仅回调编程 w.r.t 'c',我们为什么要使用?
【发布时间】:2015-04-19 11:14:10
【问题描述】:

重点是 w.r.t c,因为我更喜欢 C。

我不期待一个例子说明它是如何工作的......我期待的是我们为什么要使用回调函数或有人说它作为函数指针。

我也关注了很多博客和堆栈溢出,但对这些答案都不满意。

假设(我在这里建议一种场景,比如排序)我们应该使用回调的东西,其中方法/函数将花费更多时间来处理。

假设一个进程只有一个线程,并且程序正在进行排序,这将花费大量时间(假设 > 1 分钟)。根据这里的大量博主,我们应该使用函数指针。但它有什么用呢?

如果我们只有一个程序计数器,我们将获得一些时间来从 CPU 处理这个进程,那么它会有什么用处?

如果您认为还有其他示例可以解释函数指针的概念,请提供示例。

我看到有人建议,如果你将使用函数指针,那么你可以稍后收集结果,但这听起来真的很尴尬!即使可能,这又如何?从那里返回后如何从函数中收集一些东西?该功能会被破坏吧!!!

实时人们将它用于事件的任何变化,以便他们可以获得通知......(只需添加一个点)

我见过一些优秀的程序员使用这个函数指针,我很想知道我为什么要使用这个函数指针,当然我在这里缺少一些东西......

请回复,谢谢。

【问题讨论】:

  • stackoverflow.com/search?tab=votes&q=%5bc%5d%20callback - 已经有几十个解释和例子了。
  • 底线是当你在函数的参数列表中传递一个函数指针时,你有权力以许多方式改变该函数的行为,这是不可能的传递简单的变量/或指针。一个基本的例子是一个calc 函数,它接受一个函数指针和两个数字。如果没有传递一个不同的函数名作为参数,您可以拥有addsubtractmultiply 等。而无需更改任何其他内容。它为您的编码带来了灵活性,并极大地扩展了您可以使用的工具,以便以更有效的新方式解决问题。
  • 至于“为什么要使用它?”,事实是,在您掌握了 C 语言的所有常规过程方面之前,您可能不会有意使用函数指针。但是当你达到这一点并看到它们如何补充和扩展 C 中的正常编程范式时,你将开始更多地使用函数指针甚至函数指针数组来解决问题,而这些方法甚至是你的几个月前的工具箱。
  • 我要说的是,int iterate_lines(FILE *fptr, char *buffer, size_t bufsize, int (*action)(const char *, size_t)); 之类的东西有时是无价的。在读取到bufsize 字节的行并将字节存储在buffer 中之后,它将该行传递给指定的action 函数以及行的长度(不一定是bufsize),从而允许操作行的打印,行的打印,或您可能想对该行执行的任何其他操作。这就是回调的美妙之处:以有限的方式,您可以执行您选择的操作
  • @BUGGY 不,有时您需要它。例如,对于动态加载(google dlopen()dlsym()。)

标签: c pointers function-pointers


【解决方案1】:

由于您上一条评论中仍有一些不确定性,也许一个说明这些要点的具体示例会有所帮助。让我们从一个简单的示例开始,该示例将字符串作为用户从命令行输入(您也可以提示用户输入)。现在假设我们想让用户选择告诉我们他们想如何存储输入。就本例而言,假设选项是 (1) 以正常存储字符串,使其水平打印在一行上,(2) 存储相反的字符串也将打印在一行上的字符串,(3) 在每个字符后使用换行符存储字符串,以便垂直打印,(4) 将字符串反向存储嵌入换行符。

在解决此问题的常规方法中,您可能会编写一个switch 语句或一系列else if 语句并将字符串传递给4 个不同的例程来处理不同的情况。函数指针允许您以不同的方式解决问题。而不是 4 个不同的输入例程来处理每种情况,为什么不 1 个输入例程将函数指针作为参数,并根据作为参数传递的函数更改它处理字符串的方式。输入例程可以像复制字符串一样简单(以防止修改argv[1] 等),然后将字符串作为参数传递给指针表示的函数:

/* make copy of original string, pass to callback function */
char *input (char *d, char *s, char *(*cbf)(char *))
{
    strcpy (d, s);
    return (*cbf) (d);
}

上面的input 函数将目标字符串d、源字符串s 和指向函数cbf回调函数的缩写)的指针作为参数。让我们快速看一下函数指针语法并消化它告诉我们的内容:

char *(*cbf)(char *)
  |      |     \
return   |    argument
type     |    list
         |
    function pointer
      name/label

上面,名为cbf 的函数指针的返回类型为char *,并采用char * 类型的单个参数。 (注意:在函数指针参数列表中只指定了类型,而不是类型参数名称 -- 例如,没有char *str,只有char *) 现在,每次您想将这种类型的函数作为参数传递或在赋值中使用它时,输入的内容似乎很多。这是。有一个简单的解决方案可以减少所需的打字。您可以将typedef 用于函数指针,类似于将typedef 与结构等一起使用。创建cbf 类型的typedef 同样简单:

typedef char *(*cbf)(char *);

上面的函数指针 typedef 创建了一个类型 cbf,可以在需要函数指针类型的任何地方使用它来代替 char *(*cbf)(char *)。使用 typedef 时,您无需指定 返回类型参数列表,也不必将函数指针放在括号内。这将原始函数声明简化为:

char *input (char *d, char *s, cbf fname)
{
    strcpy (d, s);
    return fname (d);
}

对函数使用typedef 不仅简化了将函数作为参数传递,而且还简化了函数指针数组的创建。根据需要,可以使用一组函数指针来简化选择和传递给定数量的函数中的任何一个。对于我们的input 函数,我们创建了一个函数指针数组,每个指针指向一个不同的函数,可用于将输入字符串以所需的格式放置。例如,假设我们上面描述的 4 个函数有这样的声明:

/* input processing callback functions */
char *hfwd (char *s);
char *hrev (char *s);
char *vfwd (char *s);
char *vrev (char *s);

注意:每个函数都匹配char * 类型的指针定义,并接受char * 类型的单个参数。使用我们的cbf typedef,我们可以轻松地创建一个名为fnames 的函数指针数组,如下所示:

cbf fnames[] = { &hfwd, &hrev, &vfwd, &vrev };

fnames 数组可以像任何其他数组一样用于通过数组索引选择我们的任何一个函数。 (例如fnames[0] 是我们的函数hfwd)这让我们能够从用户那里获取第二个输入,一个数字,来选择我们输入字符串的格式。这提供了使用我们的任何一个回调函数的能力,只需将所需函数的数组索引作为命令行的第二个参数提供。例如,任何一个功能都可以通过调用程序来指定:

./progname my_string 1      /* to process the input with the hrev */

现在承认这个例子除了重新格式化一个字符串,但从函数指针语法的角度来看,在数组中收集函数指针,并将函数指针作为参数传递以扩展代码的功能,它涵盖了很多。看看下面的例子,如果你有任何问题,请告诉我。 (回想一下,完整的函数指针语法,在没有 typedef 的情况下,也包括在内,但已注释,以便您可以比较/对比 typedef 的使用)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define MAXS 128

/* typedef for function pointer */
typedef char *(*cbf)(char *);

/* simple string reverse function */
char *strrevstr (char *str);

/* input processing callback functions */
char *hfwd (char *s);
char *hrev (char *s);
char *vfwd (char *s);
char *vrev (char *s);

/* input function, pointer to function will determine behavior */
// char *input (char *d, char *s, char *(*cbf)(char *));
char *input (char *d, char *s, cbf fn);

int main (int argc, char **argv) {

    if (argc < 3 ) {
        fprintf (stderr, "error: insufficient input, usage: %s string int\n", argv[0]);
        return 1;
    }

    int idx = atoi(argv[2]);

    if (idx > 3 || idx < 0) {
        fprintf (stderr, "error: invalid input -- out of range, (0 !< %d !< 3)\n", idx);
        return 1;
    }

    cbf fnames[] = { &hfwd, &hrev, &vfwd, &vrev };
    // char *(*fnames[])(char *) = { &hfwd, &hrev, &vfwd, &vrev };
    char string[MAXS] = {0};

    input (string, argv[1], fnames[idx]);

    printf ("\nProcessed input ('%s' '%s'):\n\n%s\n\n", argv[1], argv[2], string);

    return 0;
}

/* strrevstr - reverse string, original is not preserved. */
char *strrevstr (char *str)
{
    if (!str) {
        printf ("%s() error: invalid string\n", __func__);
        return NULL;
    }

    char *begin = str;
    char *end = str + strlen (str) - 1;
    char tmp;

    while (end > begin)
    {
        tmp = *end;
        *end-- = *begin;
        *begin++ = tmp;
    }

    return str;
}

/* string unchanged - print horizontal */
char *hfwd (char *s)
{ return s; }

/* string reversed - print horizontal */
char *hrev (char *s)
{ return strrevstr (s); }

/* string unchanged - print vertical */
char *vfwd (char *s)
{
    char *p = s;
    static char buf[MAXS] = {0};
    char *b = buf;

    while (*p)
    {
        *b++ = *p++;
        *b++ = '\n';
    }

    *b = 0;

    b = buf;
    while (*b)
        *s++ = *b++;
    *b = 0;

    return buf;
}

/* string reversed - print vertical */
char *vrev (char *s)
{
    char *p = strrevstr (s);
    static char buf[MAXS] = {0};
    char *b = buf;

    while (*p)
    {
        *b++ = *p++;
        *b++ = '\n';
    }

    *b = 0;

    b = buf;
    while (*b)
        *s++ = *b++;
    *b = 0;

    return buf;
}

/* make copy of original string, pass to callback function */
char *input (char *d, char *s, cbf fn)
// char *input (char *d, char *s, char *(*cbf)(char *))
{
    strcpy (d, s);
    return fn (d);
//     return (*cbf) (d);
}

输出

$ ( for i in {0..3}; do ./bin/fnc_pointer my_string $i; done )

Processed input ('my_string' '0'):

my_string


Processed input ('my_string' '1'):

gnirts_ym


Processed input ('my_string' '2'):

m
y
_
s
t
r
i
n
g


Processed input ('my_string' '3'):

g
n
i
r
t
s
_
y
m

【讨论】:

  • 感谢您在这里付出的巨大努力。我对例如的理解是 1)我可以用 switch-case/if-else 编写相同的程序,可能我在 switch case 中也不会得到什么好处,所以再次为什么回调:O 2)我唯一需要的要理解这里是传递一个函数指针作为参数来扩展你的代码的功能,它涵盖了很多。,你的代码能力是什么意思,涵盖了很多 3) 意思当我在看其他建议的函数 qsort() 4) 函数指针和回调指针之间有区别吗?再次感谢
  • Buggy,传递函数指针作为参数来扩展代码的功能,它涵盖了很多内容不是你可以用几句话解释的,它是来自于对 C 的足够了解以知道何时使用函数指针或是否使用其他东西的东西。简短的回答是在它们保存重复代码时使用它们,或者它们使您能够执行您原本无法执行的操作。要识别何时使用它们只需要一件事 --- 经验......
  • 谢谢大卫会看的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多