C++ 函数指针-为什么,我什么时候可以不用?

C++ 函数指针-为什么,我什么时候可以不用?,c++,c,function,pointers,C++,C,Function,Pointers,免责声明:我已经阅读了无数关于这个主题的其他文章,但我仍然不明白。示例:为什么这样做: void func(int a, void (*callback)(int)) { /* do something with a and callback */ callback(3); } void pointme(int b) { /* do something with b */ } int main() { void (*pf)(int); pf = &am

免责声明:我已经阅读了无数关于这个主题的其他文章,但我仍然不明白。示例:为什么这样做:

void func(int a, void (*callback)(int))
{
    /* do something with a and callback */
    callback(3);
}

void pointme(int b)
{
    /* do something with b */
}

int main()
{
    void (*pf)(int);
    pf = &pointme;
    func(10, pf);
}
void func(int a)
{
    pointme(3);
    /* do something with a*/
}

void pointme(int b)
{
    /* do something with b */
}

int main()
{
    func(10);
}
当我可以简单地做到这一点时:

void func(int a, void (*callback)(int))
{
    /* do something with a and callback */
    callback(3);
}

void pointme(int b)
{
    /* do something with b */
}

int main()
{
    void (*pf)(int);
    pf = &pointme;
    func(10, pf);
}
void func(int a)
{
    pointme(3);
    /* do something with a*/
}

void pointme(int b)
{
    /* do something with b */
}

int main()
{
    func(10);
}
???我真的不明白。任何帮助都将不胜感激。谢谢

当我可以简单地做到这一点时[…]

没错,如果可以的话,应该直接调用函数。但是,在某些情况下,您无法进行直接调用,因为您试图调用的函数在代码中不存在。当然,函数将出现在完成的程序中,但在许多情况下,您将开发一个与其他人的代码交互的库,并且需要自己编译

此外,在某些情况下,您可以直接调用函数,但不希望这样做以避免代码重复

这时函数指针就派上了用场:调用者可以告诉你的函数调用他的哪个函数

考虑设计一个线程库,允许用户并行运行其函数。此库无法直接引用用户代码,原因有二:

  • 您的代码不知道用户将并发运行哪些函数,并且
  • 您不希望为用户可能决定传递给库的每种函数编写单独的函数

在C中,函数指针允许您执行以下操作:

gcc -o lib1.so -std=c99 -pedantic -Wall -Werror -fPIC -shared lib1.c
  • 创建插件架构
  • 创建“通用”函数和数据结构
还有一些我不打算讨论的问题

插件

如果您使用过任何类型的图像编辑器、音频编辑器、浏览器等,那么您可能使用过某种插件;也就是说,有些代码不是原始应用程序的一部分,而是由第三方库提供的,允许您向应用程序添加新功能,而无需升级或重建。这是通过将代码打包到共享或动态链接的库中来实现的(Windows上的
.dll
文件,Linux上的
.so
文件)。程序可以在运行时加载该文件的内容,然后执行该库中包含的函数

一个真实世界的例子需要比我们更多的空间和时间,但这里有 说明该概念的玩具程序和库:

/**
 * lib1.c
 *
 * Provides functions that are called by another program
 */
#include <stdio.h>

static char *names[] = {"func1", "func2", NULL};

char **getNames( void ) { return names; }
void func1( void )      { printf( "called func1\n" ); }
void func2( void )      { printf( "called func2\n" ); }
这将创建共享库文件
lib1.so

现在我添加了一个简单的驱动程序:

#include <stdio.h>
#include <dlfcn.h>

int main( void )
{
  /**
   * Open the shared library file
   */
  void *lib1handle = dlopen( "lib1.so", RTLD_LAZY | RTLD_LOCAL );

  /**
   * Load the "getNames" function into the current process space
   */
  char **(*libGetNames)( void ) =  dlsym( lib1handle, "getNames" );
  if ( libGetNames )
  {
    /**
     * call the "getNames" function in the shared library
     */
    char **names = libGetNames();
    while ( names && *names)
    {
      printf( "calling %s\n", *names );
      /**
       * Load each named function into the current process space
       * and execute it
       */
      void (*func)(void) =  dlsym( lib1handle, *names++ );
      if ( func )
        func();
    }
  }
  dlclose( lib1handle );
  return 0;
}
请注意,
lib1.so
文件不是build命令的一部分;
main
程序在运行之前不知道库代码

您还必须将当前目录放入
LD_LIBRARY\u PATH
变量中,否则
dlopen
将找不到库:

[fbgo448@n9dvap997]~/prototypes/dynlib: export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
这段代码只需通过库中的
getNames
函数获取函数名列表,然后依次加载并执行库中的每个函数。
libGetNames
函数指针将指向库中的
getNames
函数,
func
函数指针将依次指向
func1
func2
中的每一个函数。运行时,它将生成以下输出:

[fbgo448@n9dvap997]~/prototypes/dynlib: ./main
calling func1
called func1
calling func2
called func2
激动人心,对吧?但这正是Photoshop和Audacity等应用程序让您无需升级或重建或其他任何东西即可扩展其功能的方式;您只需下载正确的库,将其放置在正确的位置,应用程序就会加载该库的内容,并使代码对您可用

当然,您可以将库与
main
静态链接,并直接调用函数,但共享库概念的美妙之处在于,它允许您向
main
添加新函数,而无需触摸
main
本身

通用函数和数据结构

C中“泛型”函数的典型示例是
qsort
函数。使用
qsort
,可以对任何类型的数组进行排序;您所要做的就是提供一个函数来对数组中的元素进行实际比较。再一次,一个愚蠢的例子:

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

int cmpInt( const void *lhs, const void *rhs )
{
  const int *l = lhs, *r = rhs;
  return *l - *r;
}

int cmpFloat( const void *lhs, const void *rhs )
{
  const float *l = lhs, *r = rhs;
  return *l - *r;
}

char *fmtInt( char *buffer, size_t bufsize, const void *value )
{
  const int *v = value;
  sprintf( buffer, "%*d", (int) bufsize, *v );
  return buffer;
}

char *fmtFloat( char *buffer, size_t bufsize, const void *value )
{
  const float  *v = value;
  sprintf( buffer, "%*.*f", (int) bufsize, 2, *v );
  return buffer;
}

void display( const void *data, size_t count, size_t size, char *(*fmt)(char *, size_t, const void *))
{
  const char *d = data;
  char buffer[10];
  printf( "{%s", fmt( buffer, sizeof buffer, &d[0] ));
  for ( size_t i = size; i < count * size; i += size )
    printf( ", %s", fmt( buffer, sizeof buffer, &d[i] ));
  printf( "}\n" );
}

int main( void )
{
  int   iarr[] = {9, 100, 53, 99, 4, 29, 44};
  float farr[] = {9, 100, 54, 99, 4, 29, 44};

  printf( "iarr before sort: " );
  display( iarr, sizeof iarr / sizeof *iarr, sizeof *iarr, fmtInt );
  qsort( iarr, sizeof iarr / sizeof *iarr, sizeof *iarr, cmpInt );
  printf (" iarr after sort: " );
  display( iarr, sizeof iarr / sizeof *iarr, sizeof *iarr, fmtInt );

  printf( "farr before sort: " );
  display( farr, sizeof farr / sizeof *farr, sizeof *farr, fmtFloat );
  qsort( farr, sizeof farr / sizeof *farr, sizeof *farr, cmpFloat );
  printf (" farr after sort: " );
  display( farr, sizeof farr / sizeof *farr, sizeof *farr, fmtFloat );

  return 0;
}
但是,我正在将类型信息与基本排序和显示逻辑分离
qsort
不需要知道其元素的类型,它只需要知道一个元素与另一个元素的比较是“小于”还是“等于”。它调用
cmpInt
cmpFloat
函数来执行实际比较;其他逻辑都不需要类型信息。我不必为每种不同的类型复制排序算法的精髓(
sort\u int
sort\u float
sort\u foo
);我只需要为
qsort
提供正确的比较函数

类似地,
display
函数所做的全部工作就是打印出一个逗号分隔的字符串列表,由
{
}
包围。它让
fmtInt
fmtFloat
担心int和float的格式细节。我不必为不同的类型复制任何显示逻辑

到现在为止,你可能已经注意到我一直在吓唬人的引语中加上“泛型”。问题是,您必须将所有内容的地址作为
无效*
传递,这意味着您将类型安全性抛到了窗外。编译器无法保护我,以免为给定数组传递错误的比较或格式化函数;我只会得到乱码输出(或运行时错误)。像C++和java和C语言这样的语言提供模板的能力,允许你编写通用代码,但仍然保持类型安全(即编译器如果使用错误类型,仍然能够对你吼叫)。p>
函数指针还有其他用途,但我已经在这个答案上花费了太多的时间和精力

举一个简单的例子,检查函数。

void qsort (void* base, size_t num, size_t size, int (*compar)(const void*,const void*));